]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s3:librpc: add cluster_level.idl
authorStefan Metzmacher <metze@samba.org>
Tue, 14 Jul 2026 14:59:07 +0000 (16:59 +0200)
committerRalph Boehme <slow@samba.org>
Tue, 4 Aug 2026 09:51:32 +0000 (09:51 +0000)
This defines the basic structure to implement
a cluster wide functional level.

Upstream (master) will basically update the major number
if a incompatible change was made, similar to
SMB_VFS_INTERFACE_VERSION, at the same time
minor latest is changes to 0.

For any backport the minor number needs to
be updated there.

See also the large comments in the file itself.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
librpc/wscript_build
source3/librpc/idl/cluster_level.idl [new file with mode: 0644]
source3/librpc/idl/wscript_build
source3/librpc/wscript_build

index 98f2777a528ae52dff43c038e4db1e0cc8b43f30..cc88c72d0431b4331463f48eb971b578c901e907 100644 (file)
@@ -646,7 +646,7 @@ bld.SAMBA_LIBRARY('ndr-samba',
     deps='''NDR_DRSBLOBS NDR_DRSUAPI NDR_IDMAP NDR_NTLMSSP NDR_NEGOEX NDR_SCHANNEL NDR_MGMT
     NDR_DNSSERVER NDR_EPMAPPER NDR_XATTR NDR_UNIXINFO NDR_NAMED_PIPE_AUTH
     NDR_NTPRINTING NDR_FSRVP NDR_WITNESS NDR_MDSSVC NDR_OPEN_FILES NDR_SMBXSRV
-    NDR_SMB3POSIX NDR_RPCD_WITNESS
+    NDR_SMB3POSIX NDR_RPCD_WITNESS NDR_CLUSTER_LEVEL
     NDR_KRB5CCACHE NDR_WSP NDR_GKDI NDR_GMSA
     NDR_KEYCREDLINK NDR_BCRYPT_RSAKEY_BLOB NDR_TPM20_RSAKEY_BLOB''',
     private_library=True,
diff --git a/source3/librpc/idl/cluster_level.idl b/source3/librpc/idl/cluster_level.idl
new file mode 100644 (file)
index 0000000..efb6aba
--- /dev/null
@@ -0,0 +1,296 @@
+#include "idl_types.h"
+
+import "misc.idl";
+
+[
+       uuid("a84e4f68-7f89-11f1-969e-3fec00e1ddde"),
+       version(0.0),
+       pointer_default(unique),
+       helpstring("cluster functional level structures")
+]
+interface cluster_level
+{
+       /*
+        * The basic design of the cluster functional
+        * level is the following:
+        *
+        * 1. The global active functional level
+        *
+        *    There is one globally active level, which
+        *    consists of a major and a minor version
+        *    (e.g. 1.0). As it is a single global value,
+        *    the split into major and minor does not
+        *    matter here, it only becomes relevant for
+        *    the other aspects described below.
+        *
+        *    Every new Samba release (before rc1) bumps
+        *    the major version and resets the minor
+        *    version to 0, following the same logic we
+        *    use for SMB_VFS_INTERFACE_VERSION.
+        *
+        *    Backports to stable branches should be
+        *    avoided, but may be needed for critical bug
+        *    fixes or security fixes. A backport bumps
+        *    the minor version, and the checks that gate
+        *    features on specific levels need to be
+        *    adjusted accordingly.
+        *
+        * 2. The supported functional level ranges per node
+        *
+        *    Each node has an array of supported level
+        *    ranges, hardcoded in the source code.
+        *
+        *    Each range specifies a major version
+        *    together with the lowest and the highest
+        *    supported minor version; every minor
+        *    version in between is supported as well.
+        *
+        *    We start with a single range, 1.0 -> 1.0,
+        *    which means only the initial level is
+        *    supported.
+        *
+        *    As new features are added, more ranges
+        *    appear, e.g. 3.0 -> 3.0; 2.0 -> 2.5;
+        *    1.3 -> 1.9. Such a node can participate in
+        *    a cluster whose active level falls into any
+        *    of these ranges.
+        *
+        *    A backportable patchset also requires the
+        *    corresponding minor bump in master, and it has
+        *    to be applied to all older major levels there,
+        *    so that further backports remain possible.
+        *
+        *    master changes from
+        *      3.0 -> 3.0; 2.0 -> 2.5; 1.3 -> 1.9
+        *    to
+        *      3.0 -> 3.0; 2.0 -> 2.6; 1.3 -> 1.10
+        *
+        *    the latest stable branch changes from
+        *      2.0 -> 2.5; 1.0 -> 1.9
+        *    to
+        *      2.0 -> 2.6; 1.0 -> 1.10
+        *
+        *    and an older stable branch changes from
+        *      1.0 -> 1.9
+        *    to
+        *      1.0 -> 1.10
+        *
+        *    Removing old features should be avoided,
+        *    but it can happen, either by dropping the
+        *    oldest major levels or by raising the
+        *    lowest supported minor version. In the
+        *    example above, support for 1.0 -> 1.2 had
+        *    already been dropped in master some time
+        *    before.
+        *
+        * 3. Server startup (non-clustered or cluster node)
+        *
+        *    In the non-clustered case the global active
+        *    level is initialized with the highest
+        *    supported major.minor_max level, so the
+        *    latest features are always used.
+        *
+        *    In a cluster the global active level is
+        *    stored in the persistent database
+        *    "cluster_level.tdb" under the key
+        *    "CLUSTER_LEVEL_GLOBAL" as
+        *    struct cluster_level_globalB.
+        *
+        *    On a fresh installation the first process
+        *    initializes that record with its highest
+        *    supported major.minor_max level.
+        *
+        *    The node then checks whether the global
+        *    active level is compatible with its own
+        *    supported level ranges. If it is not, the
+        *    node exits instead of trying to participate
+        *    in the cluster.
+        *
+        *    The first process started on a node updates
+        *    the per node record under the key
+        *    "CLUSTER_LEVEL_NODE/0x00000000", where
+        *    0x00000000 is the vnn of the node in hex,
+        *    storing struct cluster_level_nodeB. The pid
+        *    and the start time of ctdbd are stored in
+        *    that record as well, so that a cached
+        *    record can be recognized as still valid by
+        *    comparing these values.
+        *
+        *    Once a struct messaging_context has been
+        *    created successfully, the active level is
+        *    known and can be acted upon. In most cases
+        *    this is the context returned by
+        *    global_messaging_context().
+        *
+        * 4. Gating features on the active functional level
+        *
+        *    The general design is that code is always
+        *    written such that it can read database
+        *    records and handle messages of all
+        *    supported levels.
+        *
+        *    New features are only gated at write/send
+        *    time.
+        *
+        *    That allows code like this:
+        *
+        *      if (CLUSTER_LEVEL_ACTIVATED(5, 2)) {
+        *         // handle 5.2, e.g. format_version = 3
+        *      } else if (CLUSTER_LEVEL_ACTIVATED(3, 0)) {
+        *         // handle 3.0, e.g. format_version = 2
+        *      } else if (CLUSTER_LEVEL_ACTIVATED(1, 0)) {
+        *         // handle 1.0, e.g. format_version = 1
+        *      } else if (CLUSTER_LEVEL_ACTIVATED(0, 1)) {
+        *         // handle 0.1, e.g. format_version = 0 or autodetect
+        *      } else {
+        *         // error!!! 0.0 should never be active
+        *      }
+        *
+        *    Note that the versioning of an individual
+        *    database or message remains independent of
+        *    the cluster functional level. Code like the
+        *    above only maps the global level onto the
+        *    record or message version used for a given
+        *    feature.
+        *
+        *    The basic rule for persistent databases is
+        *    that the first writer converts the whole
+        *    database in a single transaction.
+        *
+        *    For databases with a large number of
+        *    records (mostly volatile ones, but also the
+        *    persistent idmap databases) we will use
+        *    different strategies, e.g. converting each
+        *    record as it is modified, or converting the
+        *    database asynchronously in the background.
+        *
+        * 5. Raising the global active functional level
+        *
+        *    The active level may only be raised if all
+        *    nodes support the same highest
+        *    major.minor_max combination, and it can
+        *    only be raised to exactly that level.
+        *
+        *    By design there is no way to raise the
+        *    level to an intermediate level that is
+        *    supported but not the latest one, we always
+        *    raise to the latest.
+        *
+        *    In order to avoid problems, at least the
+        *    first iteration of the code additionally
+        *    verifies that all nodes have registered the
+        *    exact same set of ranges, which in practice
+        *    means that all nodes most likely run the
+        *    same software version.
+        *
+        *    The upgrade happens in a transaction,
+        *    followed by a message to all processes
+        *    connected to ctdbd on all nodes, sent via
+        *    messaging_send_all().
+        *
+        *    So for some time code may still have the
+        *    old level cached in memory and write or
+        *    send old records or messages. That is not a
+        *    real problem, because old records and
+        *    messages are handled transparently on read.
+        *
+        * 6. Additional notes for the future
+        *
+        *    As a first step the database is managed by
+        *    code outside of ctdb.
+        *
+        *    In the future (at the latest once there are
+        *    incompatible changes to the ctdb protocol
+        *    or behavior) we may let ctdbd manage the
+        *    database and expose the currently active
+        *    level via a local CTDB_CONTROL_. That
+        *    control could return the vnn as well, so
+        *    that the Samba side only needs a single
+        *    CTDB_CONTROL_ after connecting.
+        */
+
+
+       /*
+        * The following defines levels and structures
+        * for the main logic in the code.
+        */
+
+       const int CLUSTER_LEVEL_MAJOR_1                 = 0x00000001;
+       const int CLUSTER_LEVEL_MAJOR_1_MINOR_0         = 0x00000000;
+       const int CLUSTER_LEVEL_MAJOR_1_MINOR_MIN       =
+               CLUSTER_LEVEL_MAJOR_1_MINOR_0;
+       const int CLUSTER_LEVEL_MAJOR_1_MINOR_MAX       =
+               CLUSTER_LEVEL_MAJOR_1_MINOR_0;
+
+       const int CLUSTER_LEVEL_MAJOR_LATEST            =
+               CLUSTER_LEVEL_MAJOR_1;
+       const int CLUSTER_LEVEL_MAJOR_LATEST_MINOR_MAX  =
+               CLUSTER_LEVEL_MAJOR_1_MINOR_MAX;
+
+       typedef struct {
+               uint32 major;
+               uint32 minor;
+       } cluster_level_active;
+
+       typedef struct {
+               uint32 major;
+               uint32 minor_min;
+               uint32 minor_max;
+       } cluster_level_range;
+
+       typedef struct {
+               [range(1, 256)] uint32  num_ranges;
+               cluster_level_range     ranges[num_ranges];
+       } cluster_level_ranges;
+
+
+       /*
+        * The following is just for internals
+        * of the cluster_level.tdb.
+        */
+
+       const string CLUSTER_LEVEL_TDB_NAME = "cluster_level.tdb";
+
+       /*
+        * This is just versioning for the
+        * cluster_level.tdb itself, it's not a global value.
+        */
+       typedef [v1_enum] enum {
+               CLUSTER_LEVEL_DB_VERSION_1 = 0x00000001
+       } cluster_level_db_version;
+
+       typedef struct {
+               timeval                 update_time;
+               cluster_level_active    active_level;
+       } cluster_level_global1;
+
+       typedef union {
+               [case(CLUSTER_LEVEL_DB_VERSION_1)] cluster_level_global1 info1;
+               [default] ;
+       } cluster_level_globalU;
+
+       typedef [public] struct {
+               cluster_level_db_version                        version;
+               [switch_is(version)] cluster_level_globalU      info;
+       } cluster_level_globalB;
+       const string CLUSTER_LEVEL_GLOBAL_KEY = "CLUSTER_LEVEL_GLOBAL";
+
+       typedef struct {
+               timeval                 update_time;
+               timeval                 ctdbd_start_time;
+               uint32                  ctdbd_pid;
+               cluster_level_ranges    supported_ranges;
+       } cluster_level_node1;
+
+       typedef union {
+               [case(CLUSTER_LEVEL_DB_VERSION_1)] cluster_level_node1 info1;
+               [default] ;
+       } cluster_level_nodeU;
+
+       typedef [public] struct {
+               cluster_level_db_version                        version;
+               [switch_is(version)] cluster_level_nodeU        info;
+       } cluster_level_nodeB;
+       const string CLUSTER_LEVEL_NODE_KEY_PREFIX = "CLUSTER_LEVEL_NODE/";
+       const string CLUSTER_LEVEL_NODE_KEY_FMT = "CLUSTER_LEVEL_NODE/0x%08X";
+}
index 3602ba94b51e52801394b7f4c3f76c234ac6f3e7..0a22c0056f0a50beb0977793ae77136d05b8750f 100644 (file)
@@ -10,6 +10,7 @@ bld.SAMBA_PIDL_LIST('PIDL',
                        smbXsrv.idl
                        leases_db.idl
                        rpcd_witness.idl
+                       cluster_level.idl
                     ''',
                     options='--includedir=%s --header --ndr-parser --client --python' % topinclude,
                     output_dir='../gen_ndr')
index d228db7a218a62d2ad11ef1d731ab210a3be6120..ee1275ce392dabff7af237151b2fdd15d6ad1500 100644 (file)
@@ -36,6 +36,11 @@ bld.SAMBA3_SUBSYSTEM('NDR_RPCD_WITNESS',
        public_deps='ndr NDR_SERVER_ID NDR_SECURITY NDR_WITNESS'
        )
 
+bld.SAMBA3_SUBSYSTEM('NDR_CLUSTER_LEVEL',
+       source='gen_ndr/ndr_cluster_level.c',
+       public_deps='ndr'
+       )
+
 bld.SAMBA3_SUBSYSTEM('NDR_SECRETS',
        source='gen_ndr/ndr_secrets.c',
        public_deps='ndr NDR_SAMR NDR_LSA NDR_NETLOGON NDR_SECURITY'