Add validation for checking roles against flavors

Check that the flavors that are assigned to roles exist.
Also checks that the flavors have proper configuration.

Change-Id: Ia4628c2ef29400f984d8971b463e9e12b17c97f5
Implements: blueprint workflow-move-validations
This commit is contained in:
Brad P. Crochet 2018-04-18 10:31:58 -04:00
parent 70ba8b5cb6
commit 84b407e955
4 changed files with 295 additions and 0 deletions

View File

@ -0,0 +1,16 @@
---
- hosts: undercloud
vars:
metadata:
name: Collect and verify role flavors
description: >
This validation checks the flavors assigned to roles exist and have the
correct capabilities set.
groups:
- pre-deployment
- pre-upgrade
tasks:
- name: Check the flavors
check_flavors:
roles_info: "{{ lookup('roles_info', wantlist=True) }}"
flavors: "{{ lookup('nova_flavors', wantlist=True) }}"

View File

@ -0,0 +1,130 @@
#!/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.
from ansible.module_utils.basic import AnsibleModule # noqa
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 set to 'local', or
if set to 'netboot', issue a warning.
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 = []
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".')
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))
result[flavor_name] = (flavor, scale)
else:
errors.append(message.format(target, flavor_name))
return result, warnings, errors
def main():
module = AnsibleModule(argument_spec=dict(
roles_info=dict(required=True, type='list'),
flavors=dict(required=True, type='dict')
))
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()

View File

@ -0,0 +1,60 @@
#!/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.
from ansible.plugins.lookup import LookupBase
from novaclient import client as nova_client
from tripleo_validations.utils import get_auth_session
DOCUMENTATION = """
lookup: nova_flavors
description: Retrieve flavor information from Nova
long_description:
- Load flavor information using the Nova API.
author: Brad P. Crochet <brad@redhat.com>
"""
EXAMPLES = """
- name: Get all flavors from nova
debug:
msg: |
{{ lookup('nova_flavors') }}
"""
RETURN = """
_raw:
description: A Python list with results from the API call.
"""
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
"""Returns server information from nova."""
auth_url = variables.get('auth_url')
username = variables.get('username')
project_name = variables.get('project_name')
token = variables.get('os_auth_token')
session = get_auth_session(auth_url, username, project_name,
auth_token=token)
nova = nova_client.Client(2, session=session)
flavors = nova.flavors.list()
return {f.name: {'name': f.name, 'keys': f.get_keys()}
for f in flavors}

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python
# Copyright 2017 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 yaml
from ansible.plugins.lookup import LookupBase
from tripleo_validations.utils import get_swift_client
DOCUMENTATION = """
lookup: roles_info
description: Retrieve role information from Heat and Swift.
long_description:
- Load role information using the Heat API.
options:
_terms:
description: Optional filter attribute and filter value
author: Brad P. Crochet <brad@redhat.com>
"""
EXAMPLES = """
- name: Get all role info from Heat and Swift
debug:
msg: |
{{ lookup('roles_info', wantlist=True) }}
"""
RETURN = """
_raw:
description: A Python list with results from the API call.
"""
class LookupModule(LookupBase):
def _get_object_yaml(self, swiftclient, container, obj):
obj_ret = swiftclient.get_object(container=container, obj=obj)
return yaml.safe_load(obj_ret[1])
def run(self, terms, variables=None, **kwargs):
"""Returns server information from nova."""
swiftclient = get_swift_client(variables['undercloud_swift_url'],
variables['os_auth_token'])
plan = variables.get('plan')
plan_env = self._get_object_yaml(
swiftclient, plan, 'plan-environment.yaml')
roles_data = self._get_object_yaml(
swiftclient, plan, 'roles_data.yaml')
def default_role_data(role):
return {
'name': role['name'],
'count': role.get('CountDefault', 0),
'flavor': None
}
roles = list(map(default_role_data, roles_data))
parameter_defaults = plan_env.get('parameter_defaults', {})
for role in roles:
new_count = parameter_defaults.get("%sCount" % role['name'])
if new_count:
role['count'] = new_count
new_flavor = parameter_defaults.get("Overcloud%sFlavor" %
role['name'])
if new_flavor:
role['flavor'] = new_flavor
return roles