> For the complete documentation index, see [llms.txt](https://docs.trilio.io/openstack/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trilio.io/openstack/deployment/uninstall-triliovault/uninstalling-from-sunbeam-canonical.md).

# Uninstalling from Canonical OpenStack Sunbeam

{% hint style="info" %}
This page covers removing T4O 6.2.1 from a **Canonical OpenStack Sunbeam** cloud (`trilio-wlm-k8s`, `trilio-dm-api-k8s`, `trilio-data-mover-sunbeam`) — see [Trilio Installation on Canonical OpenStack (Sunbeam)](/openstack/deployment/installing-on-canonical/sunbeam-canonical.md) for the matching install procedure.
{% endhint %}

Remove components in the order given below, verifying each step before continuing to the next.

{% hint style="info" %}
Backup data already written to your NFS or S3 backup target is never affected by any step in this procedure — T4O does not delete backup-target content when its charms are removed. The workload and backup-target *metadata* held in T4O's own databases is a separate matter, addressed in the optional [Step 6](#6-remove-residual-database-message-queue-and-identity-resources) below.
{% endhint %}

{% hint style="danger" %}
**Do not use `--force` on any removal.** A forced removal skips the charms' own teardown hooks. On the control plane it can leave the `trilio-wlm-k8s` StatefulSet behind, which blocks a later reinstall until it is deleted by hand, and it can leave unrelated Sunbeam applications in an error state on their own relation-departed hooks. Let each removal complete on its own before starting the next.
{% endhint %}

{% hint style="warning" %}
A unit stuck in an error state blocks removal. Juju will not retire an application while one of its units has a failed hook. Clear it with `juju resolved --no-retry <unit>` before retrying the removal. The `--no-retry` flag matters: plain `juju resolved` re-runs the failed hook, and if that hook fails again the removal stays blocked indefinitely.
{% endhint %}

{% hint style="warning" %}
`juju remove-application` always prompts for confirmation before doing anything. When running it over SSH or from a script — anywhere without an interactive terminal to answer that prompt — include `--no-prompt`, or the command aborts immediately with `ERROR application removal: aborted` and nothing is removed. Every command below already includes it.
{% endhint %}

## Prerequisites

* Juju CLI access to the Sunbeam controller, with both the `openstack` (Kubernetes) and machine models reachable. Confirm the machine model's exact name with `juju models` before starting — some Sunbeam environments require the owner-qualified form (e.g. `admin/openstack-machines`) rather than a bare `openstack-machines`.
* kubectl access to the `openstack` namespace (on Canonical Kubernetes, this is typically `sudo k8s kubectl`, not a bare `kubectl`).
* For [Step 6](#6-remove-residual-database-message-queue-and-identity-resources) only: MySQL root credentials (via the `mysql` charm's `get-password` action), RabbitMQ access via `rabbitmqctl` inside the `rabbitmq` unit, and OpenStack admin credentials (via the `keystone` charm's `get-admin-account` action).

## Steps to uninstall the Trilio charms

### 1. Remove the control plane

Removes `trilio-wlm-k8s` and `trilio-dm-api-k8s` from the `openstack` model.

```
juju switch openstack
juju remove-application trilio-wlm-k8s trilio-dm-api-k8s --destroy-storage --no-prompt
```

{% hint style="info" %}
`--destroy-storage` removes Juju storage volumes owned directly by these two applications. `trilio-wlm-k8s` and `trilio-dm-api-k8s` declare no storage of their own, though — they are stateless, and all workload/backup-target metadata lives in the separate `mysql` application's own storage. This flag therefore does **not** delete the WLM or DataMover-API databases; see [Step 6](#6-remove-residual-database-message-queue-and-identity-resources) if you want those removed as well.
{% endhint %}

If you intend to redeploy later and keep existing backups usable, back up the `workloadmgr` database first (Step 6 shows how to reach it) — it is left untouched by this step regardless.

**Verify:**

```
juju status trilio-wlm-k8s trilio-dm-api-k8s
# Both applications should report as not found. This can take a minute
# or two to fully clear; if it still shows Scale 3/0 with units
# "Terminating", that is normal in-progress teardown -- wait and re-check.

kubectl get pods -n openstack | grep trilio
# No output -- all pods terminated.
```

### 2. Remove the data plane

Removes `trilio-data-mover` (the subordinate DataMover/DMS application) from the machine model.

```
juju switch openstack-machines
juju remove-application trilio-data-mover --destroy-storage --no-prompt
```

**Verify:**

```
juju status trilio-data-mover
# Application should report as not found.
```

{% hint style="warning" %}
Confirming the compute-node services are actually gone requires the manual cleanup in Step 3 below — removing the Juju application does not stop or uninstall anything on the compute node's underlying OS.
{% endhint %}

### 3. Clean up compute-node services and packages

Required on **every** compute node. Removing `trilio-data-mover` leaves its systemd services and apt packages installed and running; once the relation is torn down, their RabbitMQ/Keystone credentials are deleted, so both services enter a continuous failure loop until stopped. Run the following via Juju for every `openstack-hypervisor/<N>` unit (check `juju status openstack-hypervisor` for the full list):

```
juju ssh openstack-hypervisor/<N> -- sudo systemctl stop triliovault-datamover triliovault-dms
juju ssh openstack-hypervisor/<N> -- sudo systemctl disable triliovault-datamover triliovault-dms
juju ssh openstack-hypervisor/<N> -- sudo apt-get purge -y python3-tvault-contego python3-trilio-dms python3-s3-fuse-plugin
juju ssh openstack-hypervisor/<N> -- sudo systemctl daemon-reload
```

`apt-get purge` does not remove the log directory these services wrote to either. Clean it up separately:

```
juju ssh openstack-hypervisor/<N> -- sudo rm -rf /var/log/triliovault
```

{% hint style="info" %}
This is distinct from `/var/log/juju/unit-trilio-data-mover-<N>.log`, which Juju itself always preserves for a removed unit on its machine, for any application — not Trilio-specific, and not something this procedure removes.
{% endhint %}

Optional: remove the Trilio apt repository source file added during install. Confirm its exact filename first rather than assuming one:

```
juju ssh openstack-hypervisor/<N> -- ls /etc/apt/sources.list.d/ | grep -i trilio
juju ssh openstack-hypervisor/<N> -- sudo rm -f /etc/apt/sources.list.d/<confirmed-filename>
juju ssh openstack-hypervisor/<N> -- sudo apt-get update -qq
```

**Verify:**

```
juju ssh openstack-hypervisor/<N> -- systemctl is-active triliovault-datamover triliovault-dms
# Should print "inactive" for both, on every compute node.

juju ssh openstack-hypervisor/<N> -- test -d /var/log/triliovault && echo "still present" || echo "removed"
# Should print "removed", on every compute node.
```

### 4. Remove the Horizon plugin

Reverts Horizon to its pre-Trilio image, removing the Trilio dashboard plugin. Two ways to determine which image to restore, in order of preference.

**Option A — Restore the saved pre-Trilio baseline (preferred, if captured)**

If `horizon-baseline.yaml` was captured at install time, use its saved image directly instead of a live Charmhub lookup — this guarantees an exact revert to the image Horizon had before Trilio touched it, regardless of how much time has passed or whether Canonical has since published newer `horizon-k8s` resource revisions on the channel.

```
# 1. Read the saved baseline to fetch pre_trilio_image-from-baseline
cat triliovault-cfg-scripts/sunbeam-canonical/horizon-baseline.yaml

# 2. Check Horizon's currently-running image
kubectl get pod -n openstack horizon-0 -o jsonpath='{.spec.containers[?(@.name=="horizon")].image}'
echo

# 3. Re-attach the saved pre-Trilio image
juju attach-resource horizon horizon-image=<pre_trilio_image-from-baseline>
```

**Option B — No saved baseline exists (fallback)**

If no baseline file was captured at install time (e.g. an install performed before this practice was adopted), fall back to Charmhub's current published default for the channel:

```
# 1. Find the currently-deployed charm revision
juju status horizon -m openstack
# note the Rev column

# 2. Look up that revision's default horizon-image resource on Charmhub
curl -s 'https://api.charmhub.io/v2/charms/info/horizon-k8s?channel=2024.1/stable&fields=default-release.resources,default-release.revision' \
  | python3 -m json.tool
# find the "horizon-image" resource entry's "upstream-source" field
# (nested inside the "metadata-yaml" text, not a top-level field of the
# resource entry itself)

# 3. Re-attach that stock image
juju attach-resource horizon horizon-image=<upstream-source-from-step-2>
```

For example, for `horizon-k8s` revision 289 on the `2024.1/stable` channel, the resolved image was `ghcr.io/canonical/horizon:2024.1-24.04_edge`, giving:

```
juju attach-resource horizon horizon-image=ghcr.io/canonical/horizon:2024.1-24.04_edge
```

{% hint style="info" %}
Option B always restores whichever image is Charmhub's *current* default for `horizon-k8s`'s channel at the time cleanup runs — not necessarily the exact image Horizon had before Trilio was installed, since Canonical publishes `horizon-k8s` resource updates on its own schedule, independent of Trilio. Prefer Option A whenever a baseline is available.
{% endhint %}

**Verify** on every unit, since each rolls over to the new image at a different time:

```
for p in horizon-0 horizon-1 horizon-2; do
  kubectl exec -n openstack $p -c horizon -- \
    python3 -c 'import trilio_dashboard; print(trilio_dashboard.__file__)'
done
# Each should fail with: ModuleNotFoundError: No module named 'trilio_dashboard'

juju status horizon -m openstack
# All units active/idle.
```

### 5. Remove cross-model offers (optional)

If the `rabbitmq` or `keystone-credentials` offers were created specifically for T4O, and nothing else consumes them, they may be removed:

```
juju switch openstack
juju remove-offer rabbitmq:amqp
juju remove-offer keystone-credentials:identity-credentials
```

{% hint style="warning" %}
Leave the offers in place if you are unsure. An unused offer is harmless, but removing one that another application still consumes will break that application. Before removing anything, confirm what else is connected:

```
juju status -m openstack-machines --relations
```

Check the "Integration provider" section for any `rabbitmq:amqp`, `keystone-credentials:identity-credentials`, or `cert-distributor:send-ca-cert` relation pointing at an application other than Trilio's — these offers are frequently shared with Sunbeam's own core services (e.g. `openstack-hypervisor` for Nova, `cinder-volume` for Cinder). If any such relation exists, do not remove that offer.
{% endhint %}

### 6. Remove residual database, message queue, and identity resources

Optional. Because `--destroy-storage` in Step 1 does not reach the `mysql` application's own storage, the WLM and DataMover-API databases, their MySQL users, associated RabbitMQ vhosts, and the Keystone service catalog entries, endpoints, and cloud-admin trust are all left behind by Steps 1–5. This step removes them. Skip it entirely if leaving these inert resources behind is acceptable.

{% hint style="danger" %}
Dropping the databases below is destructive and irreversible — it permanently deletes all workload and backup-target metadata (which backups exist, their schedules, etc). It does not affect backup data on your NFS or S3 target. If you may reinstall and want existing backups to remain usable, skip the MySQL portion below (back up the `workloadmgr` database first if you want a safety copy).
{% endhint %}

**MySQL — leftover databases and users:**

```
juju switch openstack
juju run mysql/leader get-password

kubectl exec -n openstack mysql-0 -c mysql -- mysql -uroot -p'<password>' -e \
  "DROP DATABASE IF EXISTS workloadmgr; DROP DATABASE IF EXISTS dmapi; \
   DROP USER IF EXISTS 'charmed_dba_workloadmgr_00'@'%'; \
   DROP USER IF EXISTS 'charmed_dba_dmapi_00'@'%';"
```

**RabbitMQ — leftover vhosts** (their users are cleaned up automatically, but the vhosts themselves are not):

```
kubectl exec -n openstack rabbitmq-0 -c rabbitmq -- rabbitmqctl delete_vhost workloadmgr
kubectl exec -n openstack rabbitmq-0 -c rabbitmq -- rabbitmqctl delete_vhost dmapi
```

**Keystone — leftover service catalog entries, endpoints, and the cloud-admin trust** created during install:

```
juju run keystone/leader get-admin-account
# use the resulting openrc / OS_* values below, with --insecure for a
# self-signed certificate

openstack --insecure trust list
openstack --insecure trust delete <trust-id>   # confirm the ID before deleting

openstack --insecure service delete dmapi
openstack --insecure service delete TrilioVaultWLM
# deleting a service also removes its associated endpoints
```

**Keystone — leftover identity (service) users.** `openstack user list` on its own only shows users in the domain your current auth is scoped to (here, `admin_domain`, which holds only `admin`) — it does not list all users across all domains. Trilio's own service accounts live in a separate `service_domain`, so they stay invisible unless that domain is named explicitly:

```
openstack --insecure user list --domain service_domain
```

This surfaces (alongside the other OpenStack services' own accounts, `svc_nova-*`, `svc_cinder-*`, etc., which are **not** Trilio's and must not be touched):

```
svc_trilio_wlm_k8s-<suffix>       # trilio-wlm-k8s's own service account
svc_trilio_dm_api_k8s-<suffix>    # trilio-dm-api-k8s's own service account
datamover                         # trilio-data-mover's own service account
```

{% hint style="warning" %}
`svc_trilio_wlm_k8s` is the trustee on the cloud-admin trust created during install (trustor: `admin`) — confirm this by checking the trust's Trustee User ID matches this user's ID before deleting either. Delete both together, or neither; deleting the trust independently of the user (or vice versa) can leave a dangling reference:

```
openstack --insecure trust show <trust-id>   # confirm Trustee User ID
openstack --insecure user delete svc_trilio_wlm_k8s-<suffix>
# (the trust itself is deleted separately, above, via `trust delete`)
```

{% endhint %}

`svc_trilio_dm_api_k8s` and `datamover` have no such dependency and can be removed independently:

```
openstack --insecure user delete svc_trilio_dm_api_k8s-<suffix>
openstack --insecure user delete datamover
```

**Verify:**

```
kubectl exec -n openstack mysql-0 -c mysql -- mysql -uroot -p'<password>' -e "SHOW DATABASES;"
kubectl exec -n openstack rabbitmq-0 -c rabbitmq -- rabbitmqctl list_vhosts
openstack --insecure service list
openstack --insecure trust list
openstack --insecure user list --domain service_domain
# should show only the other OpenStack services' own accounts (svc_nova-*,
# svc_cinder-*, svc_glance-*, svc_neutron-*, svc_placement-*, horizon,
# cinder-volume, openstack-hypervisor) -- no svc_trilio_* or datamover entries
```

## Verification Summary

After Steps 1–4 (the mandatory path):

* `juju status` in both models shows no `trilio-wlm-k8s`, `trilio-dm-api-k8s`, or `trilio-data-mover` applications
* `kubectl get pods -n openstack | grep trilio` returns nothing
* `triliovault-datamover` and `triliovault-dms` are inactive on every compute node
* `python3-tvault-contego`, `python3-trilio-dms`, and `python3-s3-fuse-plugin` are not installed on any compute node
* `/var/log/triliovault` does not exist on any compute node
* `trilio_dashboard` fails to import on every Horizon unit

After Step 6 (optional, destructive):

* no `workloadmgr`/`dmapi` databases or users remain in MySQL
* no `workloadmgr`/`dmapi` vhosts remain in RabbitMQ
* no `dmapi`/`TrilioVaultWLM` services, endpoints, or admin trust remain in Keystone
* no `svc_trilio_wlm_k8s`, `svc_trilio_dm_api_k8s`, or `datamover` users remain in Keystone's `service_domain`
