Merge "Support multi-arch in deploy image validations"

This commit is contained in:
Zuul 2019-07-19 09:14:05 +00:00 committed by Gerrit Code Review
commit e0cbceaf52
10 changed files with 345 additions and 82 deletions

View 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()

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -1,3 +0,0 @@
---
deploy_kernel_name: "bm-deploy-kernel"
deploy_ramdisk_name: "bm-deploy-ramdisk"

View File

@ -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 }}' }

View File

@ -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

View File

@ -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"

View File

@ -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 }}"

View File

@ -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()