Trilio Share Protectionfor OpenStack File Shares
Guide

REST API

Resources, methods, request/response bodies, and status codes


Overview

This page documents the Abacá REST API — the primary integration surface for tenants protecting Manila shares and for operators managing the Abacá service. The API is versioned at /v1, authenticated via Keystone tokens, and governed by oslo.policy rules. Every list endpoint supports pagination; every error response carries a structured envelope that tells you whether the tenant or the operator must act to resolve the problem. If you need the machine-readable contract, the authoritative OpenAPI 3 specification lives at docs/api-ref/openapi.yaml and is generated directly from the service's route and schema definitions.


Prerequisites

Before calling the API you need:

  • A running Abacá control planeabaca-api and abaca-conductor deployed as OpenShift pods on RHOSO 18 or later (see the deployment guide).
  • Keystone credentials with an appropriate role:
    • Tenant operations: a standard project-scoped token.
    • Domain-owner operations (target templates, bucket registration): a domain-scoped token for a user holding the abaca_domain_owner role.
    • Admin operations (/v1/admin/…): an operator-scoped token with the admin policy role.
  • The Abacá service endpoint — discoverable via the Keystone service catalog under the share-protection catalog type (configurable; see [DEFAULT] catalog_type).
  • python-openstackclient with the abaca distribution installed if you plan to use the openstack share protection … CLI instead of raw HTTP calls. Install it by following the deployment instructions for your environment (RHOSO operators layer it into the Horizon pod venv via deploy/rhoso/08-dashboard.sh; developers use tox or local pip from the repo).
  • S3-compatible object storage with versioning enabled; Object Lock must be enabled at bucket creation time if you want immutable backups.
  • OpenStack Manila with at least one share to protect.

Installation

The abaca-api service is not installed as a standalone package — it is deployed as part of the Abacá control plane using the kustomize manifests and bash install scripts in deploy/rhoso/. The following steps show how to reach the API endpoint after a successful deployment.

Step 1 — Confirm the service is in the Keystone catalog

openstack catalog show share-protection

Expected output lists public, internal, and admin endpoints. If the entry is absent, re-run the catalog registration script:

bash deploy/rhoso/02-keystone.sh

Step 2 — Set your environment variables

Export the standard OpenStack auth variables (or use a clouds.yaml entry) so that the CLI and your scripts can discover the endpoint automatically:

export OS_AUTH_URL=https://keystone.example.com/v3
export OS_USERNAME=myuser
export OS_PASSWORD=secret
export OS_PROJECT_NAME=myproject
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default

Step 3 — Verify connectivity

Fetch the API version document:

curl -s -H "X-Auth-Token: $(openstack token issue -f value -c id)" \
     https://<abaca-api-endpoint>/v1/

Step 4 — Install the CLI plugin (tenant / operator workstations)

The abaca distribution provides the openstack share protection … subcommands. Follow the deployment model for your environment:

  • RHOSO operators: the plugin is layered into the Horizon pod venv by deploy/rhoso/08-dashboard.sh. No separate install step is needed on the control plane.
  • Developer workstations: use tox from the repository root, or install the package directly from the repo source into a virtual environment.

Note: Do not install from PyPI or assume a public registry — install from the repository as directed by deploy/rhoso/08-dashboard.sh or the development setup instructions.


Configuration

Abacá's API behaviour is controlled by oslo.config options declared in an INI-style .conf file. The options most relevant to the API surface are in the [api] and [DEFAULT] sections.

[api] section

OptionTypeDefaultEffect
bind_hoststring0.0.0.0IP address the API process listens on.
bind_portinteger9797TCP port for the API. The OpenShift Service and Route manifests expose this port.
noauthbooleanfalseDisable Keystone authentication. Never set true in production — intended only for the local Docker Compose dev stack.
max_limitinteger1000Maximum value a caller may specify for the ?limit query parameter on list endpoints.
default_limitinteger100Page size returned when the caller does not specify ?limit.

[DEFAULT] section (service discovery)

OptionTypeDefaultEffect
catalog_typestringshare-protectionThe Keystone service catalog type under which Abacá's endpoints are registered. Change this only if your site uses a non-standard catalog layout.
endpoint_typestringpublicURLWhich endpoint interface (publicURL, internalURL, or adminURL) the service uses when looking up sibling OpenStack services.

