Provision workflow managed/unmanaged node support
Adds boolean option 'managed' to the YAML defining baremetal deployments. When an instance is defined with 'managed: false' it indicates that the server is already deployed. Also adds option 'management_ip' to the YAML defining baremetal deployments. Any instnace with 'managed: false' must either have a 'fixed_ip' defining the IP address of the already provisioned server. Or it must have a 'management_ip' set. Adds module: tripleo_unmanaged_populate_environment which merges the 'managed: false' instance to the baremetal environment. Adding the support for 'managed' in the YAML used to define a baremetal deployment provides a unified UX for pre-provisioned and ironic/metalsmith provisioned instances. It also keeps the interface to manage neutron ports for composable networks identical for for pre-provisioned and ironic/metalsmith instances. Partial-Implements: blueprint network-data-v2-ports Change-Id: I19c1028664ee30ee1162c02e6efc723ca8816b14
This commit is contained in:
parent
a7e0d8262f
commit
abb1a56106
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
@ -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')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue