From c73cabb3a5b40794ee0b1dc1d7168b96ad4952a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Jens=C3=A5s?= Date: Thu, 12 Nov 2020 10:41:04 +0100 Subject: [PATCH] Module: tripleo_network_populate_environment Add the ansible module `tripleo_network_populate_environment`. The module populates the heat environment for deployed networks. Change-Id: Id725925331aa301cd52fef811bf67eeb6f10ec8b --- ...s-tripleo_network_populate_environment.rst | 14 ++ .../tripleo_network_populate_environment.py | 205 ++++++++++++++++++ .../test_network_populate_environment.py | 171 +++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 doc/source/modules/modules-tripleo_network_populate_environment.rst create mode 100644 tripleo_ansible/ansible_plugins/modules/tripleo_network_populate_environment.py create mode 100644 tripleo_ansible/tests/modules/test_network_populate_environment.py diff --git a/doc/source/modules/modules-tripleo_network_populate_environment.rst b/doc/source/modules/modules-tripleo_network_populate_environment.rst new file mode 100644 index 000000000..29d1bd461 --- /dev/null +++ b/doc/source/modules/modules-tripleo_network_populate_environment.rst @@ -0,0 +1,14 @@ +============================================= +Module - tripleo_network_populate_environment +============================================= + + +This module provides for the following ansible plugin: + + * tripleo_network_populate_environment + + +.. ansibleautoplugin:: + :module: tripleo_ansible/ansible_plugins/modules/tripleo_network_populate_environment.py + :documentation: true + :examples: true diff --git a/tripleo_ansible/ansible_plugins/modules/tripleo_network_populate_environment.py b/tripleo_ansible/ansible_plugins/modules/tripleo_network_populate_environment.py new file mode 100644 index 000000000..a754aab47 --- /dev/null +++ b/tripleo_ansible/ansible_plugins/modules/tripleo_network_populate_environment.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 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 ipaddress +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 +from ansible.module_utils.openstack import openstack_cloud_from_module + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: tripleo_network_populate_environment + +short_description: Create TripleO Composable network deployed environemnt + +version_added: "2.8" + +description: + - "Create TripleO Composable network deployed environemnt data" + +options: + net_data: + description: + - Structure describing a TripleO composable network + type: list +author: + - Harald Jensås +''' + +RETURN = ''' +net_ip_version_map: + description: + - Dictionary mapping network's to ip_version + returned: always + type: dict +net_cidr_map: + description: + - Dictionary mapping network to cidrs + returned: always + type: dict +''' + +EXAMPLES = ''' +- name: Populate environment + tripleo_network_populate_environment: + net_data: + - name: Baremetal + - name: External + - name: InternalApi + name_lower: internal_api + register: network_environment +''' + + +def get_net_ip_version(subnets, net_data): + + ip_versions = {subnet.ip_version for subnet in subnets} + + if {4, 6} == ip_versions: + # Full dual stack is currently not supported, operator must set + # ipv6: true in network_data if services on the network should use ipv6 + return 6 if net_data.get('ipv6') is True else 4 + + return ip_versions.pop() + + +def get_net_cidrs(subnets, ip_version): + return [subnet.cidr for subnet in subnets + if subnet.ip_version == ip_version] + + +def get_network_attrs(network): + return {'name': network.name, + 'mtu': network.mtu, + 'dns_domain': network.dns_domain, + 'tags': network.tags} + + +def get_subnet_attrs(subnet): + attrs = { + 'name': subnet.name, + 'cidr': subnet.cidr, + 'gateway_ip': subnet.gateway_ip, + 'host_routes': subnet.host_routes, + 'dns_nameservers': subnet.dns_nameservers, + 'ip_version': subnet.ip_version, + 'tags': subnet.tags, + } + + return subnet.name, attrs + + +def get_subnets_attrs(subnets): + subnets_map = dict() + for subnet in subnets: + name, attrs = get_subnet_attrs(subnet) + subnets_map[name] = attrs + + return subnets_map + + +def set_composable_network_attrs(module, conn, name_lower, net_data, attrs=None, + cidr_map=None, ip_version_map=None): + net = conn.network.find_network(net_data['name']) + if net is None: + msg = ('Failed crating deployed network environment. Network ' + '{} not found'.format(net_data['name'])) + module.fail_json(msg=msg) + + attrs['network'] = get_network_attrs(net) + + subnets = [conn.network.get_subnet(s_id) for s_id in net.subnet_ids] + + ip_version_map[name_lower] = get_net_ip_version(subnets, net_data) + cidr_map[name_lower] = get_net_cidrs(subnets, ip_version_map[name_lower]) + attrs['subnets'] = get_subnets_attrs(subnets) + + +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() + ) + + networks_data = module.params['net_data'] + + try: + _, conn = openstack_cloud_from_module(module) + net_ip_version_map = dict() + net_cidr_map = dict() + net_attr_map = dict() + for net_data in networks_data: + name_lower = net_data.get('name_lower', net_data['name'].lower()) + net_attr_map[name_lower] = dict() + + set_composable_network_attrs( + module, conn, name_lower, net_data, + attrs=net_attr_map[name_lower], + cidr_map=net_cidr_map, + ip_version_map=net_ip_version_map) + + result['environment'] = { + 'resource_registry': { + 'OS::TripleO::Network': ( + '/usr/share/openstack-tripleo-heat-templates' + '/network/deployed_networks.yaml'), + }, + 'parameter_defaults': { + 'DeployedNetworkEnvironment': { + 'net_ip_version_map': net_ip_version_map, + 'net_cidr_map': net_cidr_map, + 'net_attributes_map': net_attr_map, + } + } + } + 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() diff --git a/tripleo_ansible/tests/modules/test_network_populate_environment.py b/tripleo_ansible/tests/modules/test_network_populate_environment.py new file mode 100644 index 000000000..c2f0d5d70 --- /dev/null +++ b/tripleo_ansible/tests/modules/test_network_populate_environment.py @@ -0,0 +1,171 @@ +# Copyright 2019 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 mock +import openstack + +from tripleo_ansible.ansible_plugins.modules import ( + tripleo_network_populate_environment as plugin) +from tripleo_ansible.tests import base as tests_base +from tripleo_ansible.tests import stubs + + +class TestNetworkPopulateEnvironment(tests_base.TestCase): + + def test_get_net_ip_version(self): + net_data = {} + subnets = [stubs.FakeNeutronSubnet(ip_version=4), + stubs.FakeNeutronSubnet(ip_version=4)] + self.assertEqual(4, plugin.get_net_ip_version(subnets, net_data)) + subnets = [stubs.FakeNeutronSubnet(ip_version=6), + stubs.FakeNeutronSubnet(ip_version=6)] + self.assertEqual(6, plugin.get_net_ip_version(subnets, net_data)) + subnets = [stubs.FakeNeutronSubnet(ip_version=4), + stubs.FakeNeutronSubnet(ip_version=6)] + self.assertEqual(4, plugin.get_net_ip_version(subnets, net_data)) + net_data = {'ipv6': True} + self.assertEqual(6, plugin.get_net_ip_version(subnets, net_data)) + + def test_get_net_cidrs(self): + subnets = [ + stubs.FakeNeutronSubnet(cidr='192.168.24.0/24', ip_version=4), + stubs.FakeNeutronSubnet(cidr='192.168.25.0/24', ip_version=4), + stubs.FakeNeutronSubnet(cidr='2001:db8:a::/64', ip_version=6), + stubs.FakeNeutronSubnet(cidr='2001:db8:b::/64', ip_version=6)] + self.assertEqual(['192.168.24.0/24', '192.168.25.0/24'], + plugin.get_net_cidrs(subnets, 4)) + self.assertEqual(['2001:db8:a::/64', '2001:db8:b::/64'], + plugin.get_net_cidrs(subnets, 6)) + + def test_get_network_attrs(self): + expected = {'name': 'net_name', + 'mtu': 1500, + 'dns_domain': 'netname.localdomain.', + 'tags': ['tripleo_vlan_id=100']} + fake_network = stubs.FakeNeutronNetwork( + id='net_id', name='net_name', mtu=1500, + dns_domain='netname.localdomain.', tags=['tripleo_vlan_id=100']) + self.assertEqual(expected, plugin.get_network_attrs(fake_network)) + + def test_get_subnet_attrs(self): + fake_subnet = stubs.FakeNeutronSubnet( + name='subnet_name', cidr='192.168.24.0/24', + gateway_ip='192.168.24.1', host_routes=[], + dns_nameservers=['192.168.24.254', '192.168.24.253'], + ip_version=4, tags=['tripleo_vlan_id=1']) + expected = {'name': 'subnet_name', + 'cidr': '192.168.24.0/24', + 'gateway_ip': '192.168.24.1', + 'host_routes': [], + 'dns_nameservers': ['192.168.24.254', '192.168.24.253'], + 'ip_version': 4, 'tags': ['tripleo_vlan_id=1']} + name, attrs = plugin.get_subnet_attrs(fake_subnet) + self.assertEqual('subnet_name', name) + self.assertEqual(expected, attrs) + + def test_get_subnets_attrs(self): + fake_subnets = [ + stubs.FakeNeutronSubnet( + name='subnet01', cidr='192.168.24.0/24', + gateway_ip='192.168.24.1', + host_routes=[{'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + dns_nameservers=['192.168.24.254', '192.168.24.253'], + ip_version=4, tags=['tripleo_vlan_id=24']), + stubs.FakeNeutronSubnet( + name='subnet02', cidr='192.168.25.0/24', + gateway_ip='192.168.25.1', + host_routes=[{'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + dns_nameservers=['192.168.25.254', '192.168.25.253'], + ip_version=4, tags=['tripleo_vlan_id=25']) + ] + expected = { + 'subnet01': {'name': 'subnet01', + 'cidr': '192.168.24.0/24', + 'gateway_ip': '192.168.24.1', + 'host_routes': [{'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + 'dns_nameservers': ['192.168.24.254', + '192.168.24.253'], + 'ip_version': 4, 'tags': ['tripleo_vlan_id=24']}, + 'subnet02': {'name': 'subnet02', + 'cidr': '192.168.25.0/24', + 'gateway_ip': '192.168.25.1', + 'host_routes': [{'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + 'dns_nameservers': ['192.168.25.254', + '192.168.25.253'], + 'ip_version': 4, 'tags': ['tripleo_vlan_id=25']} + } + self.assertEqual(expected, plugin.get_subnets_attrs(fake_subnets)) + + @mock.patch.object(openstack.connection, 'Connection', autospec=True) + def test_set_composable_network_attrs(self, mock_conn): + module = mock.Mock() + net_data = {'name': 'NetName'} + fake_network = stubs.FakeNeutronNetwork( + id='net_id', name='netname', mtu=1500, + dns_domain='netname.localdomain.', tags=['tripleo_vlan_id=100'], + subnet_ids=['subnet01_id', 'subnet02_id']) + fake_subnets = [ + stubs.FakeNeutronSubnet( + name='subnet01', cidr='192.168.24.0/24', + gateway_ip='192.168.24.1', + host_routes=[{'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + dns_nameservers=['192.168.24.254', '192.168.24.253'], + ip_version=4, tags=['tripleo_vlan_id=24']), + stubs.FakeNeutronSubnet( + name='subnet02', cidr='192.168.25.0/24', + gateway_ip='192.168.25.1', + host_routes=[{'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + dns_nameservers=['192.168.25.254', '192.168.25.253'], + ip_version=4, tags=['tripleo_vlan_id=25'])] + mock_conn.network.find_network.return_value = fake_network + mock_conn.network.get_subnet.side_effect = fake_subnets + attrs = dict() + cidr_map = dict() + ip_version_map = dict() + plugin.set_composable_network_attrs( + module, mock_conn, net_data['name'].lower(), net_data, + attrs=attrs, cidr_map=cidr_map, ip_version_map=ip_version_map) + self.assertEqual( + {'network': {'dns_domain': 'netname.localdomain.', 'mtu': 1500, + 'name': 'netname', 'tags': ['tripleo_vlan_id=100']}, + 'subnets': {'subnet01': {'name': 'subnet01', + 'cidr': '192.168.24.0/24', + 'gateway_ip': '192.168.24.1', + 'host_routes': [{ + 'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + 'dns_nameservers': ['192.168.24.254', + '192.168.24.253'], + 'ip_version': 4, + 'tags': ['tripleo_vlan_id=24']}, + 'subnet02': {'name': 'subnet02', + 'cidr': '192.168.25.0/24', + 'gateway_ip': '192.168.25.1', + 'host_routes': [{ + 'destination': '192.168.24.0/24', + 'nexthop': '192.168.25.1'}], + 'dns_nameservers': ['192.168.25.254', + '192.168.25.253'], + 'ip_version': 4, + 'tags': ['tripleo_vlan_id=25']}}}, attrs) + self.assertEqual({'netname': 4}, ip_version_map) + self.assertEqual({'netname': ['192.168.24.0/24', '192.168.25.0/24']}, + cidr_map)