[database] section

OptionTypeDefaultEffect
connectionstring(required)SQLAlchemy connection string for Abacá's dedicated MySQL/MariaDB (Galera) database, e.g. mysql+pymysql://abaca:pass@db.example.com/abaca.

[conductor] section (affects API-observable behaviour)

The conductor drives the job state machine; its settings influence how quickly the API reflects job progress.

OptionTypeDefaultEffect
scheduler_intervalinteger60Seconds between policy schedule evaluations.
reconciliation_intervalinteger300Seconds between reconciliation sweeps that recover stuck jobs.
worker_heartbeat_deadline_secondsinteger60Seconds after which a worker with no heartbeat is considered dead.
queued_job_deadline_secondsinteger300Seconds a job may sit in queued state before the reconciler declares it stuck.

[enrollment] section

OptionTypeValid valuesEffect
dispatchstringrpc, inlineWhether target enrollment jobs are dispatched to a worker VM via RPC or run inline in the conductor process (development only).

Annotated sample abaca.conf (API-relevant excerpt)

[DEFAULT]
catalog_type = share-protection
endpoint_type = publicURL

[api]
bind_host = 0.0.0.0
bind_port = 9797
noauth = false
max_limit = 1000
default_limit = 100

[database]
connection = mysql+pymysql://abaca:changeme@galera.example.com/abaca

[conductor]
scheduler_interval = 60
reconciliation_interval = 300
worker_heartbeat_deadline_seconds = 60
queued_job_deadline_seconds = 300

[enrollment]
dispatch = rpc

Usage

All requests must carry a Keystone token in the X-Auth-Token header. The base path for every tenant and admin resource is /v1.

Authentication

TOKEN=$(openstack token issue -f value -c id)
ABACA=https://abaca.example.com/v1

Pagination

Every list endpoint accepts ?limit (1–1000, default 100) and ?marker (the next_marker value from the previous page). A null next_marker means you are on the last page.

GET /v1/backups?limit=25
GET /v1/backups?limit=25&marker=<next_marker>

Error envelope

Every non-2xx response returns a JSON object with four fields:

{
  "code": "target_not_found",
  "title": "Target not found",
  "detail": "No backup target with id 'abc123' exists in this project.",
  "category": "tenant_action_required"
}

category is always one of tenant_action_required (the caller must fix something — wrong credentials, missing resource) or operator_action_required (the operator must act — no worker fleet available, infrastructure fault).


Tenant-facing resources

Target templates (/v1/target-templates)

A Domain owner creates a target template to describe the S3 endpoint, region, provider, and bucket_scheme (shared or per_project). The bucket_scheme is immutable after creation — changing it would orphan existing backups.

MethodPathPurpose
GET/v1/target-templatesList templates visible to the caller's Domain.
POST/v1/target-templatesCreate a template (Domain-owner token required).
GET/v1/target-templates/{id}Fetch a single template.
PATCH/v1/target-templates/{id}Update mutable fields (name, retention defaults).
DELETE/v1/target-templates/{id}Delete a template (only if no targets reference it).

Backup targets (/v1/targets)

Registering a target triggers the preflight conformance checks (reachability, read/write, versioning, Object Lock) and initialises a Kopia repository in the bucket. This runs as an enrollment job on a worker VM.

MethodPathPurpose
GET/v1/targetsList targets.
POST/v1/targetsRegister an S3 bucket as a backup target.
GET/v1/targets/{id}Fetch target detail including status and usage_stats.
PATCH/v1/targets/{id}Rotate S3 credentials or update metadata.
DELETE/v1/targets/{id}Delete a target record (does not destroy S3 data).

Protection policies (/v1/policies)

A policy binds a Manila share to a target template and defines the backup schedule (cron expression) and retention rules.

MethodPathPurpose
GET/v1/policiesList policies.
POST/v1/policiesCreate a policy.
GET/v1/policies/{id}Fetch policy detail.
PATCH/v1/policies/{id}Update schedule, retention, or enabled flag.
DELETE/v1/policies/{id}Delete a policy (does not delete existing backups).

Policy fields:

