Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ spirit-schemas/
/acs-opcua-server-edge/test/
/.mcp.json

node_modules/
node_modules/
.worktrees/
Original file line number Diff line number Diff line change
Expand Up @@ -853,10 +853,10 @@ export default {
// Set the top level Schema_UUID
this.model.Schema_UUID = this.deviceSchema.uuid

// Set the top level Instance_UUID if it doesn't already exist
if (!this.model.Instance_UUID) {
this.set('Instance_UUID', uuidv4(), this.model)
}
// Set the top level Instance_UUID to the ConfigDB object UUID.
// This ensures stability across schema changes and alignment with the
// device's identity in ConfigDB.
this.set('Instance_UUID', this.device.uuid, this.model)

},

Expand Down
4 changes: 4 additions & 0 deletions acs-configdb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
This `acs-configdb` service satisfies the **Config Store** component of the Factory+ framework and provides a place for applications consuming Factory+ data to store configuration relating to Factory+ entities. It allows configuration to be centrally managed, and allows generic configuration to be shared between applications.

For more information about the Config Store component of Factory+ see the [specification](https://factoryplus.app.amrc.co.uk) or for an example of how to deploy this service see the [AMRC Connectivity Stack repository](https://github.com/AMRC-FactoryPlus/amrc-connectivity-stack).

## Documentation

- [Backup & Restore](docs/backup-restore.md)
98 changes: 98 additions & 0 deletions acs-configdb/docs/backup-restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# ConfigDB Backup & Restore Playbook

## Automated Backups

From version 5.0.5 onwards, a `pg_dump` runs automatically before every
`helm upgrade` via a pre-upgrade hook. Backups are stored in the
`configdb-backups` PVC and rotated to keep the last 5.

**First upgrade to 5.0.5:** The backup PVC does not exist yet, so the
automatic backup is skipped. It is advised that you run a manual backup
before upgrading (see below).

## Manual Backup

```bash
# Dump the database inside the postgres pod
kubectl exec -n factory-plus postgres-1-0 -- \
su - postgres -c "pg_dump configdb > /tmp/configdb-backup.sql"

# Copy the backup to your local machine
kubectl cp factory-plus/postgres-1-0:/tmp/configdb-backup.sql \
./configdb-backup-$(date -u +%Y-%m-%dT%H-%M-%SZ).sql

# Clean up the file in the pod
kubectl exec -n factory-plus postgres-1-0 -- rm /tmp/configdb-backup.sql
```

## Restore

### From a backup file

```bash
# Copy the backup into the postgres pod
kubectl cp configdb-backup-YYYY-MM-DDTHH-MM-SSZ.sql \
factory-plus/postgres-1-0:/tmp/restore.sql

# Exec into the pod
kubectl exec -it -n factory-plus postgres-1-0 -- su - postgres

# Inside the pod: drop and recreate the database
psql <<EOF
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'configdb' AND pid <> pg_backend_pid();

DROP DATABASE configdb;
CREATE DATABASE configdb;
EOF

# Restore
psql -d configdb -f /tmp/restore.sql

# Clean up
rm /tmp/restore.sql
```

After restoring, restart the ConfigDB pod to pick up the restored data:

```bash
kubectl rollout restart deployment/configdb -n factory-plus
```

### From the automated backup PVC

Mount the backup PVC in a temporary pod to access previous backups:

```bash
kubectl run backup-reader --rm -it \
--image=busybox \
--overrides='{
"spec": {
"containers": [{
"name": "backup-reader",
"image": "busybox",
"command": ["sh"],
"stdin": true,
"tty": true,
"volumeMounts": [{
"name": "backups",
"mountPath": "/backups"
}]
}],
"volumes": [{
"name": "backups",
"persistentVolumeClaim": {
"claimName": "configdb-backups"
}
}]
}
}' \
-n factory-plus

# Inside the pod
ls -lh /backups/
```

Copy the desired backup out with `kubectl cp` and follow the restore
steps above.
2 changes: 1 addition & 1 deletion acs-configdb/lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { DB } from '@amrc-factoryplus/pg-client'
import { App, Class, Perm, Service, SpecialObj } from './constants.js'
import { SpecialApps } from './special.js'

const DB_Version = 11;
const DB_Version = 13;

/* Well-known object IDs. This is cheating but it's stupid to keep
* looking them up. */
Expand Down
1 change: 1 addition & 0 deletions acs-configdb/sql/migrate.sql
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ BEGIN;
\ir v9.sql
\ir v10.sql
\ir v11.sql
\ir v13.sql

\ir grant.sql

Expand Down
83 changes: 0 additions & 83 deletions acs-configdb/sql/v12.sql

This file was deleted.

96 changes: 96 additions & 0 deletions acs-configdb/sql/v13.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
-- Factory+ config DB
-- DB schema v13: align Sparkplug Device UUIDs with Instance_UUIDs
-- Copyright 2026 University of Sheffield AMRC

-- When Sparkplug Device objects were registered in ConfigDB their UUIDs
-- were generated independently from the Instance_UUID recorded in their
-- DeviceInformation origin map. InfluxDB historian data is already
-- tagged with the Instance_UUID, so we change the ConfigDB object UUID
-- to match the Instance_UUID (not the reverse).

call migrate_to(13, $$
do $body$
declare
-- Sparkplug Device class UUID
sparkplug_device_class uuid
:= '18773d6d-a70d-443a-b29a-3f1583195290';
-- DeviceInformation app UUID
device_info_app uuid
:= 'a98ffed5-c613-4e70-bfd3-efeee250ade5';

dup_instance_uuid uuid;
obj_id integer;
obj_uuid uuid;
instance_uuid uuid;
begin
-- Pre-flight: check for duplicate Instance_UUIDs among
-- Sparkplug Device objects that have an origin map.
select (c.json->'originMap'->>'Instance_UUID')::uuid
into dup_instance_uuid
from all_membership m
join object o on o.id = m.id
join object app on app.uuid = device_info_app
join config c
on c.object = m.id
and c.app = app.id
where m.class = (
select id from object
where uuid = sparkplug_device_class)
and c.json->'originMap' ? 'Instance_UUID'
group by (c.json->'originMap'->>'Instance_UUID')::uuid
having count(*) > 1
limit 1;

if found then
raise exception
'Duplicate Instance_UUID % found among Sparkplug Device '
'objects — cannot safely align UUIDs; resolve duplicates '
'before running this migration.',
dup_instance_uuid;
end if;

-- Iterate over each Sparkplug Device whose object UUID does
-- not already match its Instance_UUID.
for obj_id, obj_uuid, instance_uuid in
select o.id, o.uuid, (c.json->'originMap'->>'Instance_UUID')::uuid
from all_membership m
join object o on o.id = m.id
join object app on app.uuid = device_info_app
join config c
on c.object = m.id
and c.app = app.id
where m.class = (
select id from object
where uuid = sparkplug_device_class)
and c.json->'originMap' ? 'Instance_UUID'
and o.uuid != (c.json->'originMap'->>'Instance_UUID')::uuid
loop
raise notice 'Aligning Device %: object UUID % -> Instance_UUID %',
obj_id, obj_uuid, instance_uuid;

-- Replace all occurrences of the old object UUID with the
-- Instance_UUID across the entire config table. This handles
-- JSON blobs that cross-reference this device's UUID.
update config
set json = replace(
json::text,
obj_uuid::text,
instance_uuid::text)::jsonb
where json::text like '%' || obj_uuid::text || '%';

-- Update the object UUID itself. The on-update-cascade FKs
-- in membership, subclass, and config.object propagate
-- this change automatically.
update object
set uuid = instance_uuid
where id = obj_id;
end loop;

-- Rebuild all Object Registration config entries to reflect
-- the updated UUIDs.
call update_registration(null);

raise notice 'v13 migration complete: Sparkplug Device UUIDs aligned with Instance_UUIDs.';
end;
$body$;
$$);
13 changes: 13 additions & 0 deletions deploy/templates/configdb/configdb-backups-pvc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{- if and .Values.configdb.enabled .Values.configdb.backup.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: configdb-backups
namespace: {{ .Release.Namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.configdb.backup.storageSize }}
{{- end }}
Loading
Loading