From 79accabe2003937edf8826cf565ef42a0056e9a4 Mon Sep 17 00:00:00 2001 From: spisarski Date: Thu, 2 Nov 2017 12:03:42 -0600 Subject: Added method to OpenStackHeatStack to return OpenStackRouter objects. Continuation of the story SNAPS-153 for adding creator/state machine instances for OpenStack objects deployed via Heat. JIRA: SNAPS-173 Change-Id: Iac9138ef7827c10db1637447d3a909e714a0301b Signed-off-by: spisarski --- snaps/openstack/utils/heat_utils.py | 22 ++++- snaps/openstack/utils/neutron_utils.py | 35 +++++++- snaps/openstack/utils/settings_utils.py | 96 +++++++++++++++++++--- snaps/openstack/utils/tests/heat_utils_tests.py | 79 ++++++++++++++++++ snaps/openstack/utils/tests/neutron_utils_tests.py | 2 +- .../openstack/utils/tests/settings_utils_tests.py | 2 - 6 files changed, 215 insertions(+), 21 deletions(-) (limited to 'snaps/openstack/utils') diff --git a/snaps/openstack/utils/heat_utils.py b/snaps/openstack/utils/heat_utils.py index ad354e0..02b4f7c 100644 --- a/snaps/openstack/utils/heat_utils.py +++ b/snaps/openstack/utils/heat_utils.py @@ -197,14 +197,32 @@ def get_stack_networks(heat_cli, neutron, stack): out = list() resources = get_resources(heat_cli, stack, 'OS::Neutron::Net') for resource in resources: - network = neutron_utils.get_network_by_id( - neutron, resource.id) + network = neutron_utils.get_network_by_id(neutron, resource.id) if network: out.append(network) return out +def get_stack_routers(heat_cli, neutron, stack): + """ + Returns a list of Network domain objects deployed by this stack + :param heat_cli: the OpenStack heat client object + :param neutron: the OpenStack neutron client object + :param stack: the SNAPS-OO Stack domain object + :return: a list of Network objects + """ + + out = list() + resources = get_resources(heat_cli, stack, 'OS::Neutron::Router') + for resource in resources: + router = neutron_utils.get_router_by_id(neutron, resource.id) + if router: + out.append(router) + + return out + + def get_stack_servers(heat_cli, nova, stack): """ Returns a list of VMInst domain objects associated with a Stack diff --git a/snaps/openstack/utils/neutron_utils.py b/snaps/openstack/utils/neutron_utils.py index 806bb53..24c4afd 100644 --- a/snaps/openstack/utils/neutron_utils.py +++ b/snaps/openstack/utils/neutron_utils.py @@ -231,7 +231,7 @@ def create_router(neutron, os_creds, router_settings): json_body = router_settings.dict_for_neutron(neutron, os_creds) logger.info('Creating router with name - ' + router_settings.name) os_router = neutron.create_router(json_body) - return Router(**os_router['router']) + return __map_router(neutron, os_router['router']) else: logger.error("Failed to create router.") raise NeutronException('Failed to create router') @@ -257,7 +257,7 @@ def get_router_by_id(neutron, router_id): """ router = neutron.show_router(router_id) if router: - return Router(**router['router']) + return __map_router(neutron, router['router']) def get_router(neutron, router_settings=None, router_name=None): @@ -281,11 +281,40 @@ def get_router(neutron, router_settings=None, router_name=None): return None routers = neutron.list_routers(**router_filter) + for routerInst in routers['routers']: - return Router(**routerInst) + return __map_router(neutron, routerInst) + return None +def __map_router(neutron, os_router): + """ + Takes an OpenStack router instance and maps it to a SNAPS Router domain + object + :param neutron: the neutron client + :param os_router: the OpenStack Router object + :return: + """ + device_ports = neutron.list_ports( + **{'device_id': os_router['id']})['ports'] + port_subnets = list() + + # Order by create date + sorted_ports = sorted(device_ports, key=lambda dev_port: dev_port['created_at']) + + for port in sorted_ports: + subnets = list() + for fixed_ip in port['fixed_ips']: + subnet = get_subnet_by_id(neutron, fixed_ip['subnet_id']) + if subnet and subnet.network_id == port['network_id']: + subnets.append(subnet) + port_subnets.append((Port(**port), subnets)) + + os_router['port_subnets'] = port_subnets + return Router(**os_router) + + def add_interface_router(neutron, router, subnet=None, port=None): """ Adds an interface router for OpenStack for either a subnet or port. diff --git a/snaps/openstack/utils/settings_utils.py b/snaps/openstack/utils/settings_utils.py index 2ab3c28..2e29063 100644 --- a/snaps/openstack/utils/settings_utils.py +++ b/snaps/openstack/utils/settings_utils.py @@ -21,6 +21,7 @@ from snaps.openstack.create_instance import ( from snaps.openstack.create_keypairs import KeypairSettings from snaps.openstack.create_network import ( PortSettings, SubnetSettings, NetworkSettings) +from snaps.openstack.create_router import RouterSettings from snaps.openstack.create_volume import VolumeSettings from snaps.openstack.create_volume_type import ( VolumeTypeSettings, VolumeTypeEncryptionSettings, ControlLocation) @@ -67,6 +68,60 @@ def create_subnet_settings(neutron, network): return out +def create_router_settings(neutron, router): + """ + Returns a RouterSettings object + :param neutron: the neutron client + :param router: a SNAPS-OO Router domain object + :return: + """ + ext_net_name = None + + if router.external_network_id: + network = neutron_utils.get_network_by_id( + neutron, router.external_network_id) + if network: + ext_net_name = network.name + + ports_tuple_list = list() + if router.port_subnets: + for port, subnets in router.port_subnets: + network = neutron_utils.get_network_by_id( + neutron, port.network_id) + + ip_addrs = list() + if network and router.external_fixed_ips: + for ext_fixed_ips in router.external_fixed_ips: + for subnet in subnets: + if ext_fixed_ips['subnet_id'] == subnet.id: + ip_addrs.append(ext_fixed_ips['ip_address']) + else: + for ip in port.ips: + ip_addrs.append(ip) + + ip_list = list() + if len(ip_addrs) > 0: + for ip_addr in ip_addrs: + if isinstance(ip_addr, dict): + ip_list.append(ip_addr['ip_address']) + else: + ip_list.append(ip_addr) + + ports_tuple_list.append((network, ip_list)) + + port_settings = __create_port_settings(neutron, ports_tuple_list) + + filtered_settings = list() + for port_setting in port_settings: + if port_setting.network_name != ext_net_name: + filtered_settings.append(port_setting) + + return RouterSettings( + name=router.name, external_gateway=ext_net_name, + admin_state_up=router.admin_state_up, + port_settings=filtered_settings) + + def create_volume_settings(volume): """ Returns a VolumeSettings object @@ -162,8 +217,15 @@ def create_vm_inst_settings(nova, neutron, server): kwargs = dict() kwargs['name'] = server.name kwargs['flavor'] = flavor_name + + net_tuples = list() + for net_name, ips in server.networks.items(): + network = neutron_utils.get_network(neutron, network_name=net_name) + if network: + net_tuples.append((network, ips)) + kwargs['port_settings'] = __create_port_settings( - neutron, server.networks) + neutron, net_tuples) kwargs['security_group_names'] = server.sec_grp_names kwargs['floating_ip_settings'] = __create_floatingip_settings( neutron, kwargs['port_settings']) @@ -175,24 +237,32 @@ def __create_port_settings(neutron, networks): """ Returns a list of port settings based on the networks parameter :param neutron: the neutron client - :param networks: a dict where the key is the network name and the value - is a list of IP addresses + :param networks: a list of tuples where #1 is the SNAPS Network domain + object and #2 is a list of IP addresses :return: """ out = list() - for net_name, ips in networks.items(): - network = neutron_utils.get_network(neutron, network_name=net_name) + for network, ips in networks: ports = neutron_utils.get_ports(neutron, network, ips) for port in ports: - kwargs = dict() - if port.name: - kwargs['name'] = port.name - kwargs['network_name'] = network.name - kwargs['mac_address'] = port.mac_address - kwargs['allowed_address_pairs'] = port.allowed_address_pairs - kwargs['admin_state_up'] = port.admin_state_up - out.append(PortSettings(**kwargs)) + if port.device_owner != 'network:dhcp': + ip_addrs = list() + for ip_dict in port.ips: + subnet = neutron_utils.get_subnet_by_id( + neutron, ip_dict['subnet_id']) + ip_addrs.append({'subnet_name': subnet.name, + 'ip': ip_dict['ip_address']}) + + kwargs = dict() + if port.name: + kwargs['name'] = port.name + kwargs['network_name'] = network.name + kwargs['mac_address'] = port.mac_address + kwargs['allowed_address_pairs'] = port.allowed_address_pairs + kwargs['admin_state_up'] = port.admin_state_up + kwargs['ip_addrs'] = ip_addrs + out.append(PortSettings(**kwargs)) return out diff --git a/snaps/openstack/utils/tests/heat_utils_tests.py b/snaps/openstack/utils/tests/heat_utils_tests.py index 567cf7b..56bed6b 100644 --- a/snaps/openstack/utils/tests/heat_utils_tests.py +++ b/snaps/openstack/utils/tests/heat_utils_tests.py @@ -405,6 +405,85 @@ class HeatUtilsCreateComplexStackTests(OSComponentTestCase): self.assertEqual(self.keypair_name, keypair2_settings.name) +class HeatUtilsRouterTests(OSComponentTestCase): + """ + Test Heat volume functionality + """ + + def setUp(self): + """ + Instantiates OpenStack instances that cannot be spawned by Heat + """ + guid = self.__class__.__name__ + '-' + str(uuid.uuid4()) + stack_name = guid + '-stack' + + self.net_name = guid + '-net' + self.subnet_name = guid + '-subnet' + self.router_name = guid + '-router' + + env_values = { + 'net_name': self.net_name, + 'subnet_name': self.subnet_name, + 'router_name': self.router_name, + 'external_net_name': self.ext_net_name} + + heat_tmplt_path = pkg_resources.resource_filename( + 'snaps.openstack.tests.heat', 'router_heat_template.yaml') + self.stack_settings = StackSettings( + name=stack_name, template_path=heat_tmplt_path, + env_values=env_values) + self.stack = None + self.heat_client = heat_utils.heat_client(self.os_creds) + self.neutron = neutron_utils.neutron_client(self.os_creds) + + def tearDown(self): + """ + Cleans the image and downloaded image file + """ + if self.stack: + try: + heat_utils.delete_stack(self.heat_client, self.stack) + except: + pass + + def test_create_router_with_stack(self): + """ + Tests the creation of an OpenStack router with Heat and the retrieval + of the Router Domain objects from heat_utils#get_stack_routers(). + """ + self.stack = heat_utils.create_stack( + self.heat_client, self.stack_settings) + + # Wait until stack deployment has completed + end_time = time.time() + create_stack.STACK_COMPLETE_TIMEOUT + is_active = False + while time.time() < end_time: + status = heat_utils.get_stack_status(self.heat_client, + self.stack.id) + if status == create_stack.STATUS_CREATE_COMPLETE: + is_active = True + break + elif status == create_stack.STATUS_CREATE_FAILED: + is_active = False + break + + time.sleep(3) + + self.assertTrue(is_active) + + routers = heat_utils.get_stack_routers( + self.heat_client, self.neutron, self.stack) + + self.assertEqual(1, len(routers)) + + router = routers[0] + self.assertEqual(self.router_name, router.name) + + ext_net = neutron_utils.get_network( + self.neutron, network_name=self.ext_net_name) + self.assertEqual(ext_net.id, router.external_network_id) + + class HeatUtilsVolumeTests(OSComponentTestCase): """ Test Heat volume functionality diff --git a/snaps/openstack/utils/tests/neutron_utils_tests.py b/snaps/openstack/utils/tests/neutron_utils_tests.py index 05d508d..0726920 100644 --- a/snaps/openstack/utils/tests/neutron_utils_tests.py +++ b/snaps/openstack/utils/tests/neutron_utils_tests.py @@ -352,7 +352,7 @@ class NeutronUtilsRouterTests(OSComponentTestCase): ext_net = neutron_utils.get_network( self.neutron, network_name=self.ext_net_name) self.assertEqual( - self.router.external_gateway_info['network_id'], ext_net.id) + self.router.external_network_id, ext_net.id) def test_create_router_empty_name(self): """ diff --git a/snaps/openstack/utils/tests/settings_utils_tests.py b/snaps/openstack/utils/tests/settings_utils_tests.py index 69bdf7c..3073d53 100644 --- a/snaps/openstack/utils/tests/settings_utils_tests.py +++ b/snaps/openstack/utils/tests/settings_utils_tests.py @@ -150,8 +150,6 @@ class SettingsUtilsVmInstTests(OSComponentTestCase): Instantiates the CreateImage object that is responsible for downloading and creating an OS image file within OpenStack """ - # super(self.__class__, self).__start__() - self.nova = nova_utils.nova_client(self.os_creds) self.glance = glance_utils.glance_client(self.os_creds) self.neutron = neutron_utils.neutron_client(self.os_creds) -- cgit 1.2.3-korg