diff --git a/playbooks/infra.yml b/playbooks/infra.yml index 4e5f65d1..f39ab476 100644 --- a/playbooks/infra.yml +++ b/playbooks/infra.yml @@ -7,7 +7,7 @@ - role: helper/online # 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/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..b0b0a0cf 100644 --- a/plugins/filter/main.py +++ b/plugins/filter/main.py @@ -1,7 +1,13 @@ -from ansible_collections.opennebula.deploy.plugins.module_utils.main import to_one +from ansible_collections.opennebula.deploy.plugins.module_utils.main import ( + ipv4_mac, + to_one, +) class FilterModule(object): def filters(self): - return dict(to_one=to_one) + return dict( + ipv4_mac=ipv4_mac, + to_one=to_one, + ) diff --git a/plugins/module_utils/main.py b/plugins/module_utils/main.py index f697eaf9..386378d5 100644 --- a/plugins/module_utils/main.py +++ b/plugins/module_utils/main.py @@ -3,6 +3,16 @@ # 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) + + # NOTE: It does not validate character classes or character count! # EXAMPLES: # pci -> match_address('0000:0*:00.*', sep='[:.]') diff --git a/roles/infra/README.md b/roles/infra/README.md index 7f8215a9..9df1aec4 100644 --- a/roles/infra/README.md +++ b/roles/infra/README.md @@ -11,38 +11,40 @@ Pre-installed Libvirt software (in the case of `infra` and `node` groups share h Role Variables -------------- -| Name | Type | Default | Example | Description | -|--------------------------------------|--------|--------------------|---------------------|-------------------------------------------------------------------| -| `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. | -| `cputune_shares_base` | `int` | `200` | | Base value used to compute PWS (assuming CGv2). | -| `cputune_shares_max` | `int` | `10000` | | PWS value cap (assuming CGv2). | -| `cputune_shares` | `int` | computed | | The proportional weighted share (PWS) for the domain. | -| `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. | -| | | | | | -| `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. | -| `infra_hostname` | `str` | | `n1a1` | Defines on which HV machine the Front-end VM should be deployed. | -| `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. | -| `context.ETH0_IP` | `str` | | `10.2.50.100` | IPv4 address to be set on eth0. | -| `context.ETH0_MAC` | `str` | | `02:01:0a:02:32:64` | MAC address to be set on eth0 (**MUST** match MAC set in XML.) | -| `context.ETH0_MASK` | `str` | | `255.255.255.0` | Network mask. | -| `context.ETH0_NETWORK` | `str` | | `10.2.50.0` | Network address. | -| `context.GROW_FS` | `str` | `/` | | Filesystems to grow. | -| `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. | +| Name | Type | Default | Example | Description | +|--------------------------------------|--------|------------------------------------------|---------------------|-------------------------------------------------------------------| +| `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. | +| `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_static` | `str` | `1` | | VCPU amount to be set in XML in Libvirt. | +| `vcpu_shares` | `str` | `200` | | The total CPU share for the domain. | +| | | | | | +| `infra_bridge` | `str` | `br0` | | Pre-defined bridge interface to insert VM NICs to. | +| `infra_vlan_id` | `str` | | | Optionally set the VLAN ID for the bridge. | +| `dpdk_socket_path` | `str` | `/var/run/one/vhost-socks/frontend.sock` | | 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` | `default` | | 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. | +| `context.ETH0_IP` | `str` | | `10.2.50.100` | IPv4 address to be set on eth0. | +| `context.ETH0_MAC` | `str` | | `02:01:0a:02:32:64` | MAC address to be set on eth0 (**MUST** match MAC set in XML.) | +| `context.ETH0_MASK` | `str` | | `255.255.255.0` | Network mask. | +| `context.ETH0_NETWORK` | `str` | | `10.2.50.0` | Network address. | +| `context.GROW_FS` | `str` | `/` | | Filesystems to grow. | +| `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 automatically). | **NOTE**: The `infra_hostname` and `context` dictionary should be set for members of the `frontend` group (please check the `inventory/infra.yml` example). @@ -57,7 +59,7 @@ Example Playbook - hosts: infra vars: - os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2204-6.10.0-1-20240514.qcow2 + os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2404-7.4.0-0-20260714.qcow2 passthrough_fs: - driver_type: virtiofs source_dir: /var/lib/one/datastores @@ -66,6 +68,140 @@ Example Playbook - role: opennebula.deploy.helper.facts - role: opennebula.deploy.infra +Example Inventory +----------------- + + # --- VARIANT: default --- + + infra: + vars: + os_image_url: http://10.2.11.1/images/ubuntu2404-7.4.0-0-20260714.qcow2 + os_image_size: 20G + memory_KiB: 2097152 # 2 GiB + 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 } + + node: + hosts: + u1q20: { ansible_host: 10.3.10.20 } + u1q30: { ansible_host: 10.3.10.30 } + + # --- VARIANT: openvswitch --- + + infra: + vars: + os_image_url: http://10.2.11.1/images/ubuntu2404-7.4.0-0-20260714.qcow2 + os_image_size: 20G + memory_KiB: 2097152 # 2 GiB + infra_xml_variant: openvswitch + infra_bridge: ovsbr0 + hosts: + u1q20: { ansible_host: 10.3.10.20 } + + frontend: + vars: + context: + ETH0_DNS: 10.3.10.1 + ETH0_SEARCH_DOMAIN: u1q.lh + 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 } + + node: + vars: + ovs: + iface: + eth1: {} + br: + ovsbr0: + ports: [eth1] + hosts: + u1q20: { ansible_host: 10.3.10.20 } + + # --- VARIANT: openvswitch_dpdk --- + + infra: + vars: + os_image_url: http://10.2.11.1/images/ubuntu2404-7.4.0-0-20260714.qcow2 + os_image_size: 20G + memory_KiB: 2097152 # 2 GiB + infra_xml_variant: openvswitch_dpdk + hosts: + u1q20: { ansible_host: 10.3.10.20, dpdk_socket_path: /var/run/one/vhost-socks/u1q40.sock } + + frontend: + vars: + context: + ETH0_DNS: 10.3.10.1 + ETH0_SEARCH_DOMAIN: u1q.lh + 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 } + + node: + vars: + kernel_ok_to_reboot: true + kernel_params: + - default_hugepagesz: '1G' + - hugepages: '0:4,1:4' + - intel_iommu: 'on' + kernel_modules: + - load: vfio-pci + - load: vfio_iommu_type1 + options: ['allow_unsafe_interrupts=1'] + + pci_devices: + - address: '0000:40:00.0' + + ovs: + set: + - other_config:dpdk-init: 'true' + - other_config:dpdk-socket-mem: '2048,2048' + iface: + dpdk-p0: + set: + - type: dpdk + - options:dpdk-devargs: '0000:40:00.0' + dpdk-p1: + set: + - type: dpdkvhostuserclient + - options:vhost-server-path: "{{ dpdk_socket_path }}" + br: + ovsbr1: + ports: [dpdk-p0, dpdk-p1] + set: + - datapath_type: netdev + hosts: + u1q20: { ansible_host: 10.3.10.20 } + License ------- diff --git a/roles/infra/defaults/main.yml b/roles/infra/defaults/main.yml index 0d7ac641..72d44b4d 100644 --- a/roles/infra/defaults/main.yml +++ b/roles/infra/defaults/main.yml @@ -1,13 +1,229 @@ --- runtime_dir: /var/one-deploy/ -os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2204-6.8.1-1-20240131.qcow2 +os_image_url: https://d24fmfybwxpuhu.cloudfront.net/ubuntu2404-7.4.0-0-20260714.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: [] -cputune_shares_base: 200 # twice the OpenNebula's default (for CGv2) -cputune_shares_max: 10000 -cputune_shares: "{{ ((vcpu_static | int) * (cputune_shares_base | int)) | round(0, 'ceil') }}" + +infra_bridge: br0 + +# NOTE: We assume that no more than a single FE VM is deployed on a HV +# (which should be considered a regular / default setup). +# More FE VMs can be deployed on a HV of course, but in such a case, +# please handle it directly in your inventory. +dpdk_socket_path: /var/run/one/vhost-socks/frontend.sock + +memory_KiB: 2097152 # 2 GiB + +vcpu_static: 1 +vcpu_shares: "{{ ((vcpu_static | int) * 200) | int }}" + +infra_xml_variant: default + +infra_xml: + default: + - "{{ infra_xml_base.default }}" + - "{{ infra_xml_cputune.default }}" + - "{{ infra_xml_memoryBacking.default }}" + - "{{ 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.default }}" + - "{{ infra_xml_memoryBacking.hugepages }}" + - "{{ infra_xml_interfaces.openvswitch_dpdk }}" + - "{{ infra_xml_filesystems.default }}" + +infra_xml_cputune: + default: | + + + {{ [vcpu_shares | int, 10000] | min }} + + + +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 }} + + + + /machine + + + hvm + + + + + + + + + + + + destroy + restart + destroy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/roles/infra/tasks/deploy.yml b/roles/infra/tasks/deploy.yml index fc83ec55..afbf9f98 100644 --- a/roles/infra/tasks/deploy.yml +++ b/roles/infra/tasks/deploy.yml @@ -90,7 +90,7 @@ - name: Compute VNC ports ansible.builtin.set_fact: - frontends_to_vnc_ports: >- + frontend_vnc_ports: >- {{ dict(_frontends | zip(_ports)) }} vars: _ports: >- @@ -103,9 +103,20 @@ - name: Define Front-end VMs community.libvirt.virt: command: define - xml: "{{ lookup('template', 'frontend.xml.jinja') }}" + xml: >- + {{ _variant | map('ansible.utils.from_xml') + | map('combine', _fixup, recursive=true) + | combine(recursive=true) + | ansible.utils.to_xml(full_document=false) }} autostart: true vars: + _variant: >- + {{ infra_xml.get(infra_xml_variant, undef(hint='Unknown variant "{}"'.format(infra_xml_variant))) }} + # NOTE: This is required because (for example) '' produces '{"devices": null}' + # which cannot be merged correctly. + _fixup: + domain: + devices: {} context: "{{ hostvars[frontend].context }}" loop_control: { loop_var: frontend } loop: "{{ infra_to_frontends[inventory_hostname] }}" diff --git a/roles/infra/templates/context.sh.jinja b/roles/infra/templates/context.sh.jinja index 77cac65e..24ad3ca3 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 | d("") }}' -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.START_SCRIPT_BASE64 is defined %} +{% if context.ETH0_DNS | d('') %} +ETH0_DNS='{{ context.ETH0_DNS }}' +{% endif %} +{% if context.ETH0_SEARCH_DOMAIN | d('') %} +ETH0_SEARCH_DOMAIN='{{ context.ETH0_SEARCH_DOMAIN }}' +{% endif %} +{% if context.START_SCRIPT_BASE64 | d('') %} 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 b407e975..00000000 --- a/roles/infra/templates/frontend.xml.jinja +++ /dev/null @@ -1,146 +0,0 @@ - - {{ frontend }} - {{ frontend }} - - {{ memory_KiB }} - {{ vcpu_static }} - - - {{ (cputune_shares if (cputune_shares | int) < (cputune_shares_max | int) else cputune_shares_max) | int }} - - - - /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 %} - - - - - - - - - - - - - - - - - - - - -