Merge pull request #56 from realraum/update-vm-config
authorChristian Pointner <equinox@spreadspace.org>
Sat, 2 Feb 2019 01:47:24 +0000 (02:47 +0100)
committerGitHub <noreply@github.com>
Sat, 2 Feb 2019 01:47:24 +0000 (02:47 +0100)
Playbook for updating a VM's configuration

ansible/host_vars/alfred/main.yml
ansible/host_vars/metrics/main.yml
ansible/library/wait_for_virt.py [new file with mode: 0644]
ansible/roles/vm/define/defaults/main.yml [new file with mode: 0644]
ansible/roles/vm/define/tasks/main.yml [new file with mode: 0644]
ansible/roles/vm/define/templates/libvirt-domain.xml.j2 [new file with mode: 0644]
ansible/roles/vm/install/library/wait_for_virt.py [deleted file]
ansible/roles/vm/install/tasks/main.yml
ansible/roles/vm/install/templates/libvirt-domain.xml.j2 [deleted file]
ansible/vm-update.yml [new file with mode: 0644]

index ab6ad85..ed73d0c 100644 (file)
@@ -9,4 +9,5 @@ vm_host:
     gateway: "{{ net.mgmt.gw }}"
     nameservers: "{{ net.mgmt.dns }}"
     indices:
+      metrics: 74
       testvm: 99
index 31b6b26..c1d5edf 100644 (file)
@@ -1,2 +1,35 @@
 ---
 localconfig_ssh_config_user: root
