Multi-Tenancy and Organizations¶
The portal is fully multi-tenant aware. Everything happens in the context of an "Organization". This context dictates multiple parameters, for example which service providers or service offerings are available to the organization.
Users of the platform must be part of one or multiple organizations and can have different access rights in different organizations.
Some parts of the portal are global:
- Organizations
- Users
Everything else happens in the context of an organization. Therefore, organizations are a main part of the user interface and will be prominently displayed and enforced.
Users can either be manually granted access to an organization by an organization admin, or they can get an invitation which allows them to join the organization with a certain role.
Organization Origin¶
For some functionality, like filtering available control planes or service plans, we need to know the origin of an organization. As this is a feature specific to the portal, we track the origin of the organization in the portal.
Organization origins have a specific configuration, to be managed in the portal. A default organization origin can be specified, for organizations not having a specific origin configured during creation.
Organization Origin Configuration¶
The organization origin configuration specifies certain behavior:
- Which control planes and plans are available to the organization
- Default billing entity
Audit Log¶
The portal records model changes with django-auditlog. Organizations, memberships, invitations, origins, limit profiles, service instances, compute plans and vouchers are registered, and a few events without a database diff (pause and resume, service configuration edits, backup credential reveals, support session start and end, decommission and rejection) are written by hand.
Because a log entry only carries a generic content type and object ID, the portal
stamps each entry onto the organizations it belongs to at write time
(OrganizationAuditEntry, a derived index with category and timestamp). Stamping at
write time keeps the history of deleted members and instances visible. The
organization is resolved from the entry's serialized data, so it survives deletion;
hand-written entries fall back to a lookup by object ID. Hijack (support session)
entries are stamped onto every organization the impersonated user belongs to.
Rendering happens at read time: a registry maps model and action to a sentence plus an allowlist of fields shown in the before/after table. Fields are allowlisted, never denylisted, so internal columns (Odoo identifiers, approval state, pricelists, signing secrets) never surface. Entries that render to nothing are not stamped at all, which keeps pagination exact. Entries written during a support session carry the impersonating staff user and render as "Servala Support (as ...)"; staff who are members of the organization render under their own name, decided from current membership.
Service configuration edits live in the Kubernetes resource, so auditlog never sees
them. ServiceInstance.update_spec() compares the submitted spec against the live one
and records the difference for the fields the submitting form showed as visible,
before hardcoded parameters and compute plan values are layered on. Labels and
values are stored with the entry and truncated. Changes made outside the portal
(kubectl, operators, sync jobs) are not recorded.
The frontend view is available to owners, administrators and staff under
/org/<slug>/audit-log/, with category tabs and pagination. The index can be
rebuilt at any time with:
--prune removes stamps whose entry no longer renders, which is what narrowing a
field allowlist needs.