From a9c5560dcca18a0df93341289087a9585fbca29c Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sat, 1 Jan 2022 17:13:26 +0100 Subject: add new openwrt-based testvm add new vm-deploy playbook and script --- inventory/group_vars/chaos-at-home/network.yml | 1 + inventory/host_vars/ch-testvm-openwrt.yml | 117 +++++++++++++++++++++++++ inventory/hosts.ini | 2 + 3 files changed, 120 insertions(+) create mode 100644 inventory/host_vars/ch-testvm-openwrt.yml (limited to 'inventory') diff --git a/inventory/group_vars/chaos-at-home/network.yml b/inventory/group_vars/chaos-at-home/network.yml index 2832ec59..86ab6c7a 100644 --- a/inventory/group_vars/chaos-at-home/network.yml +++ b/inventory/group_vars/chaos-at-home/network.yml @@ -70,6 +70,7 @@ network_zones: ch-iot: 30 ch-testvm-prometheus: 42 ch-testvm-phoebe: 43 + ch-testvm-openwrt: 44 ch-nic: 53 __svc_http__: 80 __svc_imap__: 143 diff --git a/inventory/host_vars/ch-testvm-openwrt.yml b/inventory/host_vars/ch-testvm-openwrt.yml new file mode 100644 index 00000000..f14bd548 --- /dev/null +++ b/inventory/host_vars/ch-testvm-openwrt.yml @@ -0,0 +1,117 @@ +--- +install: + vm: + memory: 128M + numcpus: 2 + autostart: false + disks: + primary: /dev/sda + scsi: + sda: + type: zfs + name: root + size: 15g + interfaces: + - bridge: br-svc + name: svc0 + + +openwrt_arch: x86 +openwrt_target: 64 +openwrt_profile: generic +openwrt_output_image_suffixes: + - "{{ openwrt_profile }}-ext4-combined.img.gz" + +openwrt_packages_remove: + - ppp + - ppp-mod-pppoe + - dnsmasq + - firewall + - odhcpd + - odhcpd-ipv6only +openwrt_packages_add: + - rng-tools + - htop + - ip + - less + - nano + - tcpdump-mini + - iperf + - iperf3 + - mtr + +openwrt_mixin: + /etc/dropbear/authorized_keys: + content: "{{ ssh_keys_root | join('\n') }}\n" + + /etc/htoprc: + file: "{{ global_files_dir }}/common/htoprc" + + +openwrt_uci: + system: + - name: system + options: + hostname: '{{ host_name }}' + timezone: 'CET-1CEST,M3.5.0,M10.5.0/3' + ttylogin: '0' + log_size: '64' + urandom_seed: '0' + + - name: timeserver 'ntp' + options: + enabled: '1' + enable_server: '0' + server: + - '0.at.pool.ntp.org' + - '1.at.pool.ntp.org' + - '2.at.pool.ntp.org' + - '3.at.pool.ntp.org' + + - name: rngd + options: + enabled: '1' + device: '/dev/hwrng' + + dropbear: + - name: dropbear + options: + PasswordAuth: 'off' + RootPasswordAuth: 'off' + Port: '{{ ansible_port | default(22) }}' + + network: + - name: globals 'globals' + options: + ula_prefix: "fc{{ '%02x:%04x:%04x' | format((255 | random(seed=inventory_hostname + '0')), (65535 | random(seed=inventory_hostname + '1')), (65535 | random(seed=inventory_hostname + '2'))) }}::/48" + + - name: interface 'loopback' + options: + device: lo + proto: static + ipaddr: 127.0.0.1 + netmask: 255.0.0.0 + + - name: interface 'svc' + options: + device: eth0 + proto: static + ipaddr: "{{ network_zones.svc.prefix | ipaddr(network_zones.svc.offsets[inventory_hostname]) | ipaddr('address') }}" + netmask: "{{ network_zones.svc.prefix | ipaddr('netmask') }}" + + +virsh_domxml: | + + + /srv/ch-router/vmlinuz + console=ttyS0,115200n8 noinitrd root=/dev/vda + + + + + + + + + + diff --git a/inventory/hosts.ini b/inventory/hosts.ini index 346ddd49..bed5319e 100644 --- a/inventory/hosts.ini +++ b/inventory/hosts.ini @@ -16,6 +16,7 @@ env_group=chaos-at-home ch-gnocchi host_name=gnocchi ch-phoebe host_name=phoebe ch-testvm-phoebe host_name=testvm-phoebe +ch-testvm-openwrt host_name=testvm-openwrt ch-router host_name=router ch-router-obsd host_name=router ch-gw-lan host_name=gw-lan @@ -336,6 +337,7 @@ vmhost-ch-gnocchi-guests [vmhost-ch-phoebe-guests] ch-testvm-phoebe +ch-testvm-openwrt #ch-router ch-router-obsd #ch-jump -- cgit v1.2.3 From 9a8c06e53273fb6fb35c16aeb2429320b421f6a8 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Mon, 3 Jan 2022 22:42:20 +0100 Subject: first basic version of openwrt-based vms --- ansible.cfg | 1 + common/vm-deploy.yml | 12 +- inventory/host_vars/ch-testvm-openwrt.yml | 29 ++-- library/wait_for_virt.py | 179 ++++++++++++++++++++++++ roles/vm/guest/deploy/defaults/main.yml | 7 + roles/vm/guest/deploy/tasks/main.yml | 26 ++++ roles/vm/guest/install/library/wait_for_virt.py | 179 ------------------------ 7 files changed, 228 insertions(+), 205 deletions(-) create mode 100644 library/wait_for_virt.py create mode 100644 roles/vm/guest/deploy/defaults/main.yml create mode 100644 roles/vm/guest/deploy/tasks/main.yml delete mode 100644 roles/vm/guest/install/library/wait_for_virt.py (limited to 'inventory') diff --git a/ansible.cfg b/ansible.cfg index ef74d99c..e3b31768 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -7,6 +7,7 @@ log_path = ./log remote_tmp = /tmp/.ansible/tmp filter_plugins = ./filter_plugins +library = ./library gathering = smart fact_caching = jsonfile diff --git a/common/vm-deploy.yml b/common/vm-deploy.yml index e7c9ded7..bc2e8039 100644 --- a/common/vm-deploy.yml +++ b/common/vm-deploy.yml @@ -22,11 +22,9 @@ when: - (output_images | length) != 1 - - name: fetch infos from image file - stat: - path: "{{ output_images | first }}" - get_mime: yes - register: output_images_info - - debug: - var: output_images_info.stat +- name: deploy vm + hosts: "{{ install_hostname }}" + gather_facts: no + roles: + - role: vm/guest/deploy diff --git a/inventory/host_vars/ch-testvm-openwrt.yml b/inventory/host_vars/ch-testvm-openwrt.yml index f14bd548..5a208b55 100644 --- a/inventory/host_vars/ch-testvm-openwrt.yml +++ b/inventory/host_vars/ch-testvm-openwrt.yml @@ -8,9 +8,8 @@ install: primary: /dev/sda scsi: sda: - type: zfs - name: root - size: 15g + type: image + path: /srv/nvme/ch-testvm-openwrt.img interfaces: - bridge: br-svc name: svc0 @@ -98,20 +97,12 @@ openwrt_uci: proto: static ipaddr: "{{ network_zones.svc.prefix | ipaddr(network_zones.svc.offsets[inventory_hostname]) | ipaddr('address') }}" netmask: "{{ network_zones.svc.prefix | ipaddr('netmask') }}" + gateway: "{{ network_zones.svc.gateway }}" + dns: "{{ network_zones.svc.dns }}" - -virsh_domxml: | - - - /srv/ch-router/vmlinuz - console=ttyS0,115200n8 noinitrd root=/dev/vda - - - - - - - - - - + - name: route 'lan' + options: + interface: svc + target: "{{ network_zones.lan.prefix | ipaddr('network') }}" + netmask: "{{ network_zones.lan.prefix | ipaddr('netmask') }}" + gateway: "{{ network_zones.svc.prefix | ipaddr(network_zones.svc.offsets['ch-gw-lan']) | ipaddr('address') }}" diff --git a/library/wait_for_virt.py b/library/wait_for_virt.py new file mode 100644 index 00000000..6c49fae1 --- /dev/null +++ b/library/wait_for_virt.py @@ -0,0 +1,179 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import traceback +import time + +try: + import libvirt +except ImportError: + HAS_VIRT = False +else: + HAS_VIRT = True + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +VIRT_FAILED = 1 +VIRT_SUCCESS = 0 +VIRT_UNAVAILABLE = 2 + +VIRT_STATE_NAME_MAP = { + 0: "running", + 1: "running", + 2: "running", + 3: "paused", + 4: "shutdown", + 5: "shutdown", + 6: "crashed" +} + + +class VMNotFound(Exception): + pass + + +class LibvirtConnection(object): + + def __init__(self, uri, module): + + self.module = module + + cmd = "uname -r" + rc, stdout, stderr = self.module.run_command(cmd) + + if "xen" in stdout: + conn = libvirt.open(None) + elif "esx" in uri: + auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], [], None] + conn = libvirt.openAuth(uri, auth) + else: + conn = libvirt.open(uri) + + if not conn: + raise Exception("hypervisor connection failure") + + self.conn = conn + + def find_vm(self, vmid): + """ + Extra bonus feature: vmid = -1 returns a list of everything + """ + conn = self.conn + + vms = [] + + # this block of code borrowed from virt-manager: + # get working domain's name + ids = conn.listDomainsID() + for id in ids: + vm = conn.lookupByID(id) + vms.append(vm) + # get defined domain + names = conn.listDefinedDomains() + for name in names: + vm = conn.lookupByName(name) + vms.append(vm) + + if vmid == -1: + return vms + + for vm in vms: + if vm.name() == vmid: + return vm + + raise VMNotFound("virtual machine %s not found" % vmid) + + def get_status(self, vmid): + state = self.find_vm(vmid).info()[0] + return VIRT_STATE_NAME_MAP.get(state, "unknown") + + +class Virt(object): + + def __init__(self, uri, module): + self.module = module + self.uri = uri + + def __get_conn(self): + self.conn = LibvirtConnection(self.uri, self.module) + return self.conn + + def status(self, vmid): + """ + Return a state suitable for server consumption. Aka, codes.py values, not XM output. + """ + self.__get_conn() + return self.conn.get_status(vmid) + + +def core(module): + + states = module.params.get('states', None) + guest = module.params.get('name', None) + uri = module.params.get('uri', None) + delay = module.params.get('delay', None) + sleep = module.params.get('sleep', None) + timeout = module.params.get('timeout', None) + + v = Virt(uri, module) + res = {'changed': False, 'failed': True} + + if delay > 0: + time.sleep(delay) + + for _ in range(0, timeout, sleep): + state = v.status(guest) + if state in states: + res['state'] = state + res['failed'] = False + res['msg'] = "guest '%s' has reached state: %s" % (guest, state) + return VIRT_SUCCESS, res + + time.sleep(sleep) + + res['msg'] = "timeout waiting for guest '%s' to reach one of states: %s" % (guest, ', '.join(states)) + return VIRT_FAILED, res + + +def main(): + + module = AnsibleModule(argument_spec=dict( + name=dict(aliases=['guest'], required=True), + states=dict(type='list', required=True), + uri=dict(default='qemu:///system'), + delay=dict(type='int', default=0), + sleep=dict(type='int', default=1), + timeout=dict(type='int', default=300), + )) + + if not HAS_VIRT: + module.fail_json( + msg='The `libvirt` module is not importable. Check the requirements.' + ) + + for state in module.params.get('states', None): + if state not in set(VIRT_STATE_NAME_MAP.values()): + module.fail_json( + msg="states contains invalid state '%s', must be one of %s" % (state, ', '.join(set(VIRT_STATE_NAME_MAP.values()))) + ) + + rc = VIRT_SUCCESS + try: + rc, result = core(module) + except Exception as e: + module.fail_json(msg=to_native(e), exception=traceback.format_exc()) + + if rc != 0: # something went wrong emit the msg + module.fail_json(rc=rc, msg=result) + else: + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/roles/vm/guest/deploy/defaults/main.yml b/roles/vm/guest/deploy/defaults/main.yml new file mode 100644 index 00000000..c067ecbc --- /dev/null +++ b/roles/vm/guest/deploy/defaults/main.yml @@ -0,0 +1,7 @@ +--- +vm_deploy_decompressors: + "application/octet-stream": cat + "application/gzip": gunzip + "application/zstd": unzstd + "application/x-bzip2": bunzip2 + "application/x-xz": unxz diff --git a/roles/vm/guest/deploy/tasks/main.yml b/roles/vm/guest/deploy/tasks/main.yml new file mode 100644 index 00000000..cc9733ba --- /dev/null +++ b/roles/vm/guest/deploy/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- name: fetch infos from image file + delegate_to: localhost + stat: + path: "{{ output_images | first }}" + get_mime: yes + register: output_image_info + + ## TODO: prepare directory for disk images + ## TODO: compute disk path based on install.disks.primary and install.disks.(scsi|virtio) + ## TODO: actually call this directly and make file replacement atomic!! +- name: copy disk image + pause: + prompt: | + Please copy the image to the vm-host i.e. by calling this: + + cat {{ output_image_info.stat.path }} | {{ vm_deploy_decompressors[output_image_info.stat.mimetype] }} | ssh {{ vm_host.name }} 'cat > {{ install.disks.scsi.sda.path }}' + + When done press ENTER to continue or CTRL-C then A to abort. + +- name: define vm + vars: + vm_define_installer: no + delegate_to: "{{ vm_host.name }}" + import_role: + name: vm/guest/define diff --git a/roles/vm/guest/install/library/wait_for_virt.py b/roles/vm/guest/install/library/wait_for_virt.py deleted file mode 100644 index 6c49fae1..00000000 --- a/roles/vm/guest/install/library/wait_for_virt.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -import traceback -import time - -try: - import libvirt -except ImportError: - HAS_VIRT = False -else: - HAS_VIRT = True - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native - - -VIRT_FAILED = 1 -VIRT_SUCCESS = 0 -VIRT_UNAVAILABLE = 2 - -VIRT_STATE_NAME_MAP = { - 0: "running", - 1: "running", - 2: "running", - 3: "paused", - 4: "shutdown", - 5: "shutdown", - 6: "crashed" -} - - -class VMNotFound(Exception): - pass - - -class LibvirtConnection(object): - - def __init__(self, uri, module): - - self.module = module - - cmd = "uname -r" - rc, stdout, stderr = self.module.run_command(cmd) - - if "xen" in stdout: - conn = libvirt.open(None) - elif "esx" in uri: - auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], [], None] - conn = libvirt.openAuth(uri, auth) - else: - conn = libvirt.open(uri) - - if not conn: - raise Exception("hypervisor connection failure") - - self.conn = conn - - def find_vm(self, vmid): - """ - Extra bonus feature: vmid = -1 returns a list of everything - """ - conn = self.conn - - vms = [] - - # this block of code borrowed from virt-manager: - # get working domain's name - ids = conn.listDomainsID() - for id in ids: - vm = conn.lookupByID(id) - vms.append(vm) - # get defined domain - names = conn.listDefinedDomains() - for name in names: - vm = conn.lookupByName(name) - vms.append(vm) - - if vmid == -1: - return vms - - for vm in vms: - if vm.name() == vmid: - return vm - - raise VMNotFound("virtual machine %s not found" % vmid) - - def get_status(self, vmid): - state = self.find_vm(vmid).info()[0] - return VIRT_STATE_NAME_MAP.get(state, "unknown") - - -class Virt(object): - - def __init__(self, uri, module): - self.module = module - self.uri = uri - - def __get_conn(self): - self.conn = LibvirtConnection(self.uri, self.module) - return self.conn - - def status(self, vmid): - """ - Return a state suitable for server consumption. Aka, codes.py values, not XM output. - """ - self.__get_conn() - return self.conn.get_status(vmid) - - -def core(module): - - states = module.params.get('states', None) - guest = module.params.get('name', None) - uri = module.params.get('uri', None) - delay = module.params.get('delay', None) - sleep = module.params.get('sleep', None) - timeout = module.params.get('timeout', None) - - v = Virt(uri, module) - res = {'changed': False, 'failed': True} - - if delay > 0: - time.sleep(delay) - - for _ in range(0, timeout, sleep): - state = v.status(guest) - if state in states: - res['state'] = state - res['failed'] = False - res['msg'] = "guest '%s' has reached state: %s" % (guest, state) - return VIRT_SUCCESS, res - - time.sleep(sleep) - - res['msg'] = "timeout waiting for guest '%s' to reach one of states: %s" % (guest, ', '.join(states)) - return VIRT_FAILED, res - - -def main(): - - module = AnsibleModule(argument_spec=dict( - name=dict(aliases=['guest'], required=True), - states=dict(type='list', required=True), - uri=dict(default='qemu:///system'), - delay=dict(type='int', default=0), - sleep=dict(type='int', default=1), - timeout=dict(type='int', default=300), - )) - - if not HAS_VIRT: - module.fail_json( - msg='The `libvirt` module is not importable. Check the requirements.' - ) - - for state in module.params.get('states', None): - if state not in set(VIRT_STATE_NAME_MAP.values()): - module.fail_json( - msg="states contains invalid state '%s', must be one of %s" % (state, ', '.join(set(VIRT_STATE_NAME_MAP.values()))) - ) - - rc = VIRT_SUCCESS - try: - rc, result = core(module) - except Exception as e: - module.fail_json(msg=to_native(e), exception=traceback.format_exc()) - - if rc != 0: # something went wrong emit the msg - module.fail_json(rc=rc, msg=result) - else: - module.exit_json(**result) - - -if __name__ == '__main__': - main() -- cgit v1.2.3