diff --git a/docs/kb/reference/referential-integrity.md b/docs/kb/reference/referential-integrity.md new file mode 100644 index 0000000..465e569 --- /dev/null +++ b/docs/kb/reference/referential-integrity.md @@ -0,0 +1,173 @@ +--- +title: Referential Integrity +description: What FOG 1.6 does when you delete something other records point at +context_id: referential-integrity +aliases: + - Referential Integrity + - Foreign Keys +tags: + - 1_6-changes + - database + - storage + - images + - hosts + - management +--- + +# Referential Integrity + +Up to and including FOG 1.5, nothing in the database itself recorded that one +record pointed at another. Cleaning up after a delete was the job of the PHP +that ran the delete, which meant every delete path had to remember every +dependent table — and a path that forgot left rows behind pointing at +something that no longer existed. Nobody found those rows until one of them +did something visible: a host in a group that had been deleted, a task +against an image that was gone, a storage node in no group at all. + +FOG 1.6 declares those relationships in the database. The rules below are +enforced by MariaDB on every delete, whatever performed it — the web UI, the +API, a plugin, or a query someone typed by hand. + +>[!note] +>Nothing here changes what you can *create* or *edit*. It only changes what +>happens to related records when something is deleted, and which deletes are +>refused outright. + +## The three outcomes + +Every relationship resolves to one of three behaviors: + +| Outcome | What happens | Used for | +|---|---|---| +| **Cascade** | The dependent rows go too, in the same operation | Things owned by the record — a host's MAC addresses, a group's membership rows | +| **Clear the reference** | The dependent row survives with the reference emptied | Things that *mention* the record — a host's assigned image | +| **Refuse** | The delete is rejected and nothing changes | Configuration something else is actively relying on | + +**The audit trail and history take none of these.** Audit and history rows +deliberately outlive their subject: the record of who deleted a host is worth +nothing if it disappears when the host does. Those rows stay, and they keep +naming the id that used to exist. + +## What each delete now does + +### Deleting a host + +Everything belonging to that host goes with it, in one step: its MAC +addresses, group memberships, snapin and printer assignments, inventory, +module status, screen and auto-logout settings, power management, site +membership, its tasks and snapin jobs, and its location and OU associations +if those plugins are installed. + +Its **history and audit rows stay.** + +### Deleting an image + +- **Hosts assigned that image are unassigned** — the host survives with no + image, rather than pointing at one that is gone. +- Scheduled tasks and any running task lose the image reference the same way. +- Storage-group associations for the image are removed, as are Windows key + associations if that plugin is installed. + +### Deleting a storage group + +**This is refused** while any of the following still points at the group: + +- **storage nodes** that belong to it +- **pending file deletions** queued against it +- a **location** naming it, if that plugin is installed + +Move or delete those first, then delete the group. Snapin and image +associations to the group are *not* a blocker — those are removed with it, +and a running multicast session on the group ends with it. + +>[!warning] +>Before 1.6 this delete was allowed and silently orphaned whatever was +>pointing at the group. On one real installation, deleting a single storage +>group left three storage nodes belonging to nothing. + +### Deleting a storage node + +Not refused. Anything referring to the node loses the reference and carries +on: a running multicast session drops its sender node, tasks drop their node +references, and a **location** that named that specific node falls back to +choosing the best node in its storage group — which is the same thing a +location does when no specific node is set. + +### Removing a storage node from its group + +**A storage node must belong to a storage group.** A group with no nodes is +fine; a node in no group is not — it is invisible to replication and to +multicast, and nothing will ever assign it work. + +There is therefore no "remove from group" operation any more. **To move a +node, assign it to the group you want it in** — that moves it in one step, +and leaving its old group empty is not a problem. + +### Deleting an image type, an OS, or a task state + +**Refused** while any image or task is using it. These are the fixed lists +FOG's own behavior keys off; deleting one out from under a record in use +would leave that record undescribable. + +### Deleting a user or a role + +Everything granted to or through them goes: site memberships, role +assignments, group memberships, API tokens, stored credentials, and any +LDAP or OIDC grants and identities. + +An OIDC **identity** — the record that a particular external account *is* a +particular FOG user — goes with either end. That is deliberate: left behind, +the next user created could inherit someone else's sign-in binding. + +## What a refused delete looks like + +The refusal comes from the database, and FOG surfaces the database's own +message. It names the constraint, and the constraint is named after the table +and column that is still pointing at the record: + +``` +Cannot delete or update a parent row: a foreign key constraint fails +(`fog`.`nfsGroupMembers`, CONSTRAINT `fk_nfsGroupMembers_ngmGroupID` +FOREIGN KEY (`ngmGroupID`) REFERENCES `nfsGroups` (`ngID`)) +``` + +Read it as: **`nfsGroupMembers` still has rows in this group** — a storage +node has not been moved out. `fk_