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:
parent
70ba8b5cb6
commit
84b407e955
16
validations/collect-flavors.yaml
Normal file
16
validations/collect-flavors.yaml
Normal 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) }}"
|
130
validations/library/check_flavors.py
Normal file
130
validations/library/check_flavors.py
Normal 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()
|
60
validations/lookup_plugins/nova_flavors.py
Normal file
60
validations/lookup_plugins/nova_flavors.py
Normal 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}
|
89
validations/lookup_plugins/roles_info.py
Normal file
89
validations/lookup_plugins/roles_info.py
Normal 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
|
Loading…
Reference in New Issue
Block a user