From 51ad3d4ee9f28184510a2802867535284c0f1b8b Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Tue, 31 Jul 2012 17:47:02 +0000 Subject: Adding port attribute in network parameter of boot. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example is (network:[{‘port’:}] ) The specified port will be used. A port who have already device_id can not be used to be boot. This fix is for isolating functionalities between Quantum and Nova. There are no need update nova side if port model will be changed in future quantum model. This function is for QuantumV2 api use only. Added port attribute in requested_network attribute. Fixes bug 1031096. Change-Id: Id2c86228edb0c22f536f8b36a955c87870e9d98b --- nova/api/openstack/compute/servers.py | 49 ++++++++++++++++++------ nova/exception.py | 12 ++++++ nova/network/quantumv2/api.py | 67 +++++++++++++++++++++++++++------ nova/tests/network/test_quantumv2.py | 71 ++++++++++++++++++++++++++++------- 4 files changed, 161 insertions(+), 38 deletions(-) diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 99d48d8a6..0c5a2e530 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -197,6 +197,8 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer): item["uuid"] = network_node.getAttribute("uuid") if network_node.hasAttribute("fixed_ip"): item["fixed_ip"] = network_node.getAttribute("fixed_ip") + if network_node.hasAttribute("port"): + item["port"] = network_node.getAttribute("port") networks.append(item) return networks else: @@ -498,14 +500,31 @@ class Controller(wsgi.Controller): injected_files.append((path, contents)) return injected_files + def _is_quantum_v2(self): + return FLAGS.network_api_class ==\ + "nova.network.quantumv2.api.API" + def _get_requested_networks(self, requested_networks): """Create a list of requested networks from the networks attribute.""" networks = [] for network in requested_networks: try: - network_uuid = network['uuid'] + port_id = network.get('port', None) + if port_id: + network_uuid = None + if not self._is_quantum_v2(): + # port parameter is only for qunatum v2.0 + msg = _("Unknown argment : port") + raise exc.HTTPBadRequest(explanation=msg) + if not utils.is_uuid_like(port_id): + msg = _("Bad port format: port uuid is " + "not in proper format " + "(%s)") % port_id + raise exc.HTTPBadRequest(explanation=msg) + else: + network_uuid = network['uuid'] - if not utils.is_uuid_like(network_uuid): + if not port_id and not utils.is_uuid_like(network_uuid): br_uuid = network_uuid.split('-', 1)[-1] if not utils.is_uuid_like(br_uuid): msg = _("Bad networks format: network uuid is " @@ -520,16 +539,22 @@ class Controller(wsgi.Controller): if address is not None and not utils.is_valid_ipv4(address): msg = _("Invalid fixed IP address (%s)") % address raise exc.HTTPBadRequest(explanation=msg) - # check if the network id is already present in the list, - # we don't want duplicate networks to be passed - # at the boot time - for id, ip in networks: - if id == network_uuid: - expl = (_("Duplicate networks (%s) are not allowed") % - network_uuid) - raise exc.HTTPBadRequest(explanation=expl) - - networks.append((network_uuid, address)) + + # For quantumv2, requestd_networks + # should be tuple of (network_uuid, fixed_ip, port_id) + if self._is_quantum_v2(): + networks.append((network_uuid, address, port_id)) + else: + # check if the network id is already present in the list, + # we don't want duplicate networks to be passed + # at the boot time + for id, ip in networks: + if id == network_uuid: + expl = (_("Duplicate networks" + " (%s) are not allowed") % + network_uuid) + raise exc.HTTPBadRequest(explanation=expl) + networks.append((network_uuid, address)) except KeyError as key: expl = _('Bad network format: missing %s') % key raise exc.HTTPBadRequest(explanation=expl) diff --git a/nova/exception.py b/nova/exception.py index de8982488..4d1945bad 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -588,10 +588,22 @@ class NetworkBusy(NovaException): message = _("Network %(network)s has active ports, cannot delete.") +class NetworkIsDuplicated(NovaException): + message = _("Network %(network)s is duplicated.") + + class DatastoreNotFound(NotFound): message = _("Could not find the datastore reference(s) which the VM uses.") +class PortInUse(NovaException): + message = _("Port %(port_id)s is still in use.") + + +class PortNotFound(NotFound): + message = _("Port %(port_id)s could not be found.") + + class FixedIpNotFound(NotFound): message = _("No fixed IP associated with id %(id)s.") diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py index ca5b44e25..be288f46e 100644 --- a/nova/network/quantumv2/api.py +++ b/nova/network/quantumv2/api.py @@ -62,6 +62,7 @@ class API(base.Base): 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 = {} @@ -76,28 +77,55 @@ class API(base.Base): # networks, add them to **search_opts # Tenant-only network only allowed so far requested_networks = kwargs.get('requested_networks') + ports = {} + fixed_ips = {} if requested_networks: - net_ids = [net_id for (net_id, _i) in 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') + network_id = port['network_id'] + ports[network_id] = port + elif fixed_ip: + fixed_ips[network_id] = fixed_ip + net_ids.append(network_id) search_opts['id'] = net_ids - data = quantumv2.get_client(context).list_networks(**search_opts) + data = quantum.list_networks(**search_opts) nets = data.get('networks', []) + + touched_port_ids = [] created_port_ids = [] for network in nets: - port_req_body = {'port': {'network_id': network['id'], - 'admin_state_up': True, - 'device_id': instance['uuid'], - 'tenant_id': instance['project_id']}, - } + network_id = network['id'] + zone = 'compute:%s' % FLAGS.node_availability_zone + port_req_body = {'port': {'device_id': instance['uuid'], + 'device_owner': zone}} try: - created_port_ids.append( - quantumv2.get_client(context).create_port( - port_req_body)['port']['id']) + port = ports.get(network_id) + if port: + quantum.update_port(port['id'], port_req_body) + touched_port_ids.append(port['id']) + else: + if fixed_ips.get(network_id): + port_req_body['port']['fixed_ip'] = fixed_ip + port_req_body['port']['network_id'] = network_id + port_req_body['port']['admin_state_up'] = True + port_req_body['port']['tenant_id'] = instance['project_id'] + created_port_ids.append( + quantum.create_port(port_req_body)['port']['id']) except Exception: with excutils.save_and_reraise_exception(): + for port_id in touched_port_ids: + port_in_server = quantum.show_port(port_id).get('port') + if not port_in_server: + raise Exception('Port have already lost') + port_req_body = {'port': {'device_id': None}} + quantum.update_port(port_id, port_req_body) + for port_id in created_port_ids: try: - quantumv2.get_client(context).delete_port(port_id) + quantum.delete_port(port_id) except Exception as ex: msg = _("Fail to delete port %(portid)s with" " failure: %(exception)s") @@ -147,7 +175,22 @@ class API(base.Base): if not requested_networks: return search_opts = {"tenant_id": context.project_id} - net_ids = [net_id for (net_id, _i) in requested_networks] + net_ids = [] + + for (net_id, _i, port_id) in requested_networks: + if not port_id: + net_ids.append(net_id) + continue + port = quantumv2.get_client(context).show_port(port_id).get('port') + if not port: + raise exception.PortNotFound(port_id=port_id) + if port.get('device_id', None): + raise exception.PortInUse(port_id=port_id) + net_id = port['network_id'] + if net_id in net_ids: + 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', []) diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py index 1f4d72462..7ee814b6b 100644 --- a/nova/tests/network/test_quantumv2.py +++ b/nova/tests/network/test_quantumv2.py @@ -159,6 +159,7 @@ class TestQuantumv2(test.TestCase): self.port_data1 = [{'network_id': 'my_netid1', 'device_id': 'device_id1', + 'device_owner': 'compute:nova', 'id': 'my_portid1', 'fixed_ips': [{'ip_address': '10.0.1.2', 'subnet_id': 'my_subid1'}], @@ -167,6 +168,7 @@ class TestQuantumv2(test.TestCase): self.port_data2.append(self.port_data1[0]) self.port_data2.append({'network_id': 'my_netid2', 'device_id': 'device_id2', + 'device_owner': 'compute:nova', 'id': 'my_portid2', 'fixed_ips': [{'ip_address': '10.0.2.2', 'subnet_id': 'my_subid2'}], @@ -262,8 +264,22 @@ class TestQuantumv2(test.TestCase): networks=nets).AndReturn(None) mox_list_network_params = dict(tenant_id=self.instance['project_id']) + ports = {} + fixed_ips = {} if 'requested_networks' in kwargs: - req_net_ids = [id for (id, _i) in kwargs['requested_networks']] + req_net_ids = [] + for id, fixed_ip, port_id in kwargs['requested_networks']: + if port_id: + self.moxed_client.show_port(port_id).AndReturn( + {'port': {'id': 'my_portid1', + 'network_id': 'my_netid1'}}) + req_net_ids.append('my_netid1') + ports['my_netid1'] = self.port_data1[0] + id = 'my_netid1' + else: + fixed_ips[id] = fixed_ip + req_net_ids.append(id) + mox_list_network_params['id'] = [net['id'] for net in nets if net['id'] in req_net_ids] self.moxed_client.list_networks( @@ -272,15 +288,28 @@ class TestQuantumv2(test.TestCase): for network in nets: port_req_body = { 'port': { - 'network_id': network['id'], - 'admin_state_up': True, 'device_id': self.instance['uuid'], - 'tenant_id': self.instance['project_id'], + 'device_owner': 'compute:nova', }, } - port = {'id': 'portid_' + network['id']} - self.moxed_client.create_port( - MyComparator(port_req_body)).AndReturn({'port': port}) + port = ports.get(network['id'], None) + if port: + port_id = port['id'] + self.moxed_client.update_port(port_id, + MyComparator(port_req_body) + ).AndReturn( + {'port': port}) + else: + fixed_ip = fixed_ips.get(network['id']) + if fixed_ip: + port_req_body['port']['fixed_ip'] = fixed_ip + port_req_body['port']['network_id'] = network['id'] + port_req_body['port']['admin_state_up'] = True + port_req_body['port']['tenant_id'] = \ + self.instance['project_id'] + res_port = {'port': {'id': 'fake'}} + self.moxed_client.create_port( + MyComparator(port_req_body)).AndReturn(res_port) self.mox.ReplayAll() api.allocate_for_instance(self.context, self.instance, **kwargs) @@ -294,12 +323,23 @@ class TestQuantumv2(test.TestCase): def test_allocate_for_instance_with_requested_networks(self): # specify only first and last network - requested_networks = [(net['id'], object()) + requested_networks = [(net['id'], None, None) for net in (self.nets3[0], self.nets3[-1])] - self._allocate_for_instance(net_idx=3, requested_networks=requested_networks) + def test_allocate_for_instance_with_requested_networks_with_fixedip(self): + # specify only first and last network + requested_networks = [(self.nets1[0]['id'], '10.0.1.0/24', None)] + self._allocate_for_instance(net_idx=1, + requested_networks=requested_networks) + + def test_allocate_for_instance_with_requested_networks_with_port(self): + # specify only first and last network + requested_networks = [(None, None, 'myportid1')] + self._allocate_for_instance(net_idx=1, + requested_networks=requested_networks) + def test_allocate_for_instance_ex1(self): """verify we will delete created ports if we fail to allocate all net resources. @@ -318,6 +358,7 @@ class TestQuantumv2(test.TestCase): 'network_id': network['id'], 'admin_state_up': True, 'device_id': self.instance['uuid'], + 'device_owner': 'compute:nova', 'tenant_id': self.instance['project_id'], }, } @@ -381,7 +422,8 @@ class TestQuantumv2(test.TestCase): self._deallocate_for_instance(2) def test_validate_networks(self): - requested_networks = [('my_netid1', 'test'), ('my_netid2', 'test2')] + requested_networks = [('my_netid1', 'test', None), + ('my_netid2', 'test2', None)] self.moxed_client.list_networks( id=mox.SameElementsAs(['my_netid1', 'my_netid2']), tenant_id=self.context.project_id).AndReturn( @@ -391,7 +433,8 @@ class TestQuantumv2(test.TestCase): api.validate_networks(self.context, requested_networks) def test_validate_networks_ex_1(self): - requested_networks = [('my_netid1', 'test'), ('my_netid2', 'test2')] + requested_networks = [('my_netid1', 'test', None), + ('my_netid2', 'test2', None)] self.moxed_client.list_networks( id=mox.SameElementsAs(['my_netid1', 'my_netid2']), tenant_id=self.context.project_id).AndReturn( @@ -404,9 +447,9 @@ class TestQuantumv2(test.TestCase): self.assertTrue("my_netid2" in str(ex)) def test_validate_networks_ex_2(self): - requested_networks = [('my_netid1', 'test'), - ('my_netid2', 'test2'), - ('my_netid3', 'test3')] + requested_networks = [('my_netid1', 'test', None), + ('my_netid2', 'test2', None), + ('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( -- cgit