+
+vm_host: alfred
+
+install:
+  host: "{{ vm_host }}"
+  mem: 1024
+  numcpu: 2
+  disks:
+    primary: /dev/vda
+    virtio:
+      vda:
+        vg: "{{ vm_host }}"
+        lv: "{{ inventory_hostname }}"
+        size: 10g
+      vdb:
+        vg: "{{ vm_host }}"
+        lv: "{{ inventory_hostname }}-data"
+
+  interfaces:
+  - bridge: "{{ hostvars[vm_host].vm_host.network.interface }}"
+    name: mgmt0
+  autostart: True
+
+network:
+  nameservers: "{{ hostvars[vm_host].vm_host.network.nameservers }}"
+  domain: realraum.at
+  systemd_link:
+    interfaces: "{{ install.interfaces }}"
+  primary:
+    interface: mgmt0
+    ip: "{{ (hostvars[vm_host].vm_host.network.ip+'/'+hostvars[vm_host].vm_host.network.mask) | ipaddr(hostvars[vm_host].vm_host.network.indices[inventory_hostname]) | ipaddr('address') }}"
+    mask: "{{ hostvars[vm_host].vm_host.network.mask }}"
+    gateway: "{{ hostvars[vm_host].vm_host.network.gateway | default(hostvars[vm_host].vm_host.network.ip) }}"
diff --git a/ansible/library/wait_for_virt.py b/ansible/library/wait_for_virt.py
new file mode 100644 (file)
index 0000000..6c49fae
--- /dev/null
@@ -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/ansible/roles/vm/define/defaults/main.yml b/ansible/roles/vm/define/defaults/main.yml
new file mode 100644 (file)
index 0000000..302256a
--- /dev/null
@@ -0,0 +1,4 @@
+vm_define_autostart: "{{ not vm_define_installer and hostvars[hostname].install_cooked.autostart | default(False) }}"
+vm_define_start: yes
+
+vm_define_installer: no
diff --git a/ansible/roles/vm/define/tasks/main.yml b/ansible/roles/vm/define/tasks/main.yml
new file mode 100644 (file)
index 0000000..5667658
--- /dev/null
@@ -0,0 +1,51 @@
+---
+- name: check if vm already exists
+  virt:
+    name: "{{ hostname }}"
+    command: info
+  register: vmhost_info
+
+- when: hostname in vmhost_info
+  block:
+    - name: destroy exisiting vm
+      virt:
+        name: "{{ hostname }}"
+        state: destroyed
+
+    - name: wait for vm to be destroyed
+      wait_for_virt:
+        name: "{{ hostname }}"
+        states: shutdown,crashed
+        timeout: 5
+
+    - name: undefining exisiting vm
+      virt:
+        name: "{{ hostname }}"
+        command: undefine
+
+
+- name: redefine vm
+  virt:
+    name: "{{ hostname }}"
+    command: define
+    xml: "{{ lookup('template', 'libvirt-domain.xml.j2') }}"
+
+- when: vm_define_start
+  block:
+    - name: start vm
+      virt:
+        name: "{{ hostname }}"
+        state: running
+
+    - name: wait for VM to start
+      wait_for_virt:
+        name: "{{ hostname }}"
+        states: running
+        timeout: 10
+
+- name: mark vm as autostarted
+  when: vm_define_autostart
+  virt:
+    name: "{{ hostname }}"
+    autostart: yes
+    command: info ## virt module needs either command or state
diff --git a/ansible/roles/vm/define/templates/libvirt-domain.xml.j2 b/ansible/roles/vm/define/templates/libvirt-domain.xml.j2
new file mode 100644 (file)
index 0000000..152ef51
--- /dev/null
@@ -0,0 +1,76 @@
+<domain type='kvm'>
+  <name>{{ hostname }}</name>
+  <memory>{{ hostvars[hostname].install_cooked.mem * 1024 }}</memory>
+  <currentMemory>{{ hostvars[hostname].install_cooked.mem * 1024 }}</currentMemory>
+  <vcpu>{{ hostvars[hostname].install_cooked.numcpu }}</vcpu>
+  <os>
+    <type arch='x86_64' machine='pc-0.12'>hvm</type>
+{% if vm_define_installer %}
+    <kernel>{{ debian_installer_path }}/{{ install_distro }}-{{ install_codename }}/{{ hostvars[hostname].install_cooked.arch | default('amd64') }}/linux</kernel>
+    <initrd>{{ preseed_tmpdir }}/initrd.preseed.gz</initrd>
+    <cmdline>console=ttyS0,115200n8</cmdline>
+{% endif %}
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+{% if vm_define_installer %}
+  <on_reboot>destroy</on_reboot>
+  <on_crash>destroy</on_crash>
+{% else %}
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+{% endif %}
+  <devices>
+    <emulator>/usr/bin/kvm</emulator>
+    <!-- Provide a virtualized RNG to the guest -->
+    <rng model='virtio'>
+      <!-- Allow consuming up to 10kb/s, measured over 2s -->
+      <rate period="2000" bytes="20480"/>
+      <backend model='random'>/dev/urandom</backend>
+    </rng>
+
+{% if 'virtio' in hostvars[hostname].install_cooked.disks %}
+{%   for device, lv in hostvars[hostname].install_cooked.disks.virtio.items() %}
+    <disk type='block' device='disk'>
+      <driver name='qemu' type='raw' cache='none' discard='unmap'/>
+      <source dev='/dev/mapper/{{ lv.vg | replace('-', '--') }}-{{ lv.lv | replace('-', '--') }}'/>
+      <target dev='{{ device }}' bus='virtio'/>
+    </disk>
+{%   endfor %}
+{% endif %}
+
+{% if 'scsi' in hostvars[hostname].install_cooked.disks %}
+    <controller type='scsi' index='0' model='virtio-scsi'/>
+{%   for device, lv in hostvars[hostname].install_cooked.disks.scsi.items() %}
+    <disk type='block' device='disk'>
+      <driver name='qemu' type='raw' cache='none' discard='unmap'/>
+      <source dev='/dev/mapper/{{ lv.vg | replace('-', '--') }}-{{ lv.lv | replace('-', '--') }}'/>
+      <target dev='{{ device }}' bus='scsi'/>
+    </disk>
+{%   endfor %}
+{% endif %}
+
+{% if hostvars[hostname].install_cooked.interfaces %}
+{%   for if in hostvars[hostname].install_cooked.interfaces %}
+    <interface type='bridge'>
+      <source bridge='{{ if.bridge }}'/>
+      <model type='virtio'/>
+      <address type='pci' domain='0x0000' bus='0x01' slot='0x0{{ loop.index }}' function='0x0'/>
+    </interface>
+{%   endfor %}
+{% endif %}
+
+    <serial type='pty'>
+      <target port='0'/>
+    </serial>
+    <console type='pty'>
+      <target type='serial' port='0'/>
+    </console>
+  </devices>
+</domain>
diff --git a/ansible/roles/vm/install/library/wait_for_virt.py b/ansible/roles/vm/install/library/wait_for_virt.py
deleted file mode 100644 (file)
index 6c49fae..0000000
+++ /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()
index 5043fcc..e669fa3 100644 (file)
@@ -6,30 +6,6 @@
     lv: "{{ item.value.lv }}"
     size: "{{ item.value.size }}"
 