FieldTypeNotes
share_idstringManila share UUID.
target_template_idstringTemplate to use for backup storage.
schedulestringCron expression (server-side validation).
retentionobjectRetention rules (e.g. keep_last, keep_daily).
enabledbooleanfalse suspends scheduled backups without deleting the policy.
max_retriesinteger0–10; 0 means one attempt. Only pre-transfer, operator-actionable failures are retried.

Backups (/v1/backups)

Backups are created on-demand via POST /v1/backups or automatically by the conductor according to the policy schedule. The list is sorted newest-first and hides status=expired entries by default.

MethodPathPurpose
GET/v1/backupsList backups (newest-first).
POST/v1/backupsRequest an on-demand backup.
GET/v1/backups/{id}Fetch backup detail.
DELETE/v1/backups/{id}Delete a backup (subject to retention and Object Lock).

Useful query parameters for GET /v1/backups:

  • ?share_id=<uuid> — filter to one share.
  • ?include_expired=true — include tombstoned backups.
  • ?status=<value> — filter by explicit status value.

Backup fields:

FieldTypeNotes
idstringBackup UUID.
share_idstringThe protected Manila share.
target_bucket_idstringThe target bucket that holds this backup.
statusstringCurrent status string.
crash_consistentbooleantrue if the backup reflects a snapshot; false if taken from the live share.
kopia_snapshot_idstring / nullKopia content-address; null while in progress.

Restores (/v1/restores)

A restore recovers a share (or a sub-path within it) from a backup. Two modes are supported: new_share creates a fresh Manila share from the backup data; in_place overwrites the existing share (requires force=true).

MethodPathPurpose
GET/v1/restoresList restore operations.
POST/v1/restoresStart a restore.
GET/v1/restores/{id}Fetch restore detail and status.

Restore fields:

FieldTypeNotes
backup_idstringThe backup to restore from.
modeenumnew_share or in_place.
sub_pathstring / nullOptional path within the share to restore.
forcebooleanRequired true for in_place mode.
statusstringCurrent restore status.

Jobs (/v1/jobs)

Every backup, restore, enrollment, and maintenance operation creates a Job. Poll the job to track progress.

MethodPathPurpose
GET/v1/jobsList jobs for the current project.
GET/v1/jobs/{id}Fetch job state and progress.
POST/v1/jobs/{id}/cancelRequest cancellation of a running job.

Job state machine: queuedprovisioning_networkprovisioning_sourceconnecting_repositorytransferringfinalizingreleasingavailable (success) or error (failure).

Job fields:

FieldTypeNotes
idstringJob UUID.
job_typestringE.g. backup, restore, enrollment.
stateenumCurrent state in the state machine.
progressintegerPercentage complete (0–100), heartbeat-fed from the worker.

Usage (/v1/usage)

MethodPathPurpose
GET/v1/usageTenant consumption data: protected shares, bytes stored, activity.

Admin resources (/v1/admin/…)

Admin endpoints are policy-gated and audited. They provide a cross-tenant view for operators.

Worker fleet (/v1/admin/workers)

MethodPathPurpose
GET/v1/admin/workersList all worker VMs and their state.
POST/v1/admin/workers/{id}/drainStop assigning new jobs to a worker; let running jobs finish.
POST/v1/admin/workers/{id}/retireMark a worker for deletion after draining.

Admin jobs (/v1/admin/jobs)

MethodPathPurpose
GET/v1/admin/jobsList all jobs across all tenants (paginated).
POST/v1/admin/jobs/{id}/cancelCancel any job.
POST/v1/admin/jobs/{id}/retryRetry a failed job.

Coverage (/v1/admin/coverage)

MethodPathPurpose
GET/v1/admin/coverageReport which shares are protected versus overdue.

Admin target templates (/v1/admin/target-templates)

MethodPathPurpose
GET/v1/admin/target-templatesCross-tenant listing of all templates.

Scheduling (/v1/admin/scheduling)

MethodPathPurpose
GET/v1/admin/schedulingCurrent scheduler status.
POST/v1/admin/scheduling/pausePause all policy-driven backup scheduling.
POST/v1/admin/scheduling/resumeResume scheduling.

Reconciliation (/v1/admin/reconciliation)

MethodPathPurpose
GET/v1/admin/reconciliationReconciliation loop status (last run, next run, any detected drift).
POST/v1/admin/reconciliation/triggerForce an immediate reconciliation sweep.

