From: Stefan Metzmacher Date: Tue, 14 Jul 2026 14:59:07 +0000 (+0200) Subject: s3:librpc: add cluster_level.idl X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=93edf67e0fcc5057e5d96f5dfcd3d4f665d7ca6e;p=thirdparty%2Fsamba.git s3:librpc: add cluster_level.idl 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 Reviewed-by: Ralph Boehme --- diff --git a/librpc/wscript_build b/librpc/wscript_build index 98f2777a528..cc88c72d043 100644 --- a/librpc/wscript_build +++ b/librpc/wscript_build @@ -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 index 00000000000..efb6aba449e --- /dev/null +++ b/source3/librpc/idl/cluster_level.idl @@ -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"; +} diff --git a/source3/librpc/idl/wscript_build b/source3/librpc/idl/wscript_build index 3602ba94b51..0a22c0056f0 100644 --- a/source3/librpc/idl/wscript_build +++ b/source3/librpc/idl/wscript_build @@ -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') diff --git a/source3/librpc/wscript_build b/source3/librpc/wscript_build index d228db7a218..ee1275ce392 100644 --- a/source3/librpc/wscript_build +++ b/source3/librpc/wscript_build @@ -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'