Adjusting node-disks validation for new authentication procedures

Validation will now only verify primary disk of the node.
Description of the role and validation itself now includes
further instructions and explanation.

Validation failure message now recommends introspection as a next step.

node-disks.py module, associated tests and documentation were deleted

Signed-off-by: Jiri Podivin <jpodivin@redhat.com>
Change-Id: I68b83bc9c035a4f3cfc04afb098ceb04de602691
This commit is contained in:
Jiri Podivin 2022-10-14 16:44:02 +02:00
parent 95397712d7
commit 14354b222a
7 changed files with 46 additions and 351 deletions

View File

@ -1,14 +0,0 @@
===================
Module - node_disks
===================
This module provides for the following ansible plugin:
* node_disks
.. ansibleautoplugin::
:module: library/node_disks.py
:documentation: true
:examples: true

View File

@ -1,161 +0,0 @@
#!/usr/bin/env python
# Copyright 2016 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.
"""node_disks module
Used by the 'node_disks' validation.
"""
from ansible.module_utils.basic import AnsibleModule # noqa
from yaml import safe_load as yaml_safe_load
DOCUMENTATION = '''
---
module: node_disks
short_description: Check disks, flavors and root device hints
description:
- Check if each node has a root device hint set if there is more
than one disk and compare flavors to disk sizes.
- Used by the 'node_disks' validation.
options:
nodes:
required: true
description:
- A list of nodes
type: list
flavors:
required: true
description:
- A list of flavors
type: dict
introspection_data:
required: true
description:
- Introspection data for all nodes
type: list
author: "Florian Fuchs <flfuchs@redhat.com>"
'''
EXAMPLES = '''
- hosts: undercloud
tasks:
- name: Check node disks
node_disks:
nodes: "{{ lookup('ironic_nodes') }}"
flavors: "{{ lookup('nova_flavors') }}"
introspection_data: "{{ lookup('introspection_data',
auth_url=auth_url.value, password=password.value) }}"
'''
IGNORE_BYTE_MAX = 4294967296
ONE_DISK_TOO_SMALL_ERROR = """\
The node {} only has one disk and it's too small for the "{}" flavor"""
NO_RDH_SMALLEST_DISK_TOO_SMALL_ERROR = (
'{} has more than one disk available for deployment and no '
'root device hints set. The disk that will be used is too small '
'for the flavor with the largest disk requirement ("{}").')
def _get_minimum_disk_size(flavors):
min_gb = 0
name = 'n.a.'
for key, val in flavors.items():
disk_gb = val['disk']
if disk_gb > min_gb:
min_gb = disk_gb
name = key
# convert GB to bytes to compare to introspection data
return name, min_gb * 1073741824
def _get_smallest_disk(disks):
smallest = disks[0]
for disk in disks[1:]:
if disk['size'] < smallest['size']:
smallest = disk
return smallest
def _has_root_device_hints(node_name, node_data):
rdh = node_data.get(
node_name, {}).get('properties', {}).get('root_device')
return rdh is not None
def validate_node_disks(nodes, flavors, introspection_data):
"""Validate root device hints using introspection data.
:param nodes: Ironic nodes
:param introspection_data: Introspection data for all nodes
:returns warnings: List of warning messages
errors: List of error messages
"""
errors = []
warnings = []
# Get the name of the flavor with the largest disk requirement,
# which defines the minimum disk size.
max_disk_flavor, min_disk_size = _get_minimum_disk_size(flavors)
for node, content in introspection_data.items():
disks = content.get('inventory', {}).get('disks')
valid_disks = [disk for disk in disks
if disk['size'] > IGNORE_BYTE_MAX]
root_device_hints = _has_root_device_hints(node, nodes)
smallest_disk = _get_smallest_disk(valid_disks)
if len(valid_disks) == 1:
if smallest_disk.get('size', 0) < min_disk_size:
errors.append(ONE_DISK_TOO_SMALL_ERROR.format(
node, max_disk_flavor))
elif not root_device_hints and len(valid_disks) > 1:
if smallest_disk.get('size', 0) < min_disk_size:
errors.append(NO_RDH_SMALLEST_DISK_TOO_SMALL_ERROR.format(
node, max_disk_flavor))
else:
warnings.append('{} has more than one disk available for '
'deployment'.format(node))
return errors, warnings
def main():
module = AnsibleModule(
argument_spec=yaml_safe_load(DOCUMENTATION)['options']
)
nodes = {node['name']: node for node in module.params.get('nodes')}
flavors = module.params.get('flavors')
introspection_data = {name: content for (name, content) in
module.params.get('introspection_data')}
errors, warnings = validate_node_disks(nodes,
flavors,
introspection_data)
if errors:
module.fail_json(msg="\n".join(errors))
elif warnings:
module.exit_json(warnings="\n".join(warnings))
else:
module.exit_json(msg="Root device hints are either set or not "
"necessary.")
if __name__ == '__main__':
main()

