diff --git a/ansible/group_vars/all/ceos.yml b/ansible/group_vars/vm_host/ceos.yml similarity index 100% rename from ansible/group_vars/all/ceos.yml rename to ansible/group_vars/vm_host/ceos.yml diff --git a/ansible/group_vars/vm_host/main.yml b/ansible/group_vars/vm_host/main.yml index fbb59eee1f..b2e348e2c2 100644 --- a/ansible/group_vars/vm_host/main.yml +++ b/ansible/group_vars/vm_host/main.yml @@ -1,11 +1,5 @@ supported_vm_types: [ "veos", "ceos", "vsonic", "vcisco" ] root_path: veos-vm -vm_images_url: https://acsbe.blob.core.windows.net/vmimages -cd_image_filename: Aboot-veos-serial-8.0.0.iso -hdd_image_filename: vEOS-lab-4.20.15M.vmdk -sonic_image_filename: sonic-vs.img -cisco_image_filename: vIOS-xrv9k-goldenk9-x-7.3.4-20.qcow2 -skip_image_downloading: false vm_console_base: 7000 memory: 2097152 diff --git a/ansible/group_vars/vm_host/vcisco.yml b/ansible/group_vars/vm_host/vcisco.yml new file mode 100644 index 0000000000..726728a5fa --- /dev/null +++ b/ansible/group_vars/vm_host/vcisco.yml @@ -0,0 +1,10 @@ +vcisco_image_filename: vIOS-xrv9k-goldenk9-x-7.3.4-20.qcow2 +skip_vcisco_image_downloading: false + +# Please update url to the actual URL of the image file in your environment. If the image file +# is not available on test server, the file will be downloaded from the URLs. +# The url can be a string as single URL or a list of strings as multiple URLs. If it is a list, the code +# logic will automatically try each URL in the list +vcisco_image_url: + - http://example1.com/vIOS-xrv9k-goldenk9-x-7.3.4-20.qcow2 + - http://example2.com/vIOS-xrv9k-goldenk9-x-7.3.4-20.qcow2 diff --git a/ansible/group_vars/vm_host/veos.yml b/ansible/group_vars/vm_host/veos.yml new file mode 100644 index 0000000000..423522d3e4 --- /dev/null +++ b/ansible/group_vars/vm_host/veos.yml @@ -0,0 +1,20 @@ +# Two image files required for vEOS VMs: +# 1. cd file. +# 2. hdd file. +veos_cd_image_filename: Aboot-veos-serial-8.0.0.iso +veos_hdd_image_filename: vEOS-lab-4.20.15M.vmdk + +# Please update url to the actual URL of the veos image files in your environment. If the image files +# are not available on test server, the files will be downloaded from the URLs. +# The url can be a string as single URL or a list of strings as multiple URLs. If it is a list, the code +# logic will automatically try each URL in the list +veos_cd_image_url: + - http://example1.com/Aboot-veos-serial-8.0.0.iso + - http://example2.com/Aboot-veos-serial-8.0.0.iso +veos_hdd_image_url: + - http://example1.com/vEOS-lab-4.20.15M.vmdk + - http://example2.com/vEOS-lab-4.20.15M.vmdk + +# If the variable is set to true, the code logic will not try to download the image files from the URLs when the files +# are not available on test server +skip_veos_image_downloading: false diff --git a/ansible/group_vars/vm_host/vsonic.yml b/ansible/group_vars/vm_host/vsonic.yml new file mode 100644 index 0000000000..c796f21c7d --- /dev/null +++ b/ansible/group_vars/vm_host/vsonic.yml @@ -0,0 +1,10 @@ +vsonic_image_filename: sonic-vs.img +skip_vsonic_image_downloading: false + +# Please update url to the actual URL of the image file in your environment. If the image file +# is not available on test server, the file will be downloaded from the URLs. +# The url can be a string as single URL or a list of strings as multiple URLs. If it is a list, the code +# logic will automatically try each URL in the list +vsonic_image_url: + - http://example1.com/sonic-vs.img + - http://example2.com/sonic-vs.img diff --git a/ansible/roles/vm_set/tasks/add_ceos_list.yml b/ansible/roles/vm_set/tasks/add_ceos_list.yml index 28c8786381..737fb393ab 100644 --- a/ansible/roles/vm_set/tasks/add_ceos_list.yml +++ b/ansible/roles/vm_set/tasks/add_ceos_list.yml @@ -43,12 +43,12 @@ ceos_image_urls: "{{ ceos_image_url }}" when: ceos_image_url | type_debug == 'list' - - name: Init working_ceos_image_urls list + - name: Init working_image_urls list set_fact: - working_ceos_image_urls: [] + working_image_urls: [] - name: Loop ceos_image_urls to find out working URLs - include_tasks: probe_ceos_image_url.yml + include_tasks: probe_image_url.yml loop: "{{ ceos_image_urls }}" - name: Fail if no working ceos image download url is found @@ -58,12 +58,13 @@ " 1. Fix ceos_image_url defined in ansible/group_vars/all/ceos.yml", " 2. Manually put cEOS image to {{ root_path }}/images/{{ ceos_image_filename }}", ] - when: working_ceos_image_urls | length == 0 + when: working_image_urls | length == 0 - name: Download cEOS image file from working ceos_image_urls using the first working URL get_url: - url: "{{ working_ceos_image_urls[0] }}" + url: "{{ working_image_urls[0] }}" dest: "{{ root_path }}/images/{{ ceos_image_filename }}" + environment: "{{ proxy_env | default({}) }}" register: ceos_image_download_result when: ceos_image_file_stat.stat.exists == false diff --git a/ansible/roles/vm_set/tasks/kickstart_vm.yml b/ansible/roles/vm_set/tasks/kickstart_vm.yml index ca74b1329a..1d76c6b611 100644 --- a/ansible/roles/vm_set/tasks/kickstart_vm.yml +++ b/ansible/roles/vm_set/tasks/kickstart_vm.yml @@ -13,14 +13,14 @@ - block: - name: Wait until vm {{ vm_name }} is loaded kickstart: telnet_port={{ serial_port }} - login={{ eos_default_login }} - password={{ eos_default_password }} - hostname={{ hostname }} + login="{{ eos_default_login }}" + password="{{ eos_default_password }}" + hostname="{{ hostname }}" mgmt_ip="{{ mgmt_ip_address }}/{{ mgmt_prefixlen }}" - mgmt_gw={{ vm_mgmt_gw | default(mgmt_gw) }} - new_login={{ eos_login }} - new_password={{ eos_password }} - new_root_password={{ eos_root_password }} + mgmt_gw="{{ vm_mgmt_gw | default(mgmt_gw) }}" + new_login="{{ eos_login }}" + new_password="{{ eos_password }}" + new_root_password="{{ eos_root_password }}" register: kickstart_output until: '"kickstart_code" in kickstart_output and kickstart_output.kickstart_code == 0' retries: 5 @@ -129,7 +129,7 @@ - name: Respin vm {{ vm_name }} include_tasks: respin_cisco_vm.yml vars: - src_disk_image: "{{ root_path }}/images/{{ cisco_image_filename }}" + src_disk_image: "{{ root_path }}/images/{{ vcisco_image_filename }}" disk_image: "{{ root_path }}/disks/{{ vm_name }}.img" when: vm_name in respin_vms @@ -161,7 +161,7 @@ - name: Respin failed vm {{ vm_name }} include_tasks: respin_cisco_vm.yml vars: - src_disk_image: "{{ root_path }}/images/{{ cisco_image_filename }}" + src_disk_image: "{{ root_path }}/images/{{ vcisco_image_filename }}" disk_image: "{{ root_path }}/disks/{{ vm_name }}.img" - name: Check failed cisco {{ vm_name }} reachablity diff --git a/ansible/roles/vm_set/tasks/main.yml b/ansible/roles/vm_set/tasks/main.yml index d0d7dddc47..69bbae918e 100644 --- a/ansible/roles/vm_set/tasks/main.yml +++ b/ansible/roles/vm_set/tasks/main.yml @@ -1,17 +1,19 @@ # This role creates a set of VM with veos or SONiC or cisco or Ubuntu for Kubernetes master # Input parameters for the role: -# - action: 'start', 'stop' or 'renumber' for creating, removeing, or renumbering vm set respectively +# - action: 'start', 'stop' or 'renumber' for creating, removing, or renumbering vm set respectively # - id: sequence number for vm set on the host. # - external_port: interface which will be used as parent for vlan interface creation # - vlan_base: first vlan id for the VMs # - VMs: a dictionary which contains hostnames of VMs as a key and a dictionary with parameters (num, memory, mgmt_ip) for every VM. # - topology: a dictionary which contains hostnames of VMs as a key and vlans value which define a topology (numbers of connected ports for every VM) # - mgmt_bridge: linux bridge which is used for management interface connections -# - root_path: path where disk images for VMs are created -# - hdd_image_filename: base hdd image for VMs -# - cd_image_filename: base cd image for VMs -# - vm_images_url: url where base images are located -# - vmimages_saskey: a key for Azure download service. Could be set to '' + +# Variables used by the role are mostly defined in files under ansible/group_vars/vm_host directory. +# Supported neighbor types are: veos, sonic, cisco, ubuntu, k8s +# For each of the supported neighbor types, there is a file in ansible/group_vars/vm_host directory which defines the +# the variables for the neighbor type. The neighbor VM image files usually can be manually prepared or automatically +# downloaded from the URLs defined in the variables. Please update the URLs to the actual URLs of the image files in +# your environment. # Need latest ubuntu 4.10 kernel to fix a openvswitch bug # https://bugs.launchpad.net/ubuntu/+source/kernel-package/+bug/1685742 diff --git a/ansible/roles/vm_set/tasks/probe_ceos_image_url.yml b/ansible/roles/vm_set/tasks/probe_ceos_image_url.yml deleted file mode 100644 index 769bf40088..0000000000 --- a/ansible/roles/vm_set/tasks/probe_ceos_image_url.yml +++ /dev/null @@ -1,14 +0,0 @@ -- name: Probe if the URL works - uri: - url: "{{ item }}" - method: HEAD - status_code: 200 - return_content: no - timeout: 3 - register: ceos_image_url_probe_result - failed_when: false - -- name: Append working URL to working_ceos_image_urls list - set_fact: - working_ceos_image_urls: "{{ working_ceos_image_urls + [ item ] }}" - when: ceos_image_url_probe_result.status == 200 diff --git a/ansible/roles/vm_set/tasks/probe_image_url.yml b/ansible/roles/vm_set/tasks/probe_image_url.yml new file mode 100644 index 0000000000..dadb97a3dd --- /dev/null +++ b/ansible/roles/vm_set/tasks/probe_image_url.yml @@ -0,0 +1,15 @@ +- name: Probe if the URL works + uri: + url: "{{ item }}" + method: HEAD + status_code: 200 + return_content: no + timeout: 3 + environment: "{{ proxy_env | default({}) }}" + register: image_url_probe_result + failed_when: false + +- name: Append working URL to working_image_urls list + set_fact: + working_image_urls: "{{ working_image_urls + [ item ] }}" + when: image_url_probe_result.status == 200 diff --git a/ansible/roles/vm_set/tasks/start.yml b/ansible/roles/vm_set/tasks/start.yml index 3b77efb703..846c7e601f 100644 --- a/ansible/roles/vm_set/tasks/start.yml +++ b/ansible/roles/vm_set/tasks/start.yml @@ -20,91 +20,231 @@ - block: - name: Check hdd image - stat: path={{ root_path }}/images/{{ hdd_image_filename }} - register: hdd_stat - - - name: Fail if there are no hdd image and skip image downloading is active - fail: msg="Please put {{ hdd_image_filename }} to {{ root_path }}/images" - when: not hdd_stat.stat.exists and skip_image_downloading - - - name: Download hdd image - get_url: url="{{ vm_images_url }}/{{ hdd_image_filename }}?{{ vmimage_saskey }}" dest="{{ root_path }}/images/{{ hdd_image_filename }}" - environment: "{{ proxy_env | default({}) }}" - when: not hdd_stat.stat.exists and not skip_image_downloading + stat: path={{ root_path }}/images/{{ veos_hdd_image_filename }} + register: veos_hdd_image_file_stat + + - name: Download veos hdd image if no local file exists + block: + + - name: Fail if skip_veos_image_downloading is true + fail: + msg: "Failed, no local veos hdd image and skip_veos_image_downloading is true" + when: skip_veos_image_downloading + + - name: Init veos_hdd_image_urls when veos_hdd_image_url value type is string + set_fact: + veos_hdd_image_urls: + - "{{ veos_hdd_image_url }}" + when: veos_hdd_image_url | type_debug == 'string' + + - name: Init veos_hdd_image_urls when veos_hdd_image_url value type is list + set_fact: + veos_hdd_image_urls: "{{ veos_hdd_image_url }}" + when: veos_hdd_image_url | type_debug == 'list' + + - name: Init working_image_urls list + set_fact: + working_image_urls: [] + + - name: Loop veos_hdd_image_urls to find out working URLs + include_tasks: probe_image_url.yml + loop: "{{ veos_hdd_image_urls }}" + + - name: Fail if no working veos hdd image download url is found + fail: + msg: [ + "Failed, no working veos hdd image download URL is found. There are 2 options to fix it:", + " 1. Fix veos_hdd_image_url defined in ansible/group_vars/vm_host/veos.yml", + " 2. Manually put veos hdd image to {{ root_path }}/images/{{ veos_hdd_image_filename }}", + ] + when: working_image_urls | length == 0 + + - name: Download veos hdd image from the first URL in working_image_urls + get_url: + url: "{{ working_image_urls[0] }}" + dest: "{{ root_path }}/images/{{ veos_hdd_image_filename }}" + environment: "{{ proxy_env | default({}) }}" + + when: not veos_hdd_image_file_stat.stat.exists - name: Check cd image - stat: path={{ root_path }}/images/{{ cd_image_filename }} - register: cd_stat - - - name: Fail if there are no cd image and skip image downloading is active - fail: msg="Please put {{ cd_image_filename }} to {{ root_path }}/images" - when: not cd_stat.stat.exists and skip_image_downloading - - - name: Download cd image - get_url: url="{{ vm_images_url }}/{{ cd_image_filename }}?{{ cdimage_saskey }}" dest="{{ root_path }}/images/{{ cd_image_filename }}" - environment: "{{ proxy_env | default({}) }}" - when: not cd_stat.stat.exists and not skip_image_downloading + stat: path={{ root_path }}/images/{{ veos_cd_image_filename }} + register: veos_cd_image_file_stat + + - name: Download veos cd image if no local file exists + block: + + - name: Fail if skip_veos_image_downloading is true + fail: + msg: "Failed, no local veos cd image and skip_veos_image_downloading is true" + when: skip_veos_image_downloading + + - name: Init veos_cd_image_urls when veos_cd_image_url value type is string + set_fact: + veos_cd_image_urls: + - "{{ veos_cd_image_url }}" + when: veos_cd_image_url | type_debug == 'string' + + - name: Init veos_cd_image_urls when veos_cd_image_url value type is list + set_fact: + veos_cd_image_urls: "{{ veos_cd_image_url }}" + when: veos_cd_image_url | type_debug == 'list' + + - name: Init working_image_urls list + set_fact: + working_image_urls: [] + + - name: Loop veos_cd_image_urls to find out working URLs + include_tasks: probe_image_url.yml + loop: "{{ veos_cd_image_urls }}" + + - name: Fail if no working veos cd image download url is found + fail: + msg: [ + "Failed, no working veos cd image download URL is found. There are 2 options to fix it:", + " 1. Fix veos_cd_image_url defined in ansible/group_vars/vm_host/veos.yml", + " 2. Manually put veos cd image to {{ root_path }}/images/{{ veos_cd_image_filename }}", + ] + when: working_image_urls | length == 0 + + - name: Download veos cd image from the first URL in working_image_urls + get_url: + url: "{{ working_image_urls[0] }}" + dest: "{{ root_path }}/images/{{ veos_cd_image_filename }}" + environment: "{{ proxy_env | default({}) }}" + + when: not veos_cd_image_file_stat.stat.exists - set_fact: - src_image_name: "{{ hdd_image_filename }}" + src_image_name: "{{ veos_hdd_image_filename }}" when: (vm_type | lower) == "veos" - block: - name: Check SONiC image - stat: path={{ root_path }}/images/{{ sonic_image_filename }} - register: img_stat - - - name: Fail if there are no SONiC image and skip image downloading is active - fail: msg="Please put {{ sonic_image_filename }} to {{ root_path }}/images" - when: not img_stat.stat.exists and skip_image_downloading - - - name: Fail if there is no SONiC image download URL specified - fail: msg="Please set sonic_image_url to the URL where the SONiC image can be downloaded from" - when: not img_stat.stat.exists and not skip_image_downloading and sonic_image_url is not defined - - - name: Download SONiC image - get_url: url="{{ sonic_image_url }}" dest="{{ root_path }}/images/{{ sonic_image_filename }}" - environment: "{{ proxy_env | default({}) }}" - when: not img_stat.stat.exists and not skip_image_downloading and sonic_image_url is defined + stat: path={{ root_path }}/images/{{ vsonic_image_filename }} + register: sonic_img_stat + + - name: Download SONiC image if no local file exists + block: + + - name: Fail if skip_vsonic_image_downloading is true + fail: + msg: "Failed, no local SONiC image and skip_vsonic_image_downloading is true" + when: skip_vsonic_image_downloading + + - name: Init vsonic_image_urls when vsonic_image_url value type is string + set_fact: + vsonic_image_urls: + - "{{ vsonic_image_url }}" + when: vsonic_image_url | type_debug == 'string' + + - name: Init vsonic_image_urls when vsonic_image_url value type is list + set_fact: + vsonic_image_urls: "{{ vsonic_image_url }}" + when: vsonic_image_url | type_debug == 'list' + + - name: Init working_image_urls list + set_fact: + working_image_urls: [] + + - name: Loop vsonic_image_urls to find out working URLs + include_tasks: probe_image_url.yml + loop: "{{ vsonic_image_urls }}" + + - name: Fail if no working SONiC image download url is found + fail: + msg: [ + "Failed, no working SONiC image download URL is found. There are 2 options to fix it:", + " 1. Fix vsonic_image_url defined in ansible/group_vars/vm_host/sonic.yml", + " 2. Manually put SONiC image to {{ root_path }}/images/{{ vsonic_image_filename }}", + ] + when: working_image_urls | length == 0 + + - name: Download SONiC image from the first URL in working_image_urls + get_url: + url: "{{ working_image_urls[0] }}" + dest: "{{ root_path }}/images/{{ vsonic_image_filename }}" + environment: "{{ proxy_env | default({}) }}" + + - name: Get downloaded SONiC image info + stat: path={{ root_path }}/images/{{ vsonic_image_filename }} + register: img_stat + + when: not sonic_img_stat.stat.exists - name: Get downloaded SONiC image info - stat: path={{ root_path }}/images/{{ sonic_image_filename }} - register: downloaded_img_stat + stat: path={{ root_path }}/images/{{ vsonic_image_filename }} + register: downloaded_sonic_img_stat - block: - name: Rename file to have a .gz suffix - command: mv {{ root_path }}/images/{{ sonic_image_filename }} {{ root_path }}/images/{{ sonic_image_filename }}.gz + command: mv {{ root_path }}/images/{{ vsonic_image_filename }} {{ root_path }}/images/{{ vsonic_image_filename }}.gz - name: Decompress file - command: gunzip {{ root_path }}/images/{{ sonic_image_filename }}.gz + command: gunzip {{ root_path }}/images/{{ vsonic_image_filename }}.gz - when: '"application/gzip" in downloaded_img_stat.stat.mimetype' + when: '"application/gzip" in downloaded_sonic_img_stat.stat.mimetype' - set_fact: - src_image_name: "{{ sonic_image_filename }}" + src_image_name: "{{ vsonic_image_filename }}" when: (vm_type | lower) == "vsonic" - block: - name: Check cisco image - stat: path={{ root_path }}/images/{{ cisco_image_filename }} - register: img_stat - - - name: Fail if there are no cisco image and skip image downloading is active - fail: msg="Please put {{ cisco_image_filename }} to {{ root_path }}/images" - when: not img_stat.stat.exists and skip_image_downloading - - - name: Download cisco image - get_url: url="{{ vm_images_url }}/{{ cisco_image_filename }}?{{ vciscoimage_saskey }}" dest="{{ root_path }}/images/{{ cisco_image_filename }}" - environment: "{{ proxy_env | default({}) }}" - when: not img_stat.stat.exists and not skip_image_downloading + stat: path={{ root_path }}/images/{{ vcisco_image_filename }} + register: cisco_img_stat + + - name: Download cisco image if no local file exists + block: + + - name: Fail if skip_vcisco_image_downloading is true + fail: + msg: "Failed, no local cisco image and skip_vcisco_image_downloading is true" + when: skip_vcisco_image_downloading + + - name: Init vcisco_image_urls when vcisco_image_url value type is string + set_fact: + vcisco_image_urls: + - "{{ vcisco_image_url }}" + when: vcisco_image_url | type_debug == 'string' + + - name: Init vcisco_image_urls when vcisco_image_url value type is list + set_fact: + vcisco_image_urls: "{{ vcisco_image_url }}" + when: vcisco_image_url | type_debug == 'list' + + - name: Init working_image_urls list + set_fact: + working_image_urls: [] + + - name: Loop vcisco_image_urls to find out working URLs + include_tasks: probe_image_url.yml + loop: "{{ vcisco_image_urls }}" + + - name: Fail if no working cisco image download url is found + fail: + msg: [ + "Failed, no working cisco image download URL is found. There are 2 options to fix it:", + " 1. Fix vcisco_image_url defined in ansible/group_vars/vm_host/cisco.yml", + " 2. Manually put cisco image to {{ root_path }}/images/{{ vcisco_image_filename }}", + ] + when: working_image_urls | length == 0 + + - name: Download cisco image from the first URL in working_image_urls + get_url: + url: "{{ working_image_urls[0] }}" + dest: "{{ root_path }}/images/{{ vcisco_image_filename }}" + environment: "{{ proxy_env | default({}) }}" + + when: not cisco_img_stat.stat.exists - set_fact: - src_image_name: "{{ cisco_image_filename }}" + src_image_name: "{{ vcisco_image_filename }}" when: (vm_type | lower) == "vcisco" @@ -140,7 +280,7 @@ serial_port: "{{ vm_console_base|int + vm_name[4:]|int }}" src_disk_image: "{{ root_path }}/images/{{ src_image_name }}" disk_image_dir: "{{ root_path }}/disks" - cdrom_image: "{{ root_path }}/images/{{ cd_image_filename }}" + cdrom_image: "{{ root_path }}/images/{{ veos_cd_image_filename }}" mgmt_tap: "{{ vm_name }}-m" backplane_tap: "{{ vm_name }}-back" with_items: "{{ VM_hosts }}" @@ -157,7 +297,7 @@ serial_port: "{{ vm_console_base|int + vm_name[4:]|int }}" src_disk_image: "{{ root_path }}/images/{{ src_image_name }}" disk_image_dir: "{{ root_path }}/disks" - cdrom_image: "{{ root_path }}/images/{{ cd_image_filename }}" + cdrom_image: "{{ root_path }}/images/{{ veos_cd_image_filename }}" mgmt_tap: "{{ vm_name }}-m" backplane_tap: "{{ vm_name }}-back" with_items: "{{ VM_hosts }}" diff --git a/ansible/roles/vm_set/tasks/start_vm.yml b/ansible/roles/vm_set/tasks/start_vm.yml index d746fcd38b..c7f40ef5c1 100644 --- a/ansible/roles/vm_set/tasks/start_vm.yml +++ b/ansible/roles/vm_set/tasks/start_vm.yml @@ -79,5 +79,5 @@ - name: "Pause after started every {{ batch_size }} VMs" pause: seconds="{{ interval }}" when: - - "{{ vm_index }} % {{ batch_size }} == 0" - - "{{ interval }} > 0" + - (vm_index|int % batch_size|int) == 0 + - interval|int > 0 diff --git a/ansible/testbed_start_VMs.yml b/ansible/testbed_start_VMs.yml index 26d96d837f..449dec9547 100644 --- a/ansible/testbed_start_VMs.yml +++ b/ansible/testbed_start_VMs.yml @@ -17,8 +17,6 @@ - hosts: servers:&vm_host gather_facts: no - vars_files: - - vars/azure_storage.yml tasks: roles: - { role: vm_set, action: 'start' } diff --git a/ansible/testbed_start_k8s_VMs.yml b/ansible/testbed_start_k8s_VMs.yml index 974f4a5da9..001fa2dd91 100644 --- a/ansible/testbed_start_k8s_VMs.yml +++ b/ansible/testbed_start_k8s_VMs.yml @@ -5,7 +5,6 @@ - hosts: k8s_servers:&k8s_vm_host gather_facts: no vars_files: - - vars/azure_storage.yml - group_vars/all/creds.yml roles: - { role: vm_set, action: 'start_k8s' }