Merge "Provision workflow managed/unmanaged node support"

This commit is contained in:
Zuul 2021-01-14 01:18:13 +00:00 committed by Gerrit Code Review
commit f5a99b59a0
7 changed files with 303 additions and 5 deletions

View File

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

View File

@ -85,6 +85,8 @@ _INSTANCE_SCHEMA = {
'items': {'type': 'string'} 'items': {'type': 'string'}
}, },
'user_name': {'type': 'string'}, 'user_name': {'type': 'string'},
'managed': {'type': 'boolean'},
'management_ip': {'type': 'string'},
}, },
'additionalProperties': False, 'additionalProperties': False,
} }
@ -306,7 +308,12 @@ def check_existing(instances, provisioner, baremetal):
not_found = [] not_found = []
found = [] found = []
unmanaged = []
for request in instances: for request in instances:
if not request.get('managed', True):
unmanaged.append(request)
continue
ident = request.get('name', request['hostname']) ident = request.get('name', request['hostname'])
try: try:
@ -346,7 +353,7 @@ def check_existing(instances, provisioner, baremetal):
) )
found.append(instance) found.append(instance)
return found, not_found return found, not_found, unmanaged
def populate_environment(instance_uuids, provisioner, environment, def populate_environment(instance_uuids, provisioner, environment,
@ -390,7 +397,9 @@ def validate_instances(instances, schema):
jsonschema.validate(instances, schema) jsonschema.validate(instances, schema)
hostnames = set() hostnames = set()
names = set() names = set()
fixed_ips = set()
for inst in instances: for inst in instances:
name = inst.get('hostname', inst.get('name'))
# NOTE(dtantsur): validate image parameters # NOTE(dtantsur): validate image parameters
get_source(inst) get_source(inst)
@ -406,6 +415,22 @@ def validate_instances(instances, schema):
inst['name']) inst['name'])
names.add(inst['name']) names.add(inst['name'])
inst_ips = {net['fixed_ip'] for net in inst.get('networks', [])
if net.get('fixed_ip')}
if inst_ips.intersection(fixed_ips):
raise ValueError(
'One or more IP address {ips} for Node {name} is requested '
'more than once'.format(
ips=', '.join(inst_ips.intersection(fixed_ips)),
name=name))
fixed_ips.update(inst_ips)
if not inst.get('managed', True):
if not inst_ips and not inst.get('management_ip'):
raise ValueError('Node %s that is managed: false requires '
'either a fixed IP address, or a management '
'ip address' % name)
def validate_roles(roles): def validate_roles(roles):
jsonschema.validate(roles, _ROLES_INPUT_SCHEMA) jsonschema.validate(roles, _ROLES_INPUT_SCHEMA)

View File

