diff --git a/playbooks/infra.yml b/playbooks/infra.yml
index 80f60163..70d34dbb 100644
--- a/playbooks/infra.yml
+++ b/playbooks/infra.yml
@@ -5,7 +5,7 @@
- opennebula.deploy
roles:
# This installs the interpreter only.
- # Extra OS/PyPI packages should not required by this playbook.
+ # Extra OS/PyPI packages should not be required by this playbook.
- role: helper/python3
- role: helper/facts
diff --git a/plugins/filter/explode_ranges.yml b/plugins/filter/explode_ranges.yml
new file mode 100644
index 00000000..cc305572
--- /dev/null
+++ b/plugins/filter/explode_ranges.yml
@@ -0,0 +1,35 @@
+---
+# Copyright: OpenNebula Project, OpenNebula Systems
+# Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+
+DOCUMENTATION:
+ name: explode_ranges
+ short_description: Simplify isolcpus= syntax handling.
+ description:
+ - Convert 'A-C,D' into 'A,B,C,D'.
+ options:
+ _input:
+ description:
+ - String to convert.
+ type: str
+ required: true
+ split:
+ description:
+ - When true returns a list.
+ type: bool
+ required: false
+ default: false
+ author:
+ - Michal Opala (@sk4zuzu)
+
+EXAMPLES: |
+ # '0-2,4' -> ['0', '1', '2', '4']
+ - name: Conversion example
+ ansible.builtin.debug:
+ msg: >-
+ {{ '0-2,4' | opennebula.deploy.explode_ranges(split=true) }}
+
+RETURN:
+ _value:
+ description: String (joined) or a list of indices (depending on split=).
+ type: raw
diff --git a/plugins/filter/implode_ranges.yml b/plugins/filter/implode_ranges.yml
new file mode 100644
index 00000000..0fac4f0d
--- /dev/null
+++ b/plugins/filter/implode_ranges.yml
@@ -0,0 +1,35 @@
+---
+# Copyright: OpenNebula Project, OpenNebula Systems
+# Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+
+DOCUMENTATION:
+ name: implode_ranges
+ short_description: Simplify isolcpus= syntax handling.
+ description:
+ - Convert 'A,B,C,D' into 'A-C,D'.
+ options:
+ _input:
+ description:
+ - String to convert.
+ type: str
+ required: true
+ split:
+ description:
+ - When true returns a list.
+ type: bool
+ required: false
+ default: false
+ author:
+ - Michal Opala (@sk4zuzu)
+
+EXAMPLES: |
+ # '0,1,2,4' -> ['0-2', '4']
+ - name: Conversion example
+ ansible.builtin.debug:
+ msg: >-
+ {{ '0,1,2,4' | opennebula.deploy.implode_ranges(split=true) }}
+
+RETURN:
+ _value:
+ description: String (joined) or a list of ranges (depending on split=).
+ type: raw
diff --git a/plugins/filter/ipv4_mac.yml b/plugins/filter/ipv4_mac.yml
new file mode 100644
index 00000000..58bc7cf1
--- /dev/null
+++ b/plugins/filter/ipv4_mac.yml
@@ -0,0 +1,35 @@
+---
+# Copyright: OpenNebula Project, OpenNebula Systems
+# Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+
+DOCUMENTATION:
+ name: ipv4_mac
+ short_description: Convert IPv4 into MAC
+ description:
+ - Convert IPv4 (A.B.C.D) into MAC (02:01:A:B:C:D).
+ options:
+ _input:
+ description:
+ - String to convert.
+ type: str
+ required: true
+ fmt:
+ description:
+ - Format string (e.g. 02:01:%02x:%02x:%02x:%02x).
+ type: str
+ required: false
+ default: '02:01:%02x:%02x:%02x:%02x'
+ author:
+ - Michal Opala (@sk4zuzu)
+
+EXAMPLES: |
+ # '10.11.12.13' -> '02:01:0a:0b:0c:0d'
+ - name: Conversion example
+ ansible.builtin.debug:
+ msg: >-
+ {{ '10.11.12.13' | opennebula.deploy.ipv4_mac }}
+
+RETURN:
+ _value:
+ description: String containing MAC.
+ type: str
diff --git a/plugins/filter/main.py b/plugins/filter/main.py
index 0f0a1bda..ca0ab197 100644
--- a/plugins/filter/main.py
+++ b/plugins/filter/main.py
@@ -1,7 +1,17 @@
-from ansible_collections.opennebula.deploy.plugins.module_utils.main import to_one
+from ansible_collections.opennebula.deploy.plugins.module_utils.main import (
+ ipv4_mac,
+ explode_ranges,
+ implode_ranges,
+ to_one,
+)
class FilterModule(object):
def filters(self):
- return dict(to_one=to_one)
+ return dict(
+ ipv4_mac=ipv4_mac,
+ explode_ranges=explode_ranges,
+ implode_ranges=implode_ranges,
+ to_one=to_one,
+ )
diff --git a/plugins/module_utils/main.py b/plugins/module_utils/main.py
index f697eaf9..f795be36 100644
--- a/plugins/module_utils/main.py
+++ b/plugins/module_utils/main.py
@@ -3,6 +3,69 @@
# Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+# EXAMPLES:
+# '10.11.12.13' -> '02:01:0a:0b:0c:0d'
+def ipv4_mac(ipv4, fmt='02:01:%02x:%02x:%02x:%02x'):
+ """Converts IPv4 (string) into MAC."""
+
+ import ipaddress
+
+ return fmt % tuple(ipaddress.IPv4Address(ipv4).packed)
+
+
+# EXAMPLES:
+# '0-2,3,5-7' -> '0,1,2,3,5,6,7'
+def explode_ranges(ranges, split=False):
+ """Converts ranges to indices."""
+
+ import re
+
+ if not ranges:
+ return [] if split else ''
+
+ def g():
+ for z in re.split('[, ]', ranges):
+ y = z.split('-')
+
+ if len(y) > 0:
+ if len(y) == 1:
+ yield str(y[0])
+ else:
+ for x in range(int(y[0]), int(y[1]) + 1):
+ yield str(x)
+
+ return list(g()) if split else ','.join(g())
+
+
+# EXAMPLES:
+# '0,1,2,3,5,6,7' -> '0-3,5-7'
+def implode_ranges(indices, split=False):
+ """Converts indices to ranges."""
+
+ import re
+
+ if not indices:
+ return [] if split else ''
+
+ def index_or_range(y):
+ return str(y[0]) if len(y) == 1 else str(y[0]) + '-' + str(y[-1])
+
+ def g():
+ y = []
+
+ for x in re.split('[, ]', indices):
+ if len(y) > 0:
+ if y[-1] + 1 != int(x):
+ yield index_or_range(y)
+ y.clear()
+
+ y.append(int(x))
+
+ yield index_or_range(y)
+
+ return list(g()) if split else ','.join(g())
+
+
# NOTE: It does not validate character classes or character count!
# EXAMPLES:
# pci -> match_address('0000:0*:00.*', sep='[:.]')
diff --git a/pyproject.toml b/pyproject.toml
index 7386da1f..369e0d18 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,7 @@ dependencies = [
"netaddr",
"pyone",
"six",
+ "xmltodict",
]
# NOTE: If you experience more dynamic linker issues in your OS,
# you may want to extend LD_LIBRARY_PATH further.
diff --git a/roles/helper/numa/README.md b/roles/helper/numa/README.md
new file mode 100644
index 00000000..e3400b4d
--- /dev/null
+++ b/roles/helper/numa/README.md
@@ -0,0 +1,39 @@
+Role: opennebula.deploy.helper.numa
+===================================
+
+A role that queries NUMA resources.
+
+Requirements
+------------
+
+N/A
+
+Role Variables
+--------------
+
+| Name | Type | Default | Description |
+|------|------|---------|-------------|
+| | | | |
+
+Dependencies
+------------
+
+N/A
+
+Example Playbook
+----------------
+
+ - hosts: node
+ roles:
+ - role: opennebula.deploy.helper.facts
+ - role: opennebula.deploy.helper.numa
+
+License
+-------
+
+Apache-2.0
+
+Author Information
+------------------
+
+[OpenNebula Systems](https://opennebula.io/)
diff --git a/roles/helper/numa/meta/main.yml b/roles/helper/numa/meta/main.yml
new file mode 100644
index 00000000..78ac06b1
--- /dev/null
+++ b/roles/helper/numa/meta/main.yml
@@ -0,0 +1,5 @@
+---
+collections:
+ - opennebula.deploy
+
+allow_duplicates: true
diff --git a/roles/helper/numa/tasks/main.yml b/roles/helper/numa/tasks/main.yml
new file mode 100644
index 00000000..98d0a385
--- /dev/null
+++ b/roles/helper/numa/tasks/main.yml
@@ -0,0 +1,82 @@
+---
+- vars:
+ _query: >-
+ {{ shell_cpus_numa.stdout | split(_sep1)
+ | select('truthy')
+ | map('split', _sep2)
+ | map('map', 'split', _sep3) }}
+ _sep1: "\n\n"
+ _sep2: "\n"
+ _sep3: ":\t" # noqa no-tabs
+
+ _cpus: >-
+ {%- set output = {} -%}
+ {%- for k, v in _query.0 -%}
+ {{-
+ output.update({
+ k: (output.get(k, []) + [v | opennebula.deploy.explode_ranges(split=true)]) | flatten | unique,
+ })
+ -}}
+ {%- endfor -%}
+ {{-
+ output | combine({
+ "system": output.present | difference(output.isolated),
+
+ "free": output.isolated | difference(output.allowed)
+ | intersect(output.present),
+ })
+ -}}
+
+ _numa: >-
+ {%- set output = {} -%}
+ {%- for k, v in _query.1 -%}
+ {{-
+ output.update({
+ k: {
+ "cpus": {
+ "free": v.split(';').0 | opennebula.deploy.explode_ranges(split=true)
+ | intersect(_cpus.free),
+ },
+ "pages1g": { "free": v.split(';').1 },
+ "pages2m": { "free": v.split(';').2 },
+ },
+ })
+ -}}
+ {%- endfor -%}
+ {{- output -}}
+ block:
+ - name: Query CPU and NUMA resources
+ ansible.builtin.shell:
+ cmd: |
+ set -o errexit; shopt -s nullglob
+ # Query CPU availability and affinity.
+ PRESENT="$(head -1 '/sys/devices/system/cpu/present')"
+ echo -e "present:\t$PRESENT"
+ ISOLATED="$(head -1 '/sys/devices/system/cpu/isolated')"
+ echo -e "isolated:\t$ISOLATED"
+ find '/proc' -maxdepth 4 -path '/proc/[0-9]*/task/[0-9]*/status' -exec gawk "$AWK_CPUS_ALLOWED" {} +
+ echo
+ # Query NUMA topology.
+ for DIR in '/sys/devices/system/node/node'*; do
+ NODE="$(basename "$DIR")"
+ CPUS="$(head -1 "$DIR/cpulist")"
+ HP1G="$(head -1 "$DIR/hugepages/hugepages-1048576kB/free_hugepages")"
+ HP2M="$(head -1 "$DIR/hugepages/hugepages-2048kB/free_hugepages")"
+ echo -e "${NODE#node}:\t$CPUS;$HP1G;$HP2M"
+ done
+ echo
+ executable: /bin/bash
+ environment:
+ AWK_CPUS_ALLOWED: |
+ BEGINFILE { if (ERRNO) nextfile }
+ $1 == "Kthread:" { Kthread=$2; if (Kthread) nextfile }
+ $1 == "Cpus_allowed_list:" { Cpus_allowed_list=$2 }
+ ENDFILE { if (!Kthread) print "allowed:\t" Cpus_allowed_list }
+ register: shell_cpus_numa
+ changed_when: false
+
+ - name: Store query results in the numa_query fact
+ ansible.builtin.set_fact:
+ numa_query:
+ cpus: "{{ _cpus }}"
+ numa: "{{ _numa }}"
diff --git a/roles/infra/README.md b/roles/infra/README.md
index ea186458..75cc41bc 100644
--- a/roles/infra/README.md
+++ b/roles/infra/README.md
@@ -15,20 +15,25 @@ Role Variables
|--------------------------------------|--------|--------------------|---------------------|-------------------------------------------------------------------|
| `frontend_group` | `str` | `frontend` | | Custom name of the Frontend group in the inventory. |
| `infra_group` | `str` | `infra` | | Custom name of the Infra group in the inventory. |
-| | | | | |
+| | | | | |
| `runtime_dir` | `str` | `/var/one-deploy/` | | Directory used to store QCOW2 and ISO images. |
| `os_image_url` | `str` | | | HTTP(S) link to Debian/RedHat-like image running `one-contextd`. |
| `os_image_size` | `str` | `20G` | | The size to which one-deploy will **try** to adjust the OS image. |
| `memory_KiB` | `str` | `2097152` | | Memory amount to be set in XML in Libvirt. |
-| `vcpu_static` | `str` | `1` | | VCPU amount to be set in XML in Libvirt. |
| `vnc_max_port` | `str` | `65535` | | Upper limit for VNC ports to start counting-down from. |
| `passthrough_fs` | `list` | `[]` | (check below) | Shared HV filesystems to attach to the Front-end VMs. |
-| | | | | |
+| | | | | |
+| `vcpu_pinned` | `str` | | `1-2,4` | List of isolcpus= ranges (comma-separated). |
+| `vcpu_static` | `str` | `1` | | VCPU amount to be set in XML in Libvirt. |
+| `vcpu_shares` | `str` | `200` | | The proportional weighted share (PWS) for the domain. |
+| | | | | |
| `infra_bridge` | `str` | `br0` | | Pre-defined bridge interface to insert VM NICs to. |
-| `infra_bridge_type` | `str` | `bridge` | | Supported values: bridge, openvswitch, openvswitch_dpdk |
-| `infra_dpdk_socket_path` | `str` | | | Path for existing socket when using OVS with DPDK. |
| `infra_vlan_id` | `str` | | | Optionally set the VLAN ID for the bridge. |
+| `dpdk_socket_path` | `str` | | | Path for existing socket when using OVS with DPDK. |
+| | | | | |
| `infra_hostname` | `str` | | `n1a1` | Defines on which HV machine the Front-end VM should be deployed. |
+| `infra_xml_variant` | `str` | undefined | `pinned` | Defined which domain XML variant will be used in Libvirt. |
+| | | | | |
| `context.ETH0_DNS` | `str` | | `1.1.1.1` | DNS server. |
| `context.ETH0_SEARCH_DOMAIN` | `str` | | `1.1.1.1` | DNS search domain. |
| `context.ETH0_GATEWAY` | `str` | | `10.2.50.1` | Gateway. |
@@ -40,6 +45,7 @@ Role Variables
| `context.PASSWORD` | `str` | `opennebula` | | Root's password. |
| `context.SET_HOSTNAME` | `str` | name of the FE VM | | Hostname. |
| `context.SSH_PUBLIC_KEY` | `str` | | (check below) | Root's extra authorized keys. |
+| `context.START_SCRIPT_BASE64` | `str` | | | Start script (base64-encoded). |
**NOTE**: The `infra_hostname` and `context` dictionary should be set for members of the `frontend` group (please check the `inventory/infra.yml` example).
@@ -49,19 +55,51 @@ Dependencies
- `community.libvirt`
- `ansible.posix`
-Example Playbook
-----------------
+Example Inventory
+-----------------
+
+ infra:
+ vars:
+ os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2404-7.2.0-0-20260330.qcow2
+ os_image_size: 20G
+ memory_KiB: 2097152 # 2 GiB
+ infra_xml_variant: pinned
+ vcpu_pinned: '1,3'
+ hosts:
+ u1q20: { ansible_host: 10.3.10.20 }
+ u1q30: { ansible_host: 10.3.10.30 }
+
+ frontend:
+ vars:
+ context:
+ ETH0_DNS: 10.3.10.1
+ ETH0_GATEWAY: 10.3.10.1
+ ETH0_MASK: 255.255.255.0
+ ETH0_NETWORK: 10.3.10.0
+ ETH0_IP: "{{ ansible_host }}"
+ PASSWORD: asd
+ SSH_PUBLIC_KEY: |-
+ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF5ndznuZTNZ8u7FCYKgv6Q3/HUVxnaha3tPDUXPfIIw
+ hosts:
+ u1q40: { ansible_host: 10.3.10.40, infra_hostname: u1q20 }
+ u1q50: { ansible_host: 10.3.10.50, infra_hostname: u1q30 }
- - hosts: infra
+ node:
vars:
- os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2204-6.10.0-1-20240514.qcow2
- passthrough_fs:
- - driver_type: virtiofs
- source_dir: /var/lib/one/datastores
- target_dir: /var/lib/one/datastores
- roles:
- - role: opennebula.deploy.helper.facts
- - role: opennebula.deploy.infra
+ kernel_ok_to_reboot: true
+ kernel_params:
+ - isolcpus: "1-3,5-7"
+ - nohz_full: "1-3,5-7"
+ - rcu_nocbs: "1-3,5-7"
+ - irqaffinity: "0,4"
+ - kthread_cpus: "0-4"
+ - systemd.cpu_affinity: "0,4"
+ - default_hugepagesz: "1G"
+ - hugepages: "0:2,1:2"
+ - intel_iommu: "on"
+ hosts:
+ u1q20: { ansible_host: 10.3.10.20 }
+ u1q30: { ansible_host: 10.3.10.30 }
License
-------
diff --git a/roles/infra/defaults/main.yml b/roles/infra/defaults/main.yml
index d6df3480..8782dd17 100644
--- a/roles/infra/defaults/main.yml
+++ b/roles/infra/defaults/main.yml
@@ -2,9 +2,249 @@
runtime_dir: /var/one-deploy/
os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2204-6.8.1-1-20240131.qcow2
os_image_size: 20G
-memory_KiB: 2097152 # 2 GiB
-vcpu_static: 1
vnc_max_port: 65535
-infra_bridge: br0
-infra_bridge_type: bridge
passthrough_fs: []
+
+infra_bridge: br0
+
+dpdk_socket_path: "/var/run/one/vhost-socks/frontend-{{ frontend | md5 | truncate(4, true, '') }}.sock"
+
+memory_KiB: 2097152 # 2 GiB
+
+# NOTE: In case multiple Front-ends are to be deployed on the same infra Node
+# pinned CPUs will be shared.
+vcpu_pinned: ''
+
+vcpu_static: "{{ vcpu_pinned | split(',') | select | count | d(1, true) }}"
+
+# NOTE: In case of non-isolated variants this is reused as CPUWeight inside
+# the infra slice.
+vcpu_shares: 200
+
+infra_xml_variant: "{{ undef() }}"
+
+infra_xml:
+ default:
+ - "{{ infra_xml_base.default }}"
+ - "{{ infra_xml_cputune.default }}"
+ - "{{ infra_xml_memoryBacking.default }}"
+ - "{{ infra_xml_interfaces.default }}"
+ - "{{ infra_xml_filesystems.default }}"
+ pinned:
+ - "{{ infra_xml_base.default }}"
+ - "{{ infra_xml_cputune.pinned }}"
+ - "{{ infra_xml_memoryBacking.hugepages }}"
+ - "{{ infra_xml_interfaces.default }}"
+ - "{{ infra_xml_filesystems.default }}"
+ openvswitch:
+ - "{{ infra_xml_base.default }}"
+ - "{{ infra_xml_cputune.default }}"
+ - "{{ infra_xml_memoryBacking.default }}"
+ - "{{ infra_xml_interfaces.openvswitch }}"
+ - "{{ infra_xml_filesystems.default }}"
+ openvswitch_dpdk:
+ - "{{ infra_xml_base.default }}"
+ - "{{ infra_xml_cputune.pinned }}"
+ - "{{ infra_xml_memoryBacking.hugepages }}"
+ - "{{ infra_xml_interfaces.openvswitch_dpdk }}"
+ - "{{ infra_xml_filesystems.default }}"
+
+infra_xml_cputune:
+ default: |
+
+
+ {% if vcpu_shares | int < 10000 %}
+ {{ vcpu_shares | int }}
+ {% else %}
+ 10000
+ {% endif %}
+
+
+ pinned: |
+
+
+ {% for v in vcpu_pinned | split(',') %}
+
+ {% endfor %}
+
+
+
+infra_xml_memoryBacking:
+ default: |
+
+
+
+
+
+ hugepages: |
+
+
+
+
+
+
+
+infra_xml_interfaces:
+ default: |
+
+
+
+
+
+ {% if infra_vlan_id is defined %}
+
+
+
+ {% endif %}
+
+
+
+
+
+ openvswitch: |
+
+
+
+
+
+
+ {% if infra_vlan_id is defined %}
+
+
+
+ {% endif %}
+
+
+
+
+
+ openvswitch_dpdk: |
+
+
+
+
+
+
+ {% if infra_vlan_id is defined %}
+
+
+
+ {% endif %}
+
+
+
+
+
+
+infra_xml_filesystems:
+ default: |
+
+
+ {% for fs in passthrough_fs %}
+
+ {% if fs.driver_type is defined %}
+
+ {% endif %}
+
+
+
+ {% endfor %}
+
+
+
+infra_xml_base:
+ default: |
+
+ {{ frontend }}
+ {{ frontend }}
+
+ {{ memory_KiB }}
+ {{ vcpu_static }}
+
+
+
+ /infra
+
+
+ hvm
+
+
+
+
+
+
+
+
+
+
+
+ destroy
+ restart
+ destroy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/roles/infra/tasks/deploy.yml b/roles/infra/tasks/deploy.yml
index 90dbdb77..aba8c53d 100644
--- a/roles/infra/tasks/deploy.yml
+++ b/roles/infra/tasks/deploy.yml
@@ -87,7 +87,7 @@
- name: Compute VNC ports
ansible.builtin.set_fact:
- frontends_to_vnc_ports: >-
+ frontend_vnc_ports: >-
{{ dict(_frontends | zip(_ports)) }}
vars:
_ports: >-
@@ -97,12 +97,32 @@
_frontend_group: >-
{{ frontend_group | d('frontend') }}
+- ansible.builtin.include_tasks:
+ file: "{{ role_path }}/tasks/limits.yml"
+
- name: Define Front-end VMs
community.libvirt.virt:
command: define
- xml: "{{ lookup('template', 'frontend.xml.jinja') }}"
+ xml: >-
+ {%- set output = {} -%}
+ {%- for v in infra_xml[infra_xml_variant] -%}
+ {{-
+ output.update(output | combine(
+ v | ansible.utils.from_xml | combine(_fixup, recursive=true),
+ recursive=true,
+ list_merge='append_rp',
+ ))
+ -}}
+ {%- endfor -%}
+ {{- output | ansible.utils.to_xml(full_document=false) -}}
autostart: true
vars:
+ # NOTE: This is required because (for example) '' produces '{"devices": null}'
+ # which cannot be merged recursively.
+ _fixup:
+ domain:
+ cputune: {}
+ devices: {}
context: "{{ hostvars[frontend].context }}"
loop_control: { loop_var: frontend }
loop: "{{ infra_to_frontends[inventory_hostname] }}"
diff --git a/roles/infra/tasks/limits.yml b/roles/infra/tasks/limits.yml
new file mode 100644
index 00000000..06e99069
--- /dev/null
+++ b/roles/infra/tasks/limits.yml
@@ -0,0 +1,86 @@
+---
+- when: infra_xml_variant in ['default', 'openvswitch']
+ vars:
+ _frontends: >-
+ {{ infra_to_frontends[inventory_hostname] }}
+
+ _memory_min: >-
+ {{ (_frontends | count) * (memory_KiB * 1.10 + 150 * 1024) }}
+
+ _memory_low: >-
+ {{ (_frontends | count) * (memory_KiB * 1.15 + 250 * 1024) }}
+
+ _cpu_weight: >-
+ {{ vcpu_shares if (vcpu_shares | int) < 10000 else 10000 }}
+ block:
+ - name: Update MemoryMin / MemoryMin / CPUWeight on infra slice
+ ansible.builtin.shell:
+ cmd: |
+ set -o errexit -o pipefail
+ BEFORE="$(systemctl show infra.slice -p MemoryMin -p MemoryLow -p CPUWeight | sort)"
+ systemctl set-property infra.slice 'MemoryMin={{ _memory_min | int }}K' \
+ 'MemoryLow={{ _memory_low | int }}K' \
+ 'CPUWeight={{ _cpu_weight | int }}'
+ AFTER="$(systemctl show infra.slice -p MemoryMin -p MemoryLow -p CPUWeight | sort)"
+ if [[ "$AFTER" == "$BEFORE" ]]; then
+ exit 0
+ else
+ exit 78 # EREMCHG
+ fi
+ executable: /bin/bash
+ register: shell
+ changed_when:
+ - shell.rc in [78]
+ failed_when:
+ - shell.rc not in [0, 78]
+
+- when: infra_xml_variant in ['pinned', 'openvswitch_dpdk']
+ vars:
+ _parsed:
+ machine: >-
+ {{ shell_allowed_cpus.stdout_lines.0 | d() | opennebula.deploy.explode_ranges(split=true) }}
+ infra: >-
+ {{ shell_allowed_cpus.stdout_lines.1 | d() | opennebula.deploy.explode_ranges(split=true) }}
+
+ _required:
+ machine: >-
+ {{ numa_query.cpus.system }}
+ infra: >-
+ {{ vcpu_pinned | opennebula.deploy.explode_ranges(split=true) }}
+ block:
+ - name: Assert that vcpu_pinned is defined
+ ansible.builtin.assert:
+ that:
+ - vcpu_pinned is string
+ - vcpu_pinned | length > 0
+ fail_msg: Please ensure vcpu_pinned is a string following the isolcpus= syntax (i.e. '1-2,4').
+
+ - ansible.builtin.include_role:
+ name: helper/numa
+
+ - name: Assert that vcpu_pinned refers to isolated cores
+ ansible.builtin.assert:
+ that: _required.infra | intersect(numa_query.cpus.isolated) | count > 0
+ fail_msg: Please ensure vcpu_pinned cores exist and are properly isolated.
+
+ - name: Query AllowedCPUs on machine and infra slices
+ ansible.builtin.shell:
+ cmd: |
+ set -o errexit
+ systemctl show machine.slice -p AllowedCPUs --value
+ systemctl show infra.slice -p AllowedCPUs --value
+ executable: /bin/bash
+ register: shell_allowed_cpus
+ changed_when: false
+
+ # NOTE: OpenNebula places VMs inside machine.slice, we exclude isolated cores
+ # from that slice so libvirt is not allowed to use them.
+ - name: Update AllowedCPUs on machine and infra slices
+ ansible.builtin.shell:
+ cmd: |
+ set -o errexit
+ systemctl set-property machine.slice 'AllowedCPUs={{ _required.machine | join(',') }}'
+ systemctl set-property infra.slice 'AllowedCPUs={{ _required.infra | join(',') }}'
+ executable: /bin/bash
+ changed_when: true
+ when: _parsed != _required
diff --git a/roles/infra/tasks/main.yml b/roles/infra/tasks/main.yml
index 0b724e0c..b5a3a95f 100644
--- a/roles/infra/tasks/main.yml
+++ b/roles/infra/tasks/main.yml
@@ -3,7 +3,7 @@
ansible.builtin.package:
name: "{{ _common + _specific[ansible_os_family] }}"
vars:
- _common: [acl, genisoimage, python3-libvirt, python3-lxml]
+ _common: [acl, gawk, genisoimage, python3-libvirt, python3-lxml]
_specific:
Debian: [qemu-utils]
RedHat: [qemu-img]
diff --git a/roles/infra/templates/context.sh.jinja b/roles/infra/templates/context.sh.jinja
index 284fe014..57b131ee 100644
--- a/roles/infra/templates/context.sh.jinja
+++ b/roles/infra/templates/context.sh.jinja
@@ -1,18 +1,22 @@
# Context variables generated by one-deploy
DISK_ID='1'
-ETH0_DNS='{{ context.ETH0_DNS }}'
-ETH0_SEARCH_DOMAIN='{{ context.ETH0_SEARCH_DOMAIN }}'
-ETH0_GATEWAY='{{ context.ETH0_GATEWAY }}'
ETH0_IP='{{ context.ETH0_IP }}'
-ETH0_MAC='{{ context.ETH0_MAC | d("02:01:%02x:%02x:%02x:%02x" | format(*(context.ETH0_IP.split(".") | map("int")))) }}'
+ETH0_MAC='{{ context.ETH0_MAC | d(context.ETH0_IP | opennebula.deploy.ipv4_mac) }}'
ETH0_MASK='{{ context.ETH0_MASK }}'
ETH0_NETWORK='{{ context.ETH0_NETWORK }}'
+ETH0_GATEWAY='{{ context.ETH0_GATEWAY }}'
GROW_FS='{{ context.GROW_FS | d("/") }}'
NETWORK='YES'
PASSWORD='{{ context.PASSWORD | d("opennebula") }}'
SET_HOSTNAME='{{ context.SET_HOSTNAME | d(frontend) }}'
SSH_PUBLIC_KEY='{{ context.SSH_PUBLIC_KEY | d("") }}'
TARGET='hda'
+{% if context.ETH0_DNS is defined %}
+ETH0_DNS='{{ context.ETH0_DNS }}'
+{% endif %}
+{% if context.ETH0_SEARCH_DOMAIN %}
+ETH0_SEARCH_DOMAIN='{{ context.ETH0_SEARCH_DOMAIN }}'
+{% endif %}
{% if context.START_SCRIPT_BASE64 is defined %}
START_SCRIPT_BASE64='{{ context.START_SCRIPT_BASE64 | b64encode }}'
{% endif %}
diff --git a/roles/infra/templates/frontend.xml.jinja b/roles/infra/templates/frontend.xml.jinja
deleted file mode 100644
index 31ac150f..00000000
--- a/roles/infra/templates/frontend.xml.jinja
+++ /dev/null
@@ -1,143 +0,0 @@
-
- {{ frontend }}
- {{ frontend }}
-
- {{ memory_KiB }}
- {{ vcpu_static }}
-
-
-
- /machine
-
-
- hvm
-
-
-
-
-
-
-
-
-
-
-
- destroy
- restart
- destroy
-
-{% set use_dpdk = (infra_bridge_type is defined and infra_bridge_type == 'openvswitch_dpdk') %}
-{% set use_virtiofs = ('virtiofs' in (passthrough_fs | map(attribute='driver_type') | map('default', None) | select | map('lower'))) %}
-
-{% if use_dpdk or use_virtiofs %}
-
-{% if use_dpdk %}
-
-{% endif %}
-{% if use_virtiofs and not use_dpdk %}
-
-{% endif %}
-
-
-{% endif %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{% for fs in passthrough_fs %}
-
-{% if fs.driver_type is defined and fs.driver_type is truthy %}
-
-{% endif %}
-
-
-
-{% endfor %}
-
-
-
-
-
-{% if infra_bridge_type is defined and infra_bridge_type in ['bridge', 'openvswitch', 'openvswitch_dpdk'] %}
-{% if infra_bridge_type in ['bridge', 'openvswitch'] %}
-
-
-
-{% elif infra_bridge_type == 'openvswitch_dpdk' %}
-
-
-
-{% endif %}
-
-{% if infra_bridge_type == 'openvswitch' %}
-
-{% endif %}
-{% if infra_vlan_id is defined and infra_vlan_id %}
-
-
-
-{% endif %}
-
-
-
-{% endif %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-