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'}
|
||||
},
|
||||
'user_name': {'type': 'string'},
|
||||
'managed': {'type': 'boolean'},
|
||||
'management_ip': {'type': 'string'},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
@ -306,7 +308,12 @@ def check_existing(instances, provisioner, baremetal):
|
|||
|
||||
not_found = []
|
||||
found = []
|
||||
unmanaged = []
|
||||
for request in instances:
|
||||
if not request.get('managed', True):
|
||||
unmanaged.append(request)
|
||||
continue
|
||||
|
||||
ident = request.get('name', request['hostname'])
|
||||
|
||||
try:
|
||||
|
@ -346,7 +353,7 @@ def check_existing(instances, provisioner, baremetal):
|
|||
)
|
||||
found.append(instance)
|
||||
|
||||
return found, not_found
|
||||
return found, not_found, unmanaged
|
||||
|
||||
|
||||
def populate_environment(instance_uuids, provisioner, environment,
|
||||
|
@ -390,7 +397,9 @@ def validate_instances(instances, schema):
|
|||
jsonschema.validate(instances, schema)
|
||||
hostnames = set()
|
||||
names = set()
|
||||
fixed_ips = set()
|
||||
for inst in instances:
|
||||
name = inst.get('hostname', inst.get('name'))
|
||||
# NOTE(dtantsur): validate image parameters
|
||||
get_source(inst)
|
||||
|
||||
|
@ -406,6 +415,22 @@ def validate_instances(instances, schema):
|
|||
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):
|
||||
jsonschema.validate(roles, _ROLES_INPUT_SCHEMA)
|
||||
|
|
|
@ -114,7 +114,7 @@ def main():
|
|||
provisioner = metalsmith.Provisioner(cloud_region=cloud.config)
|
||||
|
||||
try:
|
||||
found, not_found = bd.check_existing(
|
||||
found, not_found, pre_provisioned = bd.check_existing(
|
||||
instances=module.params['instances'],
|
||||
provisioner=provisioner,
|
||||
baremetal=cloud.baremetal
|
||||
|
@ -126,6 +126,9 @@ def main():
|
|||
if not_found:
|
||||
msg += ('Instance(s) %s do not exist. '
|
||||
% ', '.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 = [{
|
||||
'name': i.node.name or i.uuid,
|
||||
|
@ -136,7 +139,8 @@ def main():
|
|||
changed=False,
|
||||
msg=msg,
|
||||
instances=instances,
|
||||
not_found=not_found
|
||||
not_found=not_found,
|
||||
pre_provisioned=pre_provisioned
|
||||
)
|
||||
except Exception as 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 }}"
|
||||
instances: "{{ baremetal_instances.instances }}"
|
||||
provisioned_instances: "{{ baremetal_provisioned.instances + baremetal_existing.instances }}"
|
||||
hostname_role_map: "{{ baremetal_instances.hostname_role_map }}"
|
||||
state: present
|
||||
register: instance_network_ports
|
||||
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(""),
|
||||
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([{
|
||||
|
@ -986,7 +987,8 @@ class TestCheckExistingInstances(base.TestCase):
|
|||
existing.uuid = 'aaaa'
|
||||
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(
|
||||
name='host2', node='server2', resource_class='compute')
|
||||
|
||||
|
|
Loading…
Reference in New Issue