CLI equivalents

The abaca distribution provides the openstack share protection … CLI (via python-openstackclient). Each subcommand maps to an API call:

# List policies
openstack share protection policy list

# Create an on-demand backup
openstack share protection backup create --share <share-id> --policy <policy-id>

# Show a job
openstack share protection job show <job-id>

# Restore a backup to a new share
openstack share protection restore create \
  --backup <backup-id> \
  --mode new_share

Examples

1 — Register an S3 bucket as a backup target

This assumes a Domain-owner token is in $TOKEN and the target template already exists.

curl -s -X POST \
  -H "X-Auth-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "bucket_name": "acme-backups-prod",
    "template_id": "tmpl-11111111",
    "s3_access_key_ref": "<barbican-href>",
    "s3_secret_key_ref": "<barbican-href>"
  }' \
  "$ABACA/targets"

Expected response (202 Accepted):

{
  "id": "tgt-22222222",
  "bucket_name": "acme-backups-prod",
  "template_id": "tmpl-11111111",
  "status": "enrolling",
  "project_id": "proj-33333333",
  "registered_by": "user-44444444",
  "kopia_key_ref": null,
  "usage_stats": null
}

The status will transition to available once the enrollment job (which runs preflight checks and initialises the Kopia repository) completes. Poll the job or the target to track progress.


2 — Request an on-demand backup

curl -s -X POST \
  -H "X-Auth-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "share_id": "share-55555555",
    "policy_id": "pol-66666666"
  }' \
  "$ABACA/backups"

Expected response (202 Accepted):

{
  "id": "bkp-77777777",
  "share_id": "share-55555555",
  "target_bucket_id": "tgt-22222222",
  "status": "running",
  "crash_consistent": true,
  "kopia_snapshot_id": null
}

The kopia_snapshot_id is populated once the transfer completes.


3 — Poll a job until it reaches a terminal state

JOB_ID=job-88888888

while true; do
  STATE=$(curl -s -H "X-Auth-Token: $TOKEN" \
    "$ABACA/jobs/$JOB_ID" | jq -r '.state')
  echo "$(date -u +%H:%M:%S)  state=$STATE"
  [[ "$STATE" == "available" || "$STATE" == "error" ]] && break
  sleep 10
done

Sample output:

14:02:10  state=provisioning_network
14:02:20  state=provisioning_source
14:02:30  state=connecting_repository
14:02:40  state=transferring
14:04:55  state=finalizing
14:05:05  state=available

4 — Restore a backup to a new share (sub-path)

curl -s -X POST \
  -H "X-Auth-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "backup_id": "bkp-77777777",
    "mode": "new_share",
    "sub_path": "reports/2025-Q1"
  }' \
  "$ABACA/restores"

Expected response (202 Accepted):

{
  "id": "rst-99999999",
  "backup_id": "bkp-77777777",
  "mode": "new_share",
  "sub_path": "reports/2025-Q1",
  "force": false,
  "status": "running"
}

5 — Admin: pause scheduling before a maintenance window

# Pause
curl -s -X POST \
  -H "X-Auth-Token: $ADMIN_TOKEN" \
  "$ABACA/admin/scheduling/pause"
# Response: 202 Accepted

# Confirm
curl -s -H "X-Auth-Token: $ADMIN_TOKEN" \
  "$ABACA/admin/scheduling" | jq .

Expected status response:

{
  "paused": true,
  "paused_at": "2025-07-14T14:00:00Z",
  "next_run": null
}

Resume after maintenance:

curl -s -X POST \
  -H "X-Auth-Token: $ADMIN_TOKEN" \
  "$ABACA/admin/scheduling/resume"

6 — Admin: drain a worker before rolling a new image

WORKER_ID=wkr-aaaaaaaa

curl -s -X POST \
  -H "X-Auth-Token: $ADMIN_TOKEN" \
  "$ABACA/admin/workers/$WORKER_ID/drain"
# Response: 202 Accepted

# Watch until the worker has no running jobs, then retire it
curl -s -X POST \
  -H "X-Auth-Token: $ADMIN_TOKEN" \
  "$ABACA/admin/workers/$WORKER_ID/retire"

7 — List backups for a specific share (newest-first, including expired)

