diff --git a/README.md b/README.md index e7db734..7174d58 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,21 @@ dependency: driver: name: molecule-qemu platforms: - - name: instance-1 - vm_image: ~/Downloads/debian-11-generic-amd64.qcow2 - vm_ssh_port: 10022 - - name: instance-2 - vm_image: ~/Downloads/debian-11-generic-amd64.qcow2 - vm_ssh_port: 10023 + - name: ubuntu-1 + image: ~/Downloads/focal-server-cloudimg-arm64.img + image_arch: aarch64 + ssh_port: 10022 + ssh_user: ubuntu + - name: ubuntu-2 + image: ~/Downloads/focal-server-cloudimg-amd64.img + image_arch: x86_64 # default + ssh_port: 10023 + ssh_user: ubuntu + - name: debian-1 + image: ~/Downloads/debian-11-generic-amd64.qcow2 + image_arch: x86_64 # default + ssh_port: 10024 + ssh_user: debian provisioner: name: ansible diff --git a/molecule_qemu/playbooks/create.yml b/molecule_qemu/playbooks/create.yml index 9ff83c3..6637363 100644 --- a/molecule_qemu/playbooks/create.yml +++ b/molecule_qemu/playbooks/create.yml @@ -8,36 +8,33 @@ molecule_ephemeral_directory: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}" molecule_scenario_name: "{{ lookup('env', 'MOLECULE_SCENARIO_NAME') }}" molecule_project_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" - qemu_vm_arch: "x86_64" + qemu_cap_hvf: false + qemu_vm_image_arch: "x86_64" + qemu_vm_image_format: "qcow2" qemu_vm_memory: "1024" + qemu_vm_cpus: "1" tasks: - - name: Create run directory exists - ansible.builtin.file: - path: "{{ molecule_ephemeral_directory }}/run/" - state: directory - mode: "0755" - - - name: Generate OpenSSH key pair - community.crypto.openssh_keypair: - path: "{{ molecule_ephemeral_directory }}/run/id_ed25519" - type: ed25519 - register: vm_ssh_keypair - + ### configuration ######################################################### - name: Register VMs data ansible.builtin.set_fact: instance: { "instance": "molecule-{{ molecule_project_name }}-{{ molecule_scenario_name }}-{{ item.name }}", "name": "{{ item.name }}", - "vm_arch": "{{ item.vm_arch | default(qemu_vm_arch) }}", - "vm_image": "{{ item.vm_image }}", - "vm_image_format": "{{ item.vm_image_format | default('qcow2') }}", + + "image": "{{ item.image }}", + "image_arch": "{{ item.image_arch | default(qemu_vm_image_arch) }}", + "image_format": "{{ item.image_format | default(qemu_vm_image_format) }}", + + "ssh_host": "{{ item.ssh_host | default('127.0.0.1') }}", + "ssh_port": "{{ item.ssh_port | default(10022) }}", + "ssh_user": "{{ item.ssh_user }}", + + "vm_cpus": "{{ item.vm_cpus | default(qemu_vm_cpus) }}", "vm_memory": "{{ item.vm_memory | default(qemu_vm_memory) }}", - "vm_ssh_host": "{{ item.vm_ssh_host | default('127.0.0.1') }}", - "vm_ssh_port": "{{ item.vm_ssh_port | default(10022) }}", - "vm_ssh_user": "{{ item.vm_ssh_user | default('debian') }}", - "vm_run_pidfile": "{{ molecule_ephemeral_directory }}/run/{{ item.name }}.pid", - "vm_run_diskfile": "{{ molecule_ephemeral_directory }}/run/{{ item.name }}.qcow2", + + "path_disk": "{{ molecule_ephemeral_directory }}/run/{{ item.name }}.qcow2", + "path_pid": "{{ molecule_ephemeral_directory }}/run/{{ item.name }}.pid", } loop: "{{ molecule_yml.platforms }}" loop_control: @@ -48,18 +45,60 @@ ansible.builtin.set_fact: molecule_instances: "{{ molecule_instances.results | map(attribute='ansible_facts.instance') | list }}" - - name: Create VMs disks based on provided image - ansible.builtin.command: > - qemu-img create - -f qcow2 - -o backing_file={{ item.vm_image }},backing_fmt={{ item.vm_image_format }} - {{ item.vm_run_diskfile }} - args: - creates: "{{ item.vm_run_diskfile }}" + ### assertions ############################################################ + - name: Assert VMs configuration + ansible.builtin.assert: + that: + - molecule_instances is defined + - molecule_instances | length > 0 + - molecule_instances | length == molecule_yml.platforms | length + - molecule_instances | map(attribute='ssh_port') | list | unique | length == molecule_instances | length + fail_msg: "Molecule instances are not properly defined" + success_msg: "Molecule instances are properly defined" + + - name: Assert supported VMs configuration + ansible.builtin.assert: + that: + - item.image_arch in ['x86_64', 'aarch64'] + fail_msg: "Molecule instance {{ item.name }} configuration is not supported" + success_msg: "Molecule instance {{ item.name }} configuration is supported" loop: "{{ molecule_instances }}" loop_control: label: "{{ item.name }}" + ### capabilities ########################################################## + - name: Read kern.hv_support + ansible.builtin.command: sysctl kern.hv_support + register: hv_support + changed_when: false + ignore_errors: true + + - name: Set hvf support + ansible.builtin.set_fact: + qemu_cap_hvf: "{{ hv_support.stdout | trim == 'kern.hv_support: 1' }}" + when: hv_support is defined + + ### prerequisites ######################################################### + - name: Create run directory + ansible.builtin.file: + path: "{{ molecule_ephemeral_directory }}/run/" + state: directory + mode: "0755" + + - name: Create OpenSSH key pair + community.crypto.openssh_keypair: + path: "{{ molecule_ephemeral_directory }}/run/id_ed25519" + type: ed25519 + register: ssh_keypair + + - name: Fetch ARM VMs bios + ansible.builtin.get_url: + url: "https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd" + dest: "{{ molecule_ephemeral_directory }}/run/QEMU_EFI.fd" + mode: "0644" + when: "'aarch64' in molecule_instances | map(attribute='image_arch') | list | unique" + + ### cloud-init ############################################################ - name: Create cloud-init folders ansible.builtin.file: path: "{{ molecule_ephemeral_directory }}/run/cloud-init/{{ item.name }}/" @@ -101,26 +140,50 @@ loop_control: label: "{{ item.name }}" + ### qemu ################################################################## + - name: Create VMs disks + ansible.builtin.command: > + qemu-img create + -f qcow2 + -o backing_file={{ item.image }},backing_fmt={{ item.image_format }} + {{ item.path_disk }} + args: + creates: "{{ item.path_disk }}" + loop: "{{ molecule_instances }}" + loop_control: + label: "{{ item.name }}" + - name: Launch VMs # noqa: no-changed-when ansible.builtin.command: > - qemu-system-{{ item.vm_arch }} + qemu-system-{{ item.image_arch }} + -name {{ item.name }} -boot d -cdrom {{ molecule_ephemeral_directory }}/run/cloud-init/{{ item.name }}.iso - -hda {{ item.vm_run_diskfile }} + -hda {{ item.path_disk }} -m {{ item.vm_memory }} + -smp {{ item.vm_cpus }} -net nic - -net user,hostfwd=tcp::{{ item.vm_ssh_port }}-:22 + -net user,hostfwd=tcp::{{ item.ssh_port }}-:22 -display none -daemonize - -pidfile {{ item.vm_run_pidfile }} + -pidfile {{ item.path_pid }} + {% if item.image_arch == 'aarch64' %} + -bios {{ molecule_ephemeral_directory }}/run/QEMU_EFI.fd + -cpu cortex-a72 + -machine virt,highmem=off + {% if qemu_cap_hvf %} + -accel hvf + {% endif %} + {% endif %} loop: "{{ molecule_instances }}" loop_control: label: "{{ item.name }}" - - name: Wait for SSH to be ready + ### ssh ################################################################### + - name: Wait for SSH ansible.builtin.wait_for: - host: "{{ item.vm_ssh_host }}" - port: "{{ item.vm_ssh_port }}" + host: "{{ item.ssh_host }}" + port: "{{ item.ssh_port }}" delay: 5 timeout: 180 search_regex: "OpenSSH" @@ -131,7 +194,7 @@ poll: 0 register: qemu_launch - - name: Wait for launch jobs to finish + - name: Wait for SSH to be ready ansible.builtin.async_status: jid: "{{ item.ansible_job_id }}" loop: "{{ qemu_launch.results }}" @@ -142,17 +205,18 @@ retries: 30 delay: 10 + ### molecule ############################################################## - name: Prepare VMs config dict ansible.builtin.set_fact: instance_conf_dict: { "instance": "{{ item.instance }}", "name": "{{ item.name }}", - "address": "{{ item.vm_ssh_host }}", - "user": "{{ item.vm_ssh_user }}", - "port": "{{ item.vm_ssh_port }}", - "identity_file": "{{ vm_ssh_keypair.filename }}", - "pidfile": "{{ item.vm_run_pidfile }}", - "diskfile": "{{ item.vm_run_diskfile }}", + "address": "{{ item.ssh_host }}", + "user": "{{ item.ssh_user }}", + "port": "{{ item.ssh_port }}", + "identity_file": "{{ ssh_keypair.filename }}", + "pidfile": "{{ item.path_pid }}", + "diskfile": "{{ item.path_disk }}", } loop: "{{ molecule_instances }}" loop_control: diff --git a/molecule_qemu/playbooks/templates/user-data.j2 b/molecule_qemu/playbooks/templates/user-data.j2 index 389babe..eb84d90 100644 --- a/molecule_qemu/playbooks/templates/user-data.j2 +++ b/molecule_qemu/playbooks/templates/user-data.j2 @@ -1,6 +1,6 @@ #cloud-config ssh_authorized_keys: -- {{ vm_ssh_keypair.public_key }} +- {{ ssh_keypair.public_key }} output: all: ">> /var/log/cloud-init-output.log"