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:
Harald Jensås 2020-12-07 01:04:56 +01:00
parent 4fdace8c98
commit 98dcadffe5
6 changed files with 939 additions and 7 deletions

View File

@ -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"""

View File

@ -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

View File

@ -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'],
),
]

View File

@ -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)

View File

@ -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)