curl -s \
  -H "X-Auth-Token: $TOKEN" \
  "$ABACA/backups?share_id=share-55555555&include_expired=true&limit=10" \
  | jq '.items[] | {id, status, crash_consistent, kopia_snapshot_id}'

8 — Using the CLI plugin

# Create a protection policy
openstack share protection policy create \
  --share share-55555555 \
  --target-template tmpl-11111111 \
  --schedule "0 2 * * *" \
  --retention '{"keep_last": 7}' \
  my-daily-policy

# List backups for a share
openstack share protection backup list --share share-55555555

# Show admin coverage report
openstack share protection admin coverage show

Troubleshooting

Job stays in queued indefinitely

Symptom: GET /v1/jobs/{id} returns state=queued for longer than the queued_job_deadline_seconds value (default 300 s), and the reconciler has not moved it forward.

Likely causes:

  • No worker VMs are available or all workers are at capacity (capacity_slots exhausted).
  • The conductor is not running or is not connected to RabbitMQ.

Fix:

  1. Check the worker fleet: GET /v1/admin/workers. If it is empty, the conductor needs to boot workers — verify [conductor] min_workers, worker_boot_image, worker_boot_flavor, and worker_boot_network in abaca.conf.
  2. Check conductor logs in OpenShift: oc logs -n abaca deployment/abaca-conductor.
  3. Force a reconciliation sweep to unstick the job: POST /v1/admin/reconciliation/trigger.

403 Forbidden on admin endpoints

Symptom: POST /v1/admin/scheduling/pause (or any /v1/admin/… call) returns 403 with category=tenant_action_required.

Likely cause: The token used does not carry the required admin policy role.

Fix: Obtain a token for a user with the operator role and retry. Check the effective policy with openstack policy show or review policy.yaml in the abaca configuration directory.


Target enrollment fails (job reaches error in connecting_repository)

Symptom: The enrollment job transitions to error at the connecting_repository stage. The error detail says the bucket is unreachable or credentials are invalid.

Likely causes:

  • S3 endpoint URL is wrong, or the bucket does not exist.
  • The Barbican hrefs passed as s3_access_key_ref / s3_secret_key_ref are for a different project or the trust has been revoked.
  • Object Lock or versioning is not enabled on the bucket (required for WORM immutability).

Fix:

  1. Verify the S3 endpoint and credentials independently (e.g. using the aws CLI or mc).
  2. Confirm that the Keystone trust is still valid: check the Barbican href is accessible from the Abacá service user's context.
  3. Verify the bucket was created with versioning and Object Lock enabled — these cannot be enabled retroactively.
  4. Retry the enrollment: POST /v1/admin/jobs/{job_id}/retry.

409 Conflict when cancelling or retrying a job

Symptom: POST /v1/admin/jobs/{id}/cancel or /retry returns 409.

Likely cause: The job is already in a terminal state (available or error) or has already been cancelled. You cannot cancel a completed job or retry one that is still running.

Fix: Fetch the current job state first with GET /v1/jobs/{id} and act based on the actual state value.


Backups list appears empty even though backups exist

Symptom: GET /v1/backups returns an empty list, but you know backups were taken.

Likely cause: Expired backups are hidden by default. If all backups for the share have been marked expired by the retention policy, the default view shows nothing.

Fix: Add ?include_expired=true to surface expired backups, or add ?status=<value> to filter by a specific status. Also check ?share_id=<uuid> to scope the query to the right share.


Worker drain returns 409

Symptom: POST /v1/admin/workers/{id}/drain returns 409.

Likely cause: The worker is already in a draining or retiring state.

Fix: Check the worker's current state with GET /v1/admin/workers. If it is already draining, wait for running jobs to complete. If it is already retired, no further action is needed.


category: operator_action_required in a backup error

Symptom: A backup job reaches error and the error envelope contains category=operator_action_required.

Meaning: The failure is infrastructure-side — the tenant cannot fix it themselves. Common causes include no available worker VMs, a RabbitMQ connectivity problem, or a Barbican service outage.

Fix: Check the conductor logs (oc logs -n abaca deployment/abaca-conductor) and the worker fleet status (GET /v1/admin/workers). Resolve the infrastructure issue, then retry the job via POST /v1/admin/jobs/{id}/retry.