81bd49d628
Signed-off-by: Jiri Podivin <jpodivin@redhat.com> Change-Id: Ia8b78c0e81d716718139aec17f959a1a9955025e
188 lines
6.9 KiB
Python
188 lines
6.9 KiB
Python
#!/usr/bin/env python
|
|
# Copyright 2018 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.
|
|
"""check_flavors module
|
|
Used by the collect_flavors_and_verify_profiles validation.
|
|
"""
|
|
from ansible.module_utils.basic import AnsibleModule # noqa
|
|
from yaml import safe_load as yaml_safe_load
|
|
|
|
import re
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: check_flavors
|
|
short_description: Check that assigned flavors exist and are configured
|
|
description:
|
|
- Validate that the flavors assigned to roles exist and have the correct
|
|
settings. Right now, that means that boot_option is unset or set to
|
|
'local', or if set to 'netboot', issue a warning.
|
|
- Used by the collect_flavors_and_verify_profiles
|
|
- Owned by the 'DFG Compute & DFG Hardware Provisioning'
|
|
options:
|
|
roles_info:
|
|
required: true
|
|
description:
|
|
- A list of role info
|
|
type: list
|
|
flavors:
|
|
required: true
|
|
description:
|
|
- A dictionary of flavors from Nova
|
|
type: dict
|
|
|
|
author: "Brad P. Crochet"
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- hosts: undercloud
|
|
tasks:
|
|
- name: Check the flavors
|
|
check_flavors:
|
|
roles_info: "{{ lookup('roles_info', wantlist=True) }}"
|
|
flavors: "{{ lookup('nova_flavors', wantlist=True) }}"
|
|
'''
|
|
|
|
|
|
def validate_roles_and_flavors(roles_info, flavors):
|
|
"""Check if roles info is correct
|
|
|
|
:param roles_info: list of role data
|
|
:param flavors: dictionary of flavors
|
|
:returns result: Flavors and scale
|
|
warnings: List of warning messages
|
|
errors: List of error messages
|
|
"""
|
|
|
|
result = {}
|
|
errors = []
|
|
warnings = []
|
|
custom_resource_class = None
|
|
custom_resource_class_val = None
|
|
|
|
message = "Flavor '{1}' provided for the role '{0}', does not exist"
|
|
missing_message = "Role '{0}' is in use, but has no flavor assigned"
|
|
warning_message = (
|
|
'Flavor {0} "capabilities:boot_option" is set to '
|
|
'"netboot". Nodes will PXE boot from the ironic '
|
|
'conductor instead of using a local bootloader. '
|
|
'Make sure that enough nodes are marked with the '
|
|
'"boot_option" capability set to "netboot".')
|
|
resource_class_missing = (
|
|
'Flavor {0} does not have a custom resource class '
|
|
'associated with it')
|
|
resource_class_name_incorrect = (
|
|
'Flavor {0} has an incorrectly named custom '
|
|
'resource class associated with it')
|
|
resource_class_value_incorrect = (
|
|
'Flavor {0} has a resource class that is not '
|
|
'offering exactly 1 resource')
|
|
disable_standard_scheduling = (
|
|
'Flavor {0} has to have scheduling based on '
|
|
'standard properties disabled by setting '
|
|
'resources:VCPU=0 resources:MEMORY_MB=0 '
|
|
'resources:DISK_GB=0 in the flavor property')
|
|
|
|
for role in roles_info:
|
|
target = role.get('name')
|
|
flavor_name = role.get('flavor')
|
|
scale = role.get('count', 0)
|
|
|
|
if flavor_name is None or not scale:
|
|
if scale:
|
|
errors.append(missing_message.format(target))
|
|
continue
|
|
|
|
old_flavor_name, old_scale = result.get(flavor_name, (None, None))
|
|
|
|
if old_flavor_name:
|
|
result[flavor_name] = (old_flavor_name, scale)
|
|
else:
|
|
flavor = flavors.get(flavor_name)
|
|
|
|
if flavor:
|
|
keys = flavor.get('keys', None)
|
|
if keys:
|
|
if keys.get('capabilities:boot_option', '') \
|
|
== 'netboot':
|
|
warnings.append(
|
|
warning_message.format(flavor_name))
|
|
# check if the baremetal flavor has custom resource class
|
|
# required for scheduling since queens
|
|
resource_specs = {key.split(
|
|
"resources:", 1)[-1]: val
|
|
for key, val in keys.items()
|
|
if key.startswith("resources:")}
|
|
if not resource_specs:
|
|
errors.append(resource_class_missing.format(
|
|
flavor_name))
|
|
else:
|
|
for key, val in resource_specs.items():
|
|
if key.startswith("CUSTOM_"):
|
|
custom_resource_class = True
|
|
match = re.match('CUSTOM_[A-Z_]+', key)
|
|
if match is None:
|
|
errors.append(
|
|
resource_class_name_incorrect,
|
|
flavor_name)
|
|
else:
|
|
if int(val) == 1:
|
|
custom_resource_class_val = True
|
|
if not custom_resource_class:
|
|
errors.append(resource_class_missing.format(
|
|
flavor_name))
|
|
if key not in ["DISK_GB", "MEMORY_MB", "VCPU"] and \
|
|
not custom_resource_class_val:
|
|
errors.append(resource_class_value_incorrect.
|
|
format(flavor_name))
|
|
disk = resource_specs.get("DISK_GB", None)
|
|
memory = resource_specs.get("MEMORY_MB", None)
|
|
vcpu = resource_specs.get("VCPU", None)
|
|
if any(int(resource) != 0 for resource in [disk,
|
|
memory, vcpu]):
|
|
errors.append(disable_standard_scheduling.
|
|
format(flavor_name))
|
|
|
|
result[flavor_name] = (flavor, scale)
|
|
else:
|
|
errors.append(message.format(target, flavor_name))
|
|
|
|
return result, warnings, errors
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=yaml_safe_load(DOCUMENTATION)['options']
|
|
)
|
|
|
|
roles_info = module.params.get('roles_info')
|
|
flavors = module.params.get('flavors')
|
|
|
|
flavor_result, warnings, errors = validate_roles_and_flavors(roles_info,
|
|
flavors)
|
|
|
|
if errors:
|
|
module.fail_json(msg="\n".join(errors))
|
|
elif warnings:
|
|
module.exit_json(warnings="\n".join(warnings))
|
|
else:
|
|
module.exit_json(
|
|
msg="All flavors configured on roles",
|
|
flavors=flavor_result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|