Support multi-arch in deploy image validations
Previously: - expect one kernel image and one ramdisk image - expect every overcloud node to use the same kernel and and ramdisk Now: - expect any number of kernel/ramdisk images - expect each overcloud node to use the most suitable kernel/ramdisk in accordance with arch/platform of that node (but recognizing that, for any given node, the form that the "most suitable" image takes may vary) Currently only Glance images are considered; support for file images as introduced in Train will come in a subsequent patch. Blueprint: multiarch-support Change-Id: I5a49b60fd32d9032b4dec1a01a3fdad6fddf42aa
This commit is contained in:
parent
2f4921b62d
commit
9750f945db
186
library/check_ironic_boot_config.py
Normal file
186
library/check_ironic_boot_config.py
Normal file
@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2019 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule # noqa
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: check_ironic_boot_config
|
||||
short_description: >
|
||||
- Check that overcloud nodes have the correct associated ramdisk and kernel
|
||||
image
|
||||
description: >
|
||||
- Each overcloud node needs to have the correct associated ramdisk and
|
||||
kernel image according to its architecture and platform. When it does
|
||||
appear that the correct image is associated, we also need to check that
|
||||
there is only image in Glance with that name.
|
||||
options:
|
||||
images:
|
||||
required: true
|
||||
description:
|
||||
- A list of images from Glance
|
||||
type: list
|
||||
nodes:
|
||||
required: true
|
||||
description:
|
||||
- A list of nodes from Ironic
|
||||
type: list
|
||||
deploy_kernel_name_base:
|
||||
required: true
|
||||
description:
|
||||
- Base name of kernel image
|
||||
type: string
|
||||
deploy_ramdisk_name_base:
|
||||
required: true
|
||||
description:
|
||||
- Base name of ramdisk image
|
||||
type: string
|
||||
|
||||
author: Jeremy Freudberg
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- hosts: undercloud
|
||||
tasks:
|
||||
- name: Check Ironic boot config
|
||||
check_ironic_boot_config:
|
||||
images: "{{ lookup('glance_images', wantlist=True) }}"
|
||||
nodes: "{{ lookup('ironic_nodes', wantlist=True) }}"
|
||||
deploy_kernel_name_base: " {{ deploy_kernel_name_base }} "
|
||||
deploy_ramdisk_name_base: " {{ deploy_ramdisk_name_base }} "
|
||||
'''
|
||||
|
||||
|
||||
def _name_helper(basename, arch=None, platform=None):
|
||||
# TODO(jfreud): add support for non-Glance ironic-python-agent images
|
||||
# TODO(jfreud): delete in favor of (eventual) tripleo-common equivalent
|
||||
if arch and platform:
|
||||
basename = platform + '-' + arch + '-' + basename
|
||||
elif arch:
|
||||
basename = arch + '-' + basename
|
||||
return basename
|
||||
|
||||
|
||||
def _all_possible_names(arch, platform, image_name_base):
|
||||
# TODO(jfreud): delete in favor of (eventual) tripleo-common equivalent
|
||||
if arch:
|
||||
if platform:
|
||||
yield _name_helper(image_name_base, arch=arch, platform=platform)
|
||||
yield _name_helper(image_name_base, arch=arch)
|
||||
yield _name_helper(image_name_base)
|
||||
|
||||
MISMATCH = (
|
||||
"\nNode {} has an incorrectly configured driver_info/deploy_{}. Expected "
|
||||
"{} but got {}."
|
||||
)
|
||||
|
||||
NO_CANDIDATES = (
|
||||
"\nNode {} has an incorrectly configured driver_info/deploy_{}. Got {}, "
|
||||
"but cannot validate because could not find any suitable {} images in "
|
||||
"Glance."
|
||||
)
|
||||
|
||||
DUPLICATE_NAME = (
|
||||
"\nNode {} appears to have a correctly configured driver_info/deploy_{} "
|
||||
"but the presence of more than one image in Glance with the name '{}' "
|
||||
"prevents the certainty of this."
|
||||
)
|
||||
|
||||
|
||||
def validate_boot_config(images,
|
||||
nodes,
|
||||
deploy_kernel_name_base,
|
||||
deploy_ramdisk_name_base):
|
||||
errors = []
|
||||
|
||||
image_map = {deploy_kernel_name_base: 'kernel',
|
||||
deploy_ramdisk_name_base: 'ramdisk'}
|
||||
|
||||
image_name_to_id = collections.defaultdict(list)
|
||||
for image in images:
|
||||
image_name_to_id[image["name"]].append(image["id"])
|
||||
|
||||
for image_name_base, image_type in image_map.items():
|
||||
for node in nodes:
|
||||
actual_image_id = (
|
||||
node["driver_info"].get("deploy_%s" % image_type, None)
|
||||
)
|
||||
arch = node["properties"].get("cpu_arch", None)
|
||||
platform = node["extra"].get("tripleo_platform", None)
|
||||
|
||||
candidates = [name for name in
|
||||
_all_possible_names(arch, platform, image_name_base)
|
||||
if name in image_name_to_id.keys()]
|
||||
if not candidates:
|
||||
errors.append(
|
||||
NO_CANDIDATES.format(
|
||||
node["uuid"],
|
||||
image_type,
|
||||
actual_image_id,
|
||||
image_type
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
expected_image_name = candidates[0]
|
||||
expected_image_id = image_name_to_id[expected_image_name][0]
|
||||
|
||||
if expected_image_id != actual_image_id:
|
||||
errors.append(
|
||||
MISMATCH.format(
|
||||
node["uuid"],
|
||||
image_type,
|
||||
expected_image_id,
|
||||
actual_image_id or "None"
|
||||
)
|
||||
)
|
||||
elif len(image_name_to_id[expected_image_name]) > 1:
|
||||
errors.append(
|
||||
DUPLICATE_NAME.format(
|
||||
node["uuid"],
|
||||
image_type,
|
||||
expected_image_name
|
||||
)
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(argument_spec=dict(
|
||||
images=dict(required=True, type='list'),
|
||||
nodes=dict(required=True, type='list'),
|
||||
deploy_kernel_name_base=dict(required=True, type='str'),
|
||||
deploy_ramdisk_name_base=dict(required=True, type='str')
|
||||
))
|
||||
|
||||
images = module.params.get('images')
|
||||
nodes = module.params.get('nodes')
|
||||
deploy_kernel_name_base = module.params.get('deploy_kernel_name_base')
|
||||
deploy_ramdisk_name_base = module.params.get('deploy_ramdisk_name_base')
|
||||
|
||||
errors = validate_boot_config(
|
||||
images, nodes, deploy_kernel_name_base, deploy_ramdisk_name_base)
|
||||
|
||||
if errors:
|
||||
module.fail_json(msg="".join(errors))
|
||||
else:
|
||||
module.exit_json()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
- hosts: undercloud
|
||||
vars:
|
||||
metadata:
|
||||
name: Verify existence of deployment images
|
||||
description: >
|
||||
This validation checks that images bm-deploy-kernel and
|
||||
bm-deploy-ramdisk exist before deploying the overcloud,
|
||||
and that only one exists by that name.
|
||||
groups:
|
||||
- pre-deployment
|
||||
- pre-upgrade
|
||||
deploy_kernel_name: "bm-deploy-kernel"
|
||||
deploy_ramdisk_name: "bm-deploy-ramdisk"
|
||||
roles:
|
||||
- deployment-images
|
@ -8,7 +8,7 @@
|
||||
groups:
|
||||
- pre-deployment
|
||||
- pre-upgrade
|
||||
deploy_kernel_name: "bm-deploy-kernel"
|
||||
deploy_ramdisk_name: "bm-deploy-ramdisk"
|
||||
deploy_kernel_name_base: "bm-deploy-kernel"
|
||||
deploy_ramdisk_name_base: "bm-deploy-ramdisk"
|
||||
roles:
|
||||
- ironic-boot-configuration
|
||||
|
@ -0,0 +1,16 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The behavior of the ``ironic-boot-configuration`` validation has changed
|
||||
in order to suppport multi-arch. It now checks that each node has the
|
||||
correct associated ramdisk and kernel image according to the node's
|
||||
architecture and platform, and, when it does appear that the correct image
|
||||
is associated, checks that there is only one image in Glance with that
|
||||
name. Also, the vars ``deploy_kernel_name`` and ``deploy_ramdisk_name``
|
||||
have changed to ``deploy_kernel_name_base`` and
|
||||
``deploy_ramdisk_name_base`` respectively.
|
||||
other:
|
||||
- |
|
||||
The ``deployment-images`` validation has been removed, as its intended
|
||||
functionality became inseparable from ``ironic-boot-configuration`` in the
|
||||
multi-arch case.
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
deploy_kernel_name: "bm-deploy-kernel"
|
||||
deploy_ramdisk_name: "bm-deploy-ramdisk"
|
@ -1,22 +0,0 @@
|
||||
---
|
||||
- name: Fetch deploy kernel by name
|
||||
set_fact:
|
||||
deploy_kernel_id: "{{ lookup('glance_images', 'name', ['{{ deploy_kernel_name }}'], wantlist=True) | map(attribute='id') | list }}"
|
||||
|
||||
- name: Fetch deploy ramdisk by name
|
||||
set_fact:
|
||||
deploy_ramdisk_id: "{{ lookup('glance_images', 'name', ['{{ deploy_ramdisk_name }}'], wantlist=True) | map(attribute='id') | list }}"
|
||||
|
||||
- name: Fail if image is not found
|
||||
fail: msg="No image with the name '{{ item.name }}' found - make sure you have uploaded boot images."
|
||||
failed_when: item.id | length < 1
|
||||
with_items:
|
||||
- { name: '{{ deploy_kernel_name }}', id: '{{ deploy_kernel_id }}' }
|
||||
- { name: '{{ deploy_ramdisk_name }}', id: '{{ deploy_ramdisk_id }}' }
|
||||
|
||||
- name: Fail if there is more than one image
|
||||
fail: msg="Please make sure there is only one image named '{{ item.name }}' in glance."
|
||||
failed_when: item.id | length > 1
|
||||
with_items:
|
||||
- { name: '{{ deploy_kernel_name }}', id: '{{ deploy_kernel_id }}' }
|
||||
- { name: '{{ deploy_ramdisk_name }}', id: '{{ deploy_ramdisk_id }}' }
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
metadata:
|
||||
name: Verify existence of deployment images
|
||||
description: >
|
||||
This validation checks that images bm-deploy-kernel and
|
||||
bm-deploy-ramdisk exist before deploying the overcloud,
|
||||
and that only one exists by that name.
|
||||
groups:
|
||||
- pre-deployment
|
||||
- pre-upgrade
|
@ -1,3 +1,3 @@
|
||||
---
|
||||
deploy_kernel_name: "bm-deploy-kernel"
|
||||
deploy_ramdisk_name: "bm-deploy-ramdisk"
|
||||
deploy_kernel_name_base: "bm-deploy-kernel"
|
||||
deploy_ramdisk_name_base: "bm-deploy-ramdisk"
|
||||
|
@ -1,28 +1,7 @@
|
||||
---
|
||||
- name: Get id for deploy kernel by name
|
||||
set_fact:
|
||||
deploy_kernel_id: "{{ lookup('glance_images', 'name', ['{{ deploy_kernel_name }}'], wantlist=True) | map(attribute='id') | join(', ') }}"
|
||||
|
||||
- name: Get id for deploy ramdisk by name
|
||||
set_fact:
|
||||
deploy_ramdisk_id: "{{ lookup('glance_images', 'name', ['{{ deploy_ramdisk_name }}'], wantlist=True) | map(attribute='id') | join(', ') }}"
|
||||
|
||||
- name: Get ironic nodes
|
||||
set_fact:
|
||||
ironic_nodes: "{{ lookup('ironic_nodes', wantlist=True) }}"
|
||||
|
||||
- name: Check each node for kernel id
|
||||
fail:
|
||||
msg: >-
|
||||
'Node {{ item.uuid }} has an incorrectly configured driver_info/deploy_kernel.
|
||||
Expected "{{ deploy_kernel_id }}" but got "{{ item.driver_info.deploy_kernel }}".'
|
||||
failed_when: item.driver_info.deploy_kernel != deploy_kernel_id
|
||||
with_items: "{{ ironic_nodes }}"
|
||||
|
||||
- name: Check each node for ramdisk id
|
||||
fail:
|
||||
msg: >-
|
||||
'Node {{ item.uuid }} has an incorrectly configured driver_info/deploy_ramdisk.
|
||||
Expected "{{ deploy_ramdisk_id }}" but got "{{ item.driver_info.deploy_ramdisk }}".'
|
||||
failed_when: item.driver_info.deploy_ramdisk != deploy_ramdisk_id
|
||||
with_items: "{{ ironic_nodes }}"
|
||||
- name: Check ironic boot config
|
||||
check_ironic_boot_config:
|
||||
images: "{{ lookup('glance_images', wantlist=True) }}"
|
||||
nodes: "{{ lookup('ironic_nodes', wantlist=True) }}"
|
||||
deploy_kernel_name_base: "{{ deploy_kernel_name_base }}"
|
||||
deploy_ramdisk_name_base: "{{ deploy_ramdisk_name_base }}"
|
||||
|
@ -0,0 +1,133 @@
|
||||
# Copyright 2019 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import library.check_ironic_boot_config as validation
|
||||
from tripleo_validations.tests import base
|
||||
|
||||
import mock
|
||||
|
||||
KERNEL_IMAGE_ID = 111
|
||||
RAMDISK_IMAGE_ID = 112
|
||||
KERNEL_NAME_BASE = "bm-deploy-kernel"
|
||||
RAMDISK_NAME_BASE = "bm-deploy-ramdisk"
|
||||
|
||||
|
||||
class TestCheckIronicBootConfig(base.TestCase):
|
||||
|
||||
def _image_helper(self, prefixes):
|
||||
# first set of images gets the magic ID
|
||||
yield {
|
||||
"id": KERNEL_IMAGE_ID,
|
||||
"name": prefixes[0] + KERNEL_NAME_BASE
|
||||
}
|
||||
yield {
|
||||
"id": RAMDISK_IMAGE_ID,
|
||||
"name": prefixes[0] + RAMDISK_NAME_BASE
|
||||
}
|
||||
if len(prefixes) > 1:
|
||||
# if there's a second set of images give them some other ID
|
||||
yield {
|
||||
"id": KERNEL_IMAGE_ID + 2,
|
||||
"name": prefixes[1] + KERNEL_NAME_BASE
|
||||
}
|
||||
yield {
|
||||
"id": RAMDISK_IMAGE_ID + 2,
|
||||
"name": prefixes[1] + RAMDISK_NAME_BASE
|
||||
}
|
||||
|
||||
def _node_helper(self, arch, platform):
|
||||
# just create one node
|
||||
nodes = [
|
||||
{"uuid": 222,
|
||||
"driver_info":
|
||||
{
|
||||
"deploy_kernel": KERNEL_IMAGE_ID, # magic ID
|
||||
"deploy_ramdisk": RAMDISK_IMAGE_ID # magic ID
|
||||
},
|
||||
"properties": {},
|
||||
"extra": {},
|
||||
}
|
||||
]
|
||||
if arch:
|
||||
nodes[0]["properties"]["cpu_arch"] = arch
|
||||
if platform:
|
||||
nodes[0]["extra"]["tripleo_platform"] = platform
|
||||
return nodes
|
||||
|
||||
def _do_test_case(
|
||||
self, image_prefixes, node_arch=None, node_platform=None):
|
||||
images = self._image_helper(image_prefixes)
|
||||
nodes = self._node_helper(node_arch, node_platform)
|
||||
return validation.validate_boot_config(
|
||||
images, nodes, KERNEL_NAME_BASE, RAMDISK_NAME_BASE)
|
||||
|
||||
def test_successes(self):
|
||||
self.assertEqual(
|
||||
[], self._do_test_case(['p9-ppc64le-'], 'ppc64le', 'p9'))
|
||||
self.assertEqual(
|
||||
[], self._do_test_case([''], 'ppc64le', 'p9'))
|
||||
self.assertEqual(
|
||||
[], self._do_test_case(
|
||||
['ppc64le-', 'p8-ppc64le-'], 'ppc64le', 'p9'))
|
||||
self.assertEqual(
|
||||
[], self._do_test_case(
|
||||
['', 'SB-x86_64-'], 'ppc64le', 'p9'))
|
||||
self.assertEqual(
|
||||
[], self._do_test_case([''], 'x86_64'))
|
||||
self.assertEqual(
|
||||
[], self._do_test_case(['']))
|
||||
|
||||
@mock.patch('library.check_ironic_boot_config.NO_CANDIDATES')
|
||||
@mock.patch('library.check_ironic_boot_config.MISMATCH')
|
||||
def test_errors(self, mismatch, no_candidates):
|
||||
self._do_test_case(['p8-ppc64le-', 'p9-ppc64le-'], 'ppc64le', 'p9')
|
||||
mismatch.format.assert_called()
|
||||
no_candidates.format.assert_not_called()
|
||||
|
||||
mismatch.reset_mock()
|
||||
no_candidates.reset_mock()
|
||||
|
||||
self._do_test_case(['ppc64le-', 'p9-ppc64le-'], 'ppc64le', 'p9')
|
||||
mismatch.format.assert_called()
|
||||
no_candidates.format.assert_not_called()
|
||||
|
||||
mismatch.reset_mock()
|
||||
no_candidates.reset_mock()
|
||||
|
||||
self._do_test_case(['', 'ppc64le-'], 'ppc64le')
|
||||
mismatch.format.assert_called()
|
||||
no_candidates.format.assert_not_called()
|
||||
|
||||
mismatch.reset_mock()
|
||||
no_candidates.reset_mock()
|
||||
|
||||
self._do_test_case(['p9-ppc64le-', ''], 'ppc64le')
|
||||
mismatch.format.assert_called()
|
||||
no_candidates.format.assert_not_called()
|
||||
|
||||
mismatch.reset_mock()
|
||||
no_candidates.reset_mock()
|
||||
|
||||
self._do_test_case(['p8-ppc64le-'], 'ppc64le', 'p9')
|
||||
mismatch.format.assert_not_called()
|
||||
no_candidates.format.assert_called()
|
||||
|
||||
mismatch.reset_mock()
|
||||
no_candidates.reset_mock()
|
||||
|
||||
self._do_test_case(['p9-ppc64le-', 'x86_64-'], 'ppc64le')
|
||||
mismatch.format.assert_not_called()
|
||||
no_candidates.format.assert_called()
|
Loading…
x
Reference in New Issue
Block a user