View File

@ -1,10 +1,11 @@
---
- hosts: undercloud
- hosts: localhost
vars:
metadata:
name: Check node disk configuration
description: |
Check node disk numbers and sizes and whether root device hints are set.
Check node primary/root disk sizes and compare with flavor expectations.
Fitness of other disks can be checked manually using introspection.
groups:
- pre-deployment
categories:
@ -15,6 +16,5 @@
- undercloud
products:
- tripleo
ironic_conf_file: "/var/lib/config-data/puppet-generated/ironic/etc/ironic/ironic.conf"
roles:
- node_disks

View File

@ -1,2 +0,0 @@
---
ironic_conf_file: "/var/lib/config-data/puppet-generated/ironic/etc/ironic/ironic.conf"

View File

@ -1,25 +1,45 @@
---
- name: Get Ironic Inspector swift auth_url
become: true
validations_read_ini:
path: "{{ ironic_conf_file }}"
section: inspector
key: auth_url
register: ironic_auth_url
- openstack.cloud.compute_flavor_info:
cloud: overcloud
register: result
- name: Get Ironic Inspector swift password
become: true
validations_read_ini:
path: "{{ ironic_conf_file }}"
section: inspector
key: password
register: ironic_password
no_log: true
- name: Set required disk size
set_fact:
disk_sizes: "{{ result | community.general.json_query('openstack_flavors[*].{disk : disk, name : name}') }}"
- name: Check node disks
node_disks:
nodes: "{{ lookup('ironic_nodes', wantlist=True) }}"
flavors: "{{ lookup('nova_flavors', wantlist=True) }}"
introspection_data: "{{ lookup('introspection_data',
auth_url=ironic_auth_url.value,
password=ironic_password.value) }}"
- name: Set minimum required disk size
set_fact:
min_disk_size: "{{ disk_sizes | max(attribute='disk') }}"
- name: Get list of baremetal nodes
openstack.cloud.baremetal_node_info:
cloud: undercloud
register: baremetal_nodes
- name: Get baremetal node details
openstack.cloud.baremetal_node_info:
cloud: undercloud
node: "{{ item }}"
with_items: "{{ baremetal_nodes | community.general.json_query('baremetal_nodes[*].name') }}"
register: result
- name: Set baremetal node details
set_fact:
baremetal_node_details: "{{ result.results | community.general.json_query('[*].baremetal_nodes[0]') }}"
- name: Compare expected with available disk sizes
set_fact:
nodes_with_undersized_disks: "{{ baremetal_node_details | community.general.json_query('[?to_number(properties.local_gb) < to_number(min_disk_size) ]') }}"
- fail:
msg: |
Following nodes have less storage available on their primary disks than requested by the flavor {{ min_disk_size.name }} :
{{ nodes_with_undersized_disks }}
Use node introspection to verify presence of possible additional disks.
when: nodes_with_undersized_disks | length > 0
- name: Print recommendation for further steps
debug:
msg: |
Validation was successful. Root disk is sufficient for all flavors.
Use node introspection to verify presence of possible additional disks.

