Skip to main content
Version: v25.1.3

Tenant service termination — data disposal procedure

Removing all tenant data upon service termination.

Purpose​

This runbook defines the procedure for terminating a tenant's service and ensuring all associated data is fully removed. The primary mechanism is the DELETE /api/tenants/{tenant_id} API endpoint, which orchestrates deletion of all tenant resources in the correct order.

important

This procedure is irreversible. Once executed, all tenant data — including VMs, storage, networking, logs, backups, and credentials — is permanently removed with no recovery path.


Data categories covered​

CategoryMechanismNotes
Virtual machines & baremetal nodesAPI — explicit per-resource delete
Networking (routers, floating IPs, security groups, networks)API — explicit per-resource deleteManagement router interfaces detached first
VPN storage bucket (configs, server certificate, cryptographic keys)API — entire S3/Swift bucket deleted
Ceph object storage (RGW user)API — RGW user and all associated data deleted
DNS zoneAPI — Designate zone deletedOnly if DNS is configured for the tenant
Cinder volumes, snapshots, backupsOpenStack — auto-removed by project deletion
OpenStack application credentialsOpenStack — auto-removed by project deletion
Grafana monitoring dashboardAPI — dashboard deleted via Grafana API
Loki logsAPI — delete request submitted to LokiAsynchronous; compactor processes in background
HedgeHog VPCsAPI — tenant VPCs removed via HedgeHog client
Database recordAPI — tenant and user-tenant mappings deleted

Prerequisites Checklist​

  • Operator-scoped API credentials for the iaas-api (Google OAuth or Keystone token)
  • The tenant_id UUID of the tenant to terminate (obtain from the operator panel or GET /api/tenants)
  • Confirmed with the account team that the tenant has been notified and the termination is authorised
  • jq installed locally
  • OpenStack CLI set up for the target environment (see below)

OpenStack CLI setup​

The admin-openrc.sh credentials file is Ansible Vault encrypted. You need a Python venv with the OpenStack client and the vault password (retrieve from Bitwarden — search "ansible vault"):

/usr/bin/python3 -m venv /tmp/venv-termination
source /tmp/venv-termination/bin/activate
pip install ansible-core==2.20.5 python-openstackclient==9.0.0

ansible-vault decrypt \
infra-management/<env>/config/admin-openrc.sh \
--ask-vault-pass \
--output /tmp/admin-openrc.sh

The decrypted file may contain http:// for the Keystone URL even though the endpoint listens on HTTPS. Fix it before sourcing:

sed -i 's|http://\(.*\):5000|https://\1:5000|' /tmp/admin-openrc.sh
source /tmp/admin-openrc.sh
export OS_INSECURE=true # needed if the CA cert is not installed locally

Smoke test:

source /tmp/venv-termination/bin/activate
source /tmp/admin-openrc.sh
export OS_INSECURE=true
openstack project list -c ID -c Name | head -5

Step 1: Identify the tenant​

curl -s -H "Authorization: Bearer <token>" \
https://<iaas-api-host>/api/tenants | jq '.[] | select(.name == "<tenant-name>")'

Expected output: A single tenant object. Copy the id value — this is your <tenant_id> for all subsequent steps.


Step 2: Execute service termination​

curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-H "Authorization: Bearer <token>" \
https://<iaas-api-host>/api/tenants/<tenant_id>

Expected output: 204

If you receive 409: The tenant has active Kubernetes clusters. Delete them first via the operator panel, then retry.

note

Loki log deletion is asynchronous — the API submits a delete request for the tenant's log stream and Loki's compactor processes it in the background. All other data is removed synchronously before the 204 is returned.


Step 3: Verify full data disposal​

Run the following script to verify all data categories have been removed. Set the three variables at the top before running.

#!/usr/bin/env bash
set -euo pipefail

TENANT_ID="<tenant_id>"
IAAS_API_HOST="<iaas-api-host>"
LOKI_HOST="<loki-host>"
TOKEN="<token>"

PASS=0; FAIL=0

check() {
local label="$1"; local result="$2"; local expected="$3"
if [ "$result" = "$expected" ]; then
echo " ✓ $label"
((PASS++)) || true
else
echo " ✗ $label (got: $result, expected: $expected)"
((FAIL++)) || true
fi
}

echo "=== Tenant data disposal verification: $TENANT_ID ==="
echo

echo "--- API ---"
FOUND=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://$IAAS_API_HOST/api/tenants" \
| jq --arg id "$TENANT_ID" '[.[] | select(.id == $id)] | length')
check "Tenant record deleted" "$FOUND" "0"

echo "--- OpenStack ---"
PROJECT=$(openstack project show "$TENANT_ID" -f value -c id 2>/dev/null || echo "not_found")
check "OpenStack project deleted" "$PROJECT" "not_found"

VM_COUNT=$(openstack server list --all-projects --project "$TENANT_ID" -f value 2>/dev/null | wc -l | tr -d ' ')
check "VMs deleted" "$VM_COUNT" "0"

NET_COUNT=$(openstack network list --project "$TENANT_ID" -f value 2>/dev/null | wc -l | tr -d ' ')
check "Networks deleted" "$NET_COUNT" "0"

echo "--- Loki ---"
LOG_COUNT=$(curl -s -G "https://$LOKI_HOST/loki/api/v1/query_range" \
--data-urlencode "query={tenant_id=\"$TENANT_ID\"}" \
--data-urlencode "start=$(date -d '7 days ago' +%s)000000000" \
--data-urlencode "end=$(date +%s)000000000" \
--data-urlencode "limit=1" | jq '.data.result | length')
check "Loki logs purged" "$LOG_COUNT" "0"

echo
echo "=== Result: $PASS passed, $FAIL failed ==="
[ "$FAIL" -eq 0 ] || exit 1
note

If the Loki check fails immediately after termination, wait a few minutes for the compactor to process the delete request and re-run the script. The configured loki_retention_period (set in inventory) also ensures any remaining log data expires automatically once the retention window elapses.


✓ Done​

All tenant data has been removed once the verification script reports 0 failed.


Troubleshooting​

OpenStack project still exists after deletion​

openstack project delete <tenant_id>

VPN storage bucket not deleted​

Delete the bucket manually via the Ceph RGW admin API or S3 client, targeting the bucket named after the tenant ID.

Loki logs still present after 30 minutes​

Verify that Loki's compactor is running and that deletion_mode is set to filter-and-delete in the phoenix-observability Helm chart values. Check compactor logs for errors.

Grafana dashboard still visible​

curl -X DELETE \
-H "Authorization: Bearer <grafana-token>" \
https://<grafana-host>/api/dashboards/uid/<tenant_id>