From df1f20455af6c7fef3cae24abfc0c6bd55ec2b57 Mon Sep 17 00:00:00 2001 From: Vincenzo Mauro Date: Fri, 23 Jan 2026 11:58:01 +0100 Subject: [PATCH 1/3] added support for TNA platform --- kvirt/cluster/openshift/__init__.py | 16 ++++++-- kvirt/cluster/openshift/ctlplanes.yml | 41 +++++++++++++++++++++ kvirt/cluster/openshift/install-config.yaml | 7 +++- kvirt/cluster/openshift/kcli_default.yml | 5 +++ kvirt/common/__init__.py | 2 +- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/kvirt/cluster/openshift/__init__.py b/kvirt/cluster/openshift/__init__.py index 4a8c39dae..bb0359da2 100644 --- a/kvirt/cluster/openshift/__init__.py +++ b/kvirt/cluster/openshift/__init__.py @@ -134,7 +134,8 @@ def update_registry(config, plandir, cluster, data): patch_oc_mirror(clusterdir) -def create_ignition_files(config, plandir, cluster, domain, api_ip=None, bucket_url=None, ignition_version=None): +def create_ignition_files(config, plandir, cluster, domain, api_ip=None, bucket_url=None, ignition_version=None, + tna=False): clusterdir = os.path.expanduser(f"~/.kcli/clusters/{cluster}") ignition_overrides = {'api_ip': api_ip, 'cluster': cluster, 'domain': domain, 'role': 'master'} ctlplane_ignition = config.process_inputfile(cluster, f"{plandir}/ignition.j2", overrides=ignition_overrides) @@ -144,6 +145,12 @@ def create_ignition_files(config, plandir, cluster, domain, api_ip=None, bucket_ worker_ignition = config.process_inputfile(cluster, f"{plandir}/ignition.j2", overrides=ignition_overrides) with open(f"{clusterdir}/worker.ign", 'w') as f: f.write(worker_ignition) + if tna: + ignition_overrides['role'] = 'arbiter' + arbiter_ignition = config.process_inputfile(cluster, f"{plandir}/ignition.j2", overrides=ignition_overrides) + with open(f"{clusterdir}/arbiter.ign", 'w') as f: + f.write(arbiter_ignition) + del ignition_overrides['role'] if bucket_url is not None: if config.type == 'openstack': ignition_overrides['ca_file'] = config.k.ca_file @@ -584,7 +591,8 @@ def scale(config, plandir, cluster, overrides): return {'result': 'failure', 'reason': "Missing domain..."} os.mkdir(clusterdir) ignition_version = overrides['ignition_version'] - create_ignition_files(config, plandir, cluster, domain, api_ip=api_ip, ignition_version=ignition_version) + create_ignition_files(config, plandir, cluster, domain, api_ip=api_ip, ignition_version=ignition_version, + tna=overrides.get('tna', False)) if storedparameters and os.path.exists(f"{clusterdir}/kcli_parameters.yml"): with open(f"{clusterdir}/kcli_parameters.yml", 'r') as install: installparam = safe_load(install) @@ -663,6 +671,8 @@ def create(config, plandir, cluster, overrides, dnsconfig=None): workers = data['workers'] if workers < 0: return {'result': 'failure', 'reason': f"Invalid number of workers {workers}"} + if data.get('tna', False) and ctlplanes != 2: + return {'result': 'failure', 'reason': "TNA topology requires exactly 2 control plane nodes"} if data['dual_api_ip'] is not None: warning("Forcing dualstack") data['dualstack'] = True @@ -1607,7 +1617,7 @@ def create(config, plandir, cluster, overrides, dnsconfig=None): ignition_version = json.load(f)['ignition']['version'] installparam['ignition_version'] = ignition_version create_ignition_files(config, plandir, cluster, domain, api_ip=api_ip, bucket_url=bucket_url, - ignition_version=ignition_version) + ignition_version=ignition_version, tna=data.get('tna', False)) backup_paramfile(config.client, installparam, clusterdir, cluster, plan, image, dnsconfig) if provider in virt_providers: if provider == 'vsphere': diff --git a/kvirt/cluster/openshift/ctlplanes.yml b/kvirt/cluster/openshift/ctlplanes.yml index 99a3e2b22..1e3d84ef3 100644 --- a/kvirt/cluster/openshift/ctlplanes.yml +++ b/kvirt/cluster/openshift/ctlplanes.yml @@ -102,3 +102,44 @@ extra_files: {{ files }} {% endif %} {% endfor %} +{% if tna %} + +{{ cluster }}-arbiter-0: + domain: {{ domain }} + image: {{ image }} + pool: {{ pool or config_pool }} + enableroot: false + notify: false + flavor: {{ flavor_arbiter or flavor }} + keys: {{ keys }} + numcpus: {{ arbiter_numcpus | default(bootstrap_numcpus) }} + memory: {{ arbiter_memory | default(bootstrap_memory) }} + autostart: {{ autostart }} +{% if kvm_forcestack %} + cmdline: {{ 'ip=dhcp6' if ':' in api_ip else 'ip=dhcp' }} +{% endif %} +{% if kubevirt_api_service %} + reservedns: true +{% endif %} + nets: {{ [network] + extra_networks }} + disks: {{ [arbiter_disk_size | default(disk_size, true)] }} +{% if coredns or ipv6 %} + files: +{% if coredns %} + - path: /etc/NetworkManager/dispatcher.d/99-kcli-forcedns + origin: 99-kcli-forcedns + mode: 755 + - path: /etc/kubernetes/manifests/coredns.yml + origin: staticpods/coredns.yml + - path: /etc/kubernetes/Corefile + origin: Corefile +{% endif %} +{% if ipv6 %} + - path: /etc/NetworkManager/conf.d/kcli-ipv6.conf + origin: kcli-ipv6.conf.j2 +{% endif %} +{% endif %} +{% if files|default([]) %} + extra_files: {{ files }} +{% endif %} +{% endif %} diff --git a/kvirt/cluster/openshift/install-config.yaml b/kvirt/cluster/openshift/install-config.yaml index 7d518c360..e811ba745 100644 --- a/kvirt/cluster/openshift/install-config.yaml +++ b/kvirt/cluster/openshift/install-config.yaml @@ -44,7 +44,12 @@ compute: controlPlane: name: master replicas: {{ 1 if sno or sno_vm else ctlplanes }} -{% if techpreview %} +{% if tna %} +arbiter: + name: arbiter + replicas: 1 +featureSet: TechPreviewNoUpgrade +{% elif techpreview %} featureSet: TechPreviewNoUpgrade {% elif ctlplanes == 2 %} fencing: diff --git a/kvirt/cluster/openshift/kcli_default.yml b/kvirt/cluster/openshift/kcli_default.yml index 47cbac794..304b7999f 100644 --- a/kvirt/cluster/openshift/kcli_default.yml +++ b/kvirt/cluster/openshift/kcli_default.yml @@ -43,14 +43,19 @@ flavor: flavor_bootstrap: flavor_ctlplane: flavor_worker: +flavor_arbiter: numcpus: 8 bootstrap_numcpus: ctlplane_numcpus: worker_numcpus: +arbiter_numcpus: 2 memory: 16384 bootstrap_memory: 8192 ctlplane_memory: worker_memory: +arbiter_memory: 8192 +arbiter_disk_size: +tna: false bgp: false bgp_peers: [] bgp_extra_peers: [] diff --git a/kvirt/common/__init__.py b/kvirt/common/__init__.py index b94d73f21..7be87e342 100644 --- a/kvirt/common/__init__.py +++ b/kvirt/common/__init__.py @@ -1198,7 +1198,7 @@ def ignition(name, keys=[], cmds=[], nets=[], gateway=None, dns=None, domain=Non data['passwd']['users'].append({'name': vmuser, 'sshAuthorizedKeys': publickeys, 'groups': ['sudo', 'wheel']}) role = overrides.get('role') - if len(name.split('-')) >= 3 and name.split('-')[-2] in ['ctlplane', 'worker']: + if len(name.split('-')) >= 3 and name.split('-')[-2] in ['ctlplane', 'worker', 'arbiter']: role = name.split('-')[-2] elif len(name.split('-')) >= 2 and name.split('-')[-1] == 'bootstrap': role = name.split('-')[-1] From e166601da21e92192d35b1e7807f66288b364513 Mon Sep 17 00:00:00 2001 From: Vincenzo Mauro Date: Wed, 28 Jan 2026 15:13:00 +0100 Subject: [PATCH 2/3] changed flag from 'tna' to 'arbiters' --- kvirt/cluster/openshift/__init__.py | 11 ++++++----- kvirt/cluster/openshift/ctlplanes.yml | 6 +++--- kvirt/cluster/openshift/install-config.yaml | 4 ++-- kvirt/cluster/openshift/kcli_default.yml | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/kvirt/cluster/openshift/__init__.py b/kvirt/cluster/openshift/__init__.py index bb0359da2..f5332567c 100644 --- a/kvirt/cluster/openshift/__init__.py +++ b/kvirt/cluster/openshift/__init__.py @@ -135,7 +135,7 @@ def update_registry(config, plandir, cluster, data): def create_ignition_files(config, plandir, cluster, domain, api_ip=None, bucket_url=None, ignition_version=None, - tna=False): + arbiters=0): clusterdir = os.path.expanduser(f"~/.kcli/clusters/{cluster}") ignition_overrides = {'api_ip': api_ip, 'cluster': cluster, 'domain': domain, 'role': 'master'} ctlplane_ignition = config.process_inputfile(cluster, f"{plandir}/ignition.j2", overrides=ignition_overrides) @@ -145,7 +145,7 @@ def create_ignition_files(config, plandir, cluster, domain, api_ip=None, bucket_ worker_ignition = config.process_inputfile(cluster, f"{plandir}/ignition.j2", overrides=ignition_overrides) with open(f"{clusterdir}/worker.ign", 'w') as f: f.write(worker_ignition) - if tna: + if arbiters > 0: ignition_overrides['role'] = 'arbiter' arbiter_ignition = config.process_inputfile(cluster, f"{plandir}/ignition.j2", overrides=ignition_overrides) with open(f"{clusterdir}/arbiter.ign", 'w') as f: @@ -592,7 +592,7 @@ def scale(config, plandir, cluster, overrides): os.mkdir(clusterdir) ignition_version = overrides['ignition_version'] create_ignition_files(config, plandir, cluster, domain, api_ip=api_ip, ignition_version=ignition_version, - tna=overrides.get('tna', False)) + arbiters=overrides.get('arbiters', 0)) if storedparameters and os.path.exists(f"{clusterdir}/kcli_parameters.yml"): with open(f"{clusterdir}/kcli_parameters.yml", 'r') as install: installparam = safe_load(install) @@ -671,7 +671,8 @@ def create(config, plandir, cluster, overrides, dnsconfig=None): workers = data['workers'] if workers < 0: return {'result': 'failure', 'reason': f"Invalid number of workers {workers}"} - if data.get('tna', False) and ctlplanes != 2: + arbiters = data.get('arbiters', 0) + if arbiters > 0 and ctlplanes != 2: return {'result': 'failure', 'reason': "TNA topology requires exactly 2 control plane nodes"} if data['dual_api_ip'] is not None: warning("Forcing dualstack") @@ -1617,7 +1618,7 @@ def create(config, plandir, cluster, overrides, dnsconfig=None): ignition_version = json.load(f)['ignition']['version'] installparam['ignition_version'] = ignition_version create_ignition_files(config, plandir, cluster, domain, api_ip=api_ip, bucket_url=bucket_url, - ignition_version=ignition_version, tna=data.get('tna', False)) + ignition_version=ignition_version, arbiters=arbiters) backup_paramfile(config.client, installparam, clusterdir, cluster, plan, image, dnsconfig) if provider in virt_providers: if provider == 'vsphere': diff --git a/kvirt/cluster/openshift/ctlplanes.yml b/kvirt/cluster/openshift/ctlplanes.yml index 1e3d84ef3..6f350a699 100644 --- a/kvirt/cluster/openshift/ctlplanes.yml +++ b/kvirt/cluster/openshift/ctlplanes.yml @@ -102,9 +102,9 @@ extra_files: {{ files }} {% endif %} {% endfor %} -{% if tna %} +{% for num in range(0, arbiters|int) %} -{{ cluster }}-arbiter-0: +{{ cluster }}-arbiter-{{ num }}: domain: {{ domain }} image: {{ image }} pool: {{ pool or config_pool }} @@ -142,4 +142,4 @@ {% if files|default([]) %} extra_files: {{ files }} {% endif %} -{% endif %} +{% endfor %} diff --git a/kvirt/cluster/openshift/install-config.yaml b/kvirt/cluster/openshift/install-config.yaml index e811ba745..ba98353ca 100644 --- a/kvirt/cluster/openshift/install-config.yaml +++ b/kvirt/cluster/openshift/install-config.yaml @@ -44,10 +44,10 @@ compute: controlPlane: name: master replicas: {{ 1 if sno or sno_vm else ctlplanes }} -{% if tna %} +{% if arbiters > 0 %} arbiter: name: arbiter - replicas: 1 + replicas: {{ arbiters }} featureSet: TechPreviewNoUpgrade {% elif techpreview %} featureSet: TechPreviewNoUpgrade diff --git a/kvirt/cluster/openshift/kcli_default.yml b/kvirt/cluster/openshift/kcli_default.yml index 304b7999f..acbf99c7b 100644 --- a/kvirt/cluster/openshift/kcli_default.yml +++ b/kvirt/cluster/openshift/kcli_default.yml @@ -26,6 +26,7 @@ api_ip: ingress_ip: ctlplanes: 3 workers: 0 +arbiters: 0 ctlplane_schedulable: false fips: false cluster: myopenshift @@ -55,7 +56,6 @@ ctlplane_memory: worker_memory: arbiter_memory: 8192 arbiter_disk_size: -tna: false bgp: false bgp_peers: [] bgp_extra_peers: [] From 7610035702ef1fb03ee511c4f840dbc5e648849a Mon Sep 17 00:00:00 2001 From: Vincenzo Mauro Date: Wed, 28 Jan 2026 15:20:00 +0100 Subject: [PATCH 3/3] updated index.md documentation --- docs/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/index.md b/docs/index.md index 672afbc63..498285558 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1792,21 +1792,26 @@ Here's the list of typical variables that can be used (you can list them with `k |ingress_ip |None || |ctlplanes |1 |number of ctlplane| |workers |0 |number of workers| +|arbiters |0 |number of arbiters (TNA topology, requires ctlplanes=2)| |network_type |OVNKubernetes || |pool |default || |flavor |None || |flavor_bootstrap |None || |flavor_ctlplane |None || |flavor_worker |None || +|flavor_arbiter |None || |numcpus |4 || |bootstrap_numcpus |None || |ctlplane_numcpus |None || |worker_numcpus |None || +|arbiter_numcpus |2 || |memory |8192 || |bootstrap_memory |None || |ctlplane_memory |None || |worker_memory |None || +|arbiter_memory |8192 || |disk_size |30 |disk size in Gb for final nodes| +|arbiter_disk_size |None || |extra_disks |[] || |disconnected_url |None || |disconnected_user |None ||