View File

@ -2,6 +2,6 @@
metadata:
name: Check node disk configuration
description: >
Check node disk numbers and sizes and whether root device hints are set.
Check node root disk sizes against available flavors.
groups:
- pre-deployment

View File

@ -1,148 +0,0 @@
# -*- coding: utf-8 -*-
# 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.
try:
from unittest import mock
except ImportError:
import mock
from tripleo_validations.tests import base
from tripleo_validations.tests import fakes
from library.node_disks import _get_smallest_disk
from library.node_disks import _has_root_device_hints
from library.node_disks import validate_node_disks
# node_1: 2 disks, 1 larger than 4GB (50GB)
# node_2: 3 disks, 2 larger than 4GB (50GB, 10GB)
# node_2: 3 disks, 2 larger than 4GB (50GB, 10GB)
INTROSPECTION_DATA = {
'node_1': {
"inventory": {
"disks": [
{"name": "disk-1", "size": 53687091200},
{"name": "disk-2", "size": 4294967296},
]
}
},
'node_2': {
"inventory": {
"disks": [
{"name": "disk-1", "size": 53687091200},
{"name": "disk-2", "size": 10737418240},
{"name": "disk-3", "size": 4294967296},
]
}
},
'node_3': {
"inventory": {
"disks": [
{"name": "disk-1", "size": 53687091200},
{"name": "disk-2", "size": 10737418240},
{"name": "disk-3", "size": 4294967296},
]
}
},
}
# small: fits nodes with disks >= 10GB
# large: fits nodes with disks >= 50GB
FLAVOR_DATA = {
"small": {
"disk": 10,
"name": "small"
},
"large": {
"disk": 50,
"name": "large"
},
}
# node_1: no root device set, one disk > 4GB, fits both flavors
# node_2: root device set to name of disk-2 which fits both flavors
# node_3: no root device set, small disk only fits small flavor
NODE_DATA = {
"node_1": {"properties": {}},
"node_2": {
"properties": {
"root_device": {
"wwn": "0x4000cca77fc4dba1"
}
}
},
"node_3": {"properties": {}},
}
class TestGetSmallestDisk(base.TestCase):
def test_get_correct_disk(self):
introspection_data = INTROSPECTION_DATA['node_2']
smallest_disk = _get_smallest_disk(
introspection_data['inventory']['disks'])
self.assertEqual(smallest_disk['size'], 4294967296)
class TestHasRootDeviceHints(base.TestCase):
def test_detect_root_device_hints(self):
self.assertTrue(_has_root_device_hints('node_2', NODE_DATA))
def test_detect_no_root_device_hints(self):
self.assertFalse(_has_root_device_hints('node_1', NODE_DATA))
class TestValidateRootDeviceHints(base.TestCase):
def setUp(self):
super(TestValidateRootDeviceHints, self).setUp()
def test_node_1_no_warning(self):
introspection_data = {
'node_1': INTROSPECTION_DATA['node_1']
}
errors, warnings = validate_node_disks({},
FLAVOR_DATA,
introspection_data)
self.assertEqual([[], []], [errors, warnings])
def test_small_flavor_no_hints_warning(self):
introspection_data = {
'node_2': INTROSPECTION_DATA['node_2']
}
flavors = {
"small": FLAVOR_DATA['small']
}
expected_warnings = [
'node_2 has more than one disk available for deployment',
]
errors, warnings = validate_node_disks({},
flavors,
introspection_data)
self.assertEqual([[], expected_warnings], [errors, warnings])
def test_large_flavor_no_hints_error(self):
introspection_data = {
'node_3': INTROSPECTION_DATA['node_3']
}
expected_errors = [
'node_3 has more than one disk available for deployment and no '
'root device hints set. The disk that will be used is too small '
'for the flavor with the largest disk requirement ("large").',
]
errors, warnings = validate_node_disks({},
FLAVOR_DATA,
introspection_data)
self.assertEqual([expected_errors, []], [errors, warnings])