From f7fcbe2d3fac27316b67ea52dd2534ab80142443 Mon Sep 17 00:00:00 2001 From: Adam McCartney Date: Mon, 24 Aug 2026 12:03:50 +0000 Subject: [PATCH 1/3] feat(stratum1): support partial replication Add optional per-repository partial replication configuration to the stratum1 role, inserted between the server options include and the initial snapshot is already partial. Driven by an optional per-repo dict: cvmfs_repositories[*].partial_replication: enabled: true paths: [/software/linux/x86_64/amd/zen4, /software/linux/x86_64/amd/zen5] When enabled it writes partial-replication.spec (version 1 + paths) and sets CVMFS_PARTIAL_REPLICATION=true and CVMFS_PARTIAL_REPLICATION_SPEC in the repo server.conf before the initial snapshot. When not enabled (or enabled: false) it removes that config, restoring full-sync fallback. Partial replication requires CVMFS 2.14.0+. Co-Authored-by: Pi (asc/moonshotai/Kimi-K3) --- tasks/partial_replication.yml | 49 +++++++++++++++++++++++++++++++++++ tasks/stratum1.yml | 3 +++ 2 files changed, 52 insertions(+) create mode 100644 tasks/partial_replication.yml diff --git a/tasks/partial_replication.yml b/tasks/partial_replication.yml new file mode 100644 index 0000000..5fd5812 --- /dev/null +++ b/tasks/partial_replication.yml @@ -0,0 +1,49 @@ +--- +- name: Write partial replication specifications + ansible.builtin.copy: + dest: /etc/cvmfs/repositories.d/{{ item.repository }}/partial-replication.spec + content: "version 1\n{% for path in item.partial_replication.paths %}{{ path }}\n{% endfor %}" + mode: 0644 + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "{{ item.repository }} partial-replication.spec" + when: item.partial_replication is defined and item.partial_replication.enabled + +- name: Enable partial replication flag in server configuration + ansible.builtin.lineinfile: + path: /etc/cvmfs/repositories.d/{{ item.repository }}/server.conf + regexp: '^CVMFS_PARTIAL_REPLICATION=.*' + line: 'CVMFS_PARTIAL_REPLICATION=true' + mode: 0644 + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "{{ item.repository }}: CVMFS_PARTIAL_REPLICATION" + when: item.partial_replication is defined and item.partial_replication.enabled + +- name: Set partial replication specification path in server configuration + ansible.builtin.lineinfile: + path: /etc/cvmfs/repositories.d/{{ item.repository }}/server.conf + regexp: '^CVMFS_PARTIAL_REPLICATION_SPEC=.*' + line: 'CVMFS_PARTIAL_REPLICATION_SPEC=/etc/cvmfs/repositories.d/{{ item.repository }}/partial-replication.spec' + mode: 0644 + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "{{ item.repository }}: CVMFS_PARTIAL_REPLICATION_SPEC" + when: item.partial_replication is defined and item.partial_replication.enabled + +- name: Remove partial replication configuration (full-sync fallback) + block: + - name: Remove partial replication specification file + ansible.builtin.file: + path: /etc/cvmfs/repositories.d/{{ item.repository }}/partial-replication.spec + state: absent + - name: Remove partial replication lines from server configuration + ansible.builtin.lineinfile: + path: /etc/cvmfs/repositories.d/{{ item.repository }}/server.conf + regexp: '^CVMFS_PARTIAL_REPLICATION(_SPEC)?=.*' + state: absent + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "{{ item.repository }}: remove partial replication" + when: item.partial_replication is not defined or not item.partial_replication.enabled + diff --git a/tasks/stratum1.yml b/tasks/stratum1.yml index ab866fa..5d59da6 100644 --- a/tasks/stratum1.yml +++ b/tasks/stratum1.yml @@ -102,6 +102,9 @@ vars: _cvmfs_repo_option_key: server +- name: Include partial replication tasks + ansible.builtin.include_tasks: partial_replication.yml + - name: Perform initial snapshot ansible.builtin.command: /usr/bin/cvmfs_server snapshot {{ item.item.repository }} loop: "{{ __cvmfs_add_replica_result.results }}" From 8d67a93785db2cc05f9b6178319f7972f5f40ab8 Mon Sep 17 00:00:00 2001 From: Adam McCartney Date: Mon, 24 Aug 2026 12:43:40 +0000 Subject: [PATCH 2/3] fix(stratum1): split partial-replication cleanup out of a block Ansible rejects 'loop' on a block, which made the full-sync fallback cleanup fail at runtime. Split it into two plain tasks, each carrying its own loop/when. --- tasks/partial_replication.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tasks/partial_replication.yml b/tasks/partial_replication.yml index 5fd5812..96de32e 100644 --- a/tasks/partial_replication.yml +++ b/tasks/partial_replication.yml @@ -31,19 +31,22 @@ label: "{{ item.repository }}: CVMFS_PARTIAL_REPLICATION_SPEC" when: item.partial_replication is defined and item.partial_replication.enabled -- name: Remove partial replication configuration (full-sync fallback) - block: - - name: Remove partial replication specification file - ansible.builtin.file: - path: /etc/cvmfs/repositories.d/{{ item.repository }}/partial-replication.spec - state: absent - - name: Remove partial replication lines from server configuration - ansible.builtin.lineinfile: - path: /etc/cvmfs/repositories.d/{{ item.repository }}/server.conf - regexp: '^CVMFS_PARTIAL_REPLICATION(_SPEC)?=.*' - state: absent +- name: Remove partial replication specification file (full-sync fallback) + ansible.builtin.file: + path: /etc/cvmfs/repositories.d/{{ item.repository }}/partial-replication.spec + state: absent loop: "{{ cvmfs_repositories }}" loop_control: - label: "{{ item.repository }}: remove partial replication" + label: "{{ item.repository }}: remove partial-replication.spec" + when: item.partial_replication is not defined or not item.partial_replication.enabled + +- name: Remove partial replication lines from server configuration (full-sync fallback) + ansible.builtin.lineinfile: + path: /etc/cvmfs/repositories.d/{{ item.repository }}/server.conf + regexp: '^CVMFS_PARTIAL_REPLICATION(_SPEC)?=.*' + state: absent + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "{{ item.repository }}: clear partial replication lines" when: item.partial_replication is not defined or not item.partial_replication.enabled From a036c856cc285a150ebd6787c199e0586bc9a5d2 Mon Sep 17 00:00:00 2001 From: Adam McCartney Date: Wed, 26 Aug 2026 14:40:38 +0000 Subject: [PATCH 3/3] feat(client): support partial replica client opt-in Add optional per-repository client configuration for consuming a partial Stratum 1 (CernVM-FS >= 2.14.0), driven by: cvmfs_repositories[*].partial_replica_client: enabled: true mode: failover | fail server_url: ... # optional per-repo override full_stratum1_url: ... # required for failover For enabled repos it writes /etc/cvmfs/config.d/.local with CVMFS_PARTIAL_REPLICA_MODE, CVMFS_FULL_STRATUM1_URL (failover) and an optional CVMFS_SERVER_URL override pointing at the partial replica. config.d/.local is parsed last in the client config chain, so it overrides domain-level settings; the file is treated as fully managed. Disabled or absent config removes the file (full-replica fallback). Note: repositories.d//client.conf (used by client_options) is NOT read by the client mount chain (see OptionsManager::ParseDefault), so partial replica parameters must not go there. Co-Authored-by: Pi (asc/moonshotai/Kimi-K3) --- tasks/client.yml | 3 ++ tasks/client_partial_replica.yml | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tasks/client_partial_replica.yml diff --git a/tasks/client.yml b/tasks/client.yml index de4fa89..22f6cca 100644 --- a/tasks/client.yml +++ b/tasks/client.yml @@ -89,6 +89,9 @@ vars: _cvmfs_repo_option_key: client +- name: Include partial replica client tasks + ansible.builtin.include_tasks: client_partial_replica.yml + - name: Install cvmfs_wipecache setuid binary ansible.builtin.copy: src: cvmfs_wipecache.{{ _cvmfs_install_setuid_platform }}_{{ ansible_distribution_major_version }} diff --git a/tasks/client_partial_replica.yml b/tasks/client_partial_replica.yml new file mode 100644 index 0000000..b370446 --- /dev/null +++ b/tasks/client_partial_replica.yml @@ -0,0 +1,63 @@ +--- +# Client-side opt-in for using a partial Stratum 1 replica, driven by an +# optional per-repo dict: +# +# cvmfs_repositories[*].partial_replica_client: +# enabled: true +# mode: failover # or: fail +# # Optional per-repo override of CVMFS_SERVER_URL pointing the client +# # at the partial replica (a ';'-separated list is allowed, the +# # regular server list failover semantics apply). Without it, the +# # domain-level CVMFS_SERVER_URL is used. +# server_url: "http://partial-s1.example.org/cvmfs/repo.example.org" +# # Required for mode 'failover'. Ignored for mode 'fail'. +# full_stratum1_url: "http://full-s1.example.org/cvmfs/repo.example.org" +# +# The config is written to /etc/cvmfs/config.d/.local, the last file +# in the client's config chain, so it overrides domain-level settings. +# That file is considered fully managed here; do not mix other settings in. +# +# Requires CernVM-FS >= 2.14.0 on the client. + +- name: Validate partial replica client configuration + ansible.builtin.assert: + that: + - item.partial_replica_client.mode in ['failover', 'fail'] + - item.partial_replica_client.mode == 'fail' or + item.partial_replica_client.full_stratum1_url is defined + fail_msg: >- + Invalid partial_replica_client for {{ item.repository }}: mode must + be 'failover' or 'fail'; 'failover' requires full_stratum1_url. + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "{{ item.repository }}: validate partial_replica_client" + when: item.partial_replica_client is defined and item.partial_replica_client.enabled + +- name: Write partial replica client configuration + ansible.builtin.copy: + dest: "/etc/cvmfs/config.d/{{ item.repository }}.local" + content: | + ## This file is maintained by Ansible - CHANGES WILL BE OVERWRITTEN + {% if item.partial_replica_client.server_url is defined %} + CVMFS_SERVER_URL="{{ item.partial_replica_client.server_url }}" + {% endif %} + CVMFS_PARTIAL_REPLICA_MODE={{ item.partial_replica_client.mode }} + {% if item.partial_replica_client.mode == 'failover' %} + CVMFS_FULL_STRATUM1_URL="{{ item.partial_replica_client.full_stratum1_url }}" + {% endif %} + owner: root + group: root + mode: 0444 + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "/etc/cvmfs/config.d/{{ item.repository }}.local" + when: item.partial_replica_client is defined and item.partial_replica_client.enabled + +- name: Remove partial replica client configuration (full-replica fallback) + ansible.builtin.file: + dest: "/etc/cvmfs/config.d/{{ item.repository }}.local" + state: absent + loop: "{{ cvmfs_repositories }}" + loop_control: + label: "/etc/cvmfs/config.d/{{ item.repository }}.local" + when: item.partial_replica_client is not defined or not item.partial_replica_client.enabled