Inventory from neutron resources
It will first set up the undercloud in the inventory, then add overcloud data based on disovery of data in neutron and heat stack if available. Overcloud inventory data from neutron is added first, then data from the stack is added. With the pervasive 'setdefault' usage all values in the inventory will reflect the value first discovered. For example, a changed IP address in neutron will be reflected in the inventory even tough the overcloud heat stack is not yet aware of the change. Partial-Implements: blueprint network-data-v2-ports Change-Id: I9e1851aeb92c9e257fd18fdc500ee5c01b1eb53e
This commit is contained in:
parent
4fdace8c98
commit
98dcadffe5
|
@ -130,3 +130,7 @@ class GroupOsApplyConfigException(Exception):
|
|||
"Deployment %s with group:os-apply-config not supported with "
|
||||
"config-download." % self.deployment_name)
|
||||
super(GroupOsApplyConfigException, self).__init__(message)
|
||||
|
||||
|
||||
class MissingMandatoryNeutronResourceTag(Exception):
|
||||
"""Missing mandatory neutron resource tag"""
|
||||
|
|
|
@ -23,8 +23,12 @@ import tempfile
|
|||
import yaml
|
||||
|
||||
from heatclient.exc import HTTPNotFound
|
||||
import openstack
|
||||
|
||||
from tripleo_common import exception
|
||||
|
||||
HOST_NETWORK = 'ctlplane'
|
||||
DEFAULT_DOMAIN = 'localdomain.'
|
||||
|
||||
UNDERCLOUD_CONNECTION_SSH = 'ssh'
|
||||
|
||||
|
@ -84,6 +88,95 @@ class StackOutputs(object):
|
|||
return self.outputs.get(key, default)
|
||||
|
||||
|
||||
class NeutronData(object):
|
||||
"""Neutron inventory data.
|
||||
|
||||
A data object with for inventory generation enriched neutron data.
|
||||
"""
|
||||
def __init__(self, networks, subnets, ports):
|
||||
self.networks = networks
|
||||
self.subnets = subnets
|
||||
self.ports = ports
|
||||
self.networks_by_id = self._networks_by_id()
|
||||
self.subnets_by_id = self._subnets_by_id()
|
||||
self.ports_by_role_and_host = self._ports_by_role_and_host()
|
||||
|
||||
def _tags_to_dict(self, tags):
|
||||
tag_dict = dict()
|
||||
for tag in tags:
|
||||
if not tag.startswith('tripleo_'):
|
||||
continue
|
||||
try:
|
||||
key, value = tag.rsplit('=')
|
||||
except ValueError:
|
||||
continue
|
||||
tag_dict.update({key: value})
|
||||
|
||||
return tag_dict
|
||||
|
||||
def _ports_by_role_and_host(self):
|
||||
mandatory_tags = {'tripleo_role', 'tripleo_hostname'}
|
||||
|
||||
ports_by_role_and_host = {}
|
||||
for port in self.ports:
|
||||
tags = self._tags_to_dict(port.tags)
|
||||
# In case of missing required tags, raise an error.
|
||||
# neutron is useless as a inventory source in this case.
|
||||
if not mandatory_tags.issubset(tags):
|
||||
raise exception.MissingMandatoryNeutronResourceTag()
|
||||
hostname = tags['tripleo_hostname']
|
||||
dns_domain = self.networks_by_id[port.network_id]['dns_domain']
|
||||
net_name = self.networks_by_id[port.network_id]['name']
|
||||
ip_address = port.fixed_ips[0].get('ip_address')
|
||||
role_name = tags['tripleo_role']
|
||||
|
||||
role = ports_by_role_and_host.setdefault(role_name, {})
|
||||
host = role.setdefault(hostname, [])
|
||||
host.append(
|
||||
{'name': port.name,
|
||||
'hostname': hostname,
|
||||
'dns_domain': dns_domain,
|
||||
'network_id': port.network_id,
|
||||
'network_name': net_name,
|
||||
'fixed_ips': port.fixed_ips,
|
||||
'ip_address': ip_address,
|
||||
'tags': self._tags_to_dict(port.tags)}
|
||||
)
|
||||
|
||||
return ports_by_role_and_host
|
||||
|
||||
def _networks_by_id(self):
|
||||
networks_by_id = {}
|
||||
for net in self.networks:
|
||||
networks_by_id.update(
|
||||
{net.id: {'name': net.name,
|
||||
'subnet_ids': net.subnet_ids,
|
||||
'mtu': net.mtu,
|
||||
'dns_domain': net.dns_domain,
|
||||
'tags': self._tags_to_dict(net.tags)}
|
||||
}
|
||||
)
|
||||
|
||||
return networks_by_id
|
||||
|
||||
def _subnets_by_id(self):
|
||||
subnets_by_id = {}
|
||||
for subnet in self.subnets:
|
||||
subnets_by_id.update(
|
||||
{subnet.id: {'name': subnet.name,
|
||||
'network_id': subnet.network_id,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip,
|
||||
'cidr': subnet.cidr,
|
||||
'host_routes': subnet.host_routes,
|
||||
'dns_nameservers': subnet.dns_nameservers,
|
||||
'tags': self._tags_to_dict(subnet.tags)}
|
||||
}
|
||||
)
|
||||
|
||||
return subnets_by_id
|
||||
|
||||
|
||||
class TripleoInventory(object):
|
||||
def __init__(self, session=None, hclient=None,
|
||||
plan_name=None, auth_url=None, project_name=None,
|
||||
|
@ -153,8 +246,6 @@ class TripleoInventory(object):
|
|||
try:
|
||||
stack = self.hclient.stacks.get(self.plan_name)
|
||||
except HTTPNotFound:
|
||||
LOG.warning("Stack not found: %s. Only the undercloud will "
|
||||
"be added to the inventory.", self.plan_name)
|
||||
stack = None
|
||||
|
||||
return stack
|
||||
|
@ -302,6 +393,100 @@ class TripleoInventory(object):
|
|||
svc_host_vars.setdefault('ansible_python_interpreter',
|
||||
self.ansible_python_interpreter)
|
||||
|
||||
def _get_neutron_data(self):
|
||||
if not self.session:
|
||||
LOG.info("Session not set, neutron data will not be used to build "
|
||||
"the inventory.")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = openstack.connection.Connection(session=self.session)
|
||||
tags_filter = ['tripleo_stack_name={}'.format(self.plan_name)]
|
||||
ports = list(conn.network.ports(tags=tags_filter))
|
||||
if not ports:
|
||||
return None
|
||||
|
||||
networks = [conn.network.find_network(p.network_id)
|
||||
for p in ports]
|
||||
subnets = []
|
||||
for net in networks:
|
||||
subnets.extend(conn.network.subnets(network_id=net.id))
|
||||
|
||||
data = NeutronData(networks, subnets, ports)
|
||||
except exception.MissingMandatoryNeutronResourceTag:
|
||||
# In case of missing required tags, neutron is useless as an
|
||||
# inventory source, log warning and return None to disable the
|
||||
# neutron source.
|
||||
LOG.warning("Neutron resource without mandatory tags present. "
|
||||
"Disabling use of neutron as a source for inventory "
|
||||
"generation.")
|
||||
return None
|
||||
except openstack.connection.exceptions.EndpointNotFound:
|
||||
LOG.warning("Neutron service not installed. Disabling use of "
|
||||
"neutron as a source for inventory generation.")
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
def _add_host_from_neutron_data(self, host, ports, role_networks):
|
||||
for port in ports:
|
||||
net_name = port['network_name']
|
||||
|
||||
# Add network name to tripleo_role_networks variable
|
||||
if net_name not in role_networks:
|
||||
role_networks.append(net_name)
|
||||
|
||||
# Add variable for hostname on network
|
||||
host.setdefault('{}_hostname'.format(net_name), '.'.join(
|
||||
[port['hostname'], port['dns_domain']]))
|
||||
|
||||
# Add variable for IP address on networks
|
||||
host.setdefault('{}_ip'.format(net_name), port['ip_address'])
|
||||
|
||||
if net_name == self.host_network:
|
||||
# Add variable for ansible_host
|
||||
host.setdefault('ansible_host', port['ip_address'])
|
||||
|
||||
# Add variable for canonical hostname
|
||||
dns_domain = port.get('dns_domain')
|
||||
if dns_domain:
|
||||
canonical_dns_domain = dns_domain.partition('.')[-1]
|
||||
else:
|
||||
canonical_dns_domain = DEFAULT_DOMAIN
|
||||
host.setdefault('canonical_hostname', '.'.join(
|
||||
[port['hostname'], canonical_dns_domain]))
|
||||
|
||||
def _inventory_from_neutron_data(self, ret, children, dynamic):
|
||||
if not self.neutron_data:
|
||||
return
|
||||
|
||||
for role_name, ports_by_host in (
|
||||
self.neutron_data.ports_by_role_and_host.items()):
|
||||
role = ret.setdefault(role_name, {})
|
||||
hosts = role.setdefault('hosts', {})
|
||||
role_vars = role.setdefault('vars', {})
|
||||
role_vars.setdefault('tripleo_role_name', role_name)
|
||||
role_vars.setdefault('ansible_ssh_user', self.ansible_ssh_user)
|
||||
role_vars.setdefault('serial', self.serial)
|
||||
role_networks = role_vars.setdefault('tripleo_role_networks', [])
|
||||
for hostname, ports in ports_by_host.items():
|
||||
host = hosts.setdefault(hostname, {})
|
||||
self._add_host_from_neutron_data(host, ports, role_networks)
|
||||
|
||||
role_vars['tripleo_role_networks'] = sorted(role_networks)
|
||||
children.add(role_name)
|
||||
self.hostvars.update(hosts)
|
||||
|
||||
if dynamic:
|
||||
hosts_format = [h for h in hosts.keys()]
|
||||
hosts_format.sort()
|
||||
ret[role_name]['hosts'] = hosts_format
|
||||
|
||||
if children:
|
||||
allovercloud = ret.setdefault('allovercloud', {})
|
||||
allovercloud.setdefault('children',
|
||||
self._hosts(sorted(children), dynamic))
|
||||
|
||||
def _undercloud_inventory(self, ret, dynamic):
|
||||
undercloud = ret.setdefault('Undercloud', {})
|
||||
undercloud.setdefault('hosts', self._hosts(['undercloud'], dynamic))
|
||||
|
@ -365,7 +550,15 @@ class TripleoInventory(object):
|
|||
self.stack = self._get_stack()
|
||||
self.stack_outputs = StackOutputs(self.stack)
|
||||
|
||||
self.neutron_data = self._get_neutron_data()
|
||||
|
||||
if self.stack is None and self.neutron_data is None:
|
||||
LOG.warning("Stack not found: %s. No data found in neither "
|
||||
"neutron or heat. Only the undercloud will be added "
|
||||
"to the inventory.", self.plan_name)
|
||||
|
||||
self._undercloud_inventory(ret, dynamic)
|
||||
self._inventory_from_neutron_data(ret, children, dynamic)
|
||||
self._inventory_from_heat_outputs(ret, children, dynamic)
|
||||
|
||||
return ret
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
# 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.
|
||||
|
||||
from tripleo_common.tests.fake_neutron import stubs
|
||||
|
||||
|
||||
ctlplane_network = stubs.FakeNeutronNetwork(
|
||||
name='ctlplane',
|
||||
id='ctlplane_network_id',
|
||||
mtu=1500,
|
||||
dns_domain='ctlplane.example.com.',
|
||||
subnet_ids=['ctlplane_subnet_id'],
|
||||
tags=[],
|
||||
)
|
||||
internal_api_network = stubs.FakeNeutronNetwork(
|
||||
name='internal_api',
|
||||
id='internal_api_network_id',
|
||||
mtu=1500,
|
||||
dns_domain='inernalapi.example.com',
|
||||
subnet_ids=['internal_api_subnet_id'],
|
||||
tags=['tripleo_vip=true', 'tripleo_network_name=InternalApi'],
|
||||
)
|
||||
|
||||
ctlplane_subnet = stubs.FakeNeutronSubnet(
|
||||
name='ctlplane-subnet',
|
||||
id='ctlplane_subnet_id',
|
||||
network_id='ctlplane_network_id',
|
||||
cidr='192.0.2.0/24',
|
||||
gateway_ip='192.0.2.1',
|
||||
dns_nameservers=['192.0.2.253', '192.0.2.254'],
|
||||
host_routes=[],
|
||||
ip_version=4,
|
||||
tags=[],
|
||||
)
|
||||
internal_api_subnet = stubs.FakeNeutronSubnet(
|
||||
name='internal_api_subnet',
|
||||
id='internal_api_subnet_id',
|
||||
network_id='internal_api_network_id',
|
||||
cidr='198.51.100.128/25',
|
||||
gateway_ip='198.51.100.129',
|
||||
dns_nameservers=[],
|
||||
host_routes=[],
|
||||
ip_version=4,
|
||||
tags=['tripleo_vlan_id=20'],
|
||||
)
|
||||
|
||||
|
||||
fake_networks = [ctlplane_network, internal_api_network]
|
||||
fake_subnets = [ctlplane_subnet, internal_api_subnet]
|
||||
|
||||
controller0_ports = [
|
||||
stubs.FakeNeutronPort(name='c-0-ctlplane',
|
||||
id='controller_0_ctlplane_id',
|
||||
network_id=ctlplane_network.id,
|
||||
fixed_ips=[dict(ip_address='192.0.2.10',
|
||||
subnet_id=ctlplane_subnet.id)],
|
||||
tags=['tripleo_hostname=c-0',
|
||||
'tripleo_network_name=ctlplane',
|
||||
'tripleo_role=Controller',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
stubs.FakeNeutronPort(name='c-0-internal_api',
|
||||
id='controller_0_internal_api_id',
|
||||
network_id=internal_api_network.id,
|
||||
fixed_ips=[dict(ip_address='198.51.100.140',
|
||||
subnet_id=internal_api_subnet.id)],
|
||||
tags=['tripleo_hostname=c-0',
|
||||
'tripleo_network_name=InternalApi',
|
||||
'tripleo_role=Controller',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
]
|
||||
|
||||
controller1_ports = [
|
||||
stubs.FakeNeutronPort(name='c-1-ctlplane',
|
||||
id='controller_1_ctlplane_id',
|
||||
network_id=ctlplane_network.id,
|
||||
fixed_ips=[dict(ip_address='192.0.2.11',
|
||||
subnet_id=ctlplane_subnet.id)],
|
||||
tags=['tripleo_hostname=c-1',
|
||||
'tripleo_network_name=ctlplane',
|
||||
'tripleo_role=Controller',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
stubs.FakeNeutronPort(name='c-1-internal_api',
|
||||
id='controller_1_internal_api_id',
|
||||
network_id=internal_api_network.id,
|
||||
fixed_ips=[dict(ip_address='198.51.100.141',
|
||||
subnet_id=internal_api_subnet.id)],
|
||||
tags=['tripleo_hostname=c-1',
|
||||
'tripleo_network_name=InternalApi',
|
||||
'tripleo_role=Controller',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
]
|
||||
|
||||
controller2_ports = [
|
||||
stubs.FakeNeutronPort(name='c-2-ctlplane',
|
||||
id='controller_2_ctlplane_id',
|
||||
network_id=ctlplane_network.id,
|
||||
fixed_ips=[dict(ip_address='192.0.2.12',
|
||||
subnet_id=ctlplane_subnet.id)],
|
||||
tags=['tripleo_hostname=c-2',
|
||||
'tripleo_network_name=ctlplane',
|
||||
'tripleo_role=Controller',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
stubs.FakeNeutronPort(name='c-2-internal_api',
|
||||
id='controller_2_internal_api_id',
|
||||
network_id=internal_api_network.id,
|
||||
fixed_ips=[dict(ip_address='198.51.100.142',
|
||||
subnet_id=internal_api_subnet.id)],
|
||||
tags=['tripleo_hostname=c-2',
|
||||
'tripleo_network_name=InternalApi',
|
||||
'tripleo_role=Controller',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
]
|
||||
|
||||
compute_0_ports = [
|
||||
stubs.FakeNeutronPort(name='cp-0-ctlplane',
|
||||
id='compute_0_ctlplane_id',
|
||||
network_id=ctlplane_network.id,
|
||||
fixed_ips=[dict(ip_address='192.0.2.20',
|
||||
subnet_id=ctlplane_subnet.id)],
|
||||
tags=['tripleo_hostname=cp-0',
|
||||
'tripleo_network_name=ctlplane',
|
||||
'tripleo_role=Compute',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
stubs.FakeNeutronPort(name='cp-0-internal_api',
|
||||
id='compute_0_internal_api_id',
|
||||
network_id=internal_api_network.id,
|
||||
fixed_ips=[dict(ip_address='198.51.100.150',
|
||||
subnet_id=internal_api_subnet.id)],
|
||||
tags=['tripleo_hostname=cp-0',
|
||||
'tripleo_network_name=InternalApi',
|
||||
'tripleo_role=Compute',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
custom_0_ports = [
|
||||
stubs.FakeNeutronPort(name='cs-0-ctlplane',
|
||||
id='custom_0_ctlplane_id',
|
||||
network_id=ctlplane_network.id,
|
||||
fixed_ips=[dict(ip_address='192.0.2.200',
|
||||
subnet_id=ctlplane_subnet.id)],
|
||||
tags=['tripleo_hostname=cs-0',
|
||||
'tripleo_network_name=ctlplane',
|
||||
'tripleo_role=CustomRole',
|
||||
'tripleo_stack=overcloud'],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,141 @@
|
|||
# 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.
|
||||
|
||||
class FakeNeutronNetwork(dict):
|
||||
def __init__(self, **attrs):
|
||||
NETWORK_ATTRS = ['id',
|
||||
'name',
|
||||
'status',
|
||||
'tenant_id',
|
||||
'is_admin_state_up',
|
||||
'mtu',
|
||||
'segments',
|
||||
'is_shared',
|
||||
'subnets',
|
||||
'provider:network_type',
|
||||
'provider:physical_network',
|
||||
'provider:segmentation_id',
|
||||
'router:external',
|
||||
'availability_zones',
|
||||
'availability_zone_hints',
|
||||
'is_default',
|
||||
'tags']
|
||||
|
||||
raw = dict.fromkeys(NETWORK_ATTRS)
|
||||
raw.update(attrs)
|
||||
raw.update({
|
||||
'provider_physical_network': attrs.get(
|
||||
'provider:physical_network', None),
|
||||
'provider_network_type': attrs.get(
|
||||
'provider:network_type', None),
|
||||
'provider_segmentation_id': attrs.get(
|
||||
'provider:segmentation_id', None)
|
||||
})
|
||||
super(FakeNeutronNetwork, self).__init__(raw)
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
raise AttributeError(key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in self:
|
||||
self[key] = value
|
||||
else:
|
||||
raise AttributeError(key)
|
||||
|
||||
|
||||
class FakeNeutronPort(dict):
|
||||
def __init__(self, **attrs):
|
||||
PORT_ATTRS = ['admin_state_up',
|
||||
'allowed_address_pairs',
|
||||
'binding:host_id',
|
||||
'binding:profile',
|
||||
'binding:vif_details',
|
||||
'binding:vif_type',
|
||||
'binding:vnic_type',
|
||||
'data_plane_status',
|
||||
'description',
|
||||
'device_id',
|
||||
'device_owner',
|
||||
'dns_assignment',
|
||||
'dns_domain',
|
||||
'dns_name',
|
||||
'extra_dhcp_opts',
|
||||
'fixed_ips',
|
||||
'id',
|
||||
'mac_address',
|
||||
'name', 'network_id',
|
||||
'port_security_enabled',
|
||||
'security_group_ids',
|
||||
'status',
|
||||
'tenant_id',
|
||||
'qos_network_policy_id',
|
||||
'qos_policy_id',
|
||||
'tags',
|
||||
'uplink_status_propagation']
|
||||
|
||||
raw = dict.fromkeys(PORT_ATTRS)
|
||||
raw.update(attrs)
|
||||
super(FakeNeutronPort, self).__init__(raw)
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
raise AttributeError(key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in self:
|
||||
self[key] = value
|
||||
else:
|
||||
raise AttributeError(key)
|
||||
|
||||
|
||||
class FakeNeutronSubnet(dict):
|
||||
def __init__(self, **attrs):
|
||||
SUBNET_ATTRS = ['id',
|
||||
'name',
|
||||
'network_id',
|
||||
'cidr',
|
||||
'tenant_id',
|
||||
'is_dhcp_enabled',
|
||||
'dns_nameservers',
|
||||
'allocation_pools',
|
||||
'host_routes',
|
||||
'ip_version',
|
||||
'gateway_ip',
|
||||
'ipv6_address_mode',
|
||||
'ipv6_ra_mode',
|
||||
'subnetpool_id',
|
||||
'segment_id',
|
||||
'tags']
|
||||
|
||||
raw = dict.fromkeys(SUBNET_ATTRS)
|
||||
raw.update(attrs)
|
||||
super(FakeNeutronSubnet, self).__init__(raw)
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
raise AttributeError(key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in self:
|
||||
self[key] = value
|
||||
else:
|
||||
raise AttributeError(key)
|
|
@ -12,18 +12,21 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from collections import OrderedDict
|
||||
import fixtures
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
from unittest import mock
|
||||
|
||||
import yaml
|
||||
|
||||
from heatclient.exc import HTTPNotFound
|
||||
|
||||
from tripleo_common.inventory import NeutronData
|
||||
from tripleo_common.inventory import StackOutputs
|
||||
from tripleo_common.inventory import TripleoInventory
|
||||
from tripleo_common.tests import base
|
||||
from tripleo_common.tests.fake_neutron import fakes as neutron_fakes
|
||||
|
||||
|
||||
MOCK_ENABLED_SERVICES = {
|
||||
|
@ -144,14 +147,14 @@ class TestInventory(base.TestCase):
|
|||
}
|
||||
self.plan_name = 'overcloud'
|
||||
|
||||
self.hclient = MagicMock()
|
||||
self.hclient = mock.MagicMock()
|
||||
self.hclient.stacks.environment.return_value = {
|
||||
'parameter_defaults': {
|
||||
'AdminPassword': 'theadminpw',
|
||||
'ContainerCli': 'podman'
|
||||
}
|
||||
}
|
||||
self.mock_stack = MagicMock()
|
||||
self.mock_stack = mock.MagicMock()
|
||||
self.mock_stack.outputs = self.outputs_data['outputs']
|
||||
self.hclient.stacks.get.return_value = self.mock_stack
|
||||
|
||||
|
@ -334,10 +337,10 @@ class TestInventory(base.TestCase):
|
|||
]
|
||||
}
|
||||
plan_name = 'undercloud'
|
||||
hclient = MagicMock()
|
||||
hclient = mock.MagicMock()
|
||||
hclient.stacks.environment.return_value = {'parameter_defaults': {
|
||||
'AdminPassword': 'theadminpw', 'ContainerCli': 'podman'}}
|
||||
mock_stack = MagicMock()
|
||||
mock_stack = mock.MagicMock()
|
||||
mock_stack.outputs = outputs_data['outputs']
|
||||
hclient.stacks.get.return_value = mock_stack
|
||||
|
||||
|
@ -736,3 +739,427 @@ class TestInventory(base.TestCase):
|
|||
with open(inv_path, 'r') as f:
|
||||
loaded_inv = yaml.safe_load(f)
|
||||
self.assertEqual(expected, loaded_inv)
|
||||
|
||||
def test__add_host_from_neutron_data(self):
|
||||
neutron_data = NeutronData(networks=neutron_fakes.fake_networks,
|
||||
subnets=neutron_fakes.fake_subnets,
|
||||
ports=neutron_fakes.compute_0_ports)
|
||||
ret = OrderedDict()
|
||||
role = ret.setdefault('Compute', {})
|
||||
role_vars = role.setdefault('vars', {})
|
||||
role_networks = role_vars.setdefault('tripleo_role_networks', [])
|
||||
hosts = role.setdefault('hosts', {})
|
||||
ports = neutron_data.ports_by_role_and_host['Compute']['cp-0']
|
||||
self.inventory._add_host_from_neutron_data(hosts, ports, role_networks)
|
||||
self.assertEqual(OrderedDict([
|
||||
('Compute',
|
||||
{'hosts': {
|
||||
'ansible_host': '192.0.2.20',
|
||||
'canonical_hostname': 'cp-0.example.com.',
|
||||
'ctlplane_hostname': 'cp-0.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.20',
|
||||
'internal_api_hostname': 'cp-0.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.150'},
|
||||
'vars': {
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']
|
||||
}})
|
||||
]), ret)
|
||||
|
||||
def test__inventory_from_neutron_data(self):
|
||||
ret = OrderedDict()
|
||||
children = set()
|
||||
fake_ports = (neutron_fakes.controller0_ports +
|
||||
neutron_fakes.controller1_ports +
|
||||
neutron_fakes.compute_0_ports)
|
||||
self.inventory.neutron_data = NeutronData(
|
||||
networks=neutron_fakes.fake_networks,
|
||||
subnets=neutron_fakes.fake_subnets,
|
||||
ports=fake_ports)
|
||||
|
||||
self.inventory._inventory_from_neutron_data(ret, children, False)
|
||||
self.assertEqual({'Compute', 'Controller'}, children)
|
||||
self.assertEqual(OrderedDict([
|
||||
('Controller',
|
||||
{'hosts': {
|
||||
'c-0': {
|
||||
'ansible_host': '192.0.2.10',
|
||||
'canonical_hostname': 'c-0.example.com.',
|
||||
'ctlplane_hostname': 'c-0.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.10',
|
||||
'internal_api_hostname': 'c-0.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.140'},
|
||||
'c-1': {
|
||||
'ansible_host': '192.0.2.11',
|
||||
'canonical_hostname': 'c-1.example.com.',
|
||||
'ctlplane_hostname': 'c-1.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.11',
|
||||
'internal_api_hostname': 'c-1.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.141'}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'Controller',
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']
|
||||
}}),
|
||||
('Compute',
|
||||
{'hosts': {
|
||||
'cp-0': {
|
||||
'ansible_host': '192.0.2.20',
|
||||
'canonical_hostname': 'cp-0.example.com.',
|
||||
'ctlplane_hostname': 'cp-0.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.20',
|
||||
'internal_api_hostname': 'cp-0.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.150'}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'Compute',
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']
|
||||
}}),
|
||||
('allovercloud', {'children': {'Compute': {}, 'Controller': {}}})
|
||||
]), ret)
|
||||
|
||||
def test__inventory_from_neutron_data_dynamic(self):
|
||||
ret = OrderedDict()
|
||||
children = set()
|
||||
fake_ports = (neutron_fakes.controller0_ports +
|
||||
neutron_fakes.controller1_ports +
|
||||
neutron_fakes.compute_0_ports)
|
||||
self.inventory.neutron_data = NeutronData(
|
||||
networks=neutron_fakes.fake_networks,
|
||||
subnets=neutron_fakes.fake_subnets,
|
||||
ports=fake_ports)
|
||||
|
||||
self.inventory._inventory_from_neutron_data(ret, children, True)
|
||||
self.assertEqual({'Compute', 'Controller'}, children)
|
||||
self.assertEqual(OrderedDict([
|
||||
('Controller', {
|
||||
'hosts': ['c-0', 'c-1'],
|
||||
'vars': {'ansible_ssh_user': 'heat-admin',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'Controller',
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']
|
||||
}}),
|
||||
('Compute', {
|
||||
'hosts': ['cp-0'],
|
||||
'vars': {'ansible_ssh_user': 'heat-admin',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'Compute',
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']
|
||||
}}),
|
||||
('allovercloud', {'children': ['Compute', 'Controller']})]
|
||||
), ret)
|
||||
|
||||
@mock.patch.object(TripleoInventory, '_get_neutron_data', autospec=True)
|
||||
def test_inventory_list_with_neutron_and_heat(self, mock_get_neutron_data):
|
||||
fake_ports = (neutron_fakes.controller0_ports +
|
||||
neutron_fakes.controller1_ports +
|
||||
neutron_fakes.controller2_ports +
|
||||
neutron_fakes.compute_0_ports +
|
||||
neutron_fakes.custom_0_ports)
|
||||
mock_get_neutron_data.return_value = NeutronData(
|
||||
networks=neutron_fakes.fake_networks,
|
||||
subnets=neutron_fakes.fake_subnets,
|
||||
ports=fake_ports)
|
||||
inv_list = self.inventory.list(dynamic=False)
|
||||
c_0 = inv_list['Controller']['hosts']['c-0']
|
||||
c_1 = inv_list['Controller']['hosts']['c-1']
|
||||
c_2 = inv_list['Controller']['hosts']['c-2']
|
||||
cp_0 = inv_list['Compute']['hosts']['cp-0']
|
||||
cs_0 = inv_list['CustomRole']['hosts']['cs-0']
|
||||
|
||||
# The setdefault pattern should always put the value discovered first
|
||||
# in the inventory, neutron source run's prior to heat stack source.
|
||||
# Assert IP addresses from neutron fake are used in the
|
||||
# inventory, not the heat stack IPs.
|
||||
|
||||
# Controller
|
||||
self.assertNotEqual(
|
||||
c_0['ctlplane_ip'],
|
||||
self.outputs['RoleNetIpMap']['Controller']['ctlplane'][0])
|
||||
self.assertNotEqual(
|
||||
c_0['ansible_host'],
|
||||
self.outputs['RoleNetIpMap']['Controller']['ctlplane'][0])
|
||||
self.assertNotEqual(
|
||||
c_1['ctlplane_ip'],
|
||||
self.outputs['RoleNetIpMap']['Controller']['ctlplane'][1])
|
||||
self.assertNotEqual(
|
||||
c_1['ansible_host'],
|
||||
self.outputs['RoleNetIpMap']['Controller']['ctlplane'][1])
|
||||
self.assertNotEqual(
|
||||
c_2['ctlplane_ip'],
|
||||
self.outputs['RoleNetIpMap']['Controller']['ctlplane'][2])
|
||||
self.assertNotEqual(
|
||||
c_2['ansible_host'],
|
||||
self.outputs['RoleNetIpMap']['Controller']['ctlplane'][2])
|
||||
# Compute
|
||||
self.assertNotEqual(
|
||||
cp_0['ctlplane_ip'],
|
||||
self.outputs['RoleNetIpMap']['Compute']['ctlplane'][0])
|
||||
self.assertNotEqual(
|
||||
cp_0['ansible_host'],
|
||||
self.outputs['RoleNetIpMap']['Compute']['ctlplane'][0])
|
||||
# CustomRole
|
||||
self.assertNotEqual(
|
||||
cs_0['ctlplane_ip'],
|
||||
self.outputs['RoleNetIpMap']['CustomRole']['ctlplane'][0])
|
||||
self.assertNotEqual(
|
||||
cs_0['ansible_host'],
|
||||
self.outputs['RoleNetIpMap']['CustomRole']['ctlplane'][0])
|
||||
|
||||
# IP's and hostnames are from neutron while deploy_server_id and
|
||||
# bootstrap_server_id, serial etc are from heat.
|
||||
expected = {
|
||||
'Undercloud': {
|
||||
'hosts': {'undercloud': {}},
|
||||
'vars': {'ansible_connection': 'local',
|
||||
'ansible_host': 'localhost',
|
||||
'ansible_python_interpreter': sys.executable,
|
||||
'ansible_remote_tmp': '/tmp/ansible-${USER}',
|
||||
'auth_url': 'xyz://keystone.local',
|
||||
'cacert': 'acacert',
|
||||
'overcloud_admin_password': 'theadminpw',
|
||||
'overcloud_keystone_url': 'xyz://keystone',
|
||||
'plan': 'overcloud',
|
||||
'project_name': 'admin',
|
||||
'undercloud_service_list': [
|
||||
'tripleo_nova_compute',
|
||||
'tripleo_heat_engine',
|
||||
'tripleo_ironic_conductor',
|
||||
'tripleo_swift_container_server',
|
||||
'tripleo_swift_object_server',
|
||||
'tripleo_mistral_engine'],
|
||||
'username': 'admin'}},
|
||||
'Controller': {
|
||||
'hosts': {
|
||||
'c-0': {
|
||||
'ansible_host': '192.0.2.10',
|
||||
'canonical_hostname': 'c-0.example.com.',
|
||||
'ctlplane_hostname': 'c-0.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.10',
|
||||
'deploy_server_id': 'a',
|
||||
'internal_api_hostname': 'c-0.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.140'},
|
||||
'c-1': {
|
||||
'ansible_host': '192.0.2.11',
|
||||
'canonical_hostname': 'c-1.example.com.',
|
||||
'ctlplane_hostname': 'c-1.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.11',
|
||||
'deploy_server_id': 'b',
|
||||
'internal_api_hostname': 'c-1.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.141'},
|
||||
'c-2': {
|
||||
'ansible_host': '192.0.2.12',
|
||||
'canonical_hostname': 'c-2.example.com.',
|
||||
'ctlplane_hostname': 'c-2.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.12',
|
||||
'deploy_server_id': 'c',
|
||||
'internal_api_hostname': 'c-2.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.142'}},
|
||||
'vars': {
|
||||
'ansible_ssh_user': 'heat-admin',
|
||||
'bootstrap_server_id': 'a',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'Controller',
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']}
|
||||
},
|
||||
'Compute': {
|
||||
'hosts': {
|
||||
'cp-0': {
|
||||
'ansible_host': '192.0.2.20',
|
||||
'canonical_hostname': 'cp-0.example.com.',
|
||||
'ctlplane_hostname': 'cp-0.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.20',
|
||||
'deploy_server_id': 'd',
|
||||
'internal_api_hostname': 'cp-0.inernalapi.example.com',
|
||||
'internal_api_ip': '198.51.100.150'}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin',
|
||||
'bootstrap_server_id': 'a',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'Compute',
|
||||
'tripleo_role_networks': ['ctlplane', 'internal_api']}
|
||||
},
|
||||
'CustomRole': {
|
||||
'hosts': {
|
||||
'cs-0': {
|
||||
'ansible_host': '192.0.2.200',
|
||||
'canonical_hostname': 'cs-0.example.com.',
|
||||
'ctlplane_hostname': 'cs-0.ctlplane.example.com.',
|
||||
'ctlplane_ip': '192.0.2.200',
|
||||
'deploy_server_id': 'e'}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin',
|
||||
'bootstrap_server_id': 'a',
|
||||
'serial': 1,
|
||||
'tripleo_role_name': 'CustomRole',
|
||||
'tripleo_role_networks': ['ctlplane']}
|
||||
},
|
||||
'allovercloud': {
|
||||
'children': {'Compute': {},
|
||||
'Controller': {},
|
||||
'CustomRole': {}},
|
||||
'vars': {'container_cli': 'podman',
|
||||
'ctlplane_vip': 'x.x.x.4',
|
||||
'redis_vip': 'x.x.x.6'}
|
||||
},
|
||||
'overcloud': {'children': {'allovercloud': {}}},
|
||||
'sa': {'children': {'Controller': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'se': {'children': {'Compute': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'sd': {'children': {'Compute': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'sb': {'children': {'Controller': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'sg': {'children': {'CustomRole': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'ceph_client': {'children': {'Compute': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'sh': {'children': {'CustomRole': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
'clients': {'children': {'Compute': {}},
|
||||
'vars': {'ansible_ssh_user': 'heat-admin'}},
|
||||
}
|
||||
for k in expected:
|
||||
self.assertEqual(expected[k], inv_list[k])
|
||||
|
||||
|
||||
class TestNeutronData(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestNeutronData, self).setUp()
|
||||
fake_ports = (neutron_fakes.controller0_ports +
|
||||
neutron_fakes.controller1_ports +
|
||||
neutron_fakes.compute_0_ports)
|
||||
self.neutron_data = NeutronData(networks=neutron_fakes.fake_networks,
|
||||
subnets=neutron_fakes.fake_subnets,
|
||||
ports=fake_ports)
|
||||
|
||||
def test__tags_to_dict(self):
|
||||
tags = ['tripleo_foo=foo', 'tripleo_bar=bar', 'other_tag']
|
||||
self.assertEqual({'tripleo_foo': 'foo', 'tripleo_bar': 'bar'},
|
||||
NeutronData._tags_to_dict(self, tags))
|
||||
|
||||
def test__networks_by_id(self):
|
||||
self.assertEqual({
|
||||
'ctlplane_network_id': {
|
||||
'dns_domain': 'ctlplane.example.com.',
|
||||
'mtu': 1500,
|
||||
'name': 'ctlplane',
|
||||
'subnet_ids': ['ctlplane_subnet_id'],
|
||||
'tags': {}},
|
||||
'internal_api_network_id': {
|
||||
'dns_domain': 'inernalapi.example.com',
|
||||
'mtu': 1500,
|
||||
'name': 'internal_api',
|
||||
'subnet_ids': ['internal_api_subnet_id'],
|
||||
'tags': {'tripleo_network_name': 'InternalApi',
|
||||
'tripleo_vip': 'true'}
|
||||
},
|
||||
}, self.neutron_data.networks_by_id)
|
||||
|
||||
def test__subnets_by_id(self):
|
||||
self.assertEqual({
|
||||
'ctlplane_subnet_id': {
|
||||
'cidr': '192.0.2.0/24',
|
||||
'dns_nameservers': ['192.0.2.253', '192.0.2.254'],
|
||||
'gateway_ip': '192.0.2.1',
|
||||
'host_routes': [],
|
||||
'ip_version': 4,
|
||||
'name': 'ctlplane-subnet',
|
||||
'network_id': 'ctlplane_network_id',
|
||||
'tags': {}
|
||||
},
|
||||
'internal_api_subnet_id': {
|
||||
'cidr': '198.51.100.128/25',
|
||||
'dns_nameservers': [],
|
||||
'gateway_ip': '198.51.100.129',
|
||||
'host_routes': [],
|
||||
'ip_version': 4,
|
||||
'name': 'internal_api_subnet',
|
||||
'network_id': 'internal_api_network_id',
|
||||
'tags': {'tripleo_vlan_id': '20'}
|
||||
},
|
||||
}, self.neutron_data.subnets_by_id)
|
||||
|
||||
def test__ports_by_role_and_host(self):
|
||||
self.assertEqual({
|
||||
'Controller': {
|
||||
'c-0': [
|
||||
{'dns_domain': 'ctlplane.example.com.',
|
||||
'fixed_ips': [{'ip_address': '192.0.2.10',
|
||||
'subnet_id': 'ctlplane_subnet_id'}],
|
||||
'hostname': 'c-0',
|
||||
'ip_address': '192.0.2.10',
|
||||
'name': 'c-0-ctlplane',
|
||||
'network_id': 'ctlplane_network_id',
|
||||
'network_name': 'ctlplane',
|
||||
'tags': {'tripleo_hostname': 'c-0',
|
||||
'tripleo_network_name': 'ctlplane',
|
||||
'tripleo_role': 'Controller',
|
||||
'tripleo_stack': 'overcloud'}},
|
||||
{'dns_domain': 'inernalapi.example.com',
|
||||
'fixed_ips': [{'ip_address': '198.51.100.140',
|
||||
'subnet_id': 'internal_api_subnet_id'}],
|
||||
'hostname': 'c-0',
|
||||
'ip_address': '198.51.100.140',
|
||||
'name': 'c-0-internal_api',
|
||||
'network_id': 'internal_api_network_id',
|
||||
'network_name': 'internal_api',
|
||||
'tags': {'tripleo_hostname': 'c-0',
|
||||
'tripleo_network_name': 'InternalApi',
|
||||
'tripleo_role': 'Controller',
|
||||
'tripleo_stack': 'overcloud'}},
|
||||
],
|
||||
'c-1': [
|
||||
{'dns_domain': 'ctlplane.example.com.',
|
||||
'fixed_ips': [{'ip_address': '192.0.2.11',
|
||||
'subnet_id': 'ctlplane_subnet_id'}],
|
||||
'hostname': 'c-1',
|
||||
'ip_address': '192.0.2.11',
|
||||
'name': 'c-1-ctlplane',
|
||||
'network_id': 'ctlplane_network_id',
|
||||
'network_name': 'ctlplane',
|
||||
'tags': {'tripleo_hostname': 'c-1',
|
||||
'tripleo_network_name': 'ctlplane',
|
||||
'tripleo_role': 'Controller',
|
||||
'tripleo_stack': 'overcloud'}},
|
||||
{'dns_domain': 'inernalapi.example.com',
|
||||
'fixed_ips': [{'ip_address': '198.51.100.141',
|
||||
'subnet_id': 'internal_api_subnet_id'}],
|
||||
'hostname': 'c-1',
|
||||
'ip_address': '198.51.100.141',
|
||||
'name': 'c-1-internal_api',
|
||||
'network_id': 'internal_api_network_id',
|
||||
'network_name': 'internal_api',
|
||||
'tags': {'tripleo_hostname': 'c-1',
|
||||
'tripleo_network_name': 'InternalApi',
|
||||
'tripleo_role': 'Controller',
|
||||
'tripleo_stack': 'overcloud'}},
|
||||
]
|
||||
},
|
||||
'Compute': {
|
||||
'cp-0': [
|
||||
{'dns_domain': 'ctlplane.example.com.',
|
||||
'fixed_ips': [{'ip_address': '192.0.2.20',
|
||||
'subnet_id': 'ctlplane_subnet_id'}],
|
||||
'hostname': 'cp-0',
|
||||
'ip_address': '192.0.2.20',
|
||||
'name': 'cp-0-ctlplane',
|
||||
'network_id': 'ctlplane_network_id',
|
||||
'network_name': 'ctlplane',
|
||||
'tags': {'tripleo_hostname': 'cp-0',
|
||||
'tripleo_network_name': 'ctlplane',
|
||||
'tripleo_role': 'Compute',
|
||||
'tripleo_stack': 'overcloud'}},
|
||||
{'dns_domain': 'inernalapi.example.com',
|
||||
'fixed_ips': [{'ip_address': '198.51.100.150',
|
||||
'subnet_id': 'internal_api_subnet_id'}],
|
||||
'hostname': 'cp-0',
|
||||
'ip_address': '198.51.100.150',
|
||||
'name': 'cp-0-internal_api',
|
||||
'network_id': 'internal_api_network_id',
|
||||
'network_name': 'internal_api',
|
||||
'tags': {'tripleo_hostname': 'cp-0',
|
||||
'tripleo_network_name': 'InternalApi',
|
||||
'tripleo_role': 'Compute',
|
||||
'tripleo_stack': 'overcloud'}},
|
||||
]
|
||||
},
|
||||
}, self.neutron_data.ports_by_role_and_host)
|
||||
|
|
Loading…
Reference in New Issue