From 8e09b93f4ff4c782e6552638f0099b92523000ab Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Tue, 21 Aug 2012 23:07:11 +0900 Subject: Add public network support when launching an instance. Fixes bug 1039419. A concept of public network has been implemented in Quantum. To launch an instance connected to public network, we need to check public network (whose 'shared' attribute is True) in addition to network owned by the current tenant. Change-Id: I128e68a8b9404056f74153bf8f576cfa8b438e19 --- nova/network/quantumv2/api.py | 57 +++++++++++++--------- nova/tests/network/test_quantumv2.py | 95 +++++++++++++++++++++++++++++++----- 2 files changed, 117 insertions(+), 35 deletions(-) (limited to 'nova') diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py index be288f46e..b82801927 100644 --- a/nova/network/quantumv2/api.py +++ b/nova/network/quantumv2/api.py @@ -1,5 +1,6 @@ # Copyright 2012 OpenStack LLC. # All Rights Reserved +# Copyright (c) 2012 NEC Corporation # # 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 @@ -60,27 +61,43 @@ class API(base.Base): teardown=False): """Setup or teardown the network structures.""" + def _get_available_networks(self, context, project_id, + net_ids=None): + """Return a network list available for the tenant. + The list contains networks owned by the tenant and public networks. + If net_ids specified, it searches networks with requested IDs only. + """ + quantum = quantumv2.get_client(context) + + # If user has specified to attach instance only to specific + # networks, add them to **search_opts + # (1) Retrieve non-public network list owned by the tenant. + search_opts = {"tenant_id": project_id, 'shared': False} + if net_ids: + search_opts['id'] = net_ids + nets = quantum.list_networks(**search_opts).get('networks', []) + # (2) Retrieve public network list. + search_opts = {'shared': True} + if net_ids: + search_opts['id'] = net_ids + nets += quantum.list_networks(**search_opts).get('networks', []) + + return nets + def allocate_for_instance(self, context, instance, **kwargs): """Allocate all network resources for the instance.""" quantum = quantumv2.get_client(context) LOG.debug(_('allocate_for_instance() for %s'), instance['display_name']) - search_opts = {} - if instance['project_id']: - search_opts.update({"tenant_id": instance['project_id']}) - else: + if not instance['project_id']: msg = _('empty project id for instance %s') raise exception.InvalidInput( reason=msg % instance['display_name']) - - # If user has specified to attach instance only to specific - # networks, add them to **search_opts - # Tenant-only network only allowed so far requested_networks = kwargs.get('requested_networks') ports = {} fixed_ips = {} + net_ids = [] if requested_networks: - net_ids = [] for network_id, fixed_ip, port_id in requested_networks: if port_id: port = quantum.show_port(port_id).get('port') @@ -89,10 +106,9 @@ class API(base.Base): elif fixed_ip: fixed_ips[network_id] = fixed_ip net_ids.append(network_id) - search_opts['id'] = net_ids - data = quantum.list_networks(**search_opts) - nets = data.get('networks', []) + nets = self._get_available_networks(context, instance['project_id'], + net_ids) touched_port_ids = [] created_port_ids = [] @@ -169,12 +185,11 @@ class API(base.Base): raise NotImplementedError() def validate_networks(self, context, requested_networks): - """Validate that the tenant has the requested networks.""" + """Validate that the tenant can use the requested networks.""" LOG.debug(_('validate_networks() for %s'), requested_networks) if not requested_networks: return - search_opts = {"tenant_id": context.project_id} net_ids = [] for (net_id, _i, port_id) in requested_networks: @@ -191,9 +206,8 @@ class API(base.Base): raise exception.NetworkDuplicated(network_id=net_id) net_ids.append(net_id) - search_opts['id'] = net_ids - data = quantumv2.get_client(context).list_networks(**search_opts) - nets = data.get('networks', []) + nets = self._get_available_networks(context, context.project_id, + net_ids) if len(nets) != len(net_ids): requsted_netid_set = set(net_ids) returned_netid_set = set([net['id'] for net in nets]) @@ -297,11 +311,8 @@ class API(base.Base): data = quantumv2.get_client(context).list_ports(**search_opts) ports = data.get('ports', []) if not networks: - search_opts = {} - if instance['project_id']: - search_opts.update({"tenant_id": instance['project_id']}) - data = quantumv2.get_client(context).list_networks(**search_opts) - networks = data.get('networks', []) + networks = self._get_available_networks(context, + instance['project_id']) nw_info = network_model.NetworkInfo() for port in ports: network_name = None @@ -310,12 +321,12 @@ class API(base.Base): network_name = net['name'] break - subnets = self._get_subnets_from_port(context, port) network_IPs = [network_model.FixedIP(address=ip_address) for ip_address in [ip['ip_address'] for ip in port['fixed_ips']]] # TODO(gongysh) get floating_ips for each fixed_ip + subnets = self._get_subnets_from_port(context, port) for subnet in subnets: subnet['ips'] = [fixed_ip for fixed_ip in network_IPs if fixed_ip.is_in_subnet(subnet)] diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py index 7ee814b6b..4c41cfd39 100644 --- a/nova/tests/network/test_quantumv2.py +++ b/nova/tests/network/test_quantumv2.py @@ -155,7 +155,10 @@ class TestQuantumv2(test.TestCase): self.nets3 = self.nets2 + [{'id': 'my_netid3', 'name': 'my_netname3', 'tenant_id': 'my_tenantid'}] - self.nets = [self.nets1, self.nets2, self.nets3] + self.nets4 = [{'id': 'his_netid4', + 'name': 'his_netname4', + 'tenant_id': 'his_tenantid'}] + self.nets = [self.nets1, self.nets2, self.nets3, self.nets4] self.port_data1 = [{'network_id': 'my_netid1', 'device_id': 'device_id1', @@ -214,8 +217,10 @@ class TestQuantumv2(test.TestCase): {'ports': port_data}) nets = number == 1 and self.nets1 or self.nets2 self.moxed_client.list_networks( - tenant_id=self.instance['project_id']).AndReturn( - {'networks': nets}) + tenant_id=self.instance['project_id'], + shared=False).AndReturn({'networks': nets}) + self.moxed_client.list_networks( + shared=True).AndReturn({'networks': []}) for i in xrange(1, number + 1): subnet_data = i == 1 and self.subnet_data1 or self.subnet_data2 self.moxed_client.list_subnets( @@ -263,11 +268,10 @@ class TestQuantumv2(test.TestCase): self.instance, networks=nets).AndReturn(None) - mox_list_network_params = dict(tenant_id=self.instance['project_id']) ports = {} fixed_ips = {} + req_net_ids = [] if 'requested_networks' in kwargs: - req_net_ids = [] for id, fixed_ip, port_id in kwargs['requested_networks']: if port_id: self.moxed_client.show_port(port_id).AndReturn( @@ -279,12 +283,21 @@ class TestQuantumv2(test.TestCase): else: fixed_ips[id] = fixed_ip req_net_ids.append(id) + search_ids = [net['id'] for net in nets if net['id'] in req_net_ids] - mox_list_network_params['id'] = [net['id'] for net in nets - if net['id'] in req_net_ids] + mox_list_network_params = dict(tenant_id=self.instance['project_id'], + shared=False) + if search_ids: + mox_list_network_params['id'] = search_ids self.moxed_client.list_networks( **mox_list_network_params).AndReturn({'networks': nets}) + mox_list_network_params = dict(shared=True) + if search_ids: + mox_list_network_params['id'] = search_ids + self.moxed_client.list_networks( + **mox_list_network_params).AndReturn({'networks': []}) + for network in nets: port_req_body = { 'port': { @@ -349,8 +362,11 @@ class TestQuantumv2(test.TestCase): """ api = quantumapi.API() self.moxed_client.list_networks( - tenant_id=self.instance['project_id']).AndReturn( + tenant_id=self.instance['project_id'], + shared=False).AndReturn( {'networks': self.nets2}) + self.moxed_client.list_networks(shared=True).AndReturn( + {'networks': []}) index = 0 for network in self.nets2: port_req_body = { @@ -385,8 +401,11 @@ class TestQuantumv2(test.TestCase): """ api = quantumapi.API() self.moxed_client.list_networks( - tenant_id=self.instance['project_id']).AndReturn( + tenant_id=self.instance['project_id'], + shared=False).AndReturn( {'networks': self.nets2}) + self.moxed_client.list_networks(shared=True).AndReturn( + {'networks': []}) port_req_body = { 'port': { 'network_id': self.nets2[0]['id'], @@ -426,8 +445,13 @@ class TestQuantumv2(test.TestCase): ('my_netid2', 'test2', None)] self.moxed_client.list_networks( id=mox.SameElementsAs(['my_netid1', 'my_netid2']), - tenant_id=self.context.project_id).AndReturn( + tenant_id=self.context.project_id, + shared=False).AndReturn( {'networks': self.nets2}) + self.moxed_client.list_networks( + id=mox.SameElementsAs(['my_netid1', 'my_netid2']), + shared=True).AndReturn( + {'networks': []}) self.mox.ReplayAll() api = quantumapi.API() api.validate_networks(self.context, requested_networks) @@ -437,8 +461,13 @@ class TestQuantumv2(test.TestCase): ('my_netid2', 'test2', None)] self.moxed_client.list_networks( id=mox.SameElementsAs(['my_netid1', 'my_netid2']), - tenant_id=self.context.project_id).AndReturn( + tenant_id=self.context.project_id, + shared=False).AndReturn( {'networks': self.nets1}) + self.moxed_client.list_networks( + id=mox.SameElementsAs(['my_netid1', 'my_netid2']), + shared=True).AndReturn( + {'networks': []}) self.mox.ReplayAll() api = quantumapi.API() try: @@ -452,8 +481,13 @@ class TestQuantumv2(test.TestCase): ('my_netid3', 'test3', None)] self.moxed_client.list_networks( id=mox.SameElementsAs(['my_netid1', 'my_netid2', 'my_netid3']), - tenant_id=self.context.project_id).AndReturn( + tenant_id=self.context.project_id, + shared=False).AndReturn( {'networks': self.nets1}) + self.moxed_client.list_networks( + id=mox.SameElementsAs(['my_netid1', 'my_netid2', 'my_netid3']), + shared=True).AndReturn( + {'networks': []}) self.mox.ReplayAll() api = quantumapi.API() try: @@ -471,3 +505,40 @@ class TestQuantumv2(test.TestCase): result = api.get_instance_uuids_by_ip_filter(self.context, filters) self.assertEquals('device_id1', result[0]['instance_uuid']) self.assertEquals('device_id2', result[1]['instance_uuid']) + + def _get_available_networks(self, prv_nets, pub_nets, req_ids=None): + api = quantumapi.API() + nets = prv_nets + pub_nets + mox_list_network_params = dict(tenant_id=self.instance['project_id'], + shared=False) + if req_ids: + mox_list_network_params['id'] = req_ids + self.moxed_client.list_networks( + **mox_list_network_params).AndReturn({'networks': prv_nets}) + mox_list_network_params = dict(shared=True) + if req_ids: + mox_list_network_params['id'] = req_ids + self.moxed_client.list_networks( + **mox_list_network_params).AndReturn({'networks': pub_nets}) + + self.mox.ReplayAll() + rets = api._get_available_networks(self.context, + self.instance['project_id'], + req_ids) + self.assertEqual(rets, nets) + + def test_get_available_networks_all_private(self): + self._get_available_networks(prv_nets=self.nets2, pub_nets=[]) + + def test_get_available_networks_all_public(self): + self._get_available_networks(prv_nets=[], pub_nets=self.nets2) + + def test_get_available_networks_private_and_public(self): + self._get_available_networks(prv_nets=self.nets1, pub_nets=self.nets4) + + def test_get_available_networks_with_network_ids(self): + prv_nets = [self.nets3[0]] + pub_nets = [self.nets3[-1]] + # specify only first and last network + req_ids = [net['id'] for net in (self.nets3[0], self.nets3[-1])] + self._get_available_networks(prv_nets, pub_nets, req_ids) -- cgit