@ -114,7 +114,7 @@ def main():
provisioner = metalsmith.Provisioner(cloud_region=cloud.config) provisioner = metalsmith.Provisioner(cloud_region=cloud.config)
try: try:
found, not_found = bd.check_existing( found, not_found, pre_provisioned = bd.check_existing(
instances=module.params['instances'], instances=module.params['instances'],
provisioner=provisioner, provisioner=provisioner,
baremetal=cloud.baremetal baremetal=cloud.baremetal
@ -126,6 +126,9 @@ def main():
if not_found: if not_found:
msg += ('Instance(s) %s do not exist. ' msg += ('Instance(s) %s do not exist. '
% ', '.join(r['hostname'] for r in not_found)) % ', '.join(r['hostname'] for r in not_found))
if pre_provisioned:
msg += ('Instance(s) %s are pre-provisioned. '
% ', '.join(r['hostname'] for r in pre_provisioned))
instances = [{ instances = [{
'name': i.node.name or i.uuid, 'name': i.node.name or i.uuid,
@ -136,7 +139,8 @@ def main():
changed=False, changed=False,
msg=msg, msg=msg,
instances=instances, instances=instances,
not_found=not_found not_found=not_found,
pre_provisioned=pre_provisioned
) )
except Exception as e: except Exception as e:
module.fail_json(msg=str(e)) module.fail_json(msg=str(e))

View File

@ -0,0 +1,179 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2020 OpenStack Foundation
# 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.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import openstack_full_argument_spec
from ansible.module_utils.openstack import openstack_module_kwargs
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: tripleo_unmanaged_populate_environment
short_description: Add unmanaged node to existing heat environment
version_added: "2.8"
description:
- "Add unmanaged node to existing heat environment"
options:
environment:
description:
- Existing heat environment data to add to
type: dict
default: {}
instances:
description:
- List of unmanaged instances
required: true
type: list
elements: dict
node_port_map:
description:
- Structure with port data mapped by node and network, in the format
returned by the tripleo_overcloud_network_ports module.
type: dict
default: {}
ctlplane_network:
description:
- Name of control plane network
default: ctlplane
type: str
author:
- Harald Jensås <hjensas@redhat.com>
'''
RETURN = '''
parameter_defaults:
FooParam: foo
DeployedServerPortMap:
controller-0-ctlplane:
fixed_ips:
- ip_address': 1.1.1.1
compute-0-ctlplane:
fixed_ips:
- ip_address': 1.1.1.2
instance3-ctlplane:
fixed_ips:
- ip_address': 1.1.1.3
resource_registry:
OS::Fake::Resource: /path/to/fake/resource.yaml
'''
EXAMPLES = '''
- name: Populate environment with network port data
tripleo_unmanaged_populate_environment:
ctlplane_network: ctlplane
environment:
parameter_defaults:
FooParam: foo
DeployedServerPortMap:
instance3-ctlplane:
fixed_ips:
- ip_address': 1.1.1.3
resource_registry:
OS::Fake::Resource: /path/to/fake/resource.yaml
instances:
- hostname: controller-0
managed: false
networks:
- network: ctlplane
fixed_ip: 1.1.1.1
- hostname': compute-0
managed: false
networks:
- network: ctlplane
fixed_ip: 1.1.1.2
node_port_map:
controller-0:
ctlplane:
ip_address: 1.1.1.1
ip_subnet: 1.1.1.1/24
ip_address_uri: 1.1.1.1
compute-0:
ctlplane:
ip_address: 1.1.1.2
ip_subnet: 1.1.1.2/24
ip_address_uri: 1.1.1.2
register: environment
'''
def update_environment(environment, ctlplane_network, node_port_map,
instances):
parameter_defaults = environment.setdefault('parameter_defaults', {})
port_map = parameter_defaults.setdefault('DeployedServerPortMap', {})
for instance in instances:
if instance.get('managed', True):
continue
hostname = instance['hostname']
ip_address = node_port_map[hostname][ctlplane_network]['ip_address']
ctlplane = {}
ctlplane['fixed_ips'] = [{'ip_address': ip_address}]
port_map['%s-%s' % (hostname, ctlplane_network)] = ctlplane
def run_module():
result = dict(
success=False,
changed=False,
error="",
environment={},
)
argument_spec = openstack_full_argument_spec(
**yaml.safe_load(DOCUMENTATION)['options']
)
module = AnsibleModule(
argument_spec,
supports_check_mode=False,
**openstack_module_kwargs()
)
environment = result['environment'] = module.params['environment']
instances = module.params['instances']
node_port_map = module.params['node_port_map']
ctlplane_network = module.params['ctlplane_network']
try:
update_environment(environment, ctlplane_network, node_port_map,
instances)
result['success'] = True
module.exit_json(**result)
except Exception as err:
result['error'] = str(err)
result['msg'] = ("Error overcloud network provision failed!")
module.fail_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@ -134,6 +134,7 @@
concurrency: "{{ runtime_concurrency }}" concurrency: "{{ runtime_concurrency }}"
instances: "{{ baremetal_instances.instances }}" instances: "{{ baremetal_instances.instances }}"
provisioned_instances: "{{ baremetal_provisioned.instances + baremetal_existing.instances }}" provisioned_instances: "{{ baremetal_provisioned.instances + baremetal_existing.instances }}"
hostname_role_map: "{{ baremetal_instances.hostname_role_map }}"
state: present state: present
register: instance_network_ports register: instance_network_ports
when: manage_network_ports|default(false) when: manage_network_ports|default(false)

View File

@ -0,0 +1,73 @@
# Copyright 2020 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 copy
from tripleo_ansible.ansible_plugins.modules import (
tripleo_unmanaged_populate_environment as plugin)
from tripleo_ansible.tests import base as tests_base
FAKE_INSTANCES = [
{'hostname': 'instance1',
'managed': False,
'networks': [{'network': 'ctlplane', 'fixed_ip': '1.1.1.1'}]},
{'hostname': 'instance2',
'managed': False,
'networks': [{'network': 'ctlplane', 'fixed_ip': '1.1.1.2'}]},
{'hostname': 'instance3',
'networks': [{'network': 'ctlplane', 'vif': True}]},
]
FAKE_ENVIRONMENT = {
'parameter_defaults': {
'FooParam': 'foo',
'DeployedServerPortMap': {
'instance3-ctlplane': {
'fixed_ips': [{'ip_address': '1.1.1.3'}]
}
}
},
'resource_registry': {
'OS::Fake::Resource': '/path/to/fake/resource.yaml'
},
}
FAKE_NODE_PORT_MAP = {
'instance1': {
'ctlplane': {'ip_address': '1.1.1.1'}
},
'instance2': {
'ctlplane': {'ip_address': '1.1.1.2'}
},
'instance3': {
'ctlplane': {'ip_address': '1.1.1.3'}
},
}
class TestTripleoOvercloudNetworkPorts(tests_base.TestCase):
def test_update_environment(self):
env = copy.deepcopy(FAKE_ENVIRONMENT)
plugin.update_environment(env, 'ctlplane', FAKE_NODE_PORT_MAP,
FAKE_INSTANCES)
expected = copy.deepcopy(FAKE_ENVIRONMENT)
expected['parameter_defaults']['DeployedServerPortMap'].update(
{'instance1-ctlplane': {'fixed_ips': [{'ip_address': '1.1.1.1'}]},
'instance2-ctlplane': {'fixed_ips': [{'ip_address': '1.1.1.2'}]},
}
)
self.assertEqual(expected, env)

View File

@ -957,7 +957,8 @@ class TestCheckExistingInstances(base.TestCase):
metalsmith.exceptions.Error(""), metalsmith.exceptions.Error(""),
existing, existing,
] ]
found, not_found = bd.check_existing(instances, pr, baremetal) found, not_found, unmanaged = bd.check_existing(instances, pr,
baremetal)
self.assertEqual([existing], found) self.assertEqual([existing], found)
self.assertEqual([{ self.assertEqual([{
@ -986,7 +987,8 @@ class TestCheckExistingInstances(base.TestCase):
existing.uuid = 'aaaa' existing.uuid = 'aaaa'
pr.show_instance.return_value = existing pr.show_instance.return_value = existing
found, not_found = bd.check_existing(instances, pr, baremetal) found, not_found, unmanaged = bd.check_existing(instances, pr,
baremetal)
baremetal.create_allocation.assert_called_once_with( baremetal.create_allocation.assert_called_once_with(
name='host2', node='server2', resource_class='compute') name='host2', node='server2', resource_class='compute')