-- name: check if vm already exists
-  virt:
-    name: "{{ hostname }}"
-    command: info
-  register: vmhost_info
-
-- block:
-    - name: destroy exisiting vm
-      virt:
-        name: "{{ hostname }}"
-        state: destroyed
-
-    - name: wait for vm to be destroyed
-      wait_for_virt:
-        name: "{{ hostname }}"
-        states: shutdown,crashed
-        timeout: 5
-
-    - name: undefining exisiting vm
-      virt:
-        name: "{{ hostname }}"
-        command: undefine
-
-  when: hostname in vmhost_info
 
 - block:
     - name: create a temporary workdir
         etype: user
         permissions: rx
 
-    - name: define new installer vm
-      virt:
-        name: "{{ hostname }}"
-        command: define
-        xml: "{{ lookup('template', 'libvirt-domain.xml.j2') }}"
+    - import_role:
+        name: vm/define
       vars:
-        run_installer: yes
+        vm_define_installer: yes
         preseed_tmpdir: "{{ tmpdir.path }}"
 
-    - name: start vm
-      virt:
-        name: "{{ hostname }}"
-        state: running
-
-    - name: wait for installer to start
-      wait_for_virt:
-        name: "{{ hostname }}"
-        states: running
-        timeout: 10
-
     - debug:
         msg: "you can check on the status of the installer running this command 'virsh console {{ hostname }}' on host {{ inventory_hostname }}."
 
       register: installer_result
       failed_when: installer_result.failed or installer_result.state == "crashed"
 
-    - name: undefining installer vm
-      virt:
-        name: "{{ hostname }}"
-        command: undefine
-
   always:
     - name: cleanup temporary workdir
       file:
         path: "{{ tmpdir.path }}"
         state: absent
 
-- name: define new production vm
-  virt:
-    name: "{{ hostname }}"
-    command: define
-    xml: "{{ lookup('template', 'libvirt-domain.xml.j2') }}"
+- import_role:
+    name: vm/define
   vars:
