Running Operations

Five modules ask PCP to perform work: backup, restore, sync, check and prune. Each names the apps it acts on, and each creates a job that PCP runs.

They describe no desired state, so they act every time the play runs. That makes them the counterpart of a schedule rather than a replacement for one. A schedule repeats on an interval PCP controls. A playbook runs the work when Ansible reaches the task, such as before a release or after a migration.

- name: Back up the production database
  plakarkorp.plakar.backup:
    source: Production DB
    store: S3 Store
    labels: [nightly]

- name: Replicate the store offsite
  plakarkorp.plakar.sync:
    store: S3 Store
    to_store: Offsite S3

- name: Restore the latest snapshot onto the recovery target
  plakarkorp.plakar.restore:
    store: S3 Store
    destination: Recovery target

Waiting for a job

Backups and restores run asynchronously in PCP. By default a module polls until the job stops, so the task finishes when the work does, and a failed job fails the task.

wait_timeout bounds that polling and defaults to 600 seconds. A backup of a large source needs more.

Setting wait: false returns as soon as the run is accepted. The play continues immediately, and the run is followed later with job_info.

A module returns at_id, the identifier of the one-shot run it created. The run becomes a job only once the scheduler picks it up, so job_info returns an empty jobs list until then, which is what until waits out:

- name: Start the backup without blocking the play
  plakarkorp.plakar.backup:
    source: Production DB
    store: S3 Store
    wait: false
  register: started

- name: Follow the run once the scheduler materializes it
  plakarkorp.plakar.job_info:
    at_id: "{{ started.at_id }}"
  register: job
  until: job.jobs | length > 0
  retries: 30
  delay: 10

A run that has already finished carries its job in job, so job.id is what job_info takes as job_id.

Reading job state

job_info reads one job, or a list filtered by task_type and status. The filtered form reports on what PCP has been doing, rather than on what this play started:

- name: Report the backups that failed
  plakarkorp.plakar.job_info:
    task_type: backup
    status: failed
  register: failed

Pruning

prune applies a retention rule to the snapshots in a store. The rule is expressed in buckets: minute, hour, day, week, month and year set how many recent buckets of that size to keep, and per_minute through per_year set how many snapshots to keep within each bucket.

- name: Keep a daily snapshot for a week, a monthly one for a year
  plakarkorp.plakar.prune:
    store: S3 Store
    retention:
      day: 7
      per_day: 1
      month: 12
      per_month: 1
    group_by: dataset
    tags: [nightly]
    filters:
      ignore_tags: [do-not-prune]

group_by: dataset partitions the snapshots before the rule is applied, so the allowance holds for each source separately rather than across the whole store. Without it, a source that backs up frequently can consume the allowance of the others.

tags narrows the rule to snapshots carrying those tags, and filters.ignore_tags excludes snapshots from consideration entirely.

Snapshots under a legal hold are never deleted, whichever rule matches them.

Checking a rule before it runs

A prune in check mode deletes nothing. It reports what the rule would delete, what it would keep, and what it held back:

$ ansible-playbook retention.yml --check