diff --git a/ansible/ipa-build.yml b/ansible/ipa-build.yml deleted file mode 100644 index a2edf1d18..000000000 --- a/ansible/ipa-build.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- name: Ensure Ironic Python Agent images are built - hosts: controllers[0] - vars: - # This can be set to True to force rebuilding images. - ipa_build_force: False - roles: - - role: ipa-build - ipa_build_venv: "{{ virtualenv_path }}/ipa-build" - ipa_build_image_cache_path: "{{ image_cache_path }}" - ipa_build_source_checkout_path: "{{ source_checkout_path }}" - ipa_build_kernel_name: "{{ ipa_images_kernel_name }}" - ipa_build_ramdisk_name: "{{ ipa_images_ramdisk_name }}" - when: "{{ ipa_build_images | bool }}" diff --git a/ansible/ipa-images.yml b/ansible/ipa-images.yml deleted file mode 100644 index 78b422622..000000000 --- a/ansible/ipa-images.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -- name: Ensure Ironic Python Agent (IPA) images are downloaded and registered - hosts: controllers[0] - pre_tasks: - - name: Validate OpenStack password authentication parameters - fail: - msg: > - Required OpenStack authentication parameter {{ item }} is - {% if item in openstack_auth %}empty{% else %}not present{% endif %} - in openstack_auth. Have you sourced the environment file? - when: - - "{{ openstack_auth_type == 'password' }}" - - "{{ item not in openstack_auth or not openstack_auth[item] }}" - with_items: "{{ openstack_auth_password_required_params }}" - tags: - - config-validation - - - block: - - name: Check for the presence of locally built Ironic Python Agent (IPA) images - stat: - path: "{{ image_cache_path }}/{{ item }}" - get_md5: False - get_checksum: False - mime: False - with_items: - - "{{ ipa_images_kernel_name }}" - - "{{ ipa_images_ramdisk_name }}" - register: ipa_image_stat - - - name: Validate the presence of locally built Ironic Python Agent (IPA) images - fail: - msg: > - Expected locally built Ironic Python Agent (IPA) image - {{ item.item }} was not present in {{ image_cache_path }}. - with_items: "{{ ipa_image_stat.results }}" - when: "{{ not item.stat.exists }}" - when: "{{ ipa_build_images | bool }}" - tags: - - config-validation - - roles: - - role: ipa-images - ipa_images_venv: "{{ virtualenv_path }}/shade" - ipa_images_openstack_auth_type: "{{ openstack_auth_type }}" - ipa_images_openstack_auth: "{{ openstack_auth }}" - ipa_images_cache_path: "{{ image_cache_path }}" - # Don't pass the kernel and ramdisk image URLs if using built images. - ipa_images_kernel_url: "{{ ipa_build_images | ternary(None, ipa_kernel_upstream_url) }}" - ipa_images_ramdisk_url: "{{ ipa_build_images | ternary(None, ipa_ramdisk_upstream_url) }}" diff --git a/ansible/kolla-openstack.yml b/ansible/kolla-openstack.yml index bc404e731..86c691dd1 100644 --- a/ansible/kolla-openstack.yml +++ b/ansible/kolla-openstack.yml @@ -1,9 +1,74 @@ --- +- name: Ensure locally built Ironic Python Agent images are copied + hosts: controllers[0] + vars: + # These are the filenames generated by overcloud-ipa-build.yml. + ipa_image_name: "ipa" + ipa_images: + - "{{ ipa_image_name }}.vmlinuz" + - "{{ ipa_image_name }}.initramfs" + tasks: + - block: + - block: + - name: Check for the presence of locally built Ironic Python Agent (IPA) images + stat: + path: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ item }}" + get_md5: False + get_checksum: False + mime: False + with_items: "{{ ipa_images }}" + register: ipa_image_stat + + - name: Validate the presence of locally built Ironic Python Agent (IPA) images + fail: + msg: > + Expected locally built Ironic Python Agent (IPA) image + {{ item.item }} was not present in + {{ image_cache_path }}/{{ ipa_image_name }}. + with_items: "{{ ipa_image_stat.results }}" + when: not item.stat.exists + tags: + - config-validation + + - name: Check whether the image cache directory exists + local_action: + module: stat + path: "{{ hostvars.localhost.image_cache_path }}" + get_md5: False + get_checksum: False + mime: False + register: image_cache_stat + + - name: Ensure the image cache directory exists + local_action: + module: file + path: "{{ hostvars.localhost.image_cache_path }}" + state: directory + owner: "{{ lookup('env', 'USER') }}" + group: "{{ lookup('env', 'USER') }}" + become: True + when: not image_cache_stat.stat.exists + + - name: Ensure Ironic Python Agent images are copied onto the local machine + fetch: + src: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ item.src }}" + dest: "{{ hostvars.localhost.image_cache_path }}/{{ ipa_image_name }}/{{ item.dest }}" + flat: True + with_items: + - src: "{{ ipa_images[0] }}" + dest: "{{ ipa_images_kernel_name }}" + - src: "{{ ipa_images[1] }}" + dest: "{{ ipa_images_ramdisk_name }}" + when: ipa_build_images | bool + tags: + - config + - name: Ensure Kolla OpenStack components are configured hosts: config-mgmt vars: switch_type_to_device_type: dellos9: netmiko_dell_force10 + ipa_image_name: "ipa" pre_tasks: - block: - name: Check whether Kolla extra configuration files exist @@ -72,6 +137,18 @@ }] }} with_items: "{{ kolla_neutron_ml2_generic_switch_hosts }}" + + - name: Set facts containing IPA kernel and ramdisk URLs + set_fact: + kolla_inspector_ipa_kernel_upstream_url: "{{ inspector_ipa_kernel_upstream_url }}" + kolla_inspector_ipa_ramdisk_upstream_url: "{{ inspector_ipa_ramdisk_upstream_url }}" + when: not ipa_build_images | bool + + - name: Set facts containing IPA kernel and ramdisk paths + set_fact: + kolla_inspector_ipa_kernel_path: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ ipa_images_kernel_name }}" + kolla_inspector_ipa_ramdisk_path: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ ipa_images_ramdisk_name }}" + when: ipa_build_images | bool tags: - config roles: @@ -84,8 +161,6 @@ kolla_inspector_enable_discovery: "{{ inspector_enable_discovery }}" kolla_inspector_discovery_enroll_node_driver: "{{ inspector_discovery_enroll_node_driver }}" kolla_inspector_extra_kernel_options: "{{ inspector_extra_kernel_options }}" - kolla_inspector_ipa_kernel_upstream_url: "{{ inspector_ipa_kernel_upstream_url }}" - kolla_inspector_ipa_ramdisk_upstream_url: "{{ inspector_ipa_ramdisk_upstream_url }}" # Ironic inspector's dnsmasq configuration. kolla_inspector_dhcp_pool_start: "{{ inspection_net_name | net_inspection_allocation_pool_start }}" kolla_inspector_dhcp_pool_end: "{{ inspection_net_name | net_inspection_allocation_pool_end }}" diff --git a/ansible/overcloud-ipa-build.yml b/ansible/overcloud-ipa-build.yml new file mode 100644 index 000000000..e69e230bb --- /dev/null +++ b/ansible/overcloud-ipa-build.yml @@ -0,0 +1,26 @@ +--- +# Build and install an Ironic Python Agent (IPA) image for the overcloud's +# ironic and ironic-inspector services. +# +# The images will be stored in {{ image_cache_path }}/{{ ipa_image_name }}. + +- name: Ensure Ironic Python Agent images are built and installed + hosts: controllers[0] + vars: + ipa_image_name: "ipa" + tasks: + - name: Ensure Ironic Python Agent images are built + include_role: + name: stackhpc.os-images + os_images_venv: "{{ virtualenv_path }}/ipa-build-dib" + os_images_cache: "{{ image_cache_path }}" + os_images_common: "" + os_images_list: + - name: "{{ ipa_image_name }}" + elements: "{{ ipa_build_dib_elements }}" + env: "{{ ipa_build_dib_env }}" + # Avoid needing to install qemu-img for qcow2 image. + type: raw + os_images_git_elements: "{{ ipa_build_dib_git_elements }}" + os_images_upload: False + when: "{{ ipa_build_images | bool }}" diff --git a/ansible/overcloud-ipa-images.yml b/ansible/overcloud-ipa-images.yml new file mode 100644 index 000000000..04a165917 --- /dev/null +++ b/ansible/overcloud-ipa-images.yml @@ -0,0 +1,87 @@ +--- +- name: Ensure Ironic Python Agent (IPA) images are downloaded and registered + hosts: controllers[0] + vars: + # These are the filenames generated by overcloud-ipa-build.yml. + ipa_image_name: "ipa" + ipa_images: + - "{{ ipa_image_name }}.vmlinuz" + - "{{ ipa_image_name }}.initramfs" + pre_tasks: + - name: Validate OpenStack password authentication parameters + fail: + msg: > + Required OpenStack authentication parameter {{ item }} is + {% if item in openstack_auth %}empty{% else %}not present{% endif %} + in openstack_auth. Have you sourced the environment file? + when: + - openstack_auth_type == 'password' + - item not in openstack_auth or not openstack_auth[item] + with_items: "{{ openstack_auth_password_required_params }}" + tags: + - config-validation + + - block: + - name: Check for the presence of locally built Ironic Python Agent (IPA) images + stat: + path: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ item }}" + get_md5: False + get_checksum: False + mime: False + with_items: "{{ ipa_images }}" + register: ipa_image_stat + + - name: Validate the presence of locally built Ironic Python Agent (IPA) images + fail: + msg: > + Expected locally built Ironic Python Agent (IPA) image + {{ item.item }} was not present in {{ image_cache_path }}. + with_items: "{{ ipa_image_stat.results }}" + when: not item.stat.exists + when: ipa_build_images | bool + tags: + - config-validation + + - name: Set fact containing the Ironic Python Agent (IPA) image URLs + set_fact: + # Don't pass the kernel and ramdisk image URLs if using built images. + ipa_images_kernel_url: "{{ ipa_kernel_upstream_url }}" + ipa_images_ramdisk_url: "{{ ipa_ramdisk_upstream_url }}" + when: not ipa_build_images | bool + + - name: Check whether the image cache directory exists + stat: + path: "{{ image_cache_path }}" + get_md5: False + get_checksum: False + mime: False + register: image_cache_stat + + - name: Ensure the image cache directory exists + stat: + path: "{{ image_cache_path }}" + state: directory + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" + become: True + when: not image_cache_stat.stat.exists + + - name: Ensure locally built Ironic Python Agent (IPA) images are copied + copy: + src: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ item.src }}" + dest: "{{ image_cache_path }}/{{ ipa_image_name }}/{{ item.dest }}" + remote_src: True + with_items: + - src: "{{ ipa_images[0] }}" + dest: "{{ ipa_images_kernel_name }}" + - src: "{{ ipa_images[1] }}" + dest: "{{ ipa_images_ramdisk_name }}" + when: + - ipa_build_images | bool + - item.src != item.dest + roles: + - role: ipa-images + ipa_images_venv: "{{ virtualenv_path }}/shade" + ipa_images_openstack_auth_type: "{{ openstack_auth_type }}" + ipa_images_openstack_auth: "{{ openstack_auth }}" + ipa_images_cache_path: "{{ image_cache_path }}/{{ ipa_image_name }}" diff --git a/ansible/roles/ipa-build/README.md b/ansible/roles/ipa-build/README.md deleted file mode 100644 index f05a3dacd..000000000 --- a/ansible/roles/ipa-build/README.md +++ /dev/null @@ -1,64 +0,0 @@ -Ironic Python Agent (IPA) Image Build -===================================== - -This role can be used to build kernel and ramdisk images for OpenStack Ironic -Python Agent (IPA). - -Requirements ------------- - -None - -Role Variables --------------- - -`ipa_build_venv` is a path to a directory in which to create a virtualenv. - -`ipa_build_install_epel`: Whether to install EPEL repository package. - -`ipa_build_install_package_dependencies`: Whether to install package -dependencies. - -`ipa_build_cache_path`: Path to directory in which to store built images. - -`ipa_build_source_url`: URL of IPA source repository. - -`ipa_build_source_version`: Version of IPA source repository. - -`ipa_build_upper_constraints_file_url`: URL of IPA upper constraints file. - -`ipa_build_custom_upper_constraints`: Custom python package version constraints -for IPA. Dict mapping package name to upper version constraint. - -`ipa_build_kernel_name`: Name of kernel image to save. - -`ipa_build_ramdisk_name`: Name of ramdisk image to save. - -`ipa_build_force`: Whether to force rebuilding images when they already exist. - -Dependencies ------------- - -None - -Example Playbook ----------------- - -The following playbook installs openstackclient in a virtualenv. - - --- - - name: Ensure Ironic Python Agent (IPA) images are built - hosts: localhost - roles: - - role: ipa-build - ipa_build_venv: "~/ipa-build-venv" - ipa_build_cache_path: "~/ipa-build-cache" - ipa_build_source_url: "https://github.com/openstack/ironic-python-agent" - ipa_build_source_version: "master" - ipa_build_kernel_name: "ipa.vmlinuz" - ipa_build_ramdisk_name: "ipa.initramfs" - -Author Information ------------------- - -- Mark Goddard () diff --git a/ansible/roles/ipa-build/defaults/main.yml b/ansible/roles/ipa-build/defaults/main.yml deleted file mode 100644 index 77424c395..000000000 --- a/ansible/roles/ipa-build/defaults/main.yml +++ /dev/null @@ -1,37 +0,0 @@ ---- -# Path to a directory in which to create a virtualenv. -ipa_build_venv: - -# Path to a directory in which to clone the IPA source repository. -ipa_build_source_checkout_path: - -# Whether to install EPEL repository package. -ipa_build_install_epel: True - -# Whether to install package dependencies. -ipa_build_install_package_dependencies: True - -# Path to directory in which to store built images. -ipa_build_cache_path: - -# URL of IPA source repository. -ipa_build_source_url: - -# Version of IPA source repository. -ipa_build_source_version: - -# URL of IPA upper constraints file. -ipa_build_upper_constraints_file_url: - -# Custom python package version constraints for IPA. Dict mapping package name -# to upper version constraint. -ipa_build_custom_upper_constraints: - -# Name of kernel image to save. -ipa_build_kernel_name: - -# Name of ramdisk image to save. -ipa_build_ramdisk_name: - -# Whether to force rebuilding images when they already exist. -ipa_build_force: False diff --git a/ansible/roles/ipa-build/tasks/main.yml b/ansible/roles/ipa-build/tasks/main.yml deleted file mode 100644 index 104f2ed1e..000000000 --- a/ansible/roles/ipa-build/tasks/main.yml +++ /dev/null @@ -1,104 +0,0 @@ ---- -- name: Ensure EPEL repo is installed - yum: - name: epel-release - state: installed - become: True - when: "{{ ipa_build_install_epel | bool }}" - -- name: Ensure required packages are installed - yum: - name: "{{ item }}" - state: installed - become: True - with_items: - - gcc - - libffi-devel - - openssl-devel - - python-devel - - python-pip - - python-virtualenv - when: "{{ ipa_build_install_package_dependencies | bool }}" - -- name: Ensure source code checkout and image cache directories exist - file: - path: "{{ ipa_build_source_checkout_path }}" - state: directory - owner: "{{ ansible_user }}" - group: "{{ ansible_user }}" - recurse: True - with_items: - - "{{ ipa_build_source_checkout_path }}" - - "{{ ipa_build_image_cache_path }}" - become: True - -- name: Ensure Ironic Python Agent (IPA) source code checkout exists - git: - repo: "{{ ipa_build_source_url }}" - dest: "{{ ipa_build_source_checkout_path }}/ironic-python-agent" - version: "{{ ipa_build_source_version }}" - -- name: Ensure the latest version of pip is installed - pip: - name: "{{ item.name }}" - state: latest - virtualenv: "{{ ipa_build_venv or omit }}" - with_items: - - { name: pip } - -- name: Ensure required Python packages are installed - pip: - requirements: "{{ ipa_build_source_checkout_path }}/ironic-python-agent/requirements.txt" - state: present - virtualenv: "{{ ipa_build_venv or omit }}" - -- name: Ensure upper constraints file is downloaded - uri: - url: "{{ ipa_build_upper_constraints_file_url }}" - return_content: yes - when: "{{ ipa_build_upper_constraints_file_url != None }}" - register: upper_constraints - -- name: Ensure custom upper constraints are set - copy: - content: | - {% for line in upper_constraints.content.splitlines() %} - {% set package, _, version = line.partition('===') %} - {{ package }}==={{ ipa_build_custom_upper_constraints.get(package, version) }} - {% endfor %} - dest: "{{ ipa_build_source_checkout_path }}/ironic-python-agent/upper-constraints.txt" - when: "{{ ipa_build_upper_constraints_file_url != None }}" - -- name: Ensure existing Ironic Python Agent (IPA) images are removed when forcing rebuild - file: - path: "{{ ipa_build_source_checkout_path }}/ironic-python-agent/imagebuild/coreos/UPLOAD/{{ item }}" - state: absent - with_items: - - "coreos_production_pxe.vmlinuz" - - "coreos_production_pxe_image-oem.cpio.gz" - when: "{{ ipa_build_force | bool }}" - -- name: Ensure Ironic Python Agent (IPA) images are built - shell: > - {% if ipa_build_venv | bool %} - source {{ ipa_build_venv }}/bin/activate && - {% endif %} - {% if ipa_build_upper_constraints_file_url is defined %} - export UPPER_CONSTRAINTS_FILE="{{ ipa_build_source_checkout_path }}/ironic-python-agent/upper-constraints.txt" && - {% endif %} - make clean && - make - args: - chdir: "{{ ipa_build_source_checkout_path }}/ironic-python-agent/imagebuild/coreos" - creates: "{{ ipa_build_source_checkout_path }}/ironic-python-agent/imagebuild/coreos/UPLOAD/coreos_production_pxe_image-oem.cpio.gz" - -- name: Ensure built Ironic Python Agent (IPA) images are cached - copy: - src: "{{ ipa_build_source_checkout_path }}/ironic-python-agent/imagebuild/coreos/UPLOAD/{{ item.src }}" - dest: "{{ ipa_build_image_cache_path }}/{{ item.dest }}" - remote_src: True - with_items: - - src: "coreos_production_pxe.vmlinuz" - dest: "{{ ipa_build_kernel_name }}" - - src: "coreos_production_pxe_image-oem.cpio.gz" - dest: "{{ ipa_build_ramdisk_name }}" diff --git a/ansible/roles/kolla-openstack/defaults/main.yml b/ansible/roles/kolla-openstack/defaults/main.yml index b61fa99d3..0824cfc35 100644 --- a/ansible/roles/kolla-openstack/defaults/main.yml +++ b/ansible/roles/kolla-openstack/defaults/main.yml @@ -148,11 +148,21 @@ kolla_inspector_discovery_enroll_node_driver: kolla_inspector_extra_kernel_options: # URL of Ironic Python Agent (IPA) kernel image for Ironic Inspector. +# Mutually exclusive with kolla_inspector_ipa_kernel_path. kolla_inspector_ipa_kernel_upstream_url: # URL of Ironic Python Agent (IPA) ramdisk image for Ironic Inspector. +# Mutually exclusive with kolla_inspector_ipa_ramdisk_path. kolla_inspector_ipa_ramdisk_upstream_url: +# Path to Ironic Python Agent (IPA) kernel image for Ironic Inspector. +# Mutually exclusive with kolla_inspector_ipa_kernel_upstream_url. +kolla_inspector_ipa_kernel_path: + +# Path to Ironic Python Agent (IPA) ramdisk image for Ironic Inspector. +# Mutually exclusive with kolla_inspector_ipa_ramdisk_upstream_url. +kolla_inspector_ipa_ramdisk_path: + # Free form extra configuration to append to ironic-inspector.conf. kolla_extra_inspector: diff --git a/ansible/roles/kolla-openstack/tasks/config.yml b/ansible/roles/kolla-openstack/tasks/config.yml index c013e229a..fcd5c131a 100644 --- a/ansible/roles/kolla-openstack/tasks/config.yml +++ b/ansible/roles/kolla-openstack/tasks/config.yml @@ -39,7 +39,7 @@ - "{{ kolla_extra_fluentd_output_path }}/*.conf" when: "{{ kolla_extra_fluentd_output_path != None }}" -- name: Ensure the ironic inspector kernel and ramdisk exist +- name: Ensure the ironic inspector kernel and ramdisk are downloaded get_url: url: "{{ item.url }}" dest: "{{ kolla_node_custom_config_path }}/ironic/{{ item.dest }}" @@ -47,4 +47,18 @@ with_items: - { url: "{{ kolla_inspector_ipa_kernel_upstream_url }}", dest: "ironic-agent.kernel" } - { url: "{{ kolla_inspector_ipa_ramdisk_upstream_url }}", dest: "ironic-agent.initramfs" } - when: "{{ kolla_enable_ironic | bool }}" + when: + - kolla_enable_ironic | bool + - item.url != None + +- name: Ensure the ironic inspector kernel and ramdisk are copied + copy: + src: "{{ item.path }}" + dest: "{{ kolla_node_custom_config_path }}/ironic/{{ item.dest }}" + mode: 0640 + with_items: + - { path: "{{ kolla_inspector_ipa_kernel_path }}", dest: "ironic-agent.kernel" } + - { path: "{{ kolla_inspector_ipa_ramdisk_path }}", dest: "ironic-agent.initramfs" } + when: + - kolla_enable_ironic | bool + - item.path != None diff --git a/doc/source/deployment.rst b/doc/source/deployment.rst index c2a275365..65c8e43c3 100644 --- a/doc/source/deployment.rst +++ b/doc/source/deployment.rst @@ -291,6 +291,23 @@ pull images from the configured image registry:: (kayobe-venv) $ kayobe overcloud container image pull +Building Deployment Images +-------------------------- + +.. note:: + + It is possible to use prebuilt deployment images. In this case, this step + can be skipped. + +It is possible to use prebuilt deployment images from the `OpenStack hosted +tarballs `_ or another +source. In some cases it may be necessary to build images locally either to +apply local image customisation or to use a downstream version of Ironic Python +Agent (IPA). In order to build IPA images, the ``ipa_build_images`` variable +should be set to ``True``. To build images locally:: + + (kayobe-venv) $ kayobe overcloud deployment image build + Deploying Containerised Services -------------------------------- diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index e7ea550ce..c6ea1b137 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -622,13 +622,22 @@ class OvercloudContainerImageBuild(KayobeAnsibleMixin, VaultMixin, Command): extra_vars=extra_vars) +class OvercloudDeploymentImageBuild(KayobeAnsibleMixin, VaultMixin, Command): + """Build the overcloud deployment kernel and ramdisk images.""" + + def take_action(self, parsed_args): + self.app.LOG.debug("Building overcloud deployment images") + playbooks = _build_playbook_list("overcloud-ipa-build") + self.run_kayobe_playbooks(parsed_args, playbooks) + + class OvercloudPostConfigure(KayobeAnsibleMixin, VaultMixin, Command): """Perform post-deployment configuration.""" def take_action(self, parsed_args): self.app.LOG.debug("Performing post-deployment configuration") playbooks = _build_playbook_list( - "ipa-build", "ipa-images", "overcloud-introspection-rules", + "overcloud-ipa-images", "overcloud-introspection-rules", "overcloud-introspection-rules-dell-lldp-workaround", "provision-net") self.run_kayobe_playbooks(parsed_args, playbooks) diff --git a/setup.py b/setup.py index 0f6000e01..524c721ce 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ setup( 'overcloud_bios_raid_configure = kayobe.cli.commands:OvercloudBIOSRAIDConfigure', 'overcloud_container_image_build = kayobe.cli.commands:OvercloudContainerImageBuild', 'overcloud_container_image_pull = kayobe.cli.commands:OvercloudContainerImagePull', + 'overcloud_deployment_image_build = kayobe.cli.commands:OvercloudDeploymentImageBuild', 'overcloud_deprovision = kayobe.cli.commands:OvercloudDeprovision', 'overcloud_hardware_inspect = kayobe.cli.commands:OvercloudHardwareInspect', 'overcloud_host_configure = kayobe.cli.commands:OvercloudHostConfigure',