Files
tripleo-validations/library/node_disks.py
Gael Chamoulaud 08533b45c3 Use dynamic argument_spec from documentation
This change modifies the argument_spec making it so we load the options
key from the documentation constant. This will reduce the code we have
to maintain and ensure our documentation is always in sync with the
module capabilities.

Change-Id: Iaf94b6300a58de4367b4c9f2c83cc112e7c5361a
Co-Authored-by: Kevin Carter <kecarter@redhat.com>
Signed-off-by: Gael Chamoulaud <gchamoul@redhat.com>
2019-10-16 11:27:33 +02:00

159 lines
5.0 KiB
Python

#!/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.
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.
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()