-    run_installer: no
-
-- name: start vm
-  virt:
-    name: "{{ hostname }}"
-    state: running
-
-- name: mark vm as autostarted
-  virt:
-    name: "{{ hostname }}"
-    autostart: "{{ hostvars[hostname].install_cooked.autostart }}"
-    command: info ## virt module needs either command or state
-  when: hostvars[hostname].install_cooked.autostart is defined
+    vm_define_installer: no
diff --git a/ansible/roles/vm/install/templates/libvirt-domain.xml.j2 b/ansible/roles/vm/install/templates/libvirt-domain.xml.j2
deleted file mode 100644 (file)
index 9119f64..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-<domain type='kvm'>
-  <name>{{ hostname }}</name>
-  <memory>{{ hostvars[hostname].install_cooked.mem * 1024 }}</memory>
-  <currentMemory>{{ hostvars[hostname].install_cooked.mem * 1024 }}</currentMemory>
-  <vcpu>{{ hostvars[hostname].install_cooked.numcpu }}</vcpu>
-  <os>
-    <type arch='x86_64' machine='pc-0.12'>hvm</type>
-{% if run_installer %}
-    <kernel>{{ debian_installer_path }}/{{ install_distro }}-{{ install_codename }}/{{ hostvars[hostname].install_cooked.arch | default('amd64') }}/linux</kernel>
-    <initrd>{{ preseed_tmpdir }}/initrd.preseed.gz</initrd>
-    <cmdline>console=ttyS0,115200n8</cmdline>
-{% endif %}
-    <boot dev='hd'/>
-  </os>
-  <features>
-    <acpi/>
-    <apic/>
-    <pae/>
-  </features>
-  <clock offset='utc'/>
-  <on_poweroff>destroy</on_poweroff>
-{% if run_installer %}
-  <on_reboot>destroy</on_reboot>
-  <on_crash>destroy</on_crash>
-{% else %}
-  <on_reboot>restart</on_reboot>
-  <on_crash>restart</on_crash>
-{% endif %}
-  <devices>
-    <emulator>/usr/bin/kvm</emulator>
-    <!-- Provide a virtualized RNG to the guest -->
-    <rng model='virtio'>
-      <!-- Allow consuming up to 10kb/s, measured over 2s -->
-      <rate period="2000" bytes="20480"/>
-      <backend model='random'>/dev/urandom</backend>
-    </rng>
-
-{% if 'virtio' in hostvars[hostname].install_cooked.disks %}
-{%   for device, lv in hostvars[hostname].install_cooked.disks.virtio.items() %}
-    <disk type='block' device='disk'>
-      <driver name='qemu' type='raw' cache='none' discard='unmap'/>
-      <source dev='/dev/mapper/{{ lv.vg | replace('-', '--') }}-{{ lv.lv | replace('-', '--') }}'/>
-      <target dev='{{ device }}' bus='virtio'/>
-    </disk>
-{%   endfor %}
-{% endif %}
-
-{% if 'scsi' in hostvars[hostname].install_cooked.disks %}
-    <controller type='scsi' index='0' model='virtio-scsi'/>
-{%   for device, lv in hostvars[hostname].install_cooked.disks.scsi.items() %}
-    <disk type='block' device='disk'>
-      <driver name='qemu' type='raw' cache='none' discard='unmap'/>
-      <source dev='/dev/mapper/{{ lv.vg | replace('-', '--') }}-{{ lv.lv | replace('-', '--') }}'/>
-      <target dev='{{ device }}' bus='scsi'/>
-    </disk>
-{%   endfor %}
-{% endif %}
-
-{% if hostvars[hostname].install_cooked.interfaces %}
-{%   for if in hostvars[hostname].install_cooked.interfaces %}
-    <interface type='bridge'>
-      <source bridge='{{ if.bridge }}'/>
-      <model type='virtio'/>
-      <address type='pci' domain='0x0000' bus='0x01' slot='0x0{{ loop.index }}' function='0x0'/>
-    </interface>
-{%   endfor %}
-{% endif %}
-
-    <serial type='pty'>
-      <target port='0'/>
-    </serial>
-    <console type='pty'>
-      <target type='serial' port='0'/>
-    </console>
-  </devices>
-</domain>
diff --git a/ansible/vm-update.yml b/ansible/vm-update.yml
new file mode 100644 (file)
index 0000000..b40e06d
--- /dev/null
@@ -0,0 +1,51 @@
+---
+- name: preperations and sanity checks
+  hosts: "{{ hostname }}"
+  gather_facts: no
+  tasks:
+  - name: setup variables
+    set_fact:
+      network_cooked: "{{ network }}"
+      install_cooked: "{{ install }}"
+
+  - name: create temporary host group for vm host
+    changed_when: no
+    add_host:
+      name: "{{ install_cooked.host }}"
+      inventory_dir: "{{inventory_dir}}"
+      group: _vmhost_
+  # TODO: add some sanity checks
+
+- name: Update network configuration & shutdown VM
+  hosts: "{{ hostname }}"
+  roles:
+    - vm/network
+
+  post_tasks:
+    - name: Shutdown VM
+      async: 1
+      poll: 0
+      ignore_errors: true
+      shell: sleep 2 && poweroff
+
+- name: Update VM definition
+  hosts: _vmhost_
+  pre_tasks:
+    - name: Wait for VM to shut down
+      wait_for_virt:
+        name: "{{ hostname }}"
+        states: shutdown
+        timeout: 30
+
+  roles:
+    - vm/define
+
+- name: Apply the base vm/guest role
+  hosts: "{{ hostname }}"
+  pre_tasks:
+    - name: Wait for the VM to be up and running
+      wait_for_connection:
+        timeout: 120
+
+  roles:
+    - vm/guest