From 5ef94944514c3f81e31cc60d3d63b903859dca45 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 18 Aug 2011 22:15:13 -0700 Subject: add user_id and description. without user_id, there is no way for a tenant to tell which user created the server. description should be added for ec2 parity. --- nova/api/openstack/create_instance_helper.py | 3 ++- nova/api/openstack/views/servers.py | 2 ++ nova/tests/api/openstack/test_servers.py | 22 +++++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 978741682..96f817d38 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -154,7 +154,8 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=name, + display_description=\ + server_dict.get('description', ''), key_name=key_name, key_data=key_data, metadata=server_dict.get('metadata', {}), diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..c5f1e6021 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -64,6 +64,8 @@ class ViewBuilder(object): inst_dict = { 'id': inst['id'], 'name': inst['display_name'], + 'user_id': inst['user_id'], + 'description': inst['display_description'], 'status': common.status_from_power_state(inst.get('state'))} ctxt = nova.context.get_admin_context() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 437620854..7ca58b24d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -194,7 +194,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": server_name, - "display_description": "", + "display_description": "fakedescription", "locked": False, "metadata": metadata, "uuid": uuid, @@ -329,10 +329,12 @@ class ServersTest(test.TestCase): "server": { "id": 1, "uuid": FAKE_UUID, + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "server1", + "description": "fakedescription", "status": "BUILD", "hostId": '', "image": { @@ -491,10 +493,12 @@ class ServersTest(test.TestCase): "server": { "id": 1, "uuid": FAKE_UUID, + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", + "description": "fakedescription", "status": "ACTIVE", "hostId": '', "image": { @@ -582,10 +586,12 @@ class ServersTest(test.TestCase): "server": { "id": 1, "uuid": FAKE_UUID, + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", + "description": "fakedescription", "status": "ACTIVE", "hostId": '', "image": { @@ -1380,6 +1386,8 @@ class ServersTest(test.TestCase): 'uuid': FAKE_UUID, 'instance_type': dict(inst_type), 'image_ref': image_ref, + 'display_description': 'fakedescription', + 'user_id': 'fake', "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), } @@ -2719,6 +2727,8 @@ class TestServerInstanceCreation(test.TestCase): else: self.injected_files = None return [{'id': '1234', 'display_name': 'fakeinstance', + 'user_id': 'fake', + 'display_description': 'fakedescription', 'uuid': FAKE_UUID}] def set_admin_password(self, *args, **kwargs): @@ -3010,7 +3020,7 @@ class ServersViewBuilderV11Test(test.TestCase): "created_at": created_at, "updated_at": updated_at, "admin_pass": "", - "user_id": "", + "user_id": "fake", "project_id": "", "image_ref": "5", "kernel_id": "", @@ -3036,7 +3046,7 @@ class ServersViewBuilderV11Test(test.TestCase): "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": "test_server", - "display_description": "", + "display_description": "fakedescription", "locked": False, "metadata": [], #"address": , @@ -3088,10 +3098,12 @@ class ServersViewBuilderV11Test(test.TestCase): "server": { "id": 1, "uuid": self.instance['uuid'], + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", + "description": "fakedescription", "status": "BUILD", "hostId": '', "image": { @@ -3139,10 +3151,12 @@ class ServersViewBuilderV11Test(test.TestCase): "server": { "id": 1, "uuid": self.instance['uuid'], + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "test_server", + "description": "fakedescription", "status": "ACTIVE", "hostId": '', "image": { @@ -3194,10 +3208,12 @@ class ServersViewBuilderV11Test(test.TestCase): "server": { "id": 1, "uuid": self.instance['uuid'], + "user_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", + "description": "fakedescription", "status": "BUILD", "hostId": '', "image": { -- cgit From 34ef09beb3bf00fd9eb16b8517c520af24641e8c Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 10:10:51 -0700 Subject: add tenant_id to api. without tenant_id, admins can't tell which servers belong to which tenants when retrieving lists --- nova/api/openstack/servers.py | 5 +++++ nova/api/openstack/views/servers.py | 1 + nova/tests/api/openstack/test_servers.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 41e63ec3c..57ed5f45e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -107,6 +107,11 @@ class Controller(object): LOG.error(reason) raise exception.InvalidInput(reason=reason) + # translate tenant_id filter to internal project_id + if 'tenant_id' in search_opts: + search_opts['project_id'] = search_opts['tenant_id'] + del search_opts['tenant_id'] + # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index c5f1e6021..37f48b3b2 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -65,6 +65,7 @@ class ViewBuilder(object): 'id': inst['id'], 'name': inst['display_name'], 'user_id': inst['user_id'], + 'tenant_id': inst['project_id'], 'description': inst['display_description'], 'status': common.status_from_power_state(inst.get('state'))} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 7ca58b24d..480d6a370 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -330,6 +330,7 @@ class ServersTest(test.TestCase): "id": 1, "uuid": FAKE_UUID, "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, @@ -494,6 +495,7 @@ class ServersTest(test.TestCase): "id": 1, "uuid": FAKE_UUID, "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, @@ -587,6 +589,7 @@ class ServersTest(test.TestCase): "id": 1, "uuid": FAKE_UUID, "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, @@ -1152,6 +1155,25 @@ class ServersTest(test.TestCase): self.assertEqual(len(servers), 1) self.assertEqual(servers[0]['id'], 100) + def test_tenant_id_filter_converts_to_project_id_for_admin(self): + def fake_get_all(compute_self, context, search_opts=None): + self.assertNotEqual(search_opts, None) + self.assertEqual(search_opts['project_id'], 'faketenant') + self.assertFalse(search_opts.get('tenant_id')) + return [stub_instance(100)] + + self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.flags(allow_admin_api=True) + + req = webob.Request.blank('/v1.1/servers?tenant_id=faketenant') + # Use admin context + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=True) + res = req.get_response(fakes.wsgi_app(fake_auth_context=context)) + res_dict = json.loads(res.body) + # Failure in fake_get_all returns non 200 status code + self.assertEqual(res.status_int, 200) + def test_get_servers_allows_flavor_v1_1(self): def fake_get_all(compute_self, context, search_opts=None): self.assertNotEqual(search_opts, None) @@ -1388,6 +1410,7 @@ class ServersTest(test.TestCase): 'image_ref': image_ref, 'display_description': 'fakedescription', 'user_id': 'fake', + 'project_id': 'fake', "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), } @@ -2728,6 +2751,7 @@ class TestServerInstanceCreation(test.TestCase): self.injected_files = None return [{'id': '1234', 'display_name': 'fakeinstance', 'user_id': 'fake', + 'project_id': 'fake', 'display_description': 'fakedescription', 'uuid': FAKE_UUID}] @@ -3021,7 +3045,7 @@ class ServersViewBuilderV11Test(test.TestCase): "updated_at": updated_at, "admin_pass": "", "user_id": "fake", - "project_id": "", + "project_id": "fake", "image_ref": "5", "kernel_id": "", "ramdisk_id": "", @@ -3099,6 +3123,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, @@ -3152,6 +3177,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 100, @@ -3209,6 +3235,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "user_id": "fake", + "tenant_id": "fake", "updated": "2010-11-11T11:00:00Z", "created": "2010-10-10T12:00:00Z", "progress": 0, -- cgit From 49ef06ba21115a64c2efbb6fa81e0e6ee3f9095d Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 22 Aug 2011 16:21:29 -0700 Subject: xml deserialization, and test fixes --- nova/api/openstack/servers.py | 9 ++++- nova/tests/api/openstack/test_servers.py | 66 +++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 57ed5f45e..7faeb7278 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -183,6 +183,10 @@ class Controller(object): self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() + if 'description' in body['server']: + description = body['server']['description'] + update_dict['display_description'] = description.strip() + try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: @@ -836,9 +840,12 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): def _add_server_attributes(self, node, server): node.setAttribute('id', str(server['id'])) + node.setAttribute('userId', str(server['user_id'])) + node.setAttribute('tenantId', str(server['tenant_id'])) node.setAttribute('uuid', str(server['uuid'])) node.setAttribute('hostId', str(server['hostId'])) node.setAttribute('name', server['name']) + node.setAttribute('description', server['description']) node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) @@ -945,7 +952,7 @@ def create_resource(version='1.0'): "attributes": { "server": ["id", "imageId", "name", "flavorId", "hostId", "status", "progress", "adminPass", "flavorRef", - "imageRef"], + "imageRef", "userId", "tenantId", "description"], "link": ["rel", "type", "href"], }, "dict_collections": { diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 480d6a370..2f849f07a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -145,7 +145,8 @@ def instance_addresses(context, instance_id): def stub_instance(id, user_id='fake', project_id='fake', private_address=None, public_addresses=None, host=None, power_state=0, reservation_id="", uuid=FAKE_UUID, image_ref="10", - flavor_id="1", interfaces=None, name=None): + flavor_id="1", interfaces=None, name=None, + description='fakedescription'): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -194,7 +195,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": server_name, - "display_description": "fakedescription", + "display_description": description, "locked": False, "metadata": metadata, "uuid": uuid, @@ -427,9 +428,12 @@ class ServersTest(test.TestCase): expected = minidom.parseString(""" Date: Mon, 22 Aug 2011 17:02:54 -0700 Subject: fix pep8 issue --- nova/api/openstack/create_instance_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 96f817d38..b0cdd87ea 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -154,8 +154,8 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=\ - server_dict.get('description', ''), + display_description=server_dict.\ + get('description', ''), key_name=key_name, key_data=key_data, metadata=server_dict.get('metadata', {}), -- cgit From 632526f0cf7a5be3a26c3ae14683b75bfb6afbfd Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 22 Aug 2011 22:18:43 -0700 Subject: pulling all qmanager changes into a branch based on trunk, as they were previously stacked on top of melange --- bin/nova-manage | 35 ++- nova/db/api.py | 5 + nova/db/sqlalchemy/api.py | 20 +- .../versions/041_add_network_priority.py | 45 +++ nova/db/sqlalchemy/models.py | 1 + nova/network/manager.py | 37 ++- nova/network/quantum/__init__.py | 16 ++ nova/network/quantum/client.py | 306 +++++++++++++++++++++ nova/network/quantum/fake.py | 213 ++++++++++++++ nova/network/quantum/manager.py | 232 ++++++++++++++++ nova/network/quantum/melange_connection.py | 133 +++++++++ nova/network/quantum/melange_ipam_lib.py | 135 +++++++++ nova/network/quantum/nova_ipam_lib.py | 152 ++++++++++ nova/network/quantum/quantum_connection.py | 97 +++++++ nova/tests/test_quantum.py | 261 ++++++++++++++++++ 15 files changed, 1660 insertions(+), 28 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py create mode 100644 nova/network/quantum/__init__.py create mode 100644 nova/network/quantum/client.py create mode 100644 nova/network/quantum/fake.py create mode 100644 nova/network/quantum/manager.py create mode 100644 nova/network/quantum/melange_connection.py create mode 100644 nova/network/quantum/melange_ipam_lib.py create mode 100644 nova/network/quantum/nova_ipam_lib.py create mode 100644 nova/network/quantum/quantum_connection.py create mode 100644 nova/tests/test_quantum.py diff --git a/bin/nova-manage b/bin/nova-manage index 1b29d7196..9819ef206 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -58,11 +58,11 @@ import glob import json import math import netaddr +from optparse import OptionParser import os import sys import time -from optparse import OptionParser # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... @@ -681,10 +681,15 @@ class NetworkCommands(object): help='Multi host') @args('--dns1', dest="dns1", metavar="", help='First DNS') @args('--dns2', dest="dns2", metavar="", help='Second DNS') + @args('--project_id', dest="project_id", metavar="", + help='Project id') + @args('--priority', dest="priority", metavar="", + help='Network interface priority') def create(self, label=None, fixed_range_v4=None, num_networks=None, network_size=None, multi_host=None, vlan_start=None, vpn_start=None, fixed_range_v6=None, gateway_v6=None, - bridge=None, bridge_interface=None, dns1=None, dns2=None): + bridge=None, bridge_interface=None, dns1=None, dns2=None, + project_id=None, priority=None): """Creates fixed ips for host by range""" # check for certain required inputs @@ -761,11 +766,14 @@ class NetworkCommands(object): bridge=bridge, bridge_interface=bridge_interface, dns1=dns1, - dns2=dns2) + dns2=dns2, + project_id=project_id, + priority=priority) def list(self): """List all created networks""" - _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\ + "\t%-15s\t%-15s\t-15s\t-15s" print _fmt % (_('id'), _('IPv4'), _('IPv6'), @@ -774,7 +782,9 @@ class NetworkCommands(object): _('DNS2'), _('VlanID'), _('project'), - _("uuid")) + _("uuid"), + _('priority'), + _('bridge')) for network in db.network_get_all(context.get_admin_context()): print _fmt % (network.id, network.cidr, @@ -784,18 +794,19 @@ class NetworkCommands(object): network.dns2, network.vlan, network.project_id, - network.uuid) + network.uuid, + network.priority, + network.bridge) @args('--network', dest="fixed_range", metavar='', help='Network to delete') def delete(self, fixed_range): """Deletes a network""" - network = db.network_get_by_cidr(context.get_admin_context(), \ - fixed_range) - if network.project_id is not None: - raise ValueError(_('Network must be disassociated from project %s' - ' before delete' % network.project_id)) - db.network_delete_safe(context.get_admin_context(), network.id) + + # delete the network + net_manager = utils.import_object(FLAGS.network_manager) + net_manager.delete_network(context.get_admin_context(), fixed_range) + class VmCommands(object): diff --git a/nova/db/api.py b/nova/db/api.py index 2d854f24c..9ff3a1c74 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -419,6 +419,11 @@ def virtual_interface_get_by_address(context, address): return IMPL.virtual_interface_get_by_address(context, address) +def virtual_interface_get_by_uuid(context, vif_uuid): + """Gets a virtual interface from the table filtering on vif uuid.""" + return IMPL.virtual_interface_get_by_uuid(context, vif_uuid) + + def virtual_interface_get_by_fixed_ip(context, fixed_ip_id): """Gets the virtual interface fixed_ip is associated with.""" return IMPL.virtual_interface_get_by_fixed_ip(context, fixed_ip_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 04b5405f6..d96b951a1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -688,10 +688,8 @@ def fixed_ip_associate(context, address, instance_id, network_id=None): def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): session = get_session() with session.begin(): - network_or_none = or_(models.FixedIp.network_id == network_id, - models.FixedIp.network_id == None) fixed_ip_ref = session.query(models.FixedIp).\ - filter(network_or_none).\ + filter_by(network_id=network_id).\ filter_by(reserved=False).\ filter_by(deleted=False).\ filter_by(instance=None).\ @@ -928,6 +926,22 @@ def virtual_interface_get_by_address(context, address): return vif_ref +@require_context +def virtual_interface_get_by_uuid(context, vif_uuid): + """Gets a virtual interface from the table. + + :param vif_uuid: = the uuid of the interface you're looking to get + """ + session = get_session() + vif_ref = session.query(models.VirtualInterface).\ + filter_by(uuid=vif_uuid).\ + options(joinedload('network')).\ + options(joinedload('instance')).\ + options(joinedload('fixed_ips')).\ + first() + return vif_ref + + @require_context def virtual_interface_get_by_fixed_ip(context, fixed_ip_id): """Gets the virtual interface fixed_ip is associated with. diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py new file mode 100644 index 000000000..e619b1fcd --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py @@ -0,0 +1,45 @@ +# Copyright 2011 OpenStack LLC. +# 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 datetime + +from sqlalchemy import * +from migrate import * + +from nova import log as logging +from nova import utils + +meta = MetaData() + +# Add priority column to networks table +priority = Column('priority', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + # grab tables and (column for dropping later) + networks = Table('networks', meta, autoload=True) + + try: + networks.create_column(priority) + except Exception: + logging.error(_("priority column not added to networks table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(priority) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 19dc3302e..11b147802 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -560,6 +560,7 @@ class Network(BASE, NovaBase): dhcp_start = Column(String(255)) project_id = Column(String(255)) + priority = Column(Integer) host = Column(String(255)) # , ForeignKey('hosts.id')) uuid = Column(String(36)) diff --git a/nova/network/manager.py b/nova/network/manager.py index aa2a3700c..b778377a0 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -443,7 +443,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) - except exceptions.FixedIpNotFoundForInstance: + except exception.FixedIpNotFoundForInstance: fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) @@ -541,21 +541,23 @@ class NetworkManager(manager.SchedulerDependentManager): def _allocate_mac_addresses(self, context, instance_id, networks): """Generates mac addresses and creates vif rows in db for them.""" for network in networks: - vif = {'address': self.generate_mac_address(), + self.add_virtual_interface(context, instance_id, network['id']) + + def add_virtual_interface(self, context, instance_id, network_id): + vif = {'address': self.generate_mac_address(), 'instance_id': instance_id, - 'network_id': network['id'], + 'network_id': network_id, 'uuid': str(utils.gen_uuid())} - # try FLAG times to create a vif record with a unique mac_address - for i in range(FLAGS.create_unique_mac_address_attempts): - try: - self.db.virtual_interface_create(context, vif) - break - except exception.VirtualInterfaceCreateException: - vif['address'] = self.generate_mac_address() - else: - self.db.virtual_interface_delete_by_instance(context, + # try FLAG times to create a vif record with a unique mac_address + for i in range(FLAGS.create_unique_mac_address_attempts): + try: + return self.db.virtual_interface_create(context, vif) + except exception.VirtualInterfaceCreateException: + vif['address'] = self.generate_mac_address() + else: + self.db.virtual_interface_delete_by_instance(context, instance_id) - raise exception.VirtualInterfaceMacAddressException() + raise exception.VirtualInterfaceMacAddressException() def generate_mac_address(self): """Generate an Ethernet MAC address.""" @@ -784,6 +786,15 @@ class NetworkManager(manager.SchedulerDependentManager): self._create_fixed_ips(context, network['id']) return networks + def delete_network(self, context, fixed_range, require_disassociated=True): + + network = db.network_get_by_cidr(context, fixed_range) + + if require_disassociated and network.project_id is not None: + raise ValueError(_('Network must be disassociated from project %s' + ' before delete' % network.project_id)) + self.db.network_delete_safe(context, network.id) + @property def _bottom_reserved_ips(self): # pylint: disable=R0201 """Number of reserved ips at the bottom of the range.""" diff --git a/nova/network/quantum/__init__.py b/nova/network/quantum/__init__.py new file mode 100644 index 000000000..f7fbfb511 --- /dev/null +++ b/nova/network/quantum/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks +# 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. diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py new file mode 100644 index 000000000..a0c7dc6d8 --- /dev/null +++ b/nova/network/quantum/client.py @@ -0,0 +1,306 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Citrix Systems +# 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. +# @author: Tyler Smith, Cisco Systems + +import httplib +import json +import socket +import urllib + + +# this is a simple json-only serializer to use until +# we can just grab the standard serializer +# from the quantum library +class Serializer: + + def serialize(self, data, content_type): + try: + return json.dumps(data) + except TypeError: + pass + return json.dumps(to_primitive(data)) + + def deserialize(self, data, content_type): + return json.loads(data) + + +class api_call(object): + """A Decorator to add support for format and tenant overriding""" + def __init__(self, f): + self.f = f + + def __get__(self, instance, owner): + def with_params(*args, **kwargs): + # Temporarily set format and tenant for this request + (format, tenant) = (instance.format, instance.tenant) + + if 'format' in kwargs: + instance.format = kwargs['format'] + if 'tenant' in kwargs: + instance.tenant = kwargs['tenant'] + + ret = self.f(instance, *args) + (instance.format, instance.tenant) = (format, tenant) + return ret + return with_params + + +class Client(object): + + """A base client class - derived from Glance.BaseClient""" + + action_prefix = '/v0.1/tenants/{tenant_id}' + + """Action query strings""" + networks_path = "/networks" + network_path = "/networks/%s" + ports_path = "/networks/%s/ports" + port_path = "/networks/%s/ports/%s" + attachment_path = "/networks/%s/ports/%s/attachment" + + def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, + format="xml", testingStub=None, key_file=None, cert_file=None, + logger=None): + """ + Creates a new client to some service. + + :param host: The host where service resides + :param port: The port where service resides + :param use_ssl: True to use SSL, False to use HTTP + :param tenant: The tenant ID to make requests with + :param format: The format to query the server with + :param testingStub: A class that stubs basic server methods for tests + :param key_file: The SSL key file to use if use_ssl is true + :param cert_file: The SSL cert file to use if use_ssl is true + """ + self.host = host + self.port = port + self.use_ssl = use_ssl + self.tenant = tenant + self.format = format + self.connection = None + self.testingStub = testingStub + self.key_file = key_file + self.cert_file = cert_file + self.logger = logger + + def get_connection_type(self): + """ + Returns the proper connection type + """ + if self.testingStub: + return self.testingStub + if self.use_ssl: + return httplib.HTTPSConnection + else: + return httplib.HTTPConnection + + def do_request(self, method, action, body=None, + headers=None, params=None): + """ + Connects to the server and issues a request. + Returns the result data, or raises an appropriate exception if + HTTP status code is not 2xx + + :param method: HTTP method ("GET", "POST", "PUT", etc...) + :param body: string of data to send, or None (default) + :param headers: mapping of key/value pairs to add as headers + :param params: dictionary of key/value pairs to add to append + to action + + """ + + # Ensure we have a tenant id + if not self.tenant: + raise Exception("Tenant ID not set") + + # Add format and tenant_id + action += ".%s" % self.format + action = Client.action_prefix + action + action = action.replace('{tenant_id}', self.tenant) + + if type(params) is dict: + action += '?' + urllib.urlencode(params) + + try: + connection_type = self.get_connection_type() + headers = headers or {"Content-Type": + "application/%s" % self.format} + + # Open connection and send request, handling SSL certs + certs = {'key_file': self.key_file, 'cert_file': self.cert_file} + certs = dict((x, certs[x]) for x in certs if certs[x] != None) + + if self.use_ssl and len(certs): + c = connection_type(self.host, self.port, **certs) + else: + c = connection_type(self.host, self.port) + + if self.logger: + self.logger.debug("Quantum Client Request:\n" \ + + method + " " + action + "\n") + if body: + self.logger.debug(body) + + c.request(method, action, body, headers) + res = c.getresponse() + status_code = self.get_status_code(res) + data = res.read() + + if self.logger: + self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ + % (str(status_code), data)) + + if status_code in (httplib.OK, + httplib.CREATED, + httplib.ACCEPTED, + httplib.NO_CONTENT): + return self.deserialize(data, status_code) + else: + raise Exception("Server returned error: %s" % res.read()) + + except (socket.error, IOError), e: + raise Exception("Unable to connect to " + "server. Got error: %s" % e) + + def get_status_code(self, response): + """ + Returns the integer status code from the response, which + can be either a Webob.Response (used in testing) or httplib.Response + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status + + def serialize(self, data): + if data is None: + return None + elif type(data) is dict: + return Serializer().serialize(data, self.content_type()) + else: + raise Exception("unable to deserialize object of type = '%s'" \ + % type(data)) + + def deserialize(self, data, status_code): + if status_code == 202: + return data + return Serializer().deserialize(data, self.content_type()) + + def content_type(self, format=None): + if not format: + format = self.format + return "application/%s" % (format) + + @api_call + def list_networks(self): + """ + Fetches a list of all networks for a tenant + """ + return self.do_request("GET", self.networks_path) + + @api_call + def show_network_details(self, network): + """ + Fetches the details of a certain network + """ + return self.do_request("GET", self.network_path % (network)) + + @api_call + def create_network(self, body=None): + """ + Creates a new network + """ + body = self.serialize(body) + return self.do_request("POST", self.networks_path, body=body) + + @api_call + def update_network(self, network, body=None): + """ + Updates a network + """ + body = self.serialize(body) + return self.do_request("PUT", self.network_path % (network), body=body) + + @api_call + def delete_network(self, network): + """ + Deletes the specified network + """ + return self.do_request("DELETE", self.network_path % (network)) + + @api_call + def list_ports(self, network): + """ + Fetches a list of ports on a given network + """ + return self.do_request("GET", self.ports_path % (network)) + + @api_call + def show_port_details(self, network, port): + """ + Fetches the details of a certain port + """ + return self.do_request("GET", self.port_path % (network, port)) + + @api_call + def create_port(self, network, body=None): + """ + Creates a new port on a given network + """ + body = self.serialize(body) + return self.do_request("POST", self.ports_path % (network), body=body) + + @api_call + def delete_port(self, network, port): + """ + Deletes the specified port from a network + """ + return self.do_request("DELETE", self.port_path % (network, port)) + + @api_call + def set_port_state(self, network, port, body=None): + """ + Sets the state of the specified port + """ + body = self.serialize(body) + return self.do_request("PUT", + self.port_path % (network, port), body=body) + + @api_call + def show_port_attachment(self, network, port): + """ + Fetches the attachment-id associated with the specified port + """ + return self.do_request("GET", self.attachment_path % (network, port)) + + @api_call + def attach_resource(self, network, port, body=None): + """ + Sets the attachment-id of the specified port + """ + body = self.serialize(body) + return self.do_request("PUT", + self.attachment_path % (network, port), body=body) + + @api_call + def detach_resource(self, network, port): + """ + Removes the attachment-id of the specified port + """ + return self.do_request("DELETE", + self.attachment_path % (network, port)) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py new file mode 100644 index 000000000..ff2b1e9d5 --- /dev/null +++ b/nova/network/quantum/fake.py @@ -0,0 +1,213 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, 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 nova import exception +from nova import ipv6 +from nova import log as logging +from nova import utils +import math +from netaddr import IPNetwork + + +LOG = logging.getLogger("network.quantum.fake") + + +# this class can be used for unit functional/testing on nova, +# as it does not actually make remote calls to the Quantum service +class FakeQuantumClientConnection: + + def __init__(self): + self.nets = {} + + def get_networks_for_tenant(self, tenant_id): + net_ids = [] + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + net_ids.append(net_id) + return net_ids + + def create_network(self, tenant_id, network_name): + + uuid = str(utils.gen_uuid()) + self.nets[uuid] = {'net-name': network_name, + 'tenant-id': tenant_id, + 'ports': {}} + return uuid + + def delete_network(self, tenant_id, net_id): + if self.nets[net_id]['tenant-id'] == tenant_id: + del self.nets[net_id] + + def network_exists(self, tenant_id, net_id): + try: + return self.nets[net_id]['tenant-id'] == tenant_id + except: + return False + + def _confirm_not_attached(self, interface_id): + for n in self.nets.values(): + for p in n['ports'].values(): + if p['attachment-id'] == interface_id: + raise Exception("interface '%s' is already attached" %\ + interface_id) + + def create_and_attach_port(self, tenant_id, net_id, interface_id): + if not self.network_exists(tenant_id, net_id): + raise Exception("network %s does not exist for tenant %s" %\ + (net_id, tenant_id)) + + self._confirm_not_attached(interface_id) + uuid = str(utils.gen_uuid()) + self.nets[net_id]['ports'][uuid] = \ + {"port-state": "ACTIVE", + "attachment-id": interface_id} + + def detach_and_delete_port(self, tenant_id, net_id, port_id): + if not self.network_exists(tenant_id, net_id): + raise Exception("network %s does not exist for tenant %s" %\ + (net_id, tenant_id)) + del self.nets[net_id]['ports'][port_id] + + def get_port_by_attachment(self, tenant_id, attachment_id): + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + for port_id, p in n['ports'].items(): + if p['attachment-id'] == attachment_id: + return (net_id, port_id) + + return (None, None) + + +def get_ipam_lib(net_man): + return FakeQuantumIPAMLib() + + +class FakeQuantumIPAMLib(): + + def __init__(self): + self.subnets = {} + + def create_subnet(self, context, label, tenant_id, quantum_net_id, + cidr=None, gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): + if int(cidr.split("/")[1]) != 24: + raise Exception("fake ipam_lib only supports /24s") + v4_ips = [] + net_start = cidr[0:cidr.rfind(".") + 1] + subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + for i in xrange(2, subnet_size - 1): + v4_ips.append({"ip": net_start + str(i), + "allocated": False, + "virtual_interface_id": None, + "instance_id": None}) + self.subnets[quantum_net_id] = {\ + "label": label, + "gateway": net_start + "1", + "netmask": "255.255.255.0", + "broadcast": net_start + "255", + "cidr": cidr, + "gateway_v6": gateway_v6, + "cidr_v6": cidr_v6, + "dns1": dns1, + "dns2": dns2, + "project_id": tenant_id, + "v4_ips": v4_ips} + + def get_network_id_by_cidr(self, context, cidr, project_id): + for net_id, s in self.subnets.items(): + if s['cidr'] == cidr or s['cidr_v6'] == cidr: + return net_id + return None + + def delete_subnets_by_net_id(self, context, net_id, project_id): + self.verify_subnet_exists(context, project_id, net_id) + del self.subnets[net_id] + + def get_project_and_global_net_ids(self, context, project_id): + net_ids = [] + for nid, s in self.subnets.items(): + if s['project_id'] == project_id or \ + s['project_id'] == None: + net_ids.append((nid, s['project_id'])) + return net_ids + + def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): + subnet = self.subnets[quantum_net_id] + for i in xrange(0, len(subnet['v4_ips'])): + ip = subnet['v4_ips'][i] + if not ip['allocated']: + subnet['v4_ips'][i] = {'ip': ip['ip'], + 'allocated': True, + 'virtual_interface_id': vif_rec['uuid'], + 'instance_id': vif_rec['instance_id']} + return + raise Exception("Unable to find available IP for net '%s'" %\ + quantum_net_id) + + def get_subnets_by_net_id(self, context, tenant_id, net_id): + self.verify_subnet_exists(context, tenant_id, net_id) + + subnet_data = self.subnets[net_id] + subnet_data_v4 = { + 'network_id': net_id, + 'cidr': subnet_data['cidr'], + 'gateway': subnet_data['gateway'], + 'broadcast': subnet_data['broadcast'], + 'netmask': subnet_data['netmask'], + 'dns1': subnet_data['dns1'], + 'dns2': subnet_data['dns2']} + subnet_data_v6 = { + 'network_id': net_id, + 'cidr': subnet_data['cidr_v6'], + 'gateway': subnet_data['gateway_v6'], + 'broadcast': None, + 'netmask': None, + 'dns1': None, + 'dns2': None} + return (subnet_data_v4, subnet_data_v6) + + def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + self.verify_subnet_exists(context, project_id, net_id) + + subnet_data = self.subnets[net_id] + if subnet_data['cidr_v6']: + ip = ipv6.to_global(subnet_data['cidr_v6'], + "ca:fe:de:ad:be:ef", + project_id) + return [ip] + return [] + + def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + self.verify_subnet_exists(context, project_id, net_id) + + subnet_data = self.subnets[net_id] + for ip in subnet_data['v4_ips']: + if ip['virtual_interface_id'] == vif_id: + return [ip['ip']] + return [] + + def verify_subnet_exists(self, context, tenant_id, quantum_net_id): + if quantum_net_id not in self.subnets: + raise exception.NetworkNotFound(network_id=quantum_net_id) + + def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): + s = self.subnets[net_id] + for ip in s['v4_ips']: + if ip['virtual_interface_id'] == vif_ref['id']: + ip['allocated'] = False + ip['instance_id'] = None + ip['virtual_interface_id'] = None diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py new file mode 100644 index 000000000..f712a93c4 --- /dev/null +++ b/nova/network/quantum/manager.py @@ -0,0 +1,232 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, 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 nova import db +from nova import exception +from nova import flags +from nova import log as logging +from nova import manager +from nova import utils +from nova.network import manager +from nova.network.quantum import quantum_connection +from nova.network.quantum import fake + +LOG = logging.getLogger("quantum_manager") + +FLAGS = flags.FLAGS + +flags.DEFINE_string('quantum_ipam_lib', + 'nova.network.quantum.nova_ipam_lib', + "Indicates underlying IP address management library") + + +class QuantumManager(manager.FlatManager): + + def __init__(self, ipam_lib=None, *args, **kwargs): + + if FLAGS.fake_network: + self.q_conn = fake.FakeQuantumClientConnection() + else: + self.q_conn = quantum_connection.QuantumClientConnection() + + if not ipam_lib: + ipam_lib = FLAGS.quantum_ipam_lib + self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self) + + super(QuantumManager, self).__init__(*args, **kwargs) + + def create_networks(self, context, label, cidr, multi_host, num_networks, + network_size, cidr_v6, gateway_v6, bridge, + bridge_interface, dns1=None, dns2=None, **kwargs): + if num_networks != 1: + raise Exception("QuantumManager requires that only one" + " network is created per call") + q_tenant_id = kwargs["project_id"] or \ + FLAGS.quantum_default_tenant_id + quantum_net_id = bridge + if quantum_net_id: + if not q_conn.network_exists(q_tenant_id, quantum_net_id): + raise Exception("Unable to find existing quantum " \ + " network for tenant '%s' with net-id '%s'" % \ + (q_tenant_id, quantum_net_id)) + else: + # otherwise, create network from default quantum pool + quantum_net_id = self.q_conn.create_network(q_tenant_id, label) + + ipam_tenant_id = kwargs.get("project_id", None) + self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id, + cidr, gateway_v6, cidr_v6, dns1, dns2) + + def delete_network(self, context, fixed_range): + project_id = context.project_id + quantum_net_id = self.ipam.get_network_id_by_cidr( + context, fixed_range, project_id) + self.ipam.delete_subnets_by_net_id(context, quantum_net_id, + project_id) + try: + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.delete_network(q_tenant_id, quantum_net_id) + except Exception, e: + raise Exception("Unable to delete Quantum Network with " + "fixed_range = %s (ports still in use?)." % fixed_range) + + def allocate_for_instance(self, context, **kwargs): + instance_id = kwargs.pop('instance_id') + instance_type_id = kwargs['instance_type_id'] + host = kwargs.pop('host') + project_id = kwargs.pop('project_id') + LOG.debug(_("network allocations for instance %s"), instance_id) + + # if using the create-server-networks extension, 'requested_networks' + # will be defined, otherwise, just grab relevant nets from IPAM + requested_networks = kwargs.get('requested_networks') + + if requested_networks: + net_proj_pairs = [(net_id, project_id) \ + for (net_id, _i) in requested_networks] + else: + net_proj_pairs = self.ipam.get_project_and_global_net_ids(context, + project_id) + + # Create a port via quantum and attach the vif + for (net_id, project_id) in net_proj_pairs: + vif_rec = manager.FlatManager.add_virtual_interface(self, + context, instance, None) + + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.create_and_attach_port(q_tenant_id, net_id, + vif_rec['uuid']) + self.ipam.allocate_fixed_ip(context, project_id, net_id, vif_rec) + + return self.get_instance_nw_info(context, instance_id, + instance_type_id, host) + + def get_instance_nw_info(self, context, instance_id, + instance_type_id, host): + network_info = [] + project_id = context.project_id + + admin_context = context.elevated() + vifs = db.virtual_interface_get_by_instance(admin_context, + instance_id) + for vif in vifs: + q_tenant_id = project_id + ipam_tenant_id = project_id + net_id, port_id = self.q_conn.get_port_by_attachment(q_tenant_id, + vif['uuid']) + if not net_id: + q_tenant_id = FLAGS.quantum_default_tenant_id + ipam_tenant_id = None + net_id, port_id = self.q_conn.get_port_by_attachment( + q_tenant_id, vif['uuid']) + if not net_id: + raise Exception(_("No network for for virtual interface %s") %\ + vif['uuid']) + (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context, + ipam_tenant_id, net_id) + v4_ips = self.ipam.get_v4_ips_by_interface(context, + net_id, vif['uuid'], + project_id=ipam_tenant_id) + v6_ips = self.ipam.get_v6_ips_by_interface(context, + net_id, vif['uuid'], + project_id=ipam_tenant_id) + + quantum_net_id = v4_subnet['network_id'] or v6_subnet['network_id'] + + def ip_dict(ip, subnet): + return { + "ip": ip, + "netmask": subnet["netmask"], + "enabled": "1"} + + network_dict = { + 'cidr': v4_subnet['cidr'], + 'injected': True, + 'multi_host': False} + + info = { + 'gateway': v4_subnet['gateway'], + 'dhcp_server': v4_subnet['gateway'], + 'broadcast': v4_subnet['broadcast'], + 'mac': vif['address'], + 'vif_uuid': vif['uuid'], + 'dns': [], + 'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]} + + if v6_subnet['cidr']: + network_dict['cidr_v6'] = v6_subnet['cidr'] + info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] + # TODO(tr3buchet): handle ip6 routes here as well + if v6_subnet['gateway']: + info['gateway6'] = v6_subnet['gateway'] + + dns_dict = {} + for s in [v4_subnet, v6_subnet]: + for k in ['dns1', 'dns2']: + if s[k]: + dns_dict[s[k]] = None + info['dns'] = [d for d in dns_dict.keys()] + + network_info.append((network_dict, info)) + return network_info + + def deallocate_for_instance(self, context, **kwargs): + instance_id = kwargs.get('instance_id') + project_id = kwargs.pop('project_id', None) + + admin_context = context.elevated() + vifs = db.virtual_interface_get_by_instance(admin_context, + instance_id) + for vif_ref in vifs: + interface_id = vif_ref['uuid'] + q_tenant_id = project_id + ipam_tenant_id = project_id + (net_id, port_id) = self.q_conn.get_port_by_attachment(q_tenant_id, + interface_id) + if not net_id: + q_tenant_id = FLAGS.quantum_default_tenant_id + ipam_tenant_id = None + (net_id, port_id) = self.q_conn.get_port_by_attachment( + q_tenant_id, interface_id) + if not net_id: + LOG.error("Unable to find port with attachment: %s" % \ + (interface_id)) + continue + self.q_conn.detach_and_delete_port(q_tenant_id, + net_id, port_id) + + self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, + net_id, vif_ref) + + self.net_manager.db.virtual_interface_delete_by_instance(admin_context, + instance_id) + self._do_trigger_security_group_members_refresh_for_instance( + instance_id) + + # validates that this tenant has quantum networks with the associated + # UUIDs. This is called by the 'os-create-server-ext' API extension + # code so that we can return an API error code to the caller if they + # request an invalid network. + def validate_networks(self, context, networks): + if networks is None: + return + + project_id = context.project_id + for (net_id, _i) in networks: + self.ipam.verify_subnet_exists(context, project_id, net_id) + if not self.q_conn.network_exists(project_id, net_id): + raise exception.NetworkNotFound(network_id=net_id) diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py new file mode 100644 index 000000000..b3955138d --- /dev/null +++ b/nova/network/quantum/melange_connection.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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 httplib +import socket +import urllib +import json +from nova import flags + + +FLAGS = flags.FLAGS + +flags.DEFINE_string('melange_host', + '127.0.0.1', + 'HOST for connecting to melange') + +flags.DEFINE_string('melange_port', + '9898', + 'PORT for connecting to melange') + +json_content_type = {'Content-type': "application/json"} + + +class MelangeConnection(object): + + def __init__(self, host=None, port=None, use_ssl=False): + if host is None: + host = FLAGS.melange_host + if port is None: + port = int(FLAGS.melange_port) + self.host = host + self.port = port + self.use_ssl = use_ssl + + def get(self, path, params={}, headers={}): + return self.do_request("GET", path, params=params, headers=headers) + + def post(self, path, body=None, headers={}): + return self.do_request("POST", path, body=body, headers=headers) + + def delete(self, path, headers={}): + return self.do_request("DELETE", path, headers=headers) + + def _get_connection(self): + if self.use_ssl: + return httplib.HTTPSConnection(self.host, self.port) + else: + return httplib.HTTPConnection(self.host, self.port) + + def do_request(self, method, path, body=None, headers={}, params={}): + + url = path + '.json?' + urllib.urlencode(params) + + try: + connection = self._get_connection() + connection.request(method, url, body, headers) + response = connection.getresponse() + response_str = response.read() + if response.status < 400: + return response_str + raise Exception("Server returned error: %s", response_str) + except (socket.error, IOError), e: + raise Exception("Unable to connect to " + "server. Got error: %s" % e) + + def allocate_ip(self, network_id, vif_id, + project_id=None, mac_address=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + request_body = (json.dumps(dict(network=dict(mac_address=mac_address, + tenant_id=project_id))) + if mac_address else None) + url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + "interfaces/%(vif_id)s/ip_allocations" % locals()) + response = self.post(url, body=request_body, + headers=json_content_type) + return json.loads(response)['ip_addresses'] + + def create_block(self, network_id, cidr, + project_id=None, dns1=None, dns2=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + + req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id, + type='private', dns1=dns1, dns2=dns2)) + self.post(url, body=json.dumps(req_params), + headers=json_content_type) + + def delete_block(self, block_id, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = "/ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals() + + self.delete(url, headers=json_content_type) + + def get_blocks(self, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + + response = self.get(url, headers=json_content_type) + return json.loads(response) + + def get_allocated_ips(self, network_id, vif_id, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + "interfaces/%(vif_id)s/ip_allocations" % locals()) + + response = self.get(url, headers=json_content_type) + return json.loads(response)['ip_addresses'] + + def deallocate_ips(self, network_id, vif_id, project_id=None): + tenant_scope = "/tenants/%s" % project_id if project_id else "" + + url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + "interfaces/%(vif_id)s/ip_allocations" % locals()) + + self.delete(url, headers=json_content_type) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py new file mode 100644 index 000000000..46038c349 --- /dev/null +++ b/nova/network/quantum/melange_ipam_lib.py @@ -0,0 +1,135 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, 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 netaddr import IPNetwork + +from nova import flags +from nova import log as logging +from nova.network.quantum import melange_connection + +LOG = logging.getLogger("quantum_melange_ipam") + +FLAGS = flags.FLAGS + + +def get_ipam_lib(net_man): + return QuantumMelangeIPAMLib() + + +class QuantumMelangeIPAMLib: + + def __init__(self): + self.m_conn = melange_connection.MelangeConnection() + + def create_subnet(self, context, label, project_id, + quantum_net_id, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + if cidr: + self.m_conn.create_block(quantum_net_id, cidr, + project_id=tenant_id, + dns1=dns1, dns2=dns2) + if cidr_v6: + self.m_conn.create_block(quantum_net_id, cidr_v6, + project_id=tenant_id, + dns1=dns1, dns2=dns2) + + def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.m_conn.allocate_ip(quantum_net_id, + vif_ref['uuid'], project_id=tenant_id, + mac_address=vif_ref['address']) + + def get_network_id_by_cidr(self, context, cidr, project_id): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + all_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_blocks['ip_blocks']: + if b['cidr'] == cidr: + return b['network_id'] + + def delete_subnets_by_net_id(self, context, net_id, project_id): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + all_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_blocks['ip_blocks']: + if b['network_id'] == net_id: + self.m_conn.delete_block(b['id'], tenant_id) + + # get all networks with this project_id, as well as all networks + # where the project-id is not set (these are shared networks) + def get_project_and_global_net_ids(self, context, project_id): + id_proj_map = {} + if not project_id: + raise Exception("get_project_and_global_net_ids must be called" \ + " with a non-null project_id") + tenant_id = project_id + all_tenant_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_tenant_blocks['ip_blocks']: + id_proj_map[b['network_id']] = tenant_id + tenant_id = FLAGS.quantum_default_tenant_id + all_provider_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_provider_blocks['ip_blocks']: + id_proj_map[b['network_id']] = tenant_id + return id_proj_map.items() + + # FIXME: there must be a more efficient way to do this, + # talk to the melange folks + def get_subnets_by_net_id(self, context, project_id, net_id): + subnet_v4 = None + subnet_v6 = None + tenant_id = project_id or FLAGS.quantum_default_tenant_id + all_blocks = self.m_conn.get_blocks(tenant_id) + for b in all_blocks['ip_blocks']: + if b['network_id'] == net_id: + subnet = {'network_id': b['network_id'], + 'cidr': b['cidr'], + 'gateway': b['gateway'], + 'broadcast': b['broadcast'], + 'netmask': b['netmask'], + 'dns1': b['dns1'], + 'dns2': b['dns2']} + + if IPNetwork(b['cidr']).version == 6: + subnet_v6 = subnet + else: + subnet_v4 = subnet + return (subnet_v4, subnet_v6) + + def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + return self.get_ips_by_interface(context, net_id, vif_id, + project_id, 4) + + def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + return self.get_ips_by_interface(context, net_id, vif_id, + project_id, 6) + + def get_ips_by_interface(self, context, net_id, vif_id, project_id, + ip_version): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) + return [ip['address'] for ip in ip_list \ + if IPNetwork(ip['address']).version == ip_version] + + def verify_subnet_exists(self, context, project_id, quantum_net_id): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id, + quantum_net_id) + return v4_subnet is not None + + def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref): + tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py new file mode 100644 index 000000000..0fc74fa49 --- /dev/null +++ b/nova/network/quantum/nova_ipam_lib.py @@ -0,0 +1,152 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, 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 math + +#from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import ipv6 +from nova import log as logging +from nova import utils +from nova.network import manager +from nova.network.quantum import melange_connection as melange + +LOG = logging.getLogger("quantum_nova_ipam_lib") + +FLAGS = flags.FLAGS + + +def get_ipam_lib(net_man): + return QuantumNovaIPAMLib(net_man) + + +class QuantumNovaIPAMLib: + + def __init__(self, net_manager): + self.net_manager = net_manager + + def create_subnet(self, context, label, tenant_id, + quantum_net_id, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): + print "creating subnet %s" % cidr + admin_context = context.elevated() + subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + manager.FlatManager.create_networks(self.net_manager, + admin_context, label, cidr, + False, 1, subnet_size, cidr_v6, + gateway_v6, quantum_net_id, None, dns1, dns2) + + # now grab the network and update project_id + network = db.network_get_by_bridge(admin_context, quantum_net_id) + net = {"project_id": tenant_id} + db.network_update(admin_context, network['id'], net) + + def get_network_id_by_cidr(self, context, cidr, project_id): + admin_context = context.elevated() + network = db.network_get_by_cidr(admin_context, cidr) + if not network: + raise Exception("No network with fixed_range = %s" \ + % fixed_range) + return network['bridge'] + + def delete_subnets_by_net_id(self, context, net_id, project_id): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, net_id) + if not network: + raise Exception("No network with net_id = %s" % net_id) + manager.FlatManager.delete_network(self.net_manager, + admin_context, network['cidr'], + require_disassociated=False) + + def get_project_and_global_net_ids(self, context, project_id): + + # get all networks with this project_id, as well as all networks + # where the project-id is not set (these are shared networks) + admin_context = context.elevated() + networks = db.project_get_networks(admin_context, project_id, False) + networks.extend(db.project_get_networks(admin_context, None, False)) + return [(n['bridge'], n['project_id']) for n in networks] + + def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, quantum_net_id) + if network['cidr']: + address = db.fixed_ip_associate_pool(admin_context, + network['id'], + vif_rec['instance_id']) + values = {'allocated': True, + 'virtual_interface_id': vif_rec['id']} + db.fixed_ip_update(admin_context, address, values) + + def get_subnets_by_net_id(self, context, tenant_id, net_id): + n = db.network_get_by_bridge(context.elevated(), net_id) + subnet_data_v4 = { + 'network_id': n['bridge'], + 'cidr': n['cidr'], + 'gateway': n['gateway'], + 'broadcast': n['broadcast'], + 'netmask': n['netmask'], + 'dns1': n['dns1'], + 'dns2': n['dns2'] + } + subnet_data_v6 = { + 'network_id': n['bridge'], + 'cidr': n['cidr_v6'], + 'gateway': n['gateway_v6'], + 'broadcast': None, + 'netmask': None, + 'dns1': None, + 'dns2': None + } + return (subnet_data_v4, subnet_data_v6) + + def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) + fixed_ips = db.fixed_ip_get_by_virtual_interface(context, + vif_rec['id']) + return [f['address'] for f in fixed_ips] + + def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, net_id) + vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) + if network['cidr_v6']: + ip = ipv6.to_global(network['cidr_v6'], + vif_rec['address'], + project_id) + return [ip] + return [] + + def verify_subnet_exists(self, context, tenant_id, quantum_net_id): + admin_context = context.elevated() + network = db.network_get_by_bridge(admin_context, quantum_net_id) + + def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): + try: + admin_context = context.elevated() + fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context, + vif_ref['id']) + for f in fixed_ips: + db.fixed_ip_update(admin_context, f['address'], + {'allocated': False, + 'virtual_interface_id': None}) + except exception.FixedIpNotFoundForInstance: + LOG.error(_('Failed to deallocate fixed IP for vif %s' % \ + vif_ref['id'])) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py new file mode 100644 index 000000000..3aa017bcd --- /dev/null +++ b/nova/network/quantum/quantum_connection.py @@ -0,0 +1,97 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks +# 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 nova import flags +from nova import log as logging +from nova import utils + +from nova.network.quantum.client import Client + +LOG = logging.getLogger("nova.network.quantum") +FLAGS = flags.FLAGS + +flags.DEFINE_string('quantum_connection_host', + '127.0.0.1', + 'HOST for connecting to quantum') + +flags.DEFINE_string('quantum_connection_port', + '9696', + 'PORT for connecting to quantum') + +flags.DEFINE_string('quantum_default_tenant_id', + "default", + 'Default tenant id when creating quantum networks') + + +class QuantumClientConnection: + + def __init__(self): + self.client = Client(FLAGS.quantum_connection_host, + FLAGS.quantum_connection_port, + format="json", + logger=LOG) + + def create_network(self, tenant_id, network_name): + data = {'network': {'net-name': network_name}} + resdict = self.client.create_network(data, tenant=tenant_id) + return resdict["networks"]["network"]["id"] + + def delete_network(self, tenant_id, net_id): + self.client.delete_network(net_id, tenant=tenant_id) + + def network_exists(self, tenant_id, net_id): + try: + self.client.show_network_details(net_id, tenant=tenant_id) + except: + # FIXME: client lib should expose more granular exceptions + # so we can confirm we're getting a 404 and not some other error + return False + return True + + def create_and_attach_port(self, tenant_id, net_id, interface_id): + LOG.debug("Connecting interface %s to net %s for %s" % \ + (interface_id, net_id, tenant_id)) + port_data = {'port': {'port-state': 'ACTIVE'}} + resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) + port_id = resdict["ports"]["port"]["id"] + + attach_data = {'port': {'attachment-id': interface_id}} + self.client.attach_resource(net_id, port_id, attach_data, + tenant=tenant_id) + + def detach_and_delete_port(self, tenant_id, net_id, port_id): + LOG.debug("Deleteing port %s on net %s for %s" % \ + (port_id, net_id, tenant_id)) + + self.client.detach_resource(net_id, port_id, tenant=tenant_id) + self.client.delete_port(net_id, port_id, tenant=tenant_id) + + # FIXME: this will be inefficient until API implements querying + def get_port_by_attachment(self, tenant_id, attachment_id): + + net_list_resdict = self.client.list_networks(tenant=tenant_id) + for n in net_list_resdict["networks"]: + net_id = n['id'] + port_list_resdict = self.client.list_ports(net_id, + tenant=tenant_id) + for p in port_list_resdict["ports"]: + port_id = p["id"] + port_get_resdict = self.client.show_port_attachment(net_id, + port_id, tenant=tenant_id) + if attachment_id == port_get_resdict["attachment"]: + return (net_id, port_id) + return (None, None) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py new file mode 100644 index 000000000..378beb6ed --- /dev/null +++ b/nova/tests/test_quantum.py @@ -0,0 +1,261 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira, 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 nova import context +from nova import db +from nova import exception +from nova import log as logging +from nova import test +from nova.network.quantum import manager as quantum_manager + +LOG = logging.getLogger('nova.tests.quantum_network') + +networks = [{'label': 'project1-net1', + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.0.0/24', + 'cidr_v6': '2001:1db8::/64', + 'gateway_v6': '2001:1db8::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '192.168.0.1', + 'broadcast': '192.168.0.255', + 'dns1': '192.168.0.1', + 'dns2': '192.168.0.2', + 'vlan': None, + 'host': None, + 'vpn_public_address': None, + 'project_id': 'fake_project1'}, + {'label': 'project2-net1', + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.1.0/24', + 'cidr_v6': '2001:1db9::/64', + 'gateway_v6': '2001:1db9::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '192.168.1.1', + 'broadcast': '192.168.1.255', + 'dns1': '192.168.0.1', + 'dns2': '192.168.0.2', + 'vlan': None, + 'host': None, + 'project_id': 'fake_project2', + 'vpn_public_address': '192.168.1.2'}, + {'label': "public", + 'injected': False, + 'multi_host': False, + 'cidr': '10.0.0.0/24', + 'cidr_v6': '2001:1dba::/64', + 'gateway_v6': '2001:1dba::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '10.0.0.1', + 'broadcast': '10.0.0.255', + 'dns1': '10.0.0.1', + 'dns2': '10.0.0.2', + 'vlan': None, + 'host': None, + 'vpn_public_address': None, + 'project_id': None}, + {'label': "project2-net2", + 'injected': False, + 'multi_host': False, + 'cidr': '9.0.0.0/24', + 'cidr_v6': '2001:1dbb::/64', + 'gateway_v6': '2001:1dbb::1', + 'netmask_v6': '64', + 'netmask': '255.255.255.0', + 'bridge': None, + 'bridge_interface': None, + 'gateway': '9.0.0.1', + 'broadcast': '9.0.0.255', + 'dns1': '9.0.0.1', + 'dns2': '9.0.0.2', + 'vlan': None, + 'host': None, + 'vpn_public_address': None, + 'project_id': "fake_project2"}] + + +# this is a base class to be used by all other Quantum Test classes +class QuantumTestCaseBase(object): + + def test_create_and_delete_nets(self): + self._create_nets() + self._delete_nets() + + def _create_nets(self): + for n in networks: + ctx = context.RequestContext('user1', n['project_id']) + self.net_man.create_networks(ctx, + label=n['label'], cidr=n['cidr'], + multi_host=n['multi_host'], + num_networks=1, network_size=256, cidr_v6=n['cidr_v6'], + gateway_v6=n['gateway_v6'], bridge=None, + bridge_interface=None, dns1=n['dns1'], + dns2=n['dns2'], project_id=n['project_id']) + + def _delete_nets(self): + for n in networks: + ctx = context.RequestContext('user1', n['project_id']) + self.net_man.delete_network(ctx, n['cidr']) + + def test_allocate_and_deallocate_instance_static(self): + self._create_nets() + + project_id = "fake_project1" + ctx = context.RequestContext('user1', project_id) + + instance_ref = db.api.instance_create(ctx, {}) + nw_info = self.net_man.allocate_for_instance(ctx, + instance_id=instance_ref['id'], host="", + instance_type_id=instance_ref['instance_type_id'], + project_id=project_id) + + self.assertEquals(len(nw_info), 2) + + # we don't know which order the NICs will be in until we + # introduce the notion of priority + # v4 cidr + self.assertTrue(nw_info[0][0]['cidr'].startswith("10.") or \ + nw_info[1][0]['cidr'].startswith("10.")) + self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ + nw_info[1][0]['cidr'].startswith("192.")) + + # v4 address + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("10.")) + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("192.")) + + # v6 cidr + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1dba:")) + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db8:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) + + # v6 address + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db8:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:")) + + self.net_man.deallocate_for_instance(ctx, + instance_id=instance_ref['id'], + project_id=project_id) + + self._delete_nets() + + def test_allocate_and_deallocate_instance_dynamic(self): + self._create_nets() + project_id = "fake_project2" + ctx = context.RequestContext('user1', project_id) + + net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) + requested_networks = [(net_id, None) for net_id in net_ids] + + self.net_man.validate_networks(ctx, requested_networks) + + instance_ref = db.api.instance_create(ctx, {}) + nw_info = self.net_man.allocate_for_instance(ctx, + instance_id=instance_ref['id'], host="", + instance_type_id=instance_ref['instance_type_id'], + project_id=project_id, + requested_networks=requested_networks) + + self.assertEquals(len(nw_info), 2) + + # we don't know which order the NICs will be in until we + # introduce the notion of priority + # v4 cidr + self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or \ + nw_info[1][0]['cidr'].startswith("9.")) + self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ + nw_info[1][0]['cidr'].startswith("192.")) + + # v4 address + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("9.")) + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ + nw_info[1][1]['ips'][0]['ip'].startswith("192.")) + + # v6 cidr + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1dbb:")) + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or \ + nw_info[1][0]['cidr_v6'].startswith("2001:1db9:")) + + # v6 address + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:")) + self.assertTrue(\ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or \ + nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:")) + + self.net_man.deallocate_for_instance(ctx, + instance_id=instance_ref['id'], + project_id=project_id) + + self._delete_nets() + + def test_validate_bad_network(self): + ctx = context.RequestContext('user1', 'fake_project1') + self.assertRaises(exception.NetworkNotFound, + self.net_man.validate_networks, ctx, [("", None)]) + + +class QuantumFakeIPAMTestCase(QuantumTestCaseBase, test.TestCase): + + def setUp(self): + super(QuantumFakeIPAMTestCase, self).setUp() + self.net_man = quantum_manager.QuantumManager( \ + ipam_lib="nova.network.quantum.fake") + + +class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): + + def setUp(self): + super(QuantumNovaIPAMTestCase, self).setUp() + self.net_man = quantum_manager.QuantumManager( \ + ipam_lib="nova.network.quantum.nova_ipam_lib") + + # tests seem to create some networks by default, which + # don't want. So we delete them. + + ctx = context.RequestContext('user1', 'fake_project1').elevated() + for n in db.network_get_all(ctx): + db.network_delete_safe(ctx, n['id']) + +# Cannot run this unit tests auotmatically for now, as it requires +# melange to be running locally. +# +#class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase): +# +# def setUp(self): +# super(QuantumMelangeIPAMTestCase, self).setUp() +# self.net_man = quantum_manager.QuantumManager( \ +# ipam_lib="nova.network.quantum.melange_ipam_lib") -- cgit From c96a9ae1b84ee370ff5d8282a8e0531a97c5a396 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Tue, 23 Aug 2011 21:18:47 -0700 Subject: Address code review feedback from Rick and Matt --- bin/nova-manage | 11 ++-- nova/network/manager.py | 2 +- nova/network/quantum/client.py | 63 +++++++++++---------- nova/network/quantum/fake.py | 19 +++---- nova/network/quantum/manager.py | 35 ++++++------ nova/network/quantum/melange_connection.py | 6 +- nova/network/quantum/melange_ipam_lib.py | 90 +++++++++++++++--------------- nova/network/quantum/nova_ipam_lib.py | 39 ++++++------- nova/network/quantum/quantum_connection.py | 10 ++-- nova/tests/test_quantum.py | 18 +++--- 10 files changed, 146 insertions(+), 147 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 051079ef3..3a17818b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -804,11 +804,11 @@ class NetworkCommands(object): def quantum_list(self): """List all created networks with Quantum-relevant fields""" _fmt = "%-32s\t%-10s\t%-10s\t%s , %s" - print _fmt % ( _('uuid'), - _('project'), - _('priority'), - _('cidr_v4'), - _('cidr_v6')) + print _fmt % (_('uuid'), + _('project'), + _('priority'), + _('cidr_v4'), + _('cidr_v6')) for network in db.network_get_all(context.get_admin_context()): print _fmt % (network.uuid, network.project_id, @@ -825,7 +825,6 @@ class NetworkCommands(object): net_manager = utils.import_object(FLAGS.network_manager) net_manager.delete_network(context.get_admin_context(), fixed_range) - @args('--network', dest="fixed_range", metavar='', help='Network to modify') @args('--project', dest="project", metavar='', diff --git a/nova/network/manager.py b/nova/network/manager.py index b625e7823..426ff2f33 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -547,7 +547,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'network_id': network_id, 'uuid': str(utils.gen_uuid())} # try FLAG times to create a vif record with a unique mac_address - for i in range(FLAGS.create_unique_mac_address_attempts): + for i in xrange(FLAGS.create_unique_mac_address_attempts): try: return self.db.virtual_interface_create(context, vif) except exception.VirtualInterfaceCreateException: diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 613369c7d..1927015c2 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -22,11 +22,12 @@ import socket import urllib -# this is a simple json-only serializer to use until -# we can just grab the standard serializer -# from the quantum library -class Serializer: - +class JSONSerializer(object): +""" +This is a simple json-only serializer to use until we can just grab +the standard serializer from the quantum library. +TODO(danwent): replace serializer with quantum implementation +""" def serialize(self, data, content_type): try: return json.dumps(data) @@ -40,12 +41,12 @@ class Serializer: class api_call(object): """A Decorator to add support for format and tenant overriding""" - def __init__(self, f): - self.f = f + def __init__(self, func): + self.func = func def __get__(self, instance, owner): def with_params(*args, **kwargs): - # Temporarily set format and tenant for this request + """Temporarily set format and tenant for this request""" (format, tenant) = (instance.format, instance.tenant) if 'format' in kwargs: @@ -53,14 +54,16 @@ class api_call(object): if 'tenant' in kwargs: instance.tenant = kwargs['tenant'] - ret = self.f(instance, *args) - (instance.format, instance.tenant) = (format, tenant) + ret = None + try: + ret = self.func(instance, *args) + finally: + (instance.format, instance.tenant) = (format, tenant) return ret return with_params class Client(object): - """A base client class - derived from Glance.BaseClient""" action_prefix = '/v1.0/tenants/{tenant_id}' @@ -73,8 +76,8 @@ class Client(object): attachment_path = "/networks/%s/ports/%s/attachment" def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, - format="xml", testingStub=None, key_file=None, cert_file=None, - logger=None): + format="xml", testing_stub=None, key_file=None, cert_file=None, + logger=None): """ Creates a new client to some service. @@ -83,7 +86,7 @@ class Client(object): :param use_ssl: True to use SSL, False to use HTTP :param tenant: The tenant ID to make requests with :param format: The format to query the server with - :param testingStub: A class that stubs basic server methods for tests + :param testing_stub: A class that stubs basic server methods for tests :param key_file: The SSL key file to use if use_ssl is true :param cert_file: The SSL cert file to use if use_ssl is true """ @@ -93,7 +96,7 @@ class Client(object): self.tenant = tenant self.format = format self.connection = None - self.testingStub = testingStub + self.testing_stub = testing_stub self.key_file = key_file self.cert_file = cert_file self.logger = logger @@ -102,9 +105,9 @@ class Client(object): """ Returns the proper connection type """ - if self.testingStub: - return self.testingStub - if self.use_ssl: + if self.testing_stub: + return self.testing_stub + elif self.use_ssl: return httplib.HTTPSConnection else: return httplib.HTTPConnection @@ -126,7 +129,7 @@ class Client(object): # Ensure we have a tenant id if not self.tenant: - raise Exception("Tenant ID not set") + raise Exception(_("Tenant ID not set")) # Add format and tenant_id action += ".%s" % self.format @@ -151,8 +154,8 @@ class Client(object): c = connection_type(self.host, self.port) if self.logger: - self.logger.debug("Quantum Client Request:\n" \ - + method + " " + action + "\n") + self.logger.debug(_("Quantum Client Request:\n%s %s\n" % + (method, action))) if body: self.logger.debug(body) @@ -169,14 +172,14 @@ class Client(object): httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - if data is not None and len(data): + if not data: return self.deserialize(data, status_code) else: - raise Exception("Server returned error: %s" % res.read()) + raise Exception(_("Server returned error: %s" % res.read())) except (socket.error, IOError), e: - raise Exception("Unable to connect to " - "server. Got error: %s" % e) + raise Exception(_("Unable to connect to " + "server. Got error: %s" % e)) def get_status_code(self, response): """ @@ -189,18 +192,18 @@ class Client(object): return response.status def serialize(self, data): - if data is None: + if not data: return None elif type(data) is dict: - return Serializer().serialize(data, self.content_type()) + return JSONSerializer().serialize(data, self.content_type()) else: - raise Exception("unable to deserialize object of type = '%s'" \ - % type(data)) + raise Exception(_("unable to deserialize object of type = '%s'" % + type(data))) def deserialize(self, data, status_code): if status_code == 202: return data - return Serializer().deserialize(data, self.content_type()) + return JSONSerializer().deserialize(data, self.content_type()) def content_type(self, format=None): if not format: diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index 00cdd8e08..f668edfed 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -15,9 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import math -from netaddr import IPNetwork - from nova import exception from nova import ipv6 from nova import log as logging @@ -29,7 +26,7 @@ LOG = logging.getLogger("network.quantum.fake") # this class can be used for unit functional/testing on nova, # as it does not actually make remote calls to the Quantum service -class FakeQuantumClientConnection: +class FakeQuantumClientConnection(object): def __init__(self): self.nets = {} @@ -56,20 +53,20 @@ class FakeQuantumClientConnection: def network_exists(self, tenant_id, net_id): try: return self.nets[net_id]['tenant-id'] == tenant_id - except: + except KeyError: return False def _confirm_not_attached(self, interface_id): for n in self.nets.values(): for p in n['ports'].values(): if p['attachment-id'] == interface_id: - raise Exception("interface '%s' is already attached" %\ - interface_id) + raise Exception(_("interface '%s' is already attached" % + interface_id)) def create_and_attach_port(self, tenant_id, net_id, interface_id): if not self.network_exists(tenant_id, net_id): - raise Exception("network %s does not exist for tenant %s" %\ - (net_id, tenant_id)) + raise Exception(_("network %s does not exist for tenant %s" % + (net_id, tenant_id))) self._confirm_not_attached(interface_id) uuid = str(utils.gen_uuid()) @@ -79,8 +76,8 @@ class FakeQuantumClientConnection: def detach_and_delete_port(self, tenant_id, net_id, port_id): if not self.network_exists(tenant_id, net_id): - raise Exception("network %s does not exist for tenant %s" %\ - (net_id, tenant_id)) + raise Exception(_("network %s does not exist for tenant %s" %\ + (net_id, tenant_id))) del self.nets[net_id]['ports'][port_id] def get_port_by_attachment(self, tenant_id, attachment_id): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index a002a3d7b..975598324 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -84,16 +84,15 @@ class QuantumManager(manager.FlatManager): In both cases, we initialize a subnet using the IPAM lib. """ if num_networks != 1: - raise Exception("QuantumManager requires that only one" - " network is created per call") - q_tenant_id = kwargs["project_id"] or \ - FLAGS.quantum_default_tenant_id + raise Exception(_("QuantumManager requires that only one" + " network is created per call")) + q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): - raise Exception("Unable to find existing quantum " \ - " network for tenant '%s' with net-id '%s'" % \ - (q_tenant_id, quantum_net_id)) + raise Exception(_("Unable to find existing quantum " \ + " network for tenant '%s' with net-id '%s'" % \ + (q_tenant_id, quantum_net_id))) else: # otherwise, create network from default quantum pool quantum_net_id = self.q_conn.create_network(q_tenant_id, label) @@ -156,18 +155,18 @@ class QuantumManager(manager.FlatManager): # Create a port via quantum and attach the vif for (quantum_net_id, project_id) in net_proj_pairs: - # FIXME: (danwent). We'd like to have the manager be completely - # decoupled from the nova networks table. - # However, other parts of nova sometimes go behind - # our back and access network data directly from the DB. So + # FIXME(danwent): We'd like to have the manager be + # completely decoupled from the nova networks table. + # However, other parts of nova sometimes go behind our + # back and access network data directly from the DB. So # for now, the quantum manager knows that there is a nova - # networks DB table and accesses it here. - # updating the virtual_interfaces table to use UUIDs would - # be one solution, but this would require significant work + # networks DB table and accesses it here. updating the + # virtual_interfaces table to use UUIDs would be one + # solution, but this would require significant work # elsewhere. admin_context = context.elevated() network_ref = db.network_get_by_uuid(admin_context, - quantum_net_id) + quantum_net_id) vif_rec = manager.FlatManager.add_virtual_interface(self, context, instance_id, network_ref['id']) @@ -177,7 +176,7 @@ class QuantumManager(manager.FlatManager): self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id, vif_rec['uuid']) self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id, - vif_rec) + vif_rec) return self.get_instance_nw_info(context, instance_id, instance_type_id, host) @@ -214,8 +213,8 @@ class QuantumManager(manager.FlatManager): net_id, port_id = self.q_conn.get_port_by_attachment( q_tenant_id, vif['uuid']) if not net_id: - raise Exception(_("No network for for virtual interface %s") %\ - vif['uuid']) + raise Exception(_("No network for for virtual interface %s") % + vif['uuid']) (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context, ipam_tenant_id, net_id) v4_ips = self.ipam.get_v4_ips_by_interface(context, diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 2d884fa60..1ee0c29a2 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -73,10 +73,10 @@ class MelangeConnection(object): response_str = response.read() if response.status < 400: return response_str - raise Exception("Server returned error: %s", response_str) + raise Exception(_("Server returned error: %s", response_str)) except (socket.error, IOError), e: - raise Exception("Unable to connect to " - "server. Got error: %s" % e) + raise Exception(_("Unable to connect to " + "server. Got error: %s" % e)) def allocate_ip(self, network_id, vif_id, project_id=None, mac_address=None): diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 7b7baf281..24a7c5404 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -31,7 +31,7 @@ def get_ipam_lib(net_man): return QuantumMelangeIPAMLib() -class QuantumMelangeIPAMLib: +class QuantumMelangeIPAMLib(object): """ Implements Quantum IP Address Management (IPAM) interface using the Melange service, which is access using the Melange web services API. @@ -42,9 +42,9 @@ class QuantumMelangeIPAMLib: self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, - quantum_net_id, priority, cidr=None, - gateway_v6=None, cidr_v6=None, - dns1=None, dns2=None): + quantum_net_id, priority, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): """ Contact Melange and create a subnet for any non-NULL IPv4 or IPv6 subnets. @@ -56,25 +56,25 @@ class QuantumMelangeIPAMLib: tenant_id = project_id or FLAGS.quantum_default_tenant_id if cidr: self.m_conn.create_block(quantum_net_id, cidr, - project_id=tenant_id, - dns1=dns1, dns2=dns2) + project_id=tenant_id, + dns1=dns1, dns2=dns2) if cidr_v6: self.m_conn.create_block(quantum_net_id, cidr_v6, project_id=tenant_id, dns1=dns1, dns2=dns2) net = {"uuid": quantum_net_id, - "project_id": project_id, - "priority": priority, - "label": label} + "project_id": project_id, + "priority": priority, + "label": label} network = self.db.network_create_safe(context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): """ Pass call to allocate fixed IP on to Melange""" tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, - vif_ref['uuid'], project_id=tenant_id, - mac_address=vif_ref['address']) + vif_ref['uuid'], project_id=tenant_id, + mac_address=vif_ref['address']) def get_network_id_by_cidr(self, context, cidr, project_id): """ Find the Quantum UUID associated with a IPv4 CIDR @@ -85,7 +85,7 @@ class QuantumMelangeIPAMLib: for b in all_blocks['ip_blocks']: if b['cidr'] == cidr: return b['network_id'] - raise Exception("No network found for cidr %s" % cidr) + raise Exception(_("No network found for cidr %s" % cidr)) def delete_subnets_by_net_id(self, context, net_id, project_id): """ Find Melange block associated with the Quantum UUID, @@ -107,38 +107,38 @@ class QuantumMelangeIPAMLib: that are "global" (i.e., have no project set). Returns list sorted by 'priority'. """ - admin_context = context.elevated() - id_proj_map = {} if project_id is None: - raise Exception("get_project_and_global_net_ids must be called" \ - " with a non-null project_id") - tenant_id = project_id - all_tenant_blocks = self.m_conn.get_blocks(tenant_id) - for b in all_tenant_blocks['ip_blocks']: - id_proj_map[b['network_id']] = tenant_id - tenant_id = FLAGS.quantum_default_tenant_id - all_provider_blocks = self.m_conn.get_blocks(tenant_id) - for b in all_provider_blocks['ip_blocks']: - id_proj_map[b['network_id']] = tenant_id - - id_priority_map = {} - for net_id, project_id in id_project_map.item(): - network = db.network_get_by_uuid(admin_context, net_id) - if network is None: - del id_proj_map[net_id] - else: - id_priority_map[net_id] = network['priority'] - return sorted(id_priority_map.items(), - key=lambda x: id_priority_map[x[0]]) + raise Exception(_("get_project_and_global_net_ids must be called" + " with a non-null project_id")) + + admin_context = context.elevated() + + # Decorate with priority + priority_nets = [] + for tenant_id in (project_id, FLAGS.quantum_default_tenant_id): + blocks = self.m_conn.get_blocks(tenant_id) + for ip_block in blocks['ip_blocks']: + network_id = ip_block['network_id'] + network = db.network_get_by_uuid(admin_context, network_id) + if network: + priority = network['priority'] + priority_nets.append((priority, network_id, tenant_id)) + + # Sort by priority + priority_nets.sort() + + # Undecorate + return [(network_id, tenant_id) + for priority, network_id, tenant_id in priority_nets] def get_subnets_by_net_id(self, context, project_id, net_id): """ Returns information about the IPv4 and IPv6 subnets associated with a Quantum Network UUID. """ - # FIXME: (danwent) Melange actually returns the subnet info - # when we query for a particular interface. we may want to - # reworks the ipam_manager python API to let us take advantage of + # FIXME(danwent): Melange actually returns the subnet info + # when we query for a particular interface. We may want to + # rework the ipam_manager python API to let us take advantage of # this, as right now we have to get all blocks and cycle through # them. subnet_v4 = None @@ -148,12 +148,12 @@ class QuantumMelangeIPAMLib: for b in all_blocks['ip_blocks']: if b['network_id'] == net_id: subnet = {'network_id': b['network_id'], - 'cidr': b['cidr'], - 'gateway': b['gateway'], - 'broadcast': b['broadcast'], - 'netmask': b['netmask'], - 'dns1': b['dns1'], - 'dns2': b['dns2']} + 'cidr': b['cidr'], + 'gateway': b['gateway'], + 'broadcast': b['broadcast'], + 'netmask': b['netmask'], + 'dns1': b['dns1'], + 'dns2': b['dns2']} if IPNetwork(b['cidr']).version == 6: subnet_v6 = subnet @@ -182,8 +182,8 @@ class QuantumMelangeIPAMLib: """ tenant_id = project_id or FLAGS.quantum_default_tenant_id ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) - return [ip['address'] for ip in ip_list \ - if IPNetwork(ip['address']).version == ip_version] + return [ip['address'] for ip in ip_list + if IPNetwork(ip['address']).version == ip_version] def verify_subnet_exists(self, context, project_id, quantum_net_id): """ Confirms that a subnet exists that is associated with the diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index ce7d3efcb..71e723cb9 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -36,7 +36,7 @@ def get_ipam_lib(net_man): return QuantumNovaIPAMLib(net_man) -class QuantumNovaIPAMLib: +class QuantumNovaIPAMLib(object): """ Implements Quantum IP Address Management (IPAM) interface using the local Nova database. This implementation is inline with how IPAM is used by other NetworkManagers. @@ -50,9 +50,9 @@ class QuantumNovaIPAMLib: self.net_manager = net_manager def create_subnet(self, context, label, tenant_id, - quantum_net_id, priority, cidr=None, - gateway_v6=None, cidr_v6=None, - dns1=None, dns2=None): + quantum_net_id, priority, cidr=None, + gateway_v6=None, cidr_v6=None, + dns1=None, dns2=None): """ Re-use the basic FlatManager create_networks method to initialize the networks and fixed_ips tables in Nova DB. @@ -60,6 +60,7 @@ class QuantumNovaIPAMLib: are needed by Quantum but not the FlatManager. """ admin_context = context.elevated() + # FIXME(danwent): Use the netaddr library here subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, @@ -67,12 +68,12 @@ class QuantumNovaIPAMLib: gateway_v6, quantum_net_id, None, dns1, dns2) if len(networks) != 1: - raise Exception("Error creating network entry") + raise Exception(_("Error creating network entry")) network = networks[0] net = {"project_id": tenant_id, - "priority": priority, - "uuid": quantum_net_id} + "priority": priority, + "uuid": quantum_net_id} db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): @@ -80,7 +81,7 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() network = db.network_get_by_cidr(admin_context, cidr) if not network: - raise Exception("No network with fixed_range = %s" % fixed_range) + raise Exception(_("No network with fixed_range = %s" % fixed_range)) return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): @@ -90,10 +91,10 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) if not network: - raise Exception("No network with net_id = %s" % net_id) + raise Exception(_("No network with net_id = %s" % net_id)) manager.FlatManager.delete_network(self.net_manager, - admin_context, network['cidr'], - require_disassociated=False) + admin_context, network['cidr'], + require_disassociated=False) def get_project_and_global_net_ids(self, context, project_id): """ Fetches all networks associated with this project, or @@ -118,8 +119,8 @@ class QuantumNovaIPAMLib: network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: address = db.fixed_ip_associate_pool(admin_context, - network['id'], - vif_rec['instance_id']) + network['id'], + vif_rec['instance_id']) values = {'allocated': True, 'virtual_interface_id': vif_rec['id']} db.fixed_ip_update(admin_context, address, values) @@ -186,10 +187,10 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context, vif_ref['id']) - for f in fixed_ips: - db.fixed_ip_update(admin_context, f['address'], - {'allocated': False, - 'virtual_interface_id': None}) + for fixed_ip in fixed_ips: + db.fixed_ip_update(admin_context, fixed_ip['address'], + {'allocated': False, + 'virtual_interface_id': None}) except exception.FixedIpNotFoundForInstance: - LOG.error(_('No fixed IPs to deallocate for vif %s' % \ - vif_ref['id'])) + LOG.error(_('No fixed IPs to deallocate for vif %s' % + vif_ref['id'])) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index bd3592c2c..e2218c68d 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -37,7 +37,7 @@ flags.DEFINE_string('quantum_default_tenant_id', 'Default tenant id when creating quantum networks') -class QuantumClientConnection: +class QuantumClientConnection(object): """ Abstracts connection to Quantum service into higher level operations performed by the QuantumManager. @@ -71,7 +71,7 @@ class QuantumClientConnection: try: self.client.show_network_details(net_id, tenant=tenant_id) except: - # FIXME: (danwent) client lib should expose granular exceptions + # FIXME(danwent): client lib should expose granular exceptions # so we can confirm we're getting a 404 and not some other error return False return True @@ -81,8 +81,8 @@ class QuantumClientConnection: status to ACTIVE to enable traffic, and attaches the vNIC with the specified interface-id. """ - LOG.debug("Connecting interface %s to net %s for %s" % \ - (interface_id, net_id, tenant_id)) + LOG.debug(_("Connecting interface %s to net %s for %s" % + (interface_id, net_id, tenant_id))) port_data = {'port': {'state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) port_id = resdict["port"]["id"] @@ -103,7 +103,7 @@ class QuantumClientConnection: """ Given a tenant, search for the Quantum network and port UUID that has the specified interface-id attachment. """ - # FIXME: (danwent) this will be inefficient until the Quantum + # FIXME(danwent): this will be inefficient until the Quantum # API implements querying a port by the interface-id net_list_resdict = self.client.list_networks(tenant=tenant_id) for n in net_list_resdict["networks"]: diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 3efdba910..2cc83adf1 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -155,9 +155,9 @@ class QuantumTestCaseBase(object): self.assertTrue(nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) # v6 address - self.assertTrue(\ + self.assertTrue( nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) - self.assertTrue(\ + self.assertTrue( nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:")) self.net_man.deallocate_for_instance(ctx, @@ -233,24 +233,24 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): self.net_man = quantum_manager.QuantumManager( \ ipam_lib="nova.network.quantum.nova_ipam_lib") - # tests seem to create some networks by default, which - # don't want. So we delete them. + # Tests seem to create some networks by default, which + # we don't want. So we delete them. ctx = context.RequestContext('user1', 'fake_project1').elevated() for n in db.network_get_all(ctx): db.network_delete_safe(ctx, n['id']) - # I've found that other unit tests have a nasty habit of - # of creating fixed IPs and not cleaning up, which can - # confuse these tests, so we clean them all. + # NOTE(danwent): I've found that other unit tests have a nasty + # habit of of creating fixed IPs and not cleaning up, which + # can confuse these tests, so we clean them all. session = get_session() result = session.query(models.FixedIp).all() with session.begin(): for fip_ref in result: session.delete(fip_ref) -# Cannot run this unit tests auotmatically for now, as it requires -# melange to be running locally. +# FIXME(danwent): Cannot run this unit tests automatically for now, as +# it requires melange to be running locally. # #class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase): # -- cgit From 3058e5eeb5e1068b7b5f721b985c735bc5486c92 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 24 Aug 2011 02:11:56 -0700 Subject: Couple of fixes to the review feedback changes --- nova/network/quantum/client.py | 12 ++++++------ nova/network/quantum/manager.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 1927015c2..b457190c1 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -23,11 +23,11 @@ import urllib class JSONSerializer(object): -""" -This is a simple json-only serializer to use until we can just grab -the standard serializer from the quantum library. -TODO(danwent): replace serializer with quantum implementation -""" + """ + This is a simple json-only serializer to use until we can just grab + the standard serializer from the quantum library. + TODO(danwent): replace serializer with quantum implementation + """ def serialize(self, data, content_type): try: return json.dumps(data) @@ -172,7 +172,7 @@ class Client(object): httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - if not data: + if data is not None and len(data): return self.deserialize(data, status_code) else: raise Exception(_("Server returned error: %s" % res.read())) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 975598324..d29d1e06b 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -86,7 +86,7 @@ class QuantumManager(manager.FlatManager): if num_networks != 1: raise Exception(_("QuantumManager requires that only one" " network is created per call")) - q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) + q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): @@ -179,7 +179,7 @@ class QuantumManager(manager.FlatManager): vif_rec) return self.get_instance_nw_info(context, instance_id, - instance_type_id, host) + instance_type_id, host) def get_instance_nw_info(self, context, instance_id, instance_type_id, host): -- cgit From 13544ec52d4f84032b7925b1dab00fbdc5ca0c79 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 24 Aug 2011 03:07:11 -0700 Subject: pep8 fixes --- nova/network/quantum/client.py | 4 ++-- nova/network/quantum/nova_ipam_lib.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index b457190c1..82f23b6b5 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -76,8 +76,8 @@ class Client(object): attachment_path = "/networks/%s/ports/%s/attachment" def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, - format="xml", testing_stub=None, key_file=None, cert_file=None, - logger=None): + format="xml", testing_stub=None, key_file=None, + cert_file=None, logger=None): """ Creates a new client to some service. diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 71e723cb9..4f62887d1 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -81,7 +81,8 @@ class QuantumNovaIPAMLib(object): admin_context = context.elevated() network = db.network_get_by_cidr(admin_context, cidr) if not network: - raise Exception(_("No network with fixed_range = %s" % fixed_range)) + raise Exception(_("No network with fixed_range = %s" % + fixed_range)) return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): -- cgit From 551b4b1b16c894551e5337663374a40aa46663d7 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 24 Aug 2011 03:14:39 -0700 Subject: Catch exception for instances that aren't there If an instance failed to spawn it will be in a shutdown state but the instance won't actually be there when we attempt to deallocate for the instance. For now we catch the exception and just log a message. Also made some minor formatting changes. --- nova/network/quantum/manager.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index d29d1e06b..c03622218 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -287,17 +287,21 @@ class QuantumManager(manager.FlatManager): (net_id, port_id) = self.q_conn.get_port_by_attachment( q_tenant_id, interface_id) if not net_id: - LOG.error("Unable to find port with attachment: %s" % \ - (interface_id)) + LOG.error("Unable to find port with attachment: %s" % + (interface_id)) continue self.q_conn.detach_and_delete_port(q_tenant_id, - net_id, port_id) + net_id, port_id) self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, - net_id, vif_ref) + net_id, vif_ref) - db.virtual_interface_delete_by_instance(admin_context, - instance_id) + try: + db.virtual_interface_delete_by_instance(admin_context, + instance_id) + except exception.InstanceNotFound: + LOG.error(_("Attempted to deallocate non-existent instance: %s" % + (instance_id))) self._do_trigger_security_group_members_refresh_for_instance( instance_id) -- cgit From 3d17d22926e2f589648fdac26302a58e8c4e9069 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 24 Aug 2011 11:36:41 -0700 Subject: fix more tests --- nova/tests/api/openstack/contrib/test_createserverext.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index e5eed14fe..01c7587e3 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -89,6 +89,9 @@ class CreateserverextTest(test.TestCase): self.networks = None return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID, + 'user_id': 'fake', + 'project_id': 'fake', + 'display_description': 'fakedescription', 'created_at': "", 'updated_at': ""}] -- cgit From 0571c86d18c242f46e44e380b257cfc40598d31b Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 24 Aug 2011 12:17:58 -0700 Subject: use dict.get for user_id, project_id, and display_description in servers view as suggested by ed leaf, so that not all tests require these fields --- nova/api/openstack/views/servers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index fd7c040d4..6fd09aae6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -65,9 +65,9 @@ class ViewBuilder(object): inst_dict = { 'id': inst['id'], 'name': inst['display_name'], - 'user_id': inst['user_id'], - 'tenant_id': inst['project_id'], - 'description': inst['display_description'], + 'user_id': inst.get('user_id', ''), + 'tenant_id': inst.get('project_id', ''), + 'description': inst.get('display_description', ''), 'status': common.status_from_power_state(inst.get('state'))} ctxt = nova.context.get_admin_context() -- cgit From 2e7447a9ca180b68b49984d57e224e7fdd6d27e2 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Thu, 25 Aug 2011 17:13:09 -0700 Subject: Minor fixes --- nova/network/quantum/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index f712a93c4..f46eaf2f3 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -59,7 +59,7 @@ class QuantumManager(manager.FlatManager): FLAGS.quantum_default_tenant_id quantum_net_id = bridge if quantum_net_id: - if not q_conn.network_exists(q_tenant_id, quantum_net_id): + if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception("Unable to find existing quantum " \ " network for tenant '%s' with net-id '%s'" % \ (q_tenant_id, quantum_net_id)) @@ -105,7 +105,7 @@ class QuantumManager(manager.FlatManager): # Create a port via quantum and attach the vif for (net_id, project_id) in net_proj_pairs: vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance, None) + context, instance_id, None) q_tenant_id = project_id or FLAGS.quantum_default_tenant_id self.q_conn.create_and_attach_port(q_tenant_id, net_id, -- cgit From 5823a72690155d9d69e4d23a81be2ea0945809dc Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 21:09:15 -0700 Subject: add priority for static networks --- nova/network/quantum/fake.py | 13 ++++++----- nova/network/quantum/manager.py | 9 ++++---- nova/network/quantum/melange_ipam_lib.py | 30 ++++++++++++++++++++++--- nova/network/quantum/nova_ipam_lib.py | 22 ++++++++++-------- nova/tests/test_quantum.py | 38 ++++++++++++++------------------ 5 files changed, 69 insertions(+), 43 deletions(-) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index ff2b1e9d5..a923bbf1a 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -102,8 +102,8 @@ class FakeQuantumIPAMLib(): self.subnets = {} def create_subnet(self, context, label, tenant_id, quantum_net_id, - cidr=None, gateway_v6=None, cidr_v6=None, - dns1=None, dns2=None): + priority, cidr=None, gateway_v6=None, + cidr_v6=None, dns1=None, dns2=None): if int(cidr.split("/")[1]) != 24: raise Exception("fake ipam_lib only supports /24s") v4_ips = [] @@ -116,6 +116,7 @@ class FakeQuantumIPAMLib(): "instance_id": None}) self.subnets[quantum_net_id] = {\ "label": label, + "priority": priority, "gateway": net_start + "1", "netmask": "255.255.255.0", "broadcast": net_start + "255", @@ -138,12 +139,14 @@ class FakeQuantumIPAMLib(): del self.subnets[net_id] def get_project_and_global_net_ids(self, context, project_id): - net_ids = [] + net_list = [] + id_priority_map = {} for nid, s in self.subnets.items(): if s['project_id'] == project_id or \ s['project_id'] == None: - net_ids.append((nid, s['project_id'])) - return net_ids + net_list.append((nid, s['project_id'])) + id_priority_map[nid] = s['priority'] + return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): subnet = self.subnets[quantum_net_id] diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index f712a93c4..2c2fd4dd7 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -59,7 +59,7 @@ class QuantumManager(manager.FlatManager): FLAGS.quantum_default_tenant_id quantum_net_id = bridge if quantum_net_id: - if not q_conn.network_exists(q_tenant_id, quantum_net_id): + if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception("Unable to find existing quantum " \ " network for tenant '%s' with net-id '%s'" % \ (q_tenant_id, quantum_net_id)) @@ -68,8 +68,9 @@ class QuantumManager(manager.FlatManager): quantum_net_id = self.q_conn.create_network(q_tenant_id, label) ipam_tenant_id = kwargs.get("project_id", None) + priority = kwargs.get("priority", 0) self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id, - cidr, gateway_v6, cidr_v6, dns1, dns2) + priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): project_id = context.project_id @@ -105,7 +106,7 @@ class QuantumManager(manager.FlatManager): # Create a port via quantum and attach the vif for (net_id, project_id) in net_proj_pairs: vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance, None) + context, instance_id, None) q_tenant_id = project_id or FLAGS.quantum_default_tenant_id self.q_conn.create_and_attach_port(q_tenant_id, net_id, @@ -212,7 +213,7 @@ class QuantumManager(manager.FlatManager): self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, net_id, vif_ref) - self.net_manager.db.virtual_interface_delete_by_instance(admin_context, + db.virtual_interface_delete_by_instance(admin_context, instance_id) self._do_trigger_security_group_members_refresh_for_instance( instance_id) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 46038c349..526be6327 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -36,7 +36,7 @@ class QuantumMelangeIPAMLib: self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, - quantum_net_id, cidr=None, + quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -49,6 +49,13 @@ class QuantumMelangeIPAMLib: project_id=tenant_id, dns1=dns1, dns2=dns2) + # create a entry in the network table just to store + # the priority order for this network + net = {"bridge": quantum_net_id, + "project_id": project_id, + "priority": priority} + network = self.db.network_create_safe(context, net) + def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, @@ -61,19 +68,26 @@ class QuantumMelangeIPAMLib: for b in all_blocks['ip_blocks']: if b['cidr'] == cidr: return b['network_id'] + raise Exception("No network found for cidr %s" % cidr) def delete_subnets_by_net_id(self, context, net_id, project_id): + admin_context = context.elevated() tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) for b in all_blocks['ip_blocks']: if b['network_id'] == net_id: self.m_conn.delete_block(b['id'], tenant_id) + network = db.network_get_by_bridge(admin_context, net_id) + if network is not None: + db.network_delete_safe(context, network['id']) + # get all networks with this project_id, as well as all networks # where the project-id is not set (these are shared networks) def get_project_and_global_net_ids(self, context, project_id): + admin_context = context.elevated() id_proj_map = {} - if not project_id: + if project_id is None: raise Exception("get_project_and_global_net_ids must be called" \ " with a non-null project_id") tenant_id = project_id @@ -84,7 +98,17 @@ class QuantumMelangeIPAMLib: all_provider_blocks = self.m_conn.get_blocks(tenant_id) for b in all_provider_blocks['ip_blocks']: id_proj_map[b['network_id']] = tenant_id - return id_proj_map.items() + + id_priority_map = {} + network = db.network_get_by_bridge(admin_context, net_id) + for net_id, project_id in id_project_map.item(): + network = db.network_get_by_bridge(admin_context, net_id) + if network is None: + del id_proj_map[net_id] + else: + id_priority_map[net_id] = network['priority'] + return sorted(id_priority_map.items(), + key=lambda x: id_priority_map[x[0]]) # FIXME: there must be a more efficient way to do this, # talk to the melange folks diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 0fc74fa49..a9a6dcb29 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -42,10 +42,9 @@ class QuantumNovaIPAMLib: self.net_manager = net_manager def create_subnet(self, context, label, tenant_id, - quantum_net_id, cidr=None, + quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - print "creating subnet %s" % cidr admin_context = context.elevated() subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) manager.FlatManager.create_networks(self.net_manager, @@ -53,9 +52,10 @@ class QuantumNovaIPAMLib: False, 1, subnet_size, cidr_v6, gateway_v6, quantum_net_id, None, dns1, dns2) - # now grab the network and update project_id + # now grab the network and update project_id + priority network = db.network_get_by_bridge(admin_context, quantum_net_id) - net = {"project_id": tenant_id} + net = {"project_id": tenant_id, + "priority": priority} db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): @@ -82,7 +82,13 @@ class QuantumNovaIPAMLib: admin_context = context.elevated() networks = db.project_get_networks(admin_context, project_id, False) networks.extend(db.project_get_networks(admin_context, None, False)) - return [(n['bridge'], n['project_id']) for n in networks] + id_priority_map = {} + net_list = [] + for n in networks: + net_id = n['bridge'] + net_list.append((net_id, n["project_id"])) + id_priority_map[net_id] = n['priority'] + return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): admin_context = context.elevated() @@ -104,8 +110,7 @@ class QuantumNovaIPAMLib: 'broadcast': n['broadcast'], 'netmask': n['netmask'], 'dns1': n['dns1'], - 'dns2': n['dns2'] - } + 'dns2': n['dns2']} subnet_data_v6 = { 'network_id': n['bridge'], 'cidr': n['cidr_v6'], @@ -113,8 +118,7 @@ class QuantumNovaIPAMLib: 'broadcast': None, 'netmask': None, 'dns1': None, - 'dns2': None - } + 'dns2': None} return (subnet_data_v4, subnet_data_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 378beb6ed..80cab950e 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -41,7 +41,8 @@ networks = [{'label': 'project1-net1', 'vlan': None, 'host': None, 'vpn_public_address': None, - 'project_id': 'fake_project1'}, + 'project_id': 'fake_project1', + 'priority': 1}, {'label': 'project2-net1', 'injected': False, 'multi_host': False, @@ -59,7 +60,7 @@ networks = [{'label': 'project1-net1', 'vlan': None, 'host': None, 'project_id': 'fake_project2', - 'vpn_public_address': '192.168.1.2'}, + 'priority': 1}, {'label': "public", 'injected': False, 'multi_host': False, @@ -76,8 +77,8 @@ networks = [{'label': 'project1-net1', 'dns2': '10.0.0.2', 'vlan': None, 'host': None, - 'vpn_public_address': None, - 'project_id': None}, + 'project_id': None, + 'priority': 0}, {'label': "project2-net2", 'injected': False, 'multi_host': False, @@ -94,8 +95,8 @@ networks = [{'label': 'project1-net1', 'dns2': '9.0.0.2', 'vlan': None, 'host': None, - 'vpn_public_address': None, - 'project_id': "fake_project2"}] + 'project_id': "fake_project2", + 'priority': 2}] # this is a base class to be used by all other Quantum Test classes @@ -114,7 +115,8 @@ class QuantumTestCaseBase(object): num_networks=1, network_size=256, cidr_v6=n['cidr_v6'], gateway_v6=n['gateway_v6'], bridge=None, bridge_interface=None, dns1=n['dns1'], - dns2=n['dns2'], project_id=n['project_id']) + dns2=n['dns2'], project_id=n['project_id'], + priority=n['priority']) def _delete_nets(self): for n in networks: @@ -138,29 +140,21 @@ class QuantumTestCaseBase(object): # we don't know which order the NICs will be in until we # introduce the notion of priority # v4 cidr - self.assertTrue(nw_info[0][0]['cidr'].startswith("10.") or \ - nw_info[1][0]['cidr'].startswith("10.")) - self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ - nw_info[1][0]['cidr'].startswith("192.")) + self.assertTrue(nw_info[0][0]['cidr'].startswith("10.")) + self.assertTrue(nw_info[1][0]['cidr'].startswith("192.")) # v4 address - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10.") or \ - nw_info[1][1]['ips'][0]['ip'].startswith("10.")) - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ - nw_info[1][1]['ips'][0]['ip'].startswith("192.")) + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10.")) + self.assertTrue(nw_info[1][1]['ips'][0]['ip'].startswith("192.")) # v6 cidr - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:") or \ - nw_info[1][0]['cidr_v6'].startswith("2001:1dba:")) - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db8:") or \ - nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:")) + self.assertTrue(nw_info[1][0]['cidr_v6'].startswith("2001:1db8:")) # v6 address self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:") or \ - nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:")) self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db8:") or \ nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:")) self.net_man.deallocate_for_instance(ctx, -- cgit From 16290908eaca73caa6b3f2ce36fb8add0b7d3615 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 22:45:24 -0700 Subject: fix for quantum api changes, change nova-mange to have quantum_list command --- bin/nova-manage | 23 +++++++++++++++++------ nova/network/quantum/client.py | 2 +- nova/network/quantum/quantum_connection.py | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 9819ef206..4628e93b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -773,7 +773,7 @@ class NetworkCommands(object): def list(self): """List all created networks""" _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\ - "\t%-15s\t%-15s\t-15s\t-15s" + "\t%-15s\t%-15s" print _fmt % (_('id'), _('IPv4'), _('IPv6'), @@ -782,9 +782,7 @@ class NetworkCommands(object): _('DNS2'), _('VlanID'), _('project'), - _("uuid"), - _('priority'), - _('bridge')) + _("uuid")) for network in db.network_get_all(context.get_admin_context()): print _fmt % (network.id, network.cidr, @@ -794,9 +792,22 @@ class NetworkCommands(object): network.dns2, network.vlan, network.project_id, - network.uuid, + network.uuid) + + def quantum_list(self): + """List all created networks with Quantum-relevant fields""" + _fmt = "%-32s\t%-10s\t%-10s\t%s , %s" + print _fmt % ( _('bridge / quantum-id'), + _('project'), + _('priority'), + _('cidr_v4'), + _('cidr_v6')) + for network in db.network_get_all(context.get_admin_context()): + print _fmt % (network.bridge, + network.project_id, network.priority, - network.bridge) + network.cidr, + network.cidr_v6) @args('--network', dest="fixed_range", metavar='', help='Network to delete') diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index a0c7dc6d8..b57294c55 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -63,7 +63,7 @@ class Client(object): """A base client class - derived from Glance.BaseClient""" - action_prefix = '/v0.1/tenants/{tenant_id}' + action_prefix = '/v1.0/tenants/{tenant_id}' """Action query strings""" networks_path = "/networks" diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 3aa017bcd..d6f749bf2 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -46,9 +46,9 @@ class QuantumClientConnection: logger=LOG) def create_network(self, tenant_id, network_name): - data = {'network': {'net-name': network_name}} + data = {'network': {'name': network_name}} resdict = self.client.create_network(data, tenant=tenant_id) - return resdict["networks"]["network"]["id"] + return resdict["network"]["id"] def delete_network(self, tenant_id, net_id): self.client.delete_network(net_id, tenant=tenant_id) -- cgit From e8d02ac1b5e9a45cc19992d232d4148f9db720ca Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 23:02:46 -0700 Subject: rearrange imports --- bin/nova-manage | 2 +- .../migrate_repo/versions/041_add_network_priority.py | 5 ++--- nova/network/quantum/fake.py | 5 +++-- nova/network/quantum/manager.py | 2 +- nova/network/quantum/melange_connection.py | 1 + nova/network/quantum/melange_ipam_lib.py | 1 + nova/network/quantum/nova_ipam_lib.py | 4 ++-- nova/network/quantum/quantum_connection.py | 10 +++++----- nova/tests/test_quantum.py | 2 +- 9 files changed, 17 insertions(+), 15 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 4628e93b2..3dac2963c 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -1,4 +1,4 @@ -#!/usr/bin/env python +!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py index e619b1fcd..e69380199 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py @@ -1,4 +1,4 @@ -# Copyright 2011 OpenStack LLC. +# Copyright 2011 Nicira, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -13,14 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime - from sqlalchemy import * from migrate import * from nova import log as logging from nova import utils + meta = MetaData() # Add priority column to networks table diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index a923bbf1a..3f6996e63 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -15,12 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. +import math +from netaddr import IPNetwork + from nova import exception from nova import ipv6 from nova import log as logging from nova import utils -import math -from netaddr import IPNetwork LOG = logging.getLogger("network.quantum.fake") diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 2c2fd4dd7..e49d0cfdc 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -20,10 +20,10 @@ from nova import exception from nova import flags from nova import log as logging from nova import manager -from nova import utils from nova.network import manager from nova.network.quantum import quantum_connection from nova.network.quantum import fake +from nova import utils LOG = logging.getLogger("quantum_manager") diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index b3955138d..2d884fa60 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -19,6 +19,7 @@ import httplib import socket import urllib import json + from nova import flags diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 526be6327..5b58520c2 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -21,6 +21,7 @@ from nova import flags from nova import log as logging from nova.network.quantum import melange_connection + LOG = logging.getLogger("quantum_melange_ipam") FLAGS = flags.FLAGS diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index a9a6dcb29..b72edeeff 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -17,15 +17,15 @@ import math -#from nova import context from nova import db from nova import exception from nova import flags from nova import ipv6 from nova import log as logging -from nova import utils from nova.network import manager from nova.network.quantum import melange_connection as melange +from nova import utils + LOG = logging.getLogger("quantum_nova_ipam_lib") diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index d6f749bf2..7a96d23fd 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -17,9 +17,9 @@ from nova import flags from nova import log as logging +from nova.network.quantum import client as quantum_client from nova import utils -from nova.network.quantum.client import Client LOG = logging.getLogger("nova.network.quantum") FLAGS = flags.FLAGS @@ -40,10 +40,10 @@ flags.DEFINE_string('quantum_default_tenant_id', class QuantumClientConnection: def __init__(self): - self.client = Client(FLAGS.quantum_connection_host, - FLAGS.quantum_connection_port, - format="json", - logger=LOG) + self.client = quantum_client.Client(FLAGS.quantum_connection_host, + FLAGS.quantum_connection_port, + format="json", + logger=LOG) def create_network(self, tenant_id, network_name): data = {'network': {'name': network_name}} diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 80cab950e..8ef22ac7e 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -19,8 +19,8 @@ from nova import context from nova import db from nova import exception from nova import log as logging -from nova import test from nova.network.quantum import manager as quantum_manager +from nova import test LOG = logging.getLogger('nova.tests.quantum_network') -- cgit From b66840327ad183619995bc9e88a0d4ea01ab0b59 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 23:08:15 -0700 Subject: replace accidental deletion in nova-mange --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 3dac2963c..4628e93b2 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -1,4 +1,4 @@ -!/usr/bin/env python +#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the -- cgit From 87a5fefe5b0e3379ef93fede0750ddd76dd3c20d Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 25 Aug 2011 23:18:38 -0700 Subject: Add brad to Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 864679929..112791cfc 100644 --- a/Authors +++ b/Authors @@ -11,6 +11,7 @@ Antony Messerli Armando Migliaccio Arvind Somya Bilal Akhtar +Brad Hall Brian Lamar Brian Schott Brian Waldon -- cgit From 3a91fa89ea23d22cd34336aa9281a439579d4ce0 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Fri, 26 Aug 2011 15:48:53 -0700 Subject: Minor changes based on recent quantum changes --- nova/network/quantum/client.py | 3 ++- nova/network/quantum/manager.py | 3 ++- nova/network/quantum/quantum_connection.py | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index b57294c55..613369c7d 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -169,7 +169,8 @@ class Client(object): httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - return self.deserialize(data, status_code) + if data is not None and len(data): + return self.deserialize(data, status_code) else: raise Exception("Server returned error: %s" % res.read()) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index e49d0cfdc..79bb65939 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -119,7 +119,8 @@ class QuantumManager(manager.FlatManager): def get_instance_nw_info(self, context, instance_id, instance_type_id, host): network_info = [] - project_id = context.project_id + instance = db.instance_get(context, instance_id) + project_id = instance.project_id admin_context = context.elevated() vifs = db.virtual_interface_get_by_instance(admin_context, diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 7a96d23fd..c0eaad1fd 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -67,9 +67,9 @@ class QuantumClientConnection: (interface_id, net_id, tenant_id)) port_data = {'port': {'port-state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) - port_id = resdict["ports"]["port"]["id"] + port_id = resdict["port"]["id"] - attach_data = {'port': {'attachment-id': interface_id}} + attach_data = {'attachment': {'id': interface_id}} self.client.attach_resource(net_id, port_id, attach_data, tenant=tenant_id) @@ -92,6 +92,6 @@ class QuantumClientConnection: port_id = p["id"] port_get_resdict = self.client.show_port_attachment(net_id, port_id, tenant=tenant_id) - if attachment_id == port_get_resdict["attachment"]: + if attachment_id == port_get_resdict["attachment"]["id"]: return (net_id, port_id) return (None, None) -- cgit From d5b489383710605b10067550417a4e62a5f4f3e1 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sun, 28 Aug 2011 11:37:19 -0700 Subject: use 'uuid' field in networks table rather than 'bridge'. Specify project_id when creating instance in unit test --- bin/nova-manage | 16 +++++++++++----- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 13 +++++++++++++ nova/exception.py | 4 ++++ nova/network/quantum/manager.py | 5 +++-- nova/network/quantum/nova_ipam_lib.py | 30 +++++++++++++++++------------- nova/tests/test_quantum.py | 6 ++++-- 7 files changed, 57 insertions(+), 22 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 4628e93b2..0c2cee3ce 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -681,6 +681,8 @@ class NetworkCommands(object): help='Multi host') @args('--dns1', dest="dns1", metavar="", help='First DNS') @args('--dns2', dest="dns2", metavar="", help='Second DNS') + @args('--uuid', dest="net_uuid", metavar="", + help='Network UUID') @args('--project_id', dest="project_id", metavar="", help='Project id') @args('--priority', dest="priority", metavar="", @@ -689,7 +691,7 @@ class NetworkCommands(object): network_size=None, multi_host=None, vlan_start=None, vpn_start=None, fixed_range_v6=None, gateway_v6=None, bridge=None, bridge_interface=None, dns1=None, dns2=None, - project_id=None, priority=None): + project_id=None, priority=None, uuid=None): """Creates fixed ips for host by range""" # check for certain required inputs @@ -768,7 +770,8 @@ class NetworkCommands(object): dns1=dns1, dns2=dns2, project_id=project_id, - priority=priority) + priority=priority, + uuid=uuid) def list(self): """List all created networks""" @@ -797,13 +800,13 @@ class NetworkCommands(object): def quantum_list(self): """List all created networks with Quantum-relevant fields""" _fmt = "%-32s\t%-10s\t%-10s\t%s , %s" - print _fmt % ( _('bridge / quantum-id'), + print _fmt % ( _('uuid'), _('project'), _('priority'), _('cidr_v4'), _('cidr_v6')) for network in db.network_get_all(context.get_admin_context()): - print _fmt % (network.bridge, + print _fmt % (network.uuid, network.project_id, network.priority, network.cidr, @@ -811,12 +814,15 @@ class NetworkCommands(object): @args('--network', dest="fixed_range", metavar='', help='Network to delete') + @args('--uuid', dest="uuid", metavar='', + help='UUID of network to delete') def delete(self, fixed_range): """Deletes a network""" # delete the network net_manager = utils.import_object(FLAGS.network_manager) - net_manager.delete_network(context.get_admin_context(), fixed_range) + net_manager.delete_network(context.get_admin_context(), fixed_range, + uuid=None) diff --git a/nova/db/api.py b/nova/db/api.py index 9ff3a1c74..17ef0bd0b 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -708,6 +708,11 @@ def network_get_by_bridge(context, bridge): return IMPL.network_get_by_bridge(context, bridge) +def network_get_by_uuid(context, uuid): + """Get a network by uuid or raise if it does not exist.""" + return IMPL.network_get_by_uuid(context, uuid) + + def network_get_by_cidr(context, cidr): """Get a network by cidr or raise if it does not exist""" return IMPL.network_get_by_cidr(context, cidr) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d96b951a1..80ce76e8f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1846,6 +1846,19 @@ def network_get_by_bridge(context, bridge): return result +@require_admin_context +def network_get_by_uuid(context, uuid): + session = get_session() + result = session.query(models.Network).\ + filter_by(uuid=uuid).\ + filter_by(deleted=False).\ + first() + + if not result: + raise exception.NetworkNotFoundForUUID(uuid=uuid) + return result + + @require_admin_context def network_get_by_cidr(context, cidr): session = get_session() diff --git a/nova/exception.py b/nova/exception.py index 66740019b..8d6e84d74 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -411,6 +411,10 @@ class NetworkNotFoundForBridge(NetworkNotFound): message = _("Network could not be found for bridge %(bridge)s") +class NetworkNotFoundForUUID(NetworkNotFound): + message = _("Network could not be found for uuid %(uuid)s") + + class NetworkNotFoundForCidr(NetworkNotFound): message = _("Network could not be found with cidr %(cidr)s.") diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 79bb65939..932bdd82f 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -51,13 +51,14 @@ class QuantumManager(manager.FlatManager): def create_networks(self, context, label, cidr, multi_host, num_networks, network_size, cidr_v6, gateway_v6, bridge, - bridge_interface, dns1=None, dns2=None, **kwargs): + bridge_interface, dns1=None, dns2=None, uuid=None, + **kwargs): if num_networks != 1: raise Exception("QuantumManager requires that only one" " network is created per call") q_tenant_id = kwargs["project_id"] or \ FLAGS.quantum_default_tenant_id - quantum_net_id = bridge + quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception("Unable to find existing quantum " \ diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index b72edeeff..5bca9d024 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -47,15 +47,19 @@ class QuantumNovaIPAMLib: dns1=None, dns2=None): admin_context = context.elevated() subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) - manager.FlatManager.create_networks(self.net_manager, + networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, gateway_v6, quantum_net_id, None, dns1, dns2) - # now grab the network and update project_id + priority - network = db.network_get_by_bridge(admin_context, quantum_net_id) + if len(networks) != 1: + raise Exception("Error creating network entry") + + # now grab the network and update uuid, project_id, priority + network = networks[0] net = {"project_id": tenant_id, - "priority": priority} + "priority": priority, + "uuid": quantum_net_id} db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): @@ -64,11 +68,11 @@ class QuantumNovaIPAMLib: if not network: raise Exception("No network with fixed_range = %s" \ % fixed_range) - return network['bridge'] + return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) if not network: raise Exception("No network with net_id = %s" % net_id) manager.FlatManager.delete_network(self.net_manager, @@ -85,14 +89,14 @@ class QuantumNovaIPAMLib: id_priority_map = {} net_list = [] for n in networks: - net_id = n['bridge'] + net_id = n['uuid'] net_list.append((net_id, n["project_id"])) id_priority_map[net_id] = n['priority'] return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, quantum_net_id) + network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: address = db.fixed_ip_associate_pool(admin_context, network['id'], @@ -102,9 +106,9 @@ class QuantumNovaIPAMLib: db.fixed_ip_update(admin_context, address, values) def get_subnets_by_net_id(self, context, tenant_id, net_id): - n = db.network_get_by_bridge(context.elevated(), net_id) + n = db.network_get_by_uuid(context.elevated(), net_id) subnet_data_v4 = { - 'network_id': n['bridge'], + 'network_id': n['uuid'], 'cidr': n['cidr'], 'gateway': n['gateway'], 'broadcast': n['broadcast'], @@ -112,7 +116,7 @@ class QuantumNovaIPAMLib: 'dns1': n['dns1'], 'dns2': n['dns2']} subnet_data_v6 = { - 'network_id': n['bridge'], + 'network_id': n['uuid'], 'cidr': n['cidr_v6'], 'gateway': n['gateway_v6'], 'broadcast': None, @@ -129,7 +133,7 @@ class QuantumNovaIPAMLib: def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) if network['cidr_v6']: ip = ipv6.to_global(network['cidr_v6'], @@ -140,7 +144,7 @@ class QuantumNovaIPAMLib: def verify_subnet_exists(self, context, tenant_id, quantum_net_id): admin_context = context.elevated() - network = db.network_get_by_bridge(admin_context, quantum_net_id) + network = db.network_get_by_uuid(admin_context, quantum_net_id) def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): try: diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 8ef22ac7e..ae9900b67 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -129,7 +129,8 @@ class QuantumTestCaseBase(object): project_id = "fake_project1" ctx = context.RequestContext('user1', project_id) - instance_ref = db.api.instance_create(ctx, {}) + instance_ref = db.api.instance_create(ctx, + {"project_id": project_id}) nw_info = self.net_man.allocate_for_instance(ctx, instance_id=instance_ref['id'], host="", instance_type_id=instance_ref['instance_type_id'], @@ -173,7 +174,8 @@ class QuantumTestCaseBase(object): self.net_man.validate_networks(ctx, requested_networks) - instance_ref = db.api.instance_create(ctx, {}) + instance_ref = db.api.instance_create(ctx, + {"project_id": project_id}) nw_info = self.net_man.allocate_for_instance(ctx, instance_id=instance_ref['id'], host="", instance_type_id=instance_ref['instance_type_id'], -- cgit From 1bfc7ce80c3936a19434dfc45c44f8a1acfd65ed Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 12:53:32 -0700 Subject: fix issue with setting 'Active' caused by Quantum API changes. Other misc fixes --- nova/network/quantum/quantum_connection.py | 6 +++--- nova/virt/libvirt/vif.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index c0eaad1fd..a13867af2 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -65,7 +65,7 @@ class QuantumClientConnection: def create_and_attach_port(self, tenant_id, net_id, interface_id): LOG.debug("Connecting interface %s to net %s for %s" % \ (interface_id, net_id, tenant_id)) - port_data = {'port': {'port-state': 'ACTIVE'}} + port_data = {'port': {'state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) port_id = resdict["port"]["id"] @@ -74,13 +74,13 @@ class QuantumClientConnection: tenant=tenant_id) def detach_and_delete_port(self, tenant_id, net_id, port_id): - LOG.debug("Deleteing port %s on net %s for %s" % \ + LOG.debug("Deleting port %s on net %s for %s" % \ (port_id, net_id, tenant_id)) self.client.detach_resource(net_id, port_id, tenant=tenant_id) self.client.delete_port(net_id, port_id, tenant=tenant_id) - # FIXME: this will be inefficient until API implements querying + # FIXME: (danwent) this will be inefficient until API implements querying def get_port_by_attachment(self, tenant_id, attachment_id): net_list_resdict = self.client.list_networks(tenant=tenant_id) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 0b7438011..077c32474 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -101,7 +101,7 @@ class LibvirtOpenVswitchDriver(VIFDriver): """VIF driver for Open vSwitch.""" def get_dev_name(_self, iface_id): - return "tap-" + iface_id[0:15] + return "tap" + iface_id[0:11] def plug(self, instance, network, mapping): iface_id = mapping['vif_uuid'] -- cgit From 56891283f117997042363aee2e3ce00a5a12d9e0 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 15:39:27 -0700 Subject: update melange ipam lib to use network uuid, not bridge --- nova/network/quantum/melange_ipam_lib.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 5b58520c2..cfdcde045 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -52,7 +52,7 @@ class QuantumMelangeIPAMLib: # create a entry in the network table just to store # the priority order for this network - net = {"bridge": quantum_net_id, + net = {"uuid": quantum_net_id, "project_id": project_id, "priority": priority} network = self.db.network_create_safe(context, net) @@ -79,7 +79,7 @@ class QuantumMelangeIPAMLib: if b['network_id'] == net_id: self.m_conn.delete_block(b['id'], tenant_id) - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) if network is not None: db.network_delete_safe(context, network['id']) @@ -101,9 +101,8 @@ class QuantumMelangeIPAMLib: id_proj_map[b['network_id']] = tenant_id id_priority_map = {} - network = db.network_get_by_bridge(admin_context, net_id) for net_id, project_id in id_project_map.item(): - network = db.network_get_by_bridge(admin_context, net_id) + network = db.network_get_by_uuid(admin_context, net_id) if network is None: del id_proj_map[net_id] else: @@ -111,8 +110,11 @@ class QuantumMelangeIPAMLib: return sorted(id_priority_map.items(), key=lambda x: id_priority_map[x[0]]) - # FIXME: there must be a more efficient way to do this, - # talk to the melange folks + # FIXME: (danwent) Melange actually returns the subnet info + # when we query for a particular interface. we may want to + # reworks the ipam_manager python API to let us take advantage of + # this, as right now we have to get all blocks and cycle through + # them. def get_subnets_by_net_id(self, context, project_id, net_id): subnet_v4 = None subnet_v6 = None -- cgit From 431cd5d17780aa7ea9d03b028a78ec4e20b22440 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 17:37:07 -0700 Subject: always set network_id in virtual_interfaces table, otherwise API commands that show IP addresses get confused --- nova/network/quantum/manager.py | 24 +++++++++++++++++++----- nova/network/quantum/melange_ipam_lib.py | 11 ++++++++--- nova/network/quantum/nova_ipam_lib.py | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 932bdd82f..fb13a8496 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -105,14 +105,28 @@ class QuantumManager(manager.FlatManager): project_id) # Create a port via quantum and attach the vif - for (net_id, project_id) in net_proj_pairs: + for (quantum_net_id, project_id) in net_proj_pairs: + + # FIXME: (danwent). We'd like to have the manager be completely + # decoupled from the nova networks table. + # However, other parts of nova sometimes go behind + # our back and access network data directly from the DB. So + # for now, the quantum manager knows that there is a nova + # networks DB table and accesses it here. + # updating the virtual_interfaces table to use UUIDs would + # be one solution, but this would require significant work + # elsewhere. + network_ref = db.network_get_by_uuid(context, quantum_net_id) + vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance_id, None) + context, instance_id, network_ref['id']) + # talk to Quantum API to create and attach port. q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - self.q_conn.create_and_attach_port(q_tenant_id, net_id, + self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id, vif_rec['uuid']) - self.ipam.allocate_fixed_ip(context, project_id, net_id, vif_rec) + self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id, + vif_rec) return self.get_instance_nw_info(context, instance_id, instance_type_id, host) @@ -173,7 +187,7 @@ class QuantumManager(manager.FlatManager): if v6_subnet['cidr']: network_dict['cidr_v6'] = v6_subnet['cidr'] info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] - # TODO(tr3buchet): handle ip6 routes here as well + if v6_subnet['gateway']: info['gateway6'] = v6_subnet['gateway'] diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index cfdcde045..e2e09f139 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -50,11 +50,16 @@ class QuantumMelangeIPAMLib: project_id=tenant_id, dns1=dns1, dns2=dns2) - # create a entry in the network table just to store - # the priority order for this network + # create a entry in the network table, even though + # most data is stored in melange. This is used to + # store data not kept by melange (e.g., priority) + # and to 'fake' other parts of nova (e.g., the API) + # until we get get all accesses to be via the + # network manager API. net = {"uuid": quantum_net_id, "project_id": project_id, - "priority": priority} + "priority": priority, + "label": label} network = self.db.network_create_safe(context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 5bca9d024..6e7e5d244 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -55,7 +55,7 @@ class QuantumNovaIPAMLib: if len(networks) != 1: raise Exception("Error creating network entry") - # now grab the network and update uuid, project_id, priority + # now grab the network and update additional fields network = networks[0] net = {"project_id": tenant_id, "priority": priority, -- cgit From 822d92ed1f6a5f2f0951c5e43be6ce0c8fb75e65 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 19:12:43 -0700 Subject: remove fake IPAM lib, since qmanager must now access nova DB directly --- nova/network/quantum/fake.py | 124 ------------------------------------------- nova/tests/test_quantum.py | 8 --- 2 files changed, 132 deletions(-) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index 3f6996e63..00cdd8e08 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -91,127 +91,3 @@ class FakeQuantumClientConnection: return (net_id, port_id) return (None, None) - - -def get_ipam_lib(net_man): - return FakeQuantumIPAMLib() - - -class FakeQuantumIPAMLib(): - - def __init__(self): - self.subnets = {} - - def create_subnet(self, context, label, tenant_id, quantum_net_id, - priority, cidr=None, gateway_v6=None, - cidr_v6=None, dns1=None, dns2=None): - if int(cidr.split("/")[1]) != 24: - raise Exception("fake ipam_lib only supports /24s") - v4_ips = [] - net_start = cidr[0:cidr.rfind(".") + 1] - subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) - for i in xrange(2, subnet_size - 1): - v4_ips.append({"ip": net_start + str(i), - "allocated": False, - "virtual_interface_id": None, - "instance_id": None}) - self.subnets[quantum_net_id] = {\ - "label": label, - "priority": priority, - "gateway": net_start + "1", - "netmask": "255.255.255.0", - "broadcast": net_start + "255", - "cidr": cidr, - "gateway_v6": gateway_v6, - "cidr_v6": cidr_v6, - "dns1": dns1, - "dns2": dns2, - "project_id": tenant_id, - "v4_ips": v4_ips} - - def get_network_id_by_cidr(self, context, cidr, project_id): - for net_id, s in self.subnets.items(): - if s['cidr'] == cidr or s['cidr_v6'] == cidr: - return net_id - return None - - def delete_subnets_by_net_id(self, context, net_id, project_id): - self.verify_subnet_exists(context, project_id, net_id) - del self.subnets[net_id] - - def get_project_and_global_net_ids(self, context, project_id): - net_list = [] - id_priority_map = {} - for nid, s in self.subnets.items(): - if s['project_id'] == project_id or \ - s['project_id'] == None: - net_list.append((nid, s['project_id'])) - id_priority_map[nid] = s['priority'] - return sorted(net_list, key=lambda x: id_priority_map[x[0]]) - - def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): - subnet = self.subnets[quantum_net_id] - for i in xrange(0, len(subnet['v4_ips'])): - ip = subnet['v4_ips'][i] - if not ip['allocated']: - subnet['v4_ips'][i] = {'ip': ip['ip'], - 'allocated': True, - 'virtual_interface_id': vif_rec['uuid'], - 'instance_id': vif_rec['instance_id']} - return - raise Exception("Unable to find available IP for net '%s'" %\ - quantum_net_id) - - def get_subnets_by_net_id(self, context, tenant_id, net_id): - self.verify_subnet_exists(context, tenant_id, net_id) - - subnet_data = self.subnets[net_id] - subnet_data_v4 = { - 'network_id': net_id, - 'cidr': subnet_data['cidr'], - 'gateway': subnet_data['gateway'], - 'broadcast': subnet_data['broadcast'], - 'netmask': subnet_data['netmask'], - 'dns1': subnet_data['dns1'], - 'dns2': subnet_data['dns2']} - subnet_data_v6 = { - 'network_id': net_id, - 'cidr': subnet_data['cidr_v6'], - 'gateway': subnet_data['gateway_v6'], - 'broadcast': None, - 'netmask': None, - 'dns1': None, - 'dns2': None} - return (subnet_data_v4, subnet_data_v6) - - def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - self.verify_subnet_exists(context, project_id, net_id) - - subnet_data = self.subnets[net_id] - if subnet_data['cidr_v6']: - ip = ipv6.to_global(subnet_data['cidr_v6'], - "ca:fe:de:ad:be:ef", - project_id) - return [ip] - return [] - - def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - self.verify_subnet_exists(context, project_id, net_id) - - subnet_data = self.subnets[net_id] - for ip in subnet_data['v4_ips']: - if ip['virtual_interface_id'] == vif_id: - return [ip['ip']] - return [] - - def verify_subnet_exists(self, context, tenant_id, quantum_net_id): - if quantum_net_id not in self.subnets: - raise exception.NetworkNotFound(network_id=quantum_net_id) - - def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): - s = self.subnets[net_id] - for ip in s['v4_ips']: - if ip['virtual_interface_id'] == vif_ref['id']: - ip['allocated'] = False - ip['instance_id'] = None - ip['virtual_interface_id'] = None diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index ae9900b67..e7a8a01fa 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -224,14 +224,6 @@ class QuantumTestCaseBase(object): self.net_man.validate_networks, ctx, [("", None)]) -class QuantumFakeIPAMTestCase(QuantumTestCaseBase, test.TestCase): - - def setUp(self): - super(QuantumFakeIPAMTestCase, self).setUp() - self.net_man = quantum_manager.QuantumManager( \ - ipam_lib="nova.network.quantum.fake") - - class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): def setUp(self): -- cgit From 716303049eaee59841ca4679d73ecb4e5be52cfd Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 19:13:02 -0700 Subject: add doc-strings for all major modules --- nova/network/quantum/manager.py | 97 +++++++++++++++++++++++++----- nova/network/quantum/melange_ipam_lib.py | 81 ++++++++++++++++++------- nova/network/quantum/nova_ipam_lib.py | 85 ++++++++++++++++++-------- nova/network/quantum/quantum_connection.py | 28 ++++++++- 4 files changed, 226 insertions(+), 65 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index fb13a8496..a002a3d7b 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -35,8 +35,27 @@ flags.DEFINE_string('quantum_ipam_lib', class QuantumManager(manager.FlatManager): + """ NetworkManager class that communicates with a Quantum service + via a web services API to provision VM network connectivity. + + For IP Address management, QuantumManager can be configured to + use either Nova's local DB or the Melange IPAM service. + + Currently, the QuantumManager does NOT support any of the 'gateway' + functionality implemented by the Nova VlanManager, including: + * floating IPs + * DHCP + * NAT gateway + + Support for these capabilities are targted for future releases. + """ def __init__(self, ipam_lib=None, *args, **kwargs): + """ Initialize two key libraries, the connection to a + Quantum service, and the library for implementing IPAM. + + Calls inherited FlatManager constructor. + """ if FLAGS.fake_network: self.q_conn = fake.FakeQuantumClientConnection() @@ -53,6 +72,17 @@ class QuantumManager(manager.FlatManager): network_size, cidr_v6, gateway_v6, bridge, bridge_interface, dns1=None, dns2=None, uuid=None, **kwargs): + """ Unlike other NetworkManagers, with QuantumManager, each + create_networks calls should create only a single network. + + Two scenarios exist: + - no 'uuid' is specified, in which case we contact + Quantum and create a new network. + - an existing 'uuid' is specified, corresponding to + a Quantum network created out of band. + + In both cases, we initialize a subnet using the IPAM lib. + """ if num_networks != 1: raise Exception("QuantumManager requires that only one" " network is created per call") @@ -74,27 +104,46 @@ class QuantumManager(manager.FlatManager): priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): + """ Lookup network by IPv4 cidr, delete both the IPAM + subnet and the corresponding Quantum network. + """ project_id = context.project_id quantum_net_id = self.ipam.get_network_id_by_cidr( context, fixed_range, project_id) self.ipam.delete_subnets_by_net_id(context, quantum_net_id, project_id) - try: - q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - self.q_conn.delete_network(q_tenant_id, quantum_net_id) - except Exception, e: - raise Exception("Unable to delete Quantum Network with " - "fixed_range = %s (ports still in use?)." % fixed_range) + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.delete_network(q_tenant_id, quantum_net_id) def allocate_for_instance(self, context, **kwargs): + """ Called by compute when it is creating a new VM. + + There are three key tasks: + - Determine the number and order of vNICs to create + - Allocate IP addresses + - Create ports on a Quantum network and attach vNICs. + + We support two approaches to determining vNICs: + - By default, a VM gets a vNIC for any network belonging + to the VM's project, and a vNIC for any "global" network + that has a NULL project_id. vNIC order is determined + by the network's 'priority' field. + - If the 'os-create-server-ext' was used to create the VM, + only the networks in 'requested_networks' are used to + create vNICs, and the vNIC order is determiend by the + order in the requested_networks array. + + For each vNIC, use the FlatManager to create the entries + in the virtual_interfaces table, contact Quantum to + create a port and attachment the vNIC, and use the IPAM + lib to allocate IP addresses. + """ instance_id = kwargs.pop('instance_id') instance_type_id = kwargs['instance_type_id'] host = kwargs.pop('host') project_id = kwargs.pop('project_id') LOG.debug(_("network allocations for instance %s"), instance_id) - # if using the create-server-networks extension, 'requested_networks' - # will be defined, otherwise, just grab relevant nets from IPAM requested_networks = kwargs.get('requested_networks') if requested_networks: @@ -116,10 +165,12 @@ class QuantumManager(manager.FlatManager): # updating the virtual_interfaces table to use UUIDs would # be one solution, but this would require significant work # elsewhere. - network_ref = db.network_get_by_uuid(context, quantum_net_id) + admin_context = context.elevated() + network_ref = db.network_get_by_uuid(admin_context, + quantum_net_id) vif_rec = manager.FlatManager.add_virtual_interface(self, - context, instance_id, network_ref['id']) + context, instance_id, network_ref['id']) # talk to Quantum API to create and attach port. q_tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -133,6 +184,18 @@ class QuantumManager(manager.FlatManager): def get_instance_nw_info(self, context, instance_id, instance_type_id, host): + """ This method is used by compute to fetch all network data + that should be used when creating the VM. + + The method simply loops through all virtual interfaces + stored in the nova DB and queries the IPAM lib to get + the associated IP data. + + The format of returned data is 'defined' by the initial + set of NetworkManagers found in nova/network/manager.py . + Ideally this 'interface' will be more formally defined + in the future. + """ network_info = [] instance = db.instance_get(context, instance_id) project_id = instance.project_id @@ -202,6 +265,11 @@ class QuantumManager(manager.FlatManager): return network_info def deallocate_for_instance(self, context, **kwargs): + """ Called when a VM is terminated. Loop through each virtual + interface in the Nova DB and remove the Quantum port and + clear the IP allocation using the IPAM. Finally, remove + the virtual interfaces from the Nova DB. + """ instance_id = kwargs.get('instance_id') project_id = kwargs.pop('project_id', None) @@ -234,11 +302,12 @@ class QuantumManager(manager.FlatManager): self._do_trigger_security_group_members_refresh_for_instance( instance_id) - # validates that this tenant has quantum networks with the associated - # UUIDs. This is called by the 'os-create-server-ext' API extension - # code so that we can return an API error code to the caller if they - # request an invalid network. def validate_networks(self, context, networks): + """ Validates that this tenant has quantum networks with the associated + UUIDs. This is called by the 'os-create-server-ext' API extension + code so that we can return an API error code to the caller if they + request an invalid network. + """ if networks is None: return diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index e2e09f139..7b7baf281 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -32,43 +32,54 @@ def get_ipam_lib(net_man): class QuantumMelangeIPAMLib: + """ Implements Quantum IP Address Management (IPAM) interface + using the Melange service, which is access using the Melange + web services API. + """ def __init__(self): + """ Initialize class used to connect to Melange server""" self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - tenant_id = project_id or FLAGS.quantum_default_tenant_id - if cidr: - self.m_conn.create_block(quantum_net_id, cidr, + """ Contact Melange and create a subnet for any non-NULL + IPv4 or IPv6 subnets. + + Also create a entry in the Nova networks DB, but only + to store values not represented in Melange or to + temporarily provide compatibility with Nova code that + accesses IPAM data directly via the DB (e.g., nova-api) + """ + tenant_id = project_id or FLAGS.quantum_default_tenant_id + if cidr: + self.m_conn.create_block(quantum_net_id, cidr, project_id=tenant_id, dns1=dns1, dns2=dns2) - if cidr_v6: - self.m_conn.create_block(quantum_net_id, cidr_v6, + if cidr_v6: + self.m_conn.create_block(quantum_net_id, cidr_v6, project_id=tenant_id, dns1=dns1, dns2=dns2) - # create a entry in the network table, even though - # most data is stored in melange. This is used to - # store data not kept by melange (e.g., priority) - # and to 'fake' other parts of nova (e.g., the API) - # until we get get all accesses to be via the - # network manager API. - net = {"uuid": quantum_net_id, + net = {"uuid": quantum_net_id, "project_id": project_id, "priority": priority, "label": label} - network = self.db.network_create_safe(context, net) + network = self.db.network_create_safe(context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): + """ Pass call to allocate fixed IP on to Melange""" tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, vif_ref['uuid'], project_id=tenant_id, mac_address=vif_ref['address']) def get_network_id_by_cidr(self, context, cidr, project_id): + """ Find the Quantum UUID associated with a IPv4 CIDR + address for the specified tenant. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) for b in all_blocks['ip_blocks']: @@ -77,6 +88,9 @@ class QuantumMelangeIPAMLib: raise Exception("No network found for cidr %s" % cidr) def delete_subnets_by_net_id(self, context, net_id, project_id): + """ Find Melange block associated with the Quantum UUID, + then tell Melange to delete that block. + """ admin_context = context.elevated() tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) @@ -88,9 +102,11 @@ class QuantumMelangeIPAMLib: if network is not None: db.network_delete_safe(context, network['id']) - # get all networks with this project_id, as well as all networks - # where the project-id is not set (these are shared networks) def get_project_and_global_net_ids(self, context, project_id): + """ Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority'. + """ admin_context = context.elevated() id_proj_map = {} if project_id is None: @@ -115,12 +131,16 @@ class QuantumMelangeIPAMLib: return sorted(id_priority_map.items(), key=lambda x: id_priority_map[x[0]]) - # FIXME: (danwent) Melange actually returns the subnet info - # when we query for a particular interface. we may want to - # reworks the ipam_manager python API to let us take advantage of - # this, as right now we have to get all blocks and cycle through - # them. def get_subnets_by_net_id(self, context, project_id, net_id): + """ Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. + """ + + # FIXME: (danwent) Melange actually returns the subnet info + # when we query for a particular interface. we may want to + # reworks the ipam_manager python API to let us take advantage of + # this, as right now we have to get all blocks and cycle through + # them. subnet_v4 = None subnet_v6 = None tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -142,26 +162,41 @@ class QuantumMelangeIPAMLib: return (subnet_v4, subnet_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - return self.get_ips_by_interface(context, net_id, vif_id, + """ Returns a list of IPv4 address strings associated with + the specified virtual interface. + """ + return self._get_ips_by_interface(context, net_id, vif_id, project_id, 4) def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - return self.get_ips_by_interface(context, net_id, vif_id, + """ Returns a list of IPv6 address strings associated with + the specified virtual interface. + """ + return self._get_ips_by_interface(context, net_id, vif_id, project_id, 6) - def get_ips_by_interface(self, context, net_id, vif_id, project_id, + def _get_ips_by_interface(self, context, net_id, vif_id, project_id, ip_version): + """ Helper method to fetch v4 or v6 addresses for a particular + virtual interface. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) return [ip['address'] for ip in ip_list \ if IPNetwork(ip['address']).version == ip_version] def verify_subnet_exists(self, context, project_id, quantum_net_id): + """ Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id, quantum_net_id) return v4_subnet is not None def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref): + """ Deallocate all fixed IPs associated with the specified + virtual interface. + """ tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 6e7e5d244..ce7d3efcb 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -37,52 +37,69 @@ def get_ipam_lib(net_man): class QuantumNovaIPAMLib: + """ Implements Quantum IP Address Management (IPAM) interface + using the local Nova database. This implementation is inline + with how IPAM is used by other NetworkManagers. + """ def __init__(self, net_manager): + """ Holds a reference to the "parent" network manager, used + to take advantage of various FlatManager methods to avoid + code duplication. + """ self.net_manager = net_manager def create_subnet(self, context, label, tenant_id, quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - admin_context = context.elevated() - subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) - networks = manager.FlatManager.create_networks(self.net_manager, + """ Re-use the basic FlatManager create_networks method to + initialize the networks and fixed_ips tables in Nova DB. + + Also stores a few more fields in the networks table that + are needed by Quantum but not the FlatManager. + """ + admin_context = context.elevated() + subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, gateway_v6, quantum_net_id, None, dns1, dns2) - if len(networks) != 1: - raise Exception("Error creating network entry") + if len(networks) != 1: + raise Exception("Error creating network entry") - # now grab the network and update additional fields - network = networks[0] - net = {"project_id": tenant_id, + network = networks[0] + net = {"project_id": tenant_id, "priority": priority, "uuid": quantum_net_id} - db.network_update(admin_context, network['id'], net) + db.network_update(admin_context, network['id'], net) def get_network_id_by_cidr(self, context, cidr, project_id): - admin_context = context.elevated() - network = db.network_get_by_cidr(admin_context, cidr) - if not network: - raise Exception("No network with fixed_range = %s" \ - % fixed_range) - return network['uuid'] + """ Grabs Quantum network UUID based on IPv4 CIDR. """ + admin_context = context.elevated() + network = db.network_get_by_cidr(admin_context, cidr) + if not network: + raise Exception("No network with fixed_range = %s" % fixed_range) + return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): - admin_context = context.elevated() - network = db.network_get_by_uuid(admin_context, net_id) - if not network: - raise Exception("No network with net_id = %s" % net_id) - manager.FlatManager.delete_network(self.net_manager, + """ Deletes a network based on Quantum UUID. Uses FlatManager + delete_network to avoid duplication. + """ + admin_context = context.elevated() + network = db.network_get_by_uuid(admin_context, net_id) + if not network: + raise Exception("No network with net_id = %s" % net_id) + manager.FlatManager.delete_network(self.net_manager, admin_context, network['cidr'], require_disassociated=False) def get_project_and_global_net_ids(self, context, project_id): - - # get all networks with this project_id, as well as all networks - # where the project-id is not set (these are shared networks) + """ Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority'. + """ admin_context = context.elevated() networks = db.project_get_networks(admin_context, project_id, False) networks.extend(db.project_get_networks(admin_context, None, False)) @@ -95,6 +112,8 @@ class QuantumNovaIPAMLib: return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): + """ Allocates a single fixed IPv4 address for a virtual interface. + """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: @@ -106,6 +125,9 @@ class QuantumNovaIPAMLib: db.fixed_ip_update(admin_context, address, values) def get_subnets_by_net_id(self, context, tenant_id, net_id): + """ Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. + """ n = db.network_get_by_uuid(context.elevated(), net_id) subnet_data_v4 = { 'network_id': n['uuid'], @@ -126,12 +148,18 @@ class QuantumNovaIPAMLib: return (subnet_data_v4, subnet_data_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): + """ Returns a list of IPv4 address strings associated with + the specified virtual interface, based on the fixed_ips table. + """ vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) fixed_ips = db.fixed_ip_get_by_virtual_interface(context, vif_rec['id']) return [f['address'] for f in fixed_ips] def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): + """ Returns a list containing a single IPv6 address strings + associated with the specified virtual interface. + """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) @@ -143,10 +171,17 @@ class QuantumNovaIPAMLib: return [] def verify_subnet_exists(self, context, tenant_id, quantum_net_id): + """ Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. Raises an exception if no + such subnet exists. + """ admin_context = context.elevated() - network = db.network_get_by_uuid(admin_context, quantum_net_id) + db.network_get_by_uuid(admin_context, quantum_net_id) def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): + """ Deallocate all fixed IPs associated with the specified + virtual interface. + """ try: admin_context = context.elevated() fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context, @@ -156,5 +191,5 @@ class QuantumNovaIPAMLib: {'allocated': False, 'virtual_interface_id': None}) except exception.FixedIpNotFoundForInstance: - LOG.error(_('Failed to deallocate fixed IP for vif %s' % \ + LOG.error(_('No fixed IPs to deallocate for vif %s' % \ vif_ref['id'])) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index a13867af2..bd3592c2c 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -38,31 +38,49 @@ flags.DEFINE_string('quantum_default_tenant_id', class QuantumClientConnection: + """ Abstracts connection to Quantum service into higher level + operations performed by the QuantumManager. + + Separating this out as a class also let's us create a 'fake' + version of this class for unit tests. + """ def __init__(self): + """ Initialize Quantum client class based on flags. """ self.client = quantum_client.Client(FLAGS.quantum_connection_host, FLAGS.quantum_connection_port, format="json", logger=LOG) def create_network(self, tenant_id, network_name): + """ Create network using specified name, return Quantum + network UUID. + """ data = {'network': {'name': network_name}} resdict = self.client.create_network(data, tenant=tenant_id) return resdict["network"]["id"] def delete_network(self, tenant_id, net_id): + """ Deletes Quantum network with specified UUID. """ self.client.delete_network(net_id, tenant=tenant_id) def network_exists(self, tenant_id, net_id): + """ Determine if a Quantum network exists for the + specified tenant. + """ try: self.client.show_network_details(net_id, tenant=tenant_id) except: - # FIXME: client lib should expose more granular exceptions + # FIXME: (danwent) client lib should expose granular exceptions # so we can confirm we're getting a 404 and not some other error return False return True def create_and_attach_port(self, tenant_id, net_id, interface_id): + """ Creates a Quantum port on the specified network, sets + status to ACTIVE to enable traffic, and attaches the + vNIC with the specified interface-id. + """ LOG.debug("Connecting interface %s to net %s for %s" % \ (interface_id, net_id, tenant_id)) port_data = {'port': {'state': 'ACTIVE'}} @@ -74,15 +92,19 @@ class QuantumClientConnection: tenant=tenant_id) def detach_and_delete_port(self, tenant_id, net_id, port_id): + """ Detach and delete the specified Quantum port. """ LOG.debug("Deleting port %s on net %s for %s" % \ (port_id, net_id, tenant_id)) self.client.detach_resource(net_id, port_id, tenant=tenant_id) self.client.delete_port(net_id, port_id, tenant=tenant_id) - # FIXME: (danwent) this will be inefficient until API implements querying def get_port_by_attachment(self, tenant_id, attachment_id): - + """ Given a tenant, search for the Quantum network and port + UUID that has the specified interface-id attachment. + """ + # FIXME: (danwent) this will be inefficient until the Quantum + # API implements querying a port by the interface-id net_list_resdict = self.client.list_networks(tenant=tenant_id) for n in net_list_resdict["networks"]: net_id = n['id'] -- cgit From 605fe4f19af3af830a2a8c82809e9ce5909c602d Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Sun, 28 Aug 2011 20:00:38 -0700 Subject: restore fixed_ip_associate_pool in nova/db/sqlalchemy.py to its original form before this branch. Figured out how to make unit tests pass without requiring that this function changes --- nova/db/sqlalchemy/api.py | 4 +++- nova/tests/test_quantum.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 80ce76e8f..00af62682 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -688,8 +688,10 @@ def fixed_ip_associate(context, address, instance_id, network_id=None): def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): session = get_session() with session.begin(): + network_or_none = or_(models.FixedIp.network_id == network_id, + models.FixedIp.network_id == None) fixed_ip_ref = session.query(models.FixedIp).\ - filter_by(network_id=network_id).\ + filter(network_or_none).\ filter_by(reserved=False).\ filter_by(deleted=False).\ filter_by(instance=None).\ diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index e7a8a01fa..3efdba910 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -17,6 +17,8 @@ from nova import context from nova import db +from nova.db.sqlalchemy import models +from nova.db.sqlalchemy.session import get_session from nova import exception from nova import log as logging from nova.network.quantum import manager as quantum_manager @@ -238,6 +240,15 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): for n in db.network_get_all(ctx): db.network_delete_safe(ctx, n['id']) + # I've found that other unit tests have a nasty habit of + # of creating fixed IPs and not cleaning up, which can + # confuse these tests, so we clean them all. + session = get_session() + result = session.query(models.FixedIp).all() + with session.begin(): + for fip_ref in result: + session.delete(fip_ref) + # Cannot run this unit tests auotmatically for now, as it requires # melange to be running locally. # -- cgit From 81d30e900d3329f40bfd05682b73e73951c435ca Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Mon, 29 Aug 2011 08:31:56 -0700 Subject: update file name for db migrate script after merge --- .../versions/041_add_network_priority.py | 44 ---------------------- .../versions/043_add_network_priority.py | 44 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py deleted file mode 100644 index e69380199..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/041_add_network_priority.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2011 Nicira, 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 sqlalchemy import * -from migrate import * - -from nova import log as logging -from nova import utils - - -meta = MetaData() - -# Add priority column to networks table -priority = Column('priority', Integer()) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - # grab tables and (column for dropping later) - networks = Table('networks', meta, autoload=True) - - try: - networks.create_column(priority) - except Exception: - logging.error(_("priority column not added to networks table")) - raise - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - networks.drop_column(priority) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py new file mode 100644 index 000000000..e69380199 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py @@ -0,0 +1,44 @@ +# Copyright 2011 Nicira, 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 sqlalchemy import * +from migrate import * + +from nova import log as logging +from nova import utils + + +meta = MetaData() + +# Add priority column to networks table +priority = Column('priority', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + # grab tables and (column for dropping later) + networks = Table('networks', meta, autoload=True) + + try: + networks.create_column(priority) + except Exception: + logging.error(_("priority column not added to networks table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(priority) -- cgit From ed1399b5a4a023b606263b5464dfe684e460a126 Mon Sep 17 00:00:00 2001 From: "danwent@gmail.com" <> Date: Mon, 29 Aug 2011 08:33:12 -0700 Subject: update file name for db migrate script after merge (again) --- .../versions/043_add_network_priority.py | 44 ---------------------- .../versions/044_add_network_priority.py | 44 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py deleted file mode 100644 index e69380199..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/043_add_network_priority.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2011 Nicira, 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 sqlalchemy import * -from migrate import * - -from nova import log as logging -from nova import utils - - -meta = MetaData() - -# Add priority column to networks table -priority = Column('priority', Integer()) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - - # grab tables and (column for dropping later) - networks = Table('networks', meta, autoload=True) - - try: - networks.create_column(priority) - except Exception: - logging.error(_("priority column not added to networks table")) - raise - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - networks.drop_column(priority) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py new file mode 100644 index 000000000..e69380199 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -0,0 +1,44 @@ +# Copyright 2011 Nicira, 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 sqlalchemy import * +from migrate import * + +from nova import log as logging +from nova import utils + + +meta = MetaData() + +# Add priority column to networks table +priority = Column('priority', Integer()) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + # grab tables and (column for dropping later) + networks = Table('networks', meta, autoload=True) + + try: + networks.create_column(priority) + except Exception: + logging.error(_("priority column not added to networks table")) + raise + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(priority) -- cgit From dc129eeb96a0a201fb3d032078eaf8ab192ca207 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 09:03:56 -0700 Subject: add alias to mailmap --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 5c8df80e0..3c7a31e03 100644 --- a/.mailmap +++ b/.mailmap @@ -15,6 +15,7 @@ + @@ -36,7 +37,7 @@ - + -- cgit From 6d8663a887a1241b1c3136626e7b915be860273b Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 09:04:43 -0700 Subject: remove 'uuid' param for nova-manage network delete that I had add previously --- bin/nova-manage | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index a95890e36..051079ef3 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -818,15 +818,12 @@ class NetworkCommands(object): @args('--network', dest="fixed_range", metavar='', help='Network to delete') - @args('--uuid', dest="uuid", metavar='', - help='UUID of network to delete') def delete(self, fixed_range): """Deletes a network""" # delete the network net_manager = utils.import_object(FLAGS.network_manager) - net_manager.delete_network(context.get_admin_context(), fixed_range, - uuid=None) + net_manager.delete_network(context.get_admin_context(), fixed_range) @args('--network', dest="fixed_range", metavar='', -- cgit From 7e8e39160d1329f4923334fa822310d266651907 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 10:43:26 -0700 Subject: access db directly in networkmanagers's delete_network method, so stubbed test call works correctly --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index d1883ff8d..b625e7823 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -791,7 +791,7 @@ class NetworkManager(manager.SchedulerDependentManager): if require_disassociated and network.project_id is not None: raise ValueError(_('Network must be disassociated from project %s' ' before delete' % network.project_id)) - self.db.network_delete_safe(context, network.id) + db.network_delete_safe(context, network.id) @property def _bottom_reserved_ips(self): # pylint: disable=R0201 -- cgit From bb4f7129e945602a8e830520e7877a33b2539530 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 29 Aug 2011 11:04:27 -0700 Subject: remove brackets from mailmap entry --- .mailmap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 3c7a31e03..a46dd6673 100644 --- a/.mailmap +++ b/.mailmap @@ -15,7 +15,7 @@ - + danwent@gmail.com -- cgit From b515d427e05010ba5a984dd549cb6418629de50d Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Tue, 30 Aug 2011 18:18:23 +0200 Subject: With OS API, if the property 'ramdisk_id' isn't set on the AMI image, Nova can not instantiate it. With EC2 API, the AMI image can be instantiate. --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 483ff4985..269e46dba 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -283,7 +283,7 @@ class CreateInstanceHelper(object): try: ramdisk_id = image_meta['properties']['ramdisk_id'] except KeyError: - raise exception.RamdiskNotFoundForImage(image_id=image_id) + ramdisk_id = None return kernel_id, ramdisk_id -- cgit From e8db401156cdb842ef7bf15e44e34ac5ae672e46 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Tue, 30 Aug 2011 18:27:48 +0200 Subject: The exception 'RamdiskNotFoundForImage' is no longer used. --- nova/exception.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 32981f4d5..aa3407480 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -402,10 +402,6 @@ class KernelNotFoundForImage(ImageNotFound): message = _("Kernel not found for image %(image_id)s.") -class RamdiskNotFoundForImage(ImageNotFound): - message = _("Ramdisk not found for image %(image_id)s.") - - class UserNotFound(NotFound): message = _("User %(user_id)s could not be found.") -- cgit From ced3ea3e8d7cf02f988d968d6078182815226719 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 31 Aug 2011 12:23:43 -0700 Subject: fix for chris behrens' comment - move tenant_id => project_id mapping to compute.api.get_all --- nova/api/openstack/servers.py | 5 ----- nova/compute/api.py | 1 + nova/tests/api/openstack/test_servers.py | 11 ++++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3e76fa1a0..5bbb4e52e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -107,11 +107,6 @@ class Controller(object): LOG.error(reason) raise exception.InvalidInput(reason=reason) - # translate tenant_id filter to internal project_id - if 'tenant_id' in search_opts: - search_opts['project_id'] = search_opts['tenant_id'] - del search_opts['tenant_id'] - # By default, compute's get_all() will return deleted instances. # If an admin hasn't specified a 'deleted' search option, we need # to filter out deleted instances by setting the filter ourselves. diff --git a/nova/compute/api.py b/nova/compute/api.py index 60a13631a..95d6a901d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -854,6 +854,7 @@ class API(base.Base): 'image': 'image_ref', 'name': 'display_name', 'instance_name': 'name', + 'tenant_id': 'project_id', 'recurse_zones': None, 'flavor': _remap_flavor_filter, 'fixed_ip': _remap_fixed_ip_filter} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 435644dcc..1ecb35f63 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1200,13 +1200,14 @@ class ServersTest(test.TestCase): self.assertEqual(servers[0]['id'], 100) def test_tenant_id_filter_converts_to_project_id_for_admin(self): - def fake_get_all(compute_self, context, search_opts=None): - self.assertNotEqual(search_opts, None) - self.assertEqual(search_opts['project_id'], 'faketenant') - self.assertFalse(search_opts.get('tenant_id')) + def fake_get_all(context, filters=None): + self.assertNotEqual(filters, None) + self.assertEqual(filters['project_id'], 'faketenant') + self.assertFalse(filters.get('tenant_id')) return [stub_instance(100)] - self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) + self.stubs.Set(nova.db.api, 'instance_get_all_by_filters', + fake_get_all) self.flags(allow_admin_api=True) req = webob.Request.blank('/v1.1/fake/servers?tenant_id=faketenant') -- cgit From e1cd8f036f34fc622416e74a302959c9e50a798c Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 31 Aug 2011 17:06:15 -0700 Subject: Fix for LP Bug #838251 --- nova/api/ec2/cloud.py | 12 ++---------- nova/api/openstack/contrib/createserverext.py | 24 +++++++++++++++++++----- nova/api/openstack/servers.py | 18 ++++++++++++++++++ nova/utils.py | 9 +++++++++ 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9aebf92e3..d3b3a21ef 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -995,14 +995,6 @@ class CloudController(object): 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} - @staticmethod - def _convert_to_set(lst, label): - if lst is None or lst == []: - return None - if not isinstance(lst, list): - lst = [lst] - return [{label: x} for x in lst] - def _format_kernel_id(self, instance_ref, result, key): kernel_id = instance_ref['kernel_id'] if kernel_id is None: @@ -1160,7 +1152,7 @@ class CloudController(object): if instance.get('security_groups'): for security_group in instance['security_groups']: security_group_names.append(security_group['name']) - result['groupSet'] = CloudController._convert_to_set( + result['groupSet'] = utils.convert_to_set( security_group_names, 'groupId') def _format_instances(self, context, instance_id=None, use_v6=False, @@ -1224,7 +1216,7 @@ class CloudController(object): i['keyName'] = '%s (%s, %s)' % (i['keyName'], instance['project_id'], instance['host']) - i['productCodesSet'] = self._convert_to_set([], 'product_codes') + i['productCodesSet'] = utils.convert_to_set([], 'product_codes') self._format_instance_type(instance, i) i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index ba72fdb0b..4cc093891 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -14,18 +14,32 @@ # License for the specific language governing permissions and limitations # under the License +from nova import utils from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import extensions from nova.api.openstack import servers from nova.api.openstack import wsgi -class Createserverext(extensions.ExtensionDescriptor): - """The servers create ext +class CreateServerController(servers.ControllerV11): + def _build_view(self, req, instance, is_detail=False): + server = super(CreateServerController, self)._build_view(req, + instance, + is_detail) + if is_detail: + self._build_security_groups(server['server'], instance) + return server + + def _build_security_groups(self, response, inst): + sg_names = [] + if inst.get('security_groups'): + sg_names = [security_group['name'] for security_group in \ + inst['security_groups']] + response['security_groups'] = utils.convert_to_set(sg_names, 'name') - Exposes addFixedIp and removeFixedIp actions on servers. - """ +class Createserverext(extensions.ExtensionDescriptor): + """The servers create ext""" def get_name(self): return "Createserverext" @@ -58,7 +72,7 @@ class Createserverext(extensions.ExtensionDescriptor): deserializer = wsgi.RequestDeserializer(body_deserializers) res = extensions.ResourceExtension('os-create-server-ext', - controller=servers.ControllerV11(), + controller=CreateServerController(), deserializer=deserializer, serializer=serializer) resources.append(res) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 27c67e79e..e2cb46165 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -912,6 +912,11 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): server['addresses']) server_node.appendChild(addresses_node) + if 'security_groups' in server: + security_groups_node = self._create_security_groups_node(xml_doc, + server['security_groups']) + server_node.appendChild(security_groups_node) + return server_node def _server_list_to_xml(self, xml_doc, servers, detailed): @@ -964,6 +969,19 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): server_dict['server']) return self.to_xml_string(node, True) + def _security_group_to_xml(self, doc, security_group): + node = doc.createElement('security_group') + node.setAttribute('name', str(security_group.get('name'))) + return node + + def _create_security_groups_node(self, xml_doc, security_groups): + security_groups_node = xml_doc.createElement('security_groups') + if security_groups: + for security_group in security_groups: + node = self._security_group_to_xml(xml_doc, security_group) + security_groups_node.appendChild(node) + return security_groups_node + def create_resource(version='1.0'): controller = { diff --git a/nova/utils.py b/nova/utils.py index 21e6221b2..4855f6fd6 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -901,3 +901,12 @@ def monkey_patch(): func = import_class("%s.%s" % (module, key)) setattr(sys.modules[module], key,\ decorator("%s.%s" % (module, key), func)) + + +def convert_to_set(lst, label): + """Convert a value or list into a set""" + if lst is None or lst == []: + return None + if not isinstance(lst, list): + lst = [lst] + return [{label: x} for x in lst] -- cgit From 20beec509aff1bb3a30e9f1d95d3e2825f2b38ea Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 31 Aug 2011 17:30:11 -0700 Subject: Fix for LP Bug #838466 --- nova/compute/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e045ef3de..6806522f7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -383,10 +383,6 @@ class API(base.Base): If you are changing this method, be sure to update both call paths. """ - instance = dict(launch_index=num, **base_options) - instance = self.db.instance_create(context, instance) - instance_id = instance['id'] - elevated = context.elevated() if security_group is None: security_group = ['default'] @@ -400,6 +396,10 @@ class API(base.Base): security_group_name) security_groups.append(group['id']) + instance = dict(launch_index=num, **base_options) + instance = self.db.instance_create(context, instance) + instance_id = instance['id'] + for security_group_id in security_groups: self.db.instance_add_security_group(elevated, instance_id, -- cgit From f4dc231069c530f8f6055b1b7fa006750795b6e4 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Wed, 31 Aug 2011 18:54:15 -0700 Subject: Add comment for an uncommon failure case that we need to fix --- nova/network/quantum/manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index c03622218..2fd9175ae 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -213,6 +213,12 @@ class QuantumManager(manager.FlatManager): net_id, port_id = self.q_conn.get_port_by_attachment( q_tenant_id, vif['uuid']) if not net_id: + # TODO(bgh): We need to figure out a way to tell if we + # should actually be raising this exception or not. + # In the case that a VM spawn failed it may not have + # attached the vif and raising the exception here + # prevents deltion of the VM. In that case we should + # probably just log, continue, and move on. raise Exception(_("No network for for virtual interface %s") % vif['uuid']) (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context, -- cgit From 3ae639e5acd965999138e307508933ae1624b476 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 31 Aug 2011 23:12:09 -0700 Subject: add specific exceptions for quantum client. Fix doc-strings in client.py --- nova/network/quantum/client.py | 93 ++++++++++++++---------------- nova/network/quantum/quantum_connection.py | 7 +-- 2 files changed, 46 insertions(+), 54 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 82f23b6b5..22a4b4d63 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -39,6 +39,24 @@ class JSONSerializer(object): return json.loads(data) +# FIXME: (danwent) the full client lib will expose more +# granular exceptions, for now, just try to distinguish +# between the cases we care about. +class QuantumNotFoundException(Exception); + """ Indicates that Quantum Server returned 404""" + pass + + +class QuantumServerException(Exception): + """ Indicates any non-404 error from Quantum Server""" + pass + + +class QuantumIOException(Exception): + """ Indicates network IO trouble reaching Quantum Server""" + pass + + class api_call(object): """A Decorator to add support for format and tenant overriding""" def __init__(self, func): @@ -78,8 +96,7 @@ class Client(object): def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, format="xml", testing_stub=None, key_file=None, cert_file=None, logger=None): - """ - Creates a new client to some service. + """ Creates a new client to some service. :param host: The host where service resides :param port: The port where service resides @@ -102,9 +119,7 @@ class Client(object): self.logger = logger def get_connection_type(self): - """ - Returns the proper connection type - """ + """ Returns the proper connection type """ if self.testing_stub: return self.testing_stub elif self.use_ssl: @@ -114,8 +129,7 @@ class Client(object): def do_request(self, method, action, body=None, headers=None, params=None): - """ - Connects to the server and issues a request. + """ Connects to the server and issues a request. Returns the result data, or raises an appropriate exception if HTTP status code is not 2xx @@ -168,6 +182,10 @@ class Client(object): self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ % (str(status_code), data)) + if status_code == httplib.NOT_FOUND: + raise QuantumNotFoundException( + _("Quantum entity not found: %s" % data)) + if status_code in (httplib.OK, httplib.CREATED, httplib.ACCEPTED, @@ -175,15 +193,16 @@ class Client(object): if data is not None and len(data): return self.deserialize(data, status_code) else: - raise Exception(_("Server returned error: %s" % res.read())) + raise QuantumServerException( + _("Server %(status_code)s error: %(data)s" + % locals())) except (socket.error, IOError), e: - raise Exception(_("Unable to connect to " + raise QuantumIOException(_("Unable to connect to " "server. Got error: %s" % e)) def get_status_code(self, response): - """ - Returns the integer status code from the response, which + """ Returns the integer status code from the response, which can be either a Webob.Response (used in testing) or httplib.Response """ if hasattr(response, 'status_int'): @@ -212,99 +231,73 @@ class Client(object): @api_call def list_networks(self): - """ - Fetches a list of all networks for a tenant - """ + """ Fetches a list of all networks for a tenant """ return self.do_request("GET", self.networks_path) @api_call def show_network_details(self, network): - """ - Fetches the details of a certain network - """ + """ Fetches the details of a certain network """ return self.do_request("GET", self.network_path % (network)) @api_call def create_network(self, body=None): - """ - Creates a new network - """ + """ Creates a new network """ body = self.serialize(body) return self.do_request("POST", self.networks_path, body=body) @api_call def update_network(self, network, body=None): - """ - Updates a network - """ + """ Updates a network """ body = self.serialize(body) return self.do_request("PUT", self.network_path % (network), body=body) @api_call def delete_network(self, network): - """ - Deletes the specified network - """ + """ Deletes the specified network """ return self.do_request("DELETE", self.network_path % (network)) @api_call def list_ports(self, network): - """ - Fetches a list of ports on a given network - """ + """ Fetches a list of ports on a given network """ return self.do_request("GET", self.ports_path % (network)) @api_call def show_port_details(self, network, port): - """ - Fetches the details of a certain port - """ + """ Fetches the details of a certain port """ return self.do_request("GET", self.port_path % (network, port)) @api_call def create_port(self, network, body=None): - """ - Creates a new port on a given network - """ + """ Creates a new port on a given network """ body = self.serialize(body) return self.do_request("POST", self.ports_path % (network), body=body) @api_call def delete_port(self, network, port): - """ - Deletes the specified port from a network - """ + """ Deletes the specified port from a network """ return self.do_request("DELETE", self.port_path % (network, port)) @api_call def set_port_state(self, network, port, body=None): - """ - Sets the state of the specified port - """ + """ Sets the state of the specified port """ body = self.serialize(body) return self.do_request("PUT", self.port_path % (network, port), body=body) @api_call def show_port_attachment(self, network, port): - """ - Fetches the attachment-id associated with the specified port - """ + """ Fetches the attachment-id associated with the specified port """ return self.do_request("GET", self.attachment_path % (network, port)) @api_call def attach_resource(self, network, port, body=None): - """ - Sets the attachment-id of the specified port - """ + """ Sets the attachment-id of the specified port """ body = self.serialize(body) return self.do_request("PUT", self.attachment_path % (network, port), body=body) @api_call def detach_resource(self, network, port): - """ - Removes the attachment-id of the specified port - """ + """ Removes the attachment-id of the specified port """ return self.do_request("DELETE", self.attachment_path % (network, port)) diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index e2218c68d..90d5e8e04 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -70,11 +70,10 @@ class QuantumClientConnection(object): """ try: self.client.show_network_details(net_id, tenant=tenant_id) - except: - # FIXME(danwent): client lib should expose granular exceptions - # so we can confirm we're getting a 404 and not some other error + return True + except client.QuantumNotFoundException: + # Not really an error. Real errors will be propogated to caller return False - return True def create_and_attach_port(self, tenant_id, net_id, interface_id): """ Creates a Quantum port on the specified network, sets -- cgit From 02093fbe185e52a3c22b748811e60e988150a352 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 31 Aug 2011 23:37:26 -0700 Subject: more review cleanup --- nova/network/quantum/manager.py | 16 ++++++++-------- nova/network/quantum/nova_ipam_lib.py | 8 +++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 1d2c7c664..153f6c0f2 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -103,16 +103,16 @@ class QuantumManager(manager.FlatManager): priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): - """ Lookup network by IPv4 cidr, delete both the IPAM - subnet and the corresponding Quantum network. - """ - project_id = context.project_id - quantum_net_id = self.ipam.get_network_id_by_cidr( + """ Lookup network by IPv4 cidr, delete both the IPAM + subnet and the corresponding Quantum network. + """ + project_id = context.project_id + quantum_net_id = self.ipam.get_network_id_by_cidr( context, fixed_range, project_id) - self.ipam.delete_subnets_by_net_id(context, quantum_net_id, + self.ipam.delete_subnets_by_net_id(context, quantum_net_id, project_id) - q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - self.q_conn.delete_network(q_tenant_id, quantum_net_id) + q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + self.q_conn.delete_network(q_tenant_id, quantum_net_id) def allocate_for_instance(self, context, **kwargs): """ Called by compute when it is creating a new VM. diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 4f62887d1..4b08b906a 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -import math +import netaddr from nova import db from nova import exception @@ -60,8 +60,7 @@ class QuantumNovaIPAMLib(object): are needed by Quantum but not the FlatManager. """ admin_context = context.elevated() - # FIXME(danwent): Use the netaddr library here - subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1])))) + subnet_size = len(netaddr.IPNetwork(cidr)) networks = manager.FlatManager.create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, @@ -114,8 +113,7 @@ class QuantumNovaIPAMLib(object): return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): - """ Allocates a single fixed IPv4 address for a virtual interface. - """ + """ Allocates a single fixed IPv4 address for a virtual interface.""" admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: -- cgit From 76a60bf27cc8864e397139a3497b1f571ce38d88 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 31 Aug 2011 23:42:24 -0700 Subject: typo --- nova/network/quantum/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 22a4b4d63..67582d8bc 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -42,7 +42,7 @@ class JSONSerializer(object): # FIXME: (danwent) the full client lib will expose more # granular exceptions, for now, just try to distinguish # between the cases we care about. -class QuantumNotFoundException(Exception); +class QuantumNotFoundException(Exception): """ Indicates that Quantum Server returned 404""" pass -- cgit From 5b3b3d1b357c085c2088df7d76df8392118fb82e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 09:39:36 -0700 Subject: additional review cleanup --- .mailmap | 2 +- nova/network/quantum/client.py | 12 ++++++++---- nova/network/quantum/fake.py | 10 ++++++---- nova/network/quantum/manager.py | 2 +- nova/network/quantum/melange_connection.py | 4 +++- nova/network/quantum/melange_ipam_lib.py | 3 ++- nova/network/quantum/nova_ipam_lib.py | 2 +- nova/network/quantum/quantum_connection.py | 8 ++++---- nova/tests/test_quantum.py | 21 +++++++++++---------- 9 files changed, 37 insertions(+), 27 deletions(-) diff --git a/.mailmap b/.mailmap index a46dd6673..f2f59d81b 100644 --- a/.mailmap +++ b/.mailmap @@ -37,7 +37,7 @@ - + diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 67582d8bc..455bb8a79 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -22,11 +22,15 @@ import socket import urllib +#FIXME(danwent): All content in this file should be removed once the +# packaging work for the quantum client libraries is complete. +# At that point, we will be able to just install the libraries as a +# dependency and import from quantum.client.* and quantum.common.* +# Until then, we have simplified versions of these classes in this file. + class JSONSerializer(object): - """ - This is a simple json-only serializer to use until we can just grab + """ This is a simple json-only serializer to use until we can just grab the standard serializer from the quantum library. - TODO(danwent): replace serializer with quantum implementation """ def serialize(self, data, content_type): try: @@ -39,7 +43,7 @@ class JSONSerializer(object): return json.loads(data) -# FIXME: (danwent) the full client lib will expose more +# The full client lib will expose more # granular exceptions, for now, just try to distinguish # between the cases we care about. class QuantumNotFoundException(Exception): diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index f668edfed..6a4005c59 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -65,8 +65,9 @@ class FakeQuantumClientConnection(object): def create_and_attach_port(self, tenant_id, net_id, interface_id): if not self.network_exists(tenant_id, net_id): - raise Exception(_("network %s does not exist for tenant %s" % - (net_id, tenant_id))) + raise Exception( + _("network %(net_id)s does not exist for tenant %(tenant_id)" + % locals())) self._confirm_not_attached(interface_id) uuid = str(utils.gen_uuid()) @@ -76,8 +77,9 @@ class FakeQuantumClientConnection(object): def detach_and_delete_port(self, tenant_id, net_id, port_id): if not self.network_exists(tenant_id, net_id): - raise Exception(_("network %s does not exist for tenant %s" %\ - (net_id, tenant_id))) + raise exception.NotFound( + _("network %s does not exist for tenant %s" % + (net_id, tenant_id))) del self.nets[net_id]['ports'][port_id] def get_port_by_attachment(self, tenant_id, attachment_id): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 153f6c0f2..709299dd2 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -86,7 +86,7 @@ class QuantumManager(manager.FlatManager): if num_networks != 1: raise Exception(_("QuantumManager requires that only one" " network is created per call")) - q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id + q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 1ee0c29a2..4dc35d15d 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -35,7 +35,9 @@ flags.DEFINE_string('melange_port', json_content_type = {'Content-type': "application/json"} - +#FIXME(danwent): talk to the Melange folks about creating a +# client lib that we can import as a library, instead of +# have to have all of the client code in here. class MelangeConnection(object): def __init__(self, host=None, port=None, use_ssl=False): diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 24a7c5404..71c0d7ce6 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -17,6 +17,7 @@ from netaddr import IPNetwork +from nova import exception from nova import flags from nova import log as logging from nova.network.quantum import melange_connection @@ -85,7 +86,7 @@ class QuantumMelangeIPAMLib(object): for b in all_blocks['ip_blocks']: if b['cidr'] == cidr: return b['network_id'] - raise Exception(_("No network found for cidr %s" % cidr)) + raise exception.NotFound(_("No network found for cidr %s" % cidr)) def delete_subnets_by_net_id(self, context, net_id, project_id): """ Find Melange block associated with the Quantum UUID, diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 4b08b906a..17236a976 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -154,7 +154,7 @@ class QuantumNovaIPAMLib(object): vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) fixed_ips = db.fixed_ip_get_by_virtual_interface(context, vif_rec['id']) - return [f['address'] for f in fixed_ips] + return [fixed_ip['address'] for fixed_ip in fixed_ips] def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): """ Returns a list containing a single IPv6 address strings diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 90d5e8e04..93892a843 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -80,8 +80,8 @@ class QuantumClientConnection(object): status to ACTIVE to enable traffic, and attaches the vNIC with the specified interface-id. """ - LOG.debug(_("Connecting interface %s to net %s for %s" % - (interface_id, net_id, tenant_id))) + LOG.debug(_("Connecting interface %(interface_id)s to " + "net %(net_id)s for %(tenant_id)s" % locals())) port_data = {'port': {'state': 'ACTIVE'}} resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) port_id = resdict["port"]["id"] @@ -92,8 +92,8 @@ class QuantumClientConnection(object): def detach_and_delete_port(self, tenant_id, net_id, port_id): """ Detach and delete the specified Quantum port. """ - LOG.debug("Deleting port %s on net %s for %s" % \ - (port_id, net_id, tenant_id)) + LOG.debug(_("Deleting port %(port_id)s on net %(net_id)s" + " for %(tenant_id)s" % locals())) self.client.detach_resource(net_id, port_id, tenant=tenant_id) self.client.delete_port(net_id, port_id, tenant=tenant_id) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 2cc83adf1..0fa4184b1 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -189,29 +189,29 @@ class QuantumTestCaseBase(object): # we don't know which order the NICs will be in until we # introduce the notion of priority # v4 cidr - self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or \ + self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or nw_info[1][0]['cidr'].startswith("9.")) - self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or \ + self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or nw_info[1][0]['cidr'].startswith("192.")) # v4 address - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or \ + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or nw_info[1][1]['ips'][0]['ip'].startswith("9.")) - self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or \ + self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or nw_info[1][1]['ips'][0]['ip'].startswith("192.")) # v6 cidr - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or \ + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or nw_info[1][0]['cidr_v6'].startswith("2001:1dbb:")) - self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or \ + self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or nw_info[1][0]['cidr_v6'].startswith("2001:1db9:")) # v6 address self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or \ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:")) self.assertTrue(\ - nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or \ + nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:")) self.net_man.deallocate_for_instance(ctx, @@ -240,9 +240,10 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): for n in db.network_get_all(ctx): db.network_delete_safe(ctx, n['id']) - # NOTE(danwent): I've found that other unit tests have a nasty + # Other unit tests (e.g., test_compute.py) have a nasty # habit of of creating fixed IPs and not cleaning up, which - # can confuse these tests, so we clean them all. + # can confuse these tests, so we remove all existing fixed + # ips before starting. session = get_session() result = session.query(models.FixedIp).all() with session.begin(): -- cgit From 35c4cecc8d29da32bd816bb68f8b45c2d03f892f Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 09:42:44 -0700 Subject: undo change in setting q_tenant_id in quantum_manager.create_network --- nova/network/quantum/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 709299dd2..153f6c0f2 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -86,7 +86,7 @@ class QuantumManager(manager.FlatManager): if num_networks != 1: raise Exception(_("QuantumManager requires that only one" " network is created per call")) - q_tenant_id = kwargs.get("project_id", FLAGS.quantum_default_tenant_id) + q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id quantum_net_id = uuid if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): -- cgit From a36e0a1cbc5645ab47041c2627dba80b39b23cc2 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 1 Sep 2011 15:42:01 -0400 Subject: Fixes for minor network manager issues centered around deleting/accessing instances which don't have network information set. --- nova/api/openstack/views/addresses.py | 1 - nova/network/manager.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 8f07a2289..8d38bc9c3 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -88,7 +88,6 @@ class ViewBuilderV11(ViewBuilder): try: return interface['network']['label'] except (TypeError, KeyError) as exc: - LOG.exception(exc) raise TypeError def _extract_ipv4_addresses(self, interface): diff --git a/nova/network/manager.py b/nova/network/manager.py index e6b30d1a0..d7b0abdda 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -448,7 +448,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) - except exceptions.FixedIpNotFoundForInstance: + except exception.FixedIpNotFoundForInstance: fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) @@ -484,6 +484,9 @@ class NetworkManager(manager.SchedulerDependentManager): for vif in vifs: network = vif['network'] + if network is None: + continue + # determine which of the instance's IPs belong to this network network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if fixed_ip['network_id'] == network['id']] -- cgit -- cgit From 833ac1674d33cde3721b2d10a3d9545cc8320b37 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 1 Sep 2011 16:09:23 -0400 Subject: Added test for NULL network. --- nova/network/manager.py | 4 ++-- nova/tests/test_network.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index d7b0abdda..e8c5c0654 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -484,8 +484,8 @@ class NetworkManager(manager.SchedulerDependentManager): for vif in vifs: network = vif['network'] - if network is None: - continue + #if network is None: + # continue # determine which of the instance's IPs belong to this network network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 25ff940f0..2ae5a35e3 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -118,9 +118,14 @@ vifs = [{'id': 0, {'id': 1, 'address': 'DE:AD:BE:EF:00:01', 'uuid': '00000000-0000-0000-0000-0000000000000001', - 'network_id': 0, 'network_id': 1, 'network': FakeModel(**networks[1]), + 'instance_id': 0}, + {'id': 2, + 'address': 'DE:AD:BE:EF:00:02', + 'uuid': '00000000-0000-0000-0000-0000000000000002', + 'network_id': 2, + 'network': None, 'instance_id': 0}] -- cgit From aef85a2596e943299542f05e165774250476bc5b Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 1 Sep 2011 16:11:14 -0400 Subject: Probably shouldn't leave that commented out. --- nova/network/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index e8c5c0654..d7b0abdda 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -484,8 +484,8 @@ class NetworkManager(manager.SchedulerDependentManager): for vif in vifs: network = vif['network'] - #if network is None: - # continue + if network is None: + continue # determine which of the instance's IPs belong to this network network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if -- cgit From 43a392814150e49769e935f4972c9901612570af Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 1 Sep 2011 14:03:22 -0700 Subject: added unit tests and cleanup of import statements --- .../api/openstack/contrib/test_createserverext.py | 113 +++++++++++++++++---- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index e5eed14fe..739312399 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -import base64 +import datetime import json import unittest from xml.dom import minidom @@ -26,15 +26,7 @@ import webob from nova import exception from nova import flags from nova import test -from nova import utils import nova.api.openstack -from nova.api.openstack import servers -from nova.api.openstack.contrib import createserverext -import nova.compute.api - -import nova.scheduler.api -import nova.image.fake -import nova.rpc from nova.tests.api.openstack import fakes @@ -51,22 +43,41 @@ DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), INVALID_NETWORKS = [('invalid', 'invalid-ip-address')] +INSTANCE = { + "id": 1, + "display_name": "test_server", + "uuid": FAKE_UUID, + "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), + "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), + "security_groups": [{"id": 1, "name": "test"}] + } + +def return_server_by_id(context, id, session=None): + INSTANCE['id'] = id + return INSTANCE + + +def return_security_group_non_existing(context, project_id, group_name): + raise exception.SecurityGroupNotFoundForProject(project_id=project_id, + security_group_id=group_name) + +def return_security_group_get_by_name(context, project_id, group_name): + return {'id': 1, 'name': group_name} + + +def return_security_group_get(context, security_group_id, session): + return {'id': security_group_id} + + +def return_instance_add_security_group(context, instance_id, security_group_id): + pass class CreateserverextTest(test.TestCase): def setUp(self): super(CreateserverextTest, self).setUp() - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.auth_data = {} - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_auth(self.stubs) - fakes.stub_out_image_service(self.stubs) - fakes.stub_out_key_pair_funcs(self.stubs) - self.allow_admin = FLAGS.allow_admin_api def tearDown(self): - self.stubs.UnsetAll() - FLAGS.allow_admin_api = self.allow_admin super(CreateserverextTest, self).tearDown() def _setup_mock_compute_api(self): @@ -87,6 +98,7 @@ class CreateserverextTest(test.TestCase): self.networks = kwargs['requested_networks'] else: self.networks = None + return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID, 'created_at': "", @@ -107,6 +119,18 @@ class CreateserverextTest(test.TestCase): '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) return compute_api + def _create_security_group_request_dict(self, security_groups): + server = {} + server['name'] = 'new-server-test' + server['imageRef'] = 1 + server['flavorRef'] = 1 + if security_groups is not None: + sg_list = [] + for name in security_groups: + sg_list.append({'name': name}) + server['security_groups'] = sg_list + return {'server': server} + def _create_networks_request_dict(self, networks): server = {} server['name'] = 'new-server-test' @@ -304,3 +328,56 @@ class CreateserverextTest(test.TestCase): self.assertEquals(response.status_int, 202) self.assertEquals(compute_api.networks, [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) + + def test_create_instance_with_security_group_empty_list_json(self): + security_groups = [] + body_dict = self._create_security_group_request_dict(security_groups) + request = self._get_create_request_json(body_dict) + response = request.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + res_dict = json.loads(response.body) + self.assertEquals([{'name': "default"}], res_dict['server']['security_groups']) + + def test_create_instance_with_security_group_non_existing_json(self): + security_groups = ['non-existing'] + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_non_existing) + body_dict = self._create_security_group_request_dict(security_groups) + request = self._get_create_request_json(body_dict) + response = request.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_create_instance_with_security_group_json(self): + security_groups = ['test', 'test1'] + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_get_by_name) + self.stubs.Set(nova.db.api, 'instance_add_security_group', + return_instance_add_security_group) + body_dict = self._create_security_group_request_dict(security_groups) + request = self._get_create_request_json(body_dict) + response = request.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_get_server_by_id_verify_security_groups_json(self): + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + req = webob.Request.blank('/v1.1/123/os-create-server-ext/1') + req.headers['Content-Type'] = 'application/json' + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 200) + res_dict = json.loads(response.body) + expected_security_group = [{"name": "test"}] + self.assertEquals(res_dict['server']['security_groups'], + expected_security_group) + + def test_get_server_by_id_verify_security_groups_xml(self): + self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) + req = webob.Request.blank('/v1.1/123/os-create-server-ext/1') + req.headers['Accept'] = 'application/xml' + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 200) + dom = minidom.parseString(response.body) + server = dom.childNodes[0] + security_groups = server.getElementsByTagName('security_groups')[0] + security_group = security_groups.getElementsByTagName('security_group')[0] + self.assertEqual(INSTANCE['security_groups'][0]['name'], security_group.getAttribute("name")) + -- cgit From 2d2d9a5f5caed27d9ade06b2dbc56b793b7e5d3b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 1 Sep 2011 14:32:48 -0700 Subject: Deleted debug messages --- nova/tests/api/openstack/contrib/test_createserverext.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index d6600d054..ba8fb925e 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -32,8 +32,6 @@ import nova.api.openstack from nova.tests.api.openstack import fakes -from nova import log as logging -LOG = logging.getLogger("api.nova.openstack.etere") FLAGS = flags.FLAGS FLAGS.verbose = True @@ -388,7 +386,6 @@ class CreateserverextTest(test.TestCase): body_dict = self._create_security_group_request_dict(security_groups) request = self._get_create_request_json(body_dict) response = request.get_response(fakes.wsgi_app()) - LOG.debug(response) self.assertEquals(response.status_int, 202) def test_get_server_by_id_verify_security_groups_json(self): -- cgit From f0a6c35149a1b9cc278cd3ba960861da9189b5bf Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 14:39:34 -0700 Subject: remove references to MelangeIPAMTest, as they cannot be used yet --- nova/tests/test_quantum.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 0fa4184b1..42acf03e7 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -249,13 +249,3 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): with session.begin(): for fip_ref in result: session.delete(fip_ref) - -# FIXME(danwent): Cannot run this unit tests automatically for now, as -# it requires melange to be running locally. -# -#class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase): -# -# def setUp(self): -# super(QuantumMelangeIPAMTestCase, self).setUp() -# self.net_man = quantum_manager.QuantumManager( \ -# ipam_lib="nova.network.quantum.melange_ipam_lib") -- cgit From 527670d632788d20aca7a3f12495d4c97e036d51 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 14:40:30 -0700 Subject: melange testing cleanup, localization cleanup --- nova/network/quantum/client.py | 5 +++-- nova/network/quantum/fake.py | 4 ++-- nova/network/quantum/manager.py | 19 +++++++++---------- nova/network/quantum/melange_connection.py | 19 +++++++++++-------- nova/network/quantum/melange_ipam_lib.py | 7 ++++--- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index 455bb8a79..f4936695e 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -172,8 +172,9 @@ class Client(object): c = connection_type(self.host, self.port) if self.logger: - self.logger.debug(_("Quantum Client Request:\n%s %s\n" % - (method, action))) + self.logger.debug( + _("Quantum Client Request:\n%(method)s %(action)s\n" % + locals())) if body: self.logger.debug(body) diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py index 6a4005c59..4ecddd2ae 100644 --- a/nova/network/quantum/fake.py +++ b/nova/network/quantum/fake.py @@ -78,8 +78,8 @@ class FakeQuantumClientConnection(object): def detach_and_delete_port(self, tenant_id, net_id, port_id): if not self.network_exists(tenant_id, net_id): raise exception.NotFound( - _("network %s does not exist for tenant %s" % - (net_id, tenant_id))) + _("network %(net_id)s does not exist " + "for tenant %(tenant_id)s" % locals())) del self.nets[net_id]['ports'][port_id] def get_port_by_attachment(self, tenant_id, attachment_id): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 153f6c0f2..fa16475ac 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -91,8 +91,8 @@ class QuantumManager(manager.FlatManager): if quantum_net_id: if not self.q_conn.network_exists(q_tenant_id, quantum_net_id): raise Exception(_("Unable to find existing quantum " \ - " network for tenant '%s' with net-id '%s'" % \ - (q_tenant_id, quantum_net_id))) + " network for tenant '%(q_tenant_id)s' with " + "net-id '%(quantum_net_id)s'" % locals())) else: # otherwise, create network from default quantum pool quantum_net_id = self.q_conn.create_network(q_tenant_id, label) @@ -252,17 +252,18 @@ class QuantumManager(manager.FlatManager): 'dns': [], 'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]} - if v6_subnet['cidr']: - network_dict['cidr_v6'] = v6_subnet['cidr'] - info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] + if v6_subnet: + if v6_subnet['cidr']: + network_dict['cidr_v6'] = v6_subnet['cidr'] + info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips] - if v6_subnet['gateway']: - info['gateway6'] = v6_subnet['gateway'] + if v6_subnet['gateway']: + info['gateway6'] = v6_subnet['gateway'] dns_dict = {} for s in [v4_subnet, v6_subnet]: for k in ['dns1', 'dns2']: - if s[k]: + if s and s[k]: dns_dict[s[k]] = None info['dns'] = [d for d in dns_dict.keys()] @@ -308,8 +309,6 @@ class QuantumManager(manager.FlatManager): except exception.InstanceNotFound: LOG.error(_("Attempted to deallocate non-existent instance: %s" % (instance_id))) - self._do_trigger_security_group_members_refresh_for_instance( - instance_id) def validate_networks(self, context, networks): """ Validates that this tenant has quantum networks with the associated diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 4dc35d15d..5a79eff77 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -48,6 +48,7 @@ class MelangeConnection(object): self.host = host self.port = port self.use_ssl = use_ssl + self.version = "v0.1" def get(self, path, params={}, headers={}): return self.do_request("GET", path, params=params, headers=headers) @@ -66,7 +67,9 @@ class MelangeConnection(object): def do_request(self, method, path, body=None, headers={}, params={}): - url = path + '.json?' + urllib.urlencode(params) + url = "/%s/%s.json?%s" % (self.version, + path, + urllib.urlencode(params)) try: connection = self._get_connection() @@ -75,7 +78,7 @@ class MelangeConnection(object): response_str = response.read() if response.status < 400: return response_str - raise Exception(_("Server returned error: %s", response_str)) + raise Exception(_("Server returned error: %s" % response_str)) except (socket.error, IOError), e: raise Exception(_("Unable to connect to " "server. Got error: %s" % e)) @@ -86,7 +89,7 @@ class MelangeConnection(object): request_body = (json.dumps(dict(network=dict(mac_address=mac_address, tenant_id=project_id))) if mac_address else None) - url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + url = ("ipam%(tenant_scope)s/networks/%(network_id)s/" "interfaces/%(vif_id)s/ip_allocations" % locals()) response = self.post(url, body=request_body, headers=json_content_type) @@ -96,7 +99,7 @@ class MelangeConnection(object): project_id=None, dns1=None, dns2=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + url = "ipam%(tenant_scope)s/ip_blocks" % locals() req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id, type='private', dns1=dns1, dns2=dns2)) @@ -106,14 +109,14 @@ class MelangeConnection(object): def delete_block(self, block_id, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = "/ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals() + url = "ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals() self.delete(url, headers=json_content_type) def get_blocks(self, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = "/ipam%(tenant_scope)s/ip_blocks" % locals() + url = "ipam%(tenant_scope)s/ip_blocks" % locals() response = self.get(url, headers=json_content_type) return json.loads(response) @@ -121,7 +124,7 @@ class MelangeConnection(object): def get_allocated_ips(self, network_id, vif_id, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + url = ("ipam%(tenant_scope)s/networks/%(network_id)s/" "interfaces/%(vif_id)s/ip_allocations" % locals()) response = self.get(url, headers=json_content_type) @@ -130,7 +133,7 @@ class MelangeConnection(object): def deallocate_ips(self, network_id, vif_id, project_id=None): tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/" + url = ("ipam%(tenant_scope)s/networks/%(network_id)s/" "interfaces/%(vif_id)s/ip_allocations" % locals()) self.delete(url, headers=json_content_type) diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index 71c0d7ce6..dcee0a3f9 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -17,6 +17,7 @@ from netaddr import IPNetwork +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -68,7 +69,8 @@ class QuantumMelangeIPAMLib(object): "project_id": project_id, "priority": priority, "label": label} - network = self.db.network_create_safe(context, net) + admin_context = context.elevated() + network = db.network_create_safe(admin_context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): """ Pass call to allocate fixed IP on to Melange""" @@ -100,8 +102,7 @@ class QuantumMelangeIPAMLib(object): self.m_conn.delete_block(b['id'], tenant_id) network = db.network_get_by_uuid(admin_context, net_id) - if network is not None: - db.network_delete_safe(context, network['id']) + db.network_delete_safe(context, network['id']) def get_project_and_global_net_ids(self, context, project_id): """ Fetches all networks associated with this project, or -- cgit From 38373bf8f60dd068dec69933d1456a8deb75bf8e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 1 Sep 2011 15:02:09 -0700 Subject: move content of quantum/fake.py to test_quantum.py in unit testing class (most original content has been removed anyway) --- nova/network/quantum/fake.py | 92 ----------------------------------------- nova/network/quantum/manager.py | 9 ++-- nova/tests/test_quantum.py | 76 +++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 99 deletions(-) delete mode 100644 nova/network/quantum/fake.py diff --git a/nova/network/quantum/fake.py b/nova/network/quantum/fake.py deleted file mode 100644 index 4ecddd2ae..000000000 --- a/nova/network/quantum/fake.py +++ /dev/null @@ -1,92 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Nicira Networks, 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 nova import exception -from nova import ipv6 -from nova import log as logging -from nova import utils - - -LOG = logging.getLogger("network.quantum.fake") - - -# this class can be used for unit functional/testing on nova, -# as it does not actually make remote calls to the Quantum service -class FakeQuantumClientConnection(object): - - def __init__(self): - self.nets = {} - - def get_networks_for_tenant(self, tenant_id): - net_ids = [] - for net_id, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - net_ids.append(net_id) - return net_ids - - def create_network(self, tenant_id, network_name): - - uuid = str(utils.gen_uuid()) - self.nets[uuid] = {'net-name': network_name, - 'tenant-id': tenant_id, - 'ports': {}} - return uuid - - def delete_network(self, tenant_id, net_id): - if self.nets[net_id]['tenant-id'] == tenant_id: - del self.nets[net_id] - - def network_exists(self, tenant_id, net_id): - try: - return self.nets[net_id]['tenant-id'] == tenant_id - except KeyError: - return False - - def _confirm_not_attached(self, interface_id): - for n in self.nets.values(): - for p in n['ports'].values(): - if p['attachment-id'] == interface_id: - raise Exception(_("interface '%s' is already attached" % - interface_id)) - - def create_and_attach_port(self, tenant_id, net_id, interface_id): - if not self.network_exists(tenant_id, net_id): - raise Exception( - _("network %(net_id)s does not exist for tenant %(tenant_id)" - % locals())) - - self._confirm_not_attached(interface_id) - uuid = str(utils.gen_uuid()) - self.nets[net_id]['ports'][uuid] = \ - {"port-state": "ACTIVE", - "attachment-id": interface_id} - - def detach_and_delete_port(self, tenant_id, net_id, port_id): - if not self.network_exists(tenant_id, net_id): - raise exception.NotFound( - _("network %(net_id)s does not exist " - "for tenant %(tenant_id)s" % locals())) - del self.nets[net_id]['ports'][port_id] - - def get_port_by_attachment(self, tenant_id, attachment_id): - for net_id, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - for port_id, p in n['ports'].items(): - if p['attachment-id'] == attachment_id: - return (net_id, port_id) - - return (None, None) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index fa16475ac..db2b000de 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -50,17 +50,16 @@ class QuantumManager(manager.FlatManager): Support for these capabilities are targted for future releases. """ - def __init__(self, ipam_lib=None, *args, **kwargs): + def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs): """ Initialize two key libraries, the connection to a Quantum service, and the library for implementing IPAM. Calls inherited FlatManager constructor. """ - if FLAGS.fake_network: - self.q_conn = fake.FakeQuantumClientConnection() - else: - self.q_conn = quantum_connection.QuantumClientConnection() + if not q_conn: + q_conn = quantum_connection.QuantumClientConnection() + self.q_conn = q_conn if not ipam_lib: ipam_lib = FLAGS.quantum_ipam_lib diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 42acf03e7..0b1a1f204 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -20,12 +20,82 @@ from nova import db from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from nova import exception +from nova import ipv6 from nova import log as logging from nova.network.quantum import manager as quantum_manager from nova import test +from nova import utils LOG = logging.getLogger('nova.tests.quantum_network') + +# this class can be used for unit functional/testing on nova, +# as it does not actually make remote calls to the Quantum service +class FakeQuantumClientConnection(object): + + def __init__(self): + self.nets = {} + + def get_networks_for_tenant(self, tenant_id): + net_ids = [] + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + net_ids.append(net_id) + return net_ids + + def create_network(self, tenant_id, network_name): + + uuid = str(utils.gen_uuid()) + self.nets[uuid] = {'net-name': network_name, + 'tenant-id': tenant_id, + 'ports': {}} + return uuid + + def delete_network(self, tenant_id, net_id): + if self.nets[net_id]['tenant-id'] == tenant_id: + del self.nets[net_id] + + def network_exists(self, tenant_id, net_id): + try: + return self.nets[net_id]['tenant-id'] == tenant_id + except KeyError: + return False + + def _confirm_not_attached(self, interface_id): + for n in self.nets.values(): + for p in n['ports'].values(): + if p['attachment-id'] == interface_id: + raise Exception(_("interface '%s' is already attached" % + interface_id)) + + def create_and_attach_port(self, tenant_id, net_id, interface_id): + if not self.network_exists(tenant_id, net_id): + raise Exception( + _("network %(net_id)s does not exist for tenant %(tenant_id)" + % locals())) + + self._confirm_not_attached(interface_id) + uuid = str(utils.gen_uuid()) + self.nets[net_id]['ports'][uuid] = \ + {"port-state": "ACTIVE", + "attachment-id": interface_id} + + def detach_and_delete_port(self, tenant_id, net_id, port_id): + if not self.network_exists(tenant_id, net_id): + raise exception.NotFound( + _("network %(net_id)s does not exist " + "for tenant %(tenant_id)s" % locals())) + del self.nets[net_id]['ports'][port_id] + + def get_port_by_attachment(self, tenant_id, attachment_id): + for net_id, n in self.nets.items(): + if n['tenant-id'] == tenant_id: + for port_id, p in n['ports'].items(): + if p['attachment-id'] == attachment_id: + return (net_id, port_id) + + return (None, None) + networks = [{'label': 'project1-net1', 'injected': False, 'multi_host': False, @@ -230,8 +300,10 @@ class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase): def setUp(self): super(QuantumNovaIPAMTestCase, self).setUp() - self.net_man = quantum_manager.QuantumManager( \ - ipam_lib="nova.network.quantum.nova_ipam_lib") + + self.net_man = quantum_manager.QuantumManager( + ipam_lib="nova.network.quantum.nova_ipam_lib", + q_conn=FakeQuantumClientConnection()) # Tests seem to create some networks by default, which # we don't want. So we delete them. -- cgit From e6e3f46bf449fa371a584720c12c21e0832f4160 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 1 Sep 2011 17:15:21 -0500 Subject: import filters in scheduler/host_filter.py so default_host_filter gets added to FLAGS; rework SchedulerManager() to only catch missing 'schedule_' attribute and report other missing attributes --- nova/scheduler/host_filter.py | 1 + nova/scheduler/manager.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 826a99b0a..4024ec854 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -31,6 +31,7 @@ import types from nova import exception from nova import flags import nova.scheduler +from nova.scheduler import filters FLAGS = flags.FLAGS diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 0e395ee79..92bb1ed6e 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -93,12 +93,14 @@ class SchedulerManager(manager.Manager): driver_method = 'schedule_%s' % method elevated = context.elevated() try: - host = getattr(self.driver, driver_method)(elevated, *args, - **kwargs) + real_meth = getattr(self.driver, driver_method) + args = (elevated,) + args except AttributeError, e: LOG.warning(_("Driver Method %(driver_method)s missing: %(e)s." - "Reverting to schedule()") % locals()) - host = self.driver.schedule(elevated, topic, *args, **kwargs) + "Reverting to schedule()") % locals()) + real_meth = self.driver.schedule + args = (elevated, topic) + args + real_meth(*args, **kwargs) if not host: LOG.debug(_("%(topic)s %(method)s handled in Scheduler") -- cgit From 1081b9d52026afb84128c15a1df0998f80810ce9 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 15:48:32 +0200 Subject: Correct tests associated. --- nova/tests/api/openstack/test_servers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3559e6de5..e5ebedf0e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3490,10 +3490,14 @@ class TestGetKernelRamdiskFromImage(test.TestCase): self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): - """If an ami is missing a ramdisk it should raise NotFound""" + """If an ami is missing a ramdisk, return kernel ID and None for + ramdisk ID + """ image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', 'properties': {'kernel_id': 1}} - self.assertRaises(exception.NotFound, self._get_k_r, image_meta) + kernel_id, ramdisk_id = self._get_k_r(image_meta) + self.assertEqual(kernel_id, 1) + self.assertEqual(ramdisk_id, None) def test_ami_kernel_ramdisk_present(self): """Return IDs if both kernel and ramdisk are present""" -- cgit From c5a90f842c99f94af2e321051d85e81f49c1e692 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 16:01:13 +0200 Subject: Update Authors file. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index b9e7a7d23..2961b90ef 100644 --- a/Authors +++ b/Authors @@ -30,6 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe +Edouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 78bb09eacedb91f0b5bf294ede9372768409590b Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 16:17:18 +0200 Subject: Email contact error. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 2961b90ef..eff58692e 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Edouard Thuleau +Edouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From f119805aa7c8e2dd7f0bafe666d976f3a0c08795 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 2 Sep 2011 11:00:03 -0500 Subject: Forgot to handle return value --- nova/scheduler/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 92bb1ed6e..bf18abc6c 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -100,7 +100,7 @@ class SchedulerManager(manager.Manager): "Reverting to schedule()") % locals()) real_meth = self.driver.schedule args = (elevated, topic) + args - real_meth(*args, **kwargs) + host = real_meth(*args, **kwargs) if not host: LOG.debug(_("%(topic)s %(method)s handled in Scheduler") -- cgit From 666f7152910838f866ca4b76258b025c27744ffb Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 2 Sep 2011 12:31:10 -0500 Subject: Add documentation comment --- nova/scheduler/host_filter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 4024ec854..9f7d34ea7 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -31,6 +31,11 @@ import types from nova import exception from nova import flags import nova.scheduler + +# NOTE(Vek): Even though we don't use filters in here anywhere, we +# depend on default_host_filter being available in FLAGS, +# and that happens only when filters/abstract_filter.py is +# imported. from nova.scheduler import filters -- cgit From c5acb6186318541b1743bf62daa0510c9dba9c48 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 20:25:18 +0200 Subject: Email error. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index eff58692e..1160a0741 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Edouard Thuleau +Édouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 55f0a6a058546f0ffbf4bee0e92eea2e70d8c76f Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Fri, 2 Sep 2011 20:29:58 +0200 Subject: Email error again. Tired. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 1160a0741..0beab16a4 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Édouard Thuleau +Édouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 5fe5c5dc26276a10b7dc766104a7e2d6c7793dc3 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 11:51:55 -0700 Subject: remove import of 'fake' from nova manager, now that we've moved that to test_quantum.py --- nova/network/quantum/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index db2b000de..c10dc90de 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -22,7 +22,6 @@ from nova import log as logging from nova import manager from nova.network import manager from nova.network.quantum import quantum_connection -from nova.network.quantum import fake from nova import utils LOG = logging.getLogger("quantum_manager") -- cgit From bd1bc5e3c6f52963ce088e2e0a6da41f125d29f1 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 12:11:28 -0700 Subject: more review cleanup --- .../migrate_repo/versions/044_add_network_priority.py | 4 +--- nova/network/quantum/melange_connection.py | 17 +++++++++-------- nova/tests/test_quantum.py | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py index e69380199..e3ee6a85f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -25,13 +25,11 @@ meta = MetaData() # Add priority column to networks table priority = Column('priority', Integer()) +networks = Table('networks', meta, autoload=True) def upgrade(migrate_engine): meta.bind = migrate_engine - # grab tables and (column for dropping later) - networks = Table('networks', meta, autoload=True) - try: networks.create_column(priority) except Exception: diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 5a79eff77..d5a1901aa 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -50,13 +50,13 @@ class MelangeConnection(object): self.use_ssl = use_ssl self.version = "v0.1" - def get(self, path, params={}, headers={}): + def get(self, path, params=None, headers=None): return self.do_request("GET", path, params=params, headers=headers) - def post(self, path, body=None, headers={}): + def post(self, path, body=None, headers=None): return self.do_request("POST", path, body=body, headers=headers) - def delete(self, path, headers={}): + def delete(self, path, headers=None): return self.do_request("DELETE", path, headers=headers) def _get_connection(self): @@ -65,12 +65,13 @@ class MelangeConnection(object): else: return httplib.HTTPConnection(self.host, self.port) - def do_request(self, method, path, body=None, headers={}, params={}): - - url = "/%s/%s.json?%s" % (self.version, - path, - urllib.urlencode(params)) + def do_request(self, method, path, body=None, headers=None, params=None): + headers = headers or {} + params = params or {} + url = "/%s/%s.json" % (self.version, path) + if params: + url += "?%s" % urllib.urlencode(params) try: connection = self._get_connection() connection.request(method, url, body, headers) diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 0b1a1f204..0feec9b99 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -277,10 +277,10 @@ class QuantumTestCaseBase(object): nw_info[1][0]['cidr_v6'].startswith("2001:1db9:")) # v6 address - self.assertTrue(\ + self.assertTrue( nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:")) - self.assertTrue(\ + self.assertTrue( nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:")) -- cgit From 435016f27ea36a6780897efe1289328c51e1463f Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 12:31:14 -0700 Subject: move networks declarations within upgrade/downgrade methods --- nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py index e3ee6a85f..9db950c9b 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -25,11 +25,11 @@ meta = MetaData() # Add priority column to networks table priority = Column('priority', Integer()) -networks = Table('networks', meta, autoload=True) def upgrade(migrate_engine): meta.bind = migrate_engine + networks = Table('networks', meta, autoload=True) try: networks.create_column(priority) except Exception: @@ -39,4 +39,6 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): meta.bind = migrate_engine + + networks = Table('networks', meta, autoload=True) networks.drop_column(priority) -- cgit From bcb6f7d570ed24e0bf083cd4f4c8be0f20e69918 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 12:40:55 -0700 Subject: change db migrate script again to match other similar scripts --- .../db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py index 9db950c9b..b9b0ea37c 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/044_add_network_priority.py @@ -22,6 +22,9 @@ from nova import utils meta = MetaData() +networks = Table('networks', meta, + Column("id", Integer(), primary_key=True, nullable=False)) + # Add priority column to networks table priority = Column('priority', Integer()) @@ -29,7 +32,6 @@ priority = Column('priority', Integer()) def upgrade(migrate_engine): meta.bind = migrate_engine - networks = Table('networks', meta, autoload=True) try: networks.create_column(priority) except Exception: @@ -39,6 +41,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): meta.bind = migrate_engine - - networks = Table('networks', meta, autoload=True) networks.drop_column(priority) -- cgit From cc3bd1da5edc368871d2c8de0e498ab2649ae0dd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 2 Sep 2011 12:52:02 -0700 Subject: revert description changes, use metadata['description'] if it is set to populate field in db --- nova/api/openstack/create_instance_helper.py | 9 ++++++--- nova/api/openstack/schemas/v1.1/server.rng | 1 - nova/api/openstack/servers.py | 6 +----- nova/api/openstack/views/servers.py | 1 - nova/tests/api/openstack/test_servers.py | 28 ---------------------------- 5 files changed, 7 insertions(+), 38 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index ff3be4a01..289f87921 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -161,6 +161,10 @@ class CreateInstanceHelper(object): 'config_drive': config_drive, 'password': password} + # NOTE(vish): This is solely for compatibility with anything + # that is using the display description field. + metadata = server_dict.get('metadata') or {} + display_description = metadata.get('description') or '' return (extra_values, create_method(context, inst_type, @@ -168,10 +172,9 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=server_dict.\ - get('description', ''), + display_description=display_description, key_name=key_name, - metadata=server_dict.get('metadata', {}), + metadata=metadata, access_ip_v4=server_dict.get('accessIPv4'), access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng index 203728f48..ef835e408 100644 --- a/nova/api/openstack/schemas/v1.1/server.rng +++ b/nova/api/openstack/schemas/v1.1/server.rng @@ -3,7 +3,6 @@ - diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7f5463e70..46a111cf5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -185,9 +185,6 @@ class Controller(object): self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() - if 'description' in body['server']: - description = body['server']['description'] - update_dict['display_description'] = description.strip() if 'accessIPv4' in body['server']: access_ipv4 = body['server']['accessIPv4'] update_dict['access_ip_v4'] = access_ipv4.strip() @@ -881,7 +878,6 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('uuid', str(server['uuid'])) node.setAttribute('hostId', str(server['hostId'])) node.setAttribute('name', server['name']) - node.setAttribute('description', server['description']) node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) @@ -997,7 +993,7 @@ def create_resource(version='1.0'): "attributes": { "server": ["id", "imageId", "name", "flavorId", "hostId", "status", "progress", "adminPass", "flavorRef", - "imageRef", "userId", "tenantId", "description"], + "imageRef", "userId", "tenantId"], "link": ["rel", "type", "href"], }, "dict_collections": { diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 98ccd817a..ac09b5864 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -68,7 +68,6 @@ class ViewBuilder(object): 'name': inst['display_name'], 'user_id': inst.get('user_id', ''), 'tenant_id': inst.get('project_id', ''), - 'description': inst.get('display_description', ''), 'status': common.status_from_state(vm_state, task_state)} # Return the metadata as a dictionary diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d5de1aa3c..45ad6e5a8 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2268,34 +2268,6 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['name'], 'server_test') - def test_update_server_description_v1_1(self): - DESC = 'updated_desc' - - def server_update(context, id, params): - # assert that parameter conversion from description - # to display_description worked correctly - self.assertEqual(params.get('display_description'), DESC) - return stub_instance(1, - name='server_test', - description=params['display_description']) - - self.stubs.Set(nova.db.api, 'instance_get', - return_server_with_attributes(name='server_test', - description=DESC)) - - self.stubs.Set(nova.db.api, 'instance_update', - server_update) - - req = webob.Request.blank('/v1.1/fake/servers/1') - req.method = 'PUT' - req.content_type = 'application/json' - req.body = json.dumps({'server': {'description': DESC}}) - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - res_dict = json.loads(res.body) - self.assertEqual(res_dict['server']['id'], 1) - self.assertEqual(res_dict['server']['description'], DESC) - def test_update_server_access_ipv4_v1_1(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(access_ipv4='0.0.0.0')) -- cgit From 752b6c9e26b718ab86f04c25a8c7f977bbea4a22 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 13:05:24 -0700 Subject: feedback from jk0's review, including removing a lot of spaces from docstrings --- bin/nova-manage | 3 +- nova/db/sqlalchemy/api.py | 2 +- nova/network/manager.py | 2 +- nova/network/quantum/client.py | 45 +++++++++--------- nova/network/quantum/manager.py | 76 +++++++++++++++--------------- nova/network/quantum/melange_connection.py | 2 +- nova/network/quantum/melange_ipam_lib.py | 63 +++++++++++++------------ nova/network/quantum/nova_ipam_lib.py | 56 +++++++++++----------- nova/network/quantum/quantum_connection.py | 34 ++++++------- 9 files changed, 141 insertions(+), 142 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 3a17818b2..6dd2920d1 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -779,8 +779,7 @@ class NetworkCommands(object): def list(self): """List all created networks""" - _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\ - "\t%-15s\t%-15s" + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" print _fmt % (_('id'), _('IPv4'), _('IPv6'), diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 09356e966..e0da2269d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -946,7 +946,7 @@ def virtual_interface_get_by_address(context, address): def virtual_interface_get_by_uuid(context, vif_uuid): """Gets a virtual interface from the table. - :param vif_uuid: = the uuid of the interface you're looking to get + :param vif_uuid: the uuid of the interface you're looking to get """ session = get_session() vif_ref = session.query(models.VirtualInterface).\ diff --git a/nova/network/manager.py b/nova/network/manager.py index 426ff2f33..6730e808f 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -547,7 +547,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'network_id': network_id, 'uuid': str(utils.gen_uuid())} # try FLAG times to create a vif record with a unique mac_address - for i in xrange(FLAGS.create_unique_mac_address_attempts): + for _ in xrange(FLAGS.create_unique_mac_address_attempts): try: return self.db.virtual_interface_create(context, vif) except exception.VirtualInterfaceCreateException: diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index f4936695e..40c68dfdc 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -22,14 +22,14 @@ import socket import urllib -#FIXME(danwent): All content in this file should be removed once the +# FIXME(danwent): All content in this file should be removed once the # packaging work for the quantum client libraries is complete. # At that point, we will be able to just install the libraries as a # dependency and import from quantum.client.* and quantum.common.* # Until then, we have simplified versions of these classes in this file. class JSONSerializer(object): - """ This is a simple json-only serializer to use until we can just grab + """This is a simple json-only serializer to use until we can just grab the standard serializer from the quantum library. """ def serialize(self, data, content_type): @@ -47,17 +47,17 @@ class JSONSerializer(object): # granular exceptions, for now, just try to distinguish # between the cases we care about. class QuantumNotFoundException(Exception): - """ Indicates that Quantum Server returned 404""" + """Indicates that Quantum Server returned 404""" pass class QuantumServerException(Exception): - """ Indicates any non-404 error from Quantum Server""" + """Indicates any non-404 error from Quantum Server""" pass class QuantumIOException(Exception): - """ Indicates network IO trouble reaching Quantum Server""" + """Indicates network IO trouble reaching Quantum Server""" pass @@ -100,7 +100,7 @@ class Client(object): def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, format="xml", testing_stub=None, key_file=None, cert_file=None, logger=None): - """ Creates a new client to some service. + """Creates a new client to some service. :param host: The host where service resides :param port: The port where service resides @@ -123,7 +123,7 @@ class Client(object): self.logger = logger def get_connection_type(self): - """ Returns the proper connection type """ + """Returns the proper connection type""" if self.testing_stub: return self.testing_stub elif self.use_ssl: @@ -133,7 +133,7 @@ class Client(object): def do_request(self, method, action, body=None, headers=None, params=None): - """ Connects to the server and issues a request. + """Connects to the server and issues a request. Returns the result data, or raises an appropriate exception if HTTP status code is not 2xx @@ -142,7 +142,6 @@ class Client(object): :param headers: mapping of key/value pairs to add as headers :param params: dictionary of key/value pairs to add to append to action - """ # Ensure we have a tenant id @@ -207,7 +206,7 @@ class Client(object): "server. Got error: %s" % e)) def get_status_code(self, response): - """ Returns the integer status code from the response, which + """Returns the integer status code from the response, which can be either a Webob.Response (used in testing) or httplib.Response """ if hasattr(response, 'status_int'): @@ -236,73 +235,73 @@ class Client(object): @api_call def list_networks(self): - """ Fetches a list of all networks for a tenant """ + """Fetches a list of all networks for a tenant""" return self.do_request("GET", self.networks_path) @api_call def show_network_details(self, network): - """ Fetches the details of a certain network """ + """Fetches the details of a certain network""" return self.do_request("GET", self.network_path % (network)) @api_call def create_network(self, body=None): - """ Creates a new network """ + """Creates a new network""" body = self.serialize(body) return self.do_request("POST", self.networks_path, body=body) @api_call def update_network(self, network, body=None): - """ Updates a network """ + """Updates a network""" body = self.serialize(body) return self.do_request("PUT", self.network_path % (network), body=body) @api_call def delete_network(self, network): - """ Deletes the specified network """ + """Deletes the specified network""" return self.do_request("DELETE", self.network_path % (network)) @api_call def list_ports(self, network): - """ Fetches a list of ports on a given network """ + """Fetches a list of ports on a given network""" return self.do_request("GET", self.ports_path % (network)) @api_call def show_port_details(self, network, port): - """ Fetches the details of a certain port """ + """Fetches the details of a certain port""" return self.do_request("GET", self.port_path % (network, port)) @api_call def create_port(self, network, body=None): - """ Creates a new port on a given network """ + """Creates a new port on a given network""" body = self.serialize(body) return self.do_request("POST", self.ports_path % (network), body=body) @api_call def delete_port(self, network, port): - """ Deletes the specified port from a network """ + """Deletes the specified port from a network""" return self.do_request("DELETE", self.port_path % (network, port)) @api_call def set_port_state(self, network, port, body=None): - """ Sets the state of the specified port """ + """Sets the state of the specified port""" body = self.serialize(body) return self.do_request("PUT", self.port_path % (network, port), body=body) @api_call def show_port_attachment(self, network, port): - """ Fetches the attachment-id associated with the specified port """ + """Fetches the attachment-id associated with the specified port""" return self.do_request("GET", self.attachment_path % (network, port)) @api_call def attach_resource(self, network, port, body=None): - """ Sets the attachment-id of the specified port """ + """Sets the attachment-id of the specified port""" body = self.serialize(body) return self.do_request("PUT", self.attachment_path % (network, port), body=body) @api_call def detach_resource(self, network, port): - """ Removes the attachment-id of the specified port """ + """Removes the attachment-id of the specified port""" return self.do_request("DELETE", self.attachment_path % (network, port)) diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index c10dc90de..23a9aba0d 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -24,7 +24,7 @@ from nova.network import manager from nova.network.quantum import quantum_connection from nova import utils -LOG = logging.getLogger("quantum_manager") +LOG = logging.getLogger("nova.network.quantum.manager") FLAGS = flags.FLAGS @@ -34,26 +34,26 @@ flags.DEFINE_string('quantum_ipam_lib', class QuantumManager(manager.FlatManager): - """ NetworkManager class that communicates with a Quantum service - via a web services API to provision VM network connectivity. + """NetworkManager class that communicates with a Quantum service + via a web services API to provision VM network connectivity. - For IP Address management, QuantumManager can be configured to - use either Nova's local DB or the Melange IPAM service. + For IP Address management, QuantumManager can be configured to + use either Nova's local DB or the Melange IPAM service. - Currently, the QuantumManager does NOT support any of the 'gateway' - functionality implemented by the Nova VlanManager, including: + Currently, the QuantumManager does NOT support any of the 'gateway' + functionality implemented by the Nova VlanManager, including: * floating IPs * DHCP * NAT gateway - Support for these capabilities are targted for future releases. + Support for these capabilities are targted for future releases. """ def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs): - """ Initialize two key libraries, the connection to a - Quantum service, and the library for implementing IPAM. + """Initialize two key libraries, the connection to a + Quantum service, and the library for implementing IPAM. - Calls inherited FlatManager constructor. + Calls inherited FlatManager constructor. """ if not q_conn: @@ -70,16 +70,16 @@ class QuantumManager(manager.FlatManager): network_size, cidr_v6, gateway_v6, bridge, bridge_interface, dns1=None, dns2=None, uuid=None, **kwargs): - """ Unlike other NetworkManagers, with QuantumManager, each - create_networks calls should create only a single network. + """Unlike other NetworkManagers, with QuantumManager, each + create_networks calls should create only a single network. - Two scenarios exist: + Two scenarios exist: - no 'uuid' is specified, in which case we contact Quantum and create a new network. - an existing 'uuid' is specified, corresponding to a Quantum network created out of band. - In both cases, we initialize a subnet using the IPAM lib. + In both cases, we initialize a subnet using the IPAM lib. """ if num_networks != 1: raise Exception(_("QuantumManager requires that only one" @@ -101,8 +101,8 @@ class QuantumManager(manager.FlatManager): priority, cidr, gateway_v6, cidr_v6, dns1, dns2) def delete_network(self, context, fixed_range): - """ Lookup network by IPv4 cidr, delete both the IPAM - subnet and the corresponding Quantum network. + """Lookup network by IPv4 cidr, delete both the IPAM + subnet and the corresponding Quantum network. """ project_id = context.project_id quantum_net_id = self.ipam.get_network_id_by_cidr( @@ -113,14 +113,14 @@ class QuantumManager(manager.FlatManager): self.q_conn.delete_network(q_tenant_id, quantum_net_id) def allocate_for_instance(self, context, **kwargs): - """ Called by compute when it is creating a new VM. + """Called by compute when it is creating a new VM. - There are three key tasks: + There are three key tasks: - Determine the number and order of vNICs to create - Allocate IP addresses - Create ports on a Quantum network and attach vNICs. - We support two approaches to determining vNICs: + We support two approaches to determining vNICs: - By default, a VM gets a vNIC for any network belonging to the VM's project, and a vNIC for any "global" network that has a NULL project_id. vNIC order is determined @@ -130,10 +130,10 @@ class QuantumManager(manager.FlatManager): create vNICs, and the vNIC order is determiend by the order in the requested_networks array. - For each vNIC, use the FlatManager to create the entries - in the virtual_interfaces table, contact Quantum to - create a port and attachment the vNIC, and use the IPAM - lib to allocate IP addresses. + For each vNIC, use the FlatManager to create the entries + in the virtual_interfaces table, contact Quantum to + create a port and attachment the vNIC, and use the IPAM + lib to allocate IP addresses. """ instance_id = kwargs.pop('instance_id') instance_type_id = kwargs['instance_type_id'] @@ -181,17 +181,17 @@ class QuantumManager(manager.FlatManager): def get_instance_nw_info(self, context, instance_id, instance_type_id, host): - """ This method is used by compute to fetch all network data - that should be used when creating the VM. + """This method is used by compute to fetch all network data + that should be used when creating the VM. - The method simply loops through all virtual interfaces - stored in the nova DB and queries the IPAM lib to get - the associated IP data. + The method simply loops through all virtual interfaces + stored in the nova DB and queries the IPAM lib to get + the associated IP data. - The format of returned data is 'defined' by the initial - set of NetworkManagers found in nova/network/manager.py . - Ideally this 'interface' will be more formally defined - in the future. + The format of returned data is 'defined' by the initial + set of NetworkManagers found in nova/network/manager.py . + Ideally this 'interface' will be more formally defined + in the future. """ network_info = [] instance = db.instance_get(context, instance_id) @@ -269,10 +269,10 @@ class QuantumManager(manager.FlatManager): return network_info def deallocate_for_instance(self, context, **kwargs): - """ Called when a VM is terminated. Loop through each virtual - interface in the Nova DB and remove the Quantum port and - clear the IP allocation using the IPAM. Finally, remove - the virtual interfaces from the Nova DB. + """Called when a VM is terminated. Loop through each virtual + interface in the Nova DB and remove the Quantum port and + clear the IP allocation using the IPAM. Finally, remove + the virtual interfaces from the Nova DB. """ instance_id = kwargs.get('instance_id') project_id = kwargs.pop('project_id', None) @@ -309,7 +309,7 @@ class QuantumManager(manager.FlatManager): (instance_id))) def validate_networks(self, context, networks): - """ Validates that this tenant has quantum networks with the associated + """Validates that this tenant has quantum networks with the associated UUIDs. This is called by the 'os-create-server-ext' API extension code so that we can return an API error code to the caller if they request an invalid network. diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index d5a1901aa..0c744f080 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -35,7 +35,7 @@ flags.DEFINE_string('melange_port', json_content_type = {'Content-type': "application/json"} -#FIXME(danwent): talk to the Melange folks about creating a +# FIXME(danwent): talk to the Melange folks about creating a # client lib that we can import as a library, instead of # have to have all of the client code in here. class MelangeConnection(object): diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py index dcee0a3f9..a0ac10fd3 100644 --- a/nova/network/quantum/melange_ipam_lib.py +++ b/nova/network/quantum/melange_ipam_lib.py @@ -24,7 +24,7 @@ from nova import log as logging from nova.network.quantum import melange_connection -LOG = logging.getLogger("quantum_melange_ipam") +LOG = logging.getLogger("nova.network.quantum.melange_ipam_lib") FLAGS = flags.FLAGS @@ -34,26 +34,26 @@ def get_ipam_lib(net_man): class QuantumMelangeIPAMLib(object): - """ Implements Quantum IP Address Management (IPAM) interface - using the Melange service, which is access using the Melange - web services API. + """Implements Quantum IP Address Management (IPAM) interface + using the Melange service, which is access using the Melange + web services API. """ def __init__(self): - """ Initialize class used to connect to Melange server""" + """Initialize class used to connect to Melange server""" self.m_conn = melange_connection.MelangeConnection() def create_subnet(self, context, label, project_id, quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - """ Contact Melange and create a subnet for any non-NULL - IPv4 or IPv6 subnets. + """Contact Melange and create a subnet for any non-NULL + IPv4 or IPv6 subnets. - Also create a entry in the Nova networks DB, but only - to store values not represented in Melange or to - temporarily provide compatibility with Nova code that - accesses IPAM data directly via the DB (e.g., nova-api) + Also create a entry in the Nova networks DB, but only + to store values not represented in Melange or to + temporarily provide compatibility with Nova code that + accesses IPAM data directly via the DB (e.g., nova-api) """ tenant_id = project_id or FLAGS.quantum_default_tenant_id if cidr: @@ -73,15 +73,15 @@ class QuantumMelangeIPAMLib(object): network = db.network_create_safe(admin_context, net) def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref): - """ Pass call to allocate fixed IP on to Melange""" + """Pass call to allocate fixed IP on to Melange""" tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.allocate_ip(quantum_net_id, vif_ref['uuid'], project_id=tenant_id, mac_address=vif_ref['address']) def get_network_id_by_cidr(self, context, cidr, project_id): - """ Find the Quantum UUID associated with a IPv4 CIDR - address for the specified tenant. + """Find the Quantum UUID associated with a IPv4 CIDR + address for the specified tenant. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id all_blocks = self.m_conn.get_blocks(tenant_id) @@ -91,8 +91,8 @@ class QuantumMelangeIPAMLib(object): raise exception.NotFound(_("No network found for cidr %s" % cidr)) def delete_subnets_by_net_id(self, context, net_id, project_id): - """ Find Melange block associated with the Quantum UUID, - then tell Melange to delete that block. + """Find Melange block associated with the Quantum UUID, + then tell Melange to delete that block. """ admin_context = context.elevated() tenant_id = project_id or FLAGS.quantum_default_tenant_id @@ -105,9 +105,10 @@ class QuantumMelangeIPAMLib(object): db.network_delete_safe(context, network['id']) def get_project_and_global_net_ids(self, context, project_id): - """ Fetches all networks associated with this project, or - that are "global" (i.e., have no project set). - Returns list sorted by 'priority'. + """Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority' (lowest integer value + is highest priority). """ if project_id is None: raise Exception(_("get_project_and_global_net_ids must be called" @@ -134,8 +135,8 @@ class QuantumMelangeIPAMLib(object): for priority, network_id, tenant_id in priority_nets] def get_subnets_by_net_id(self, context, project_id, net_id): - """ Returns information about the IPv4 and IPv6 subnets - associated with a Quantum Network UUID. + """Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. """ # FIXME(danwent): Melange actually returns the subnet info @@ -164,23 +165,23 @@ class QuantumMelangeIPAMLib(object): return (subnet_v4, subnet_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list of IPv4 address strings associated with - the specified virtual interface. + """Returns a list of IPv4 address strings associated with + the specified virtual interface. """ return self._get_ips_by_interface(context, net_id, vif_id, project_id, 4) def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list of IPv6 address strings associated with - the specified virtual interface. + """Returns a list of IPv6 address strings associated with + the specified virtual interface. """ return self._get_ips_by_interface(context, net_id, vif_id, project_id, 6) def _get_ips_by_interface(self, context, net_id, vif_id, project_id, ip_version): - """ Helper method to fetch v4 or v6 addresses for a particular - virtual interface. + """Helper method to fetch v4 or v6 addresses for a particular + virtual interface. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) @@ -188,8 +189,8 @@ class QuantumMelangeIPAMLib(object): if IPNetwork(ip['address']).version == ip_version] def verify_subnet_exists(self, context, project_id, quantum_net_id): - """ Confirms that a subnet exists that is associated with the - specified Quantum Network UUID. + """Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id, @@ -197,8 +198,8 @@ class QuantumMelangeIPAMLib(object): return v4_subnet is not None def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref): - """ Deallocate all fixed IPs associated with the specified - virtual interface. + """Deallocate all fixed IPs associated with the specified + virtual interface. """ tenant_id = project_id or FLAGS.quantum_default_tenant_id self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index 17236a976..21dee8f6a 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -27,7 +27,7 @@ from nova.network.quantum import melange_connection as melange from nova import utils -LOG = logging.getLogger("quantum_nova_ipam_lib") +LOG = logging.getLogger("nova.network.quantum.nova_ipam_lib") FLAGS = flags.FLAGS @@ -37,15 +37,15 @@ def get_ipam_lib(net_man): class QuantumNovaIPAMLib(object): - """ Implements Quantum IP Address Management (IPAM) interface - using the local Nova database. This implementation is inline - with how IPAM is used by other NetworkManagers. + """Implements Quantum IP Address Management (IPAM) interface + using the local Nova database. This implementation is inline + with how IPAM is used by other NetworkManagers. """ def __init__(self, net_manager): - """ Holds a reference to the "parent" network manager, used - to take advantage of various FlatManager methods to avoid - code duplication. + """Holds a reference to the "parent" network manager, used + to take advantage of various FlatManager methods to avoid + code duplication. """ self.net_manager = net_manager @@ -53,11 +53,11 @@ class QuantumNovaIPAMLib(object): quantum_net_id, priority, cidr=None, gateway_v6=None, cidr_v6=None, dns1=None, dns2=None): - """ Re-use the basic FlatManager create_networks method to - initialize the networks and fixed_ips tables in Nova DB. + """Re-use the basic FlatManager create_networks method to + initialize the networks and fixed_ips tables in Nova DB. - Also stores a few more fields in the networks table that - are needed by Quantum but not the FlatManager. + Also stores a few more fields in the networks table that + are needed by Quantum but not the FlatManager. """ admin_context = context.elevated() subnet_size = len(netaddr.IPNetwork(cidr)) @@ -85,8 +85,8 @@ class QuantumNovaIPAMLib(object): return network['uuid'] def delete_subnets_by_net_id(self, context, net_id, project_id): - """ Deletes a network based on Quantum UUID. Uses FlatManager - delete_network to avoid duplication. + """Deletes a network based on Quantum UUID. Uses FlatManager + delete_network to avoid duplication. """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) @@ -97,9 +97,9 @@ class QuantumNovaIPAMLib(object): require_disassociated=False) def get_project_and_global_net_ids(self, context, project_id): - """ Fetches all networks associated with this project, or - that are "global" (i.e., have no project set). - Returns list sorted by 'priority'. + """Fetches all networks associated with this project, or + that are "global" (i.e., have no project set). + Returns list sorted by 'priority'. """ admin_context = context.elevated() networks = db.project_get_networks(admin_context, project_id, False) @@ -113,7 +113,7 @@ class QuantumNovaIPAMLib(object): return sorted(net_list, key=lambda x: id_priority_map[x[0]]) def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec): - """ Allocates a single fixed IPv4 address for a virtual interface.""" + """Allocates a single fixed IPv4 address for a virtual interface.""" admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, quantum_net_id) if network['cidr']: @@ -125,8 +125,8 @@ class QuantumNovaIPAMLib(object): db.fixed_ip_update(admin_context, address, values) def get_subnets_by_net_id(self, context, tenant_id, net_id): - """ Returns information about the IPv4 and IPv6 subnets - associated with a Quantum Network UUID. + """Returns information about the IPv4 and IPv6 subnets + associated with a Quantum Network UUID. """ n = db.network_get_by_uuid(context.elevated(), net_id) subnet_data_v4 = { @@ -148,8 +148,8 @@ class QuantumNovaIPAMLib(object): return (subnet_data_v4, subnet_data_v6) def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list of IPv4 address strings associated with - the specified virtual interface, based on the fixed_ips table. + """Returns a list of IPv4 address strings associated with + the specified virtual interface, based on the fixed_ips table. """ vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) fixed_ips = db.fixed_ip_get_by_virtual_interface(context, @@ -157,8 +157,8 @@ class QuantumNovaIPAMLib(object): return [fixed_ip['address'] for fixed_ip in fixed_ips] def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id): - """ Returns a list containing a single IPv6 address strings - associated with the specified virtual interface. + """Returns a list containing a single IPv6 address strings + associated with the specified virtual interface. """ admin_context = context.elevated() network = db.network_get_by_uuid(admin_context, net_id) @@ -171,16 +171,16 @@ class QuantumNovaIPAMLib(object): return [] def verify_subnet_exists(self, context, tenant_id, quantum_net_id): - """ Confirms that a subnet exists that is associated with the - specified Quantum Network UUID. Raises an exception if no - such subnet exists. + """Confirms that a subnet exists that is associated with the + specified Quantum Network UUID. Raises an exception if no + such subnet exists. """ admin_context = context.elevated() db.network_get_by_uuid(admin_context, quantum_net_id) def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref): - """ Deallocate all fixed IPs associated with the specified - virtual interface. + """Deallocate all fixed IPs associated with the specified + virtual interface. """ try: admin_context = context.elevated() diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index 93892a843..21917653c 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -21,7 +21,7 @@ from nova.network.quantum import client as quantum_client from nova import utils -LOG = logging.getLogger("nova.network.quantum") +LOG = logging.getLogger("nova.network.quantum.quantum_connection") FLAGS = flags.FLAGS flags.DEFINE_string('quantum_connection_host', @@ -38,35 +38,35 @@ flags.DEFINE_string('quantum_default_tenant_id', class QuantumClientConnection(object): - """ Abstracts connection to Quantum service into higher level - operations performed by the QuantumManager. + """Abstracts connection to Quantum service into higher level + operations performed by the QuantumManager. - Separating this out as a class also let's us create a 'fake' - version of this class for unit tests. + Separating this out as a class also let's us create a 'fake' + version of this class for unit tests. """ def __init__(self): - """ Initialize Quantum client class based on flags. """ + """Initialize Quantum client class based on flags.""" self.client = quantum_client.Client(FLAGS.quantum_connection_host, FLAGS.quantum_connection_port, format="json", logger=LOG) def create_network(self, tenant_id, network_name): - """ Create network using specified name, return Quantum - network UUID. + """Create network using specified name, return Quantum + network UUID. """ data = {'network': {'name': network_name}} resdict = self.client.create_network(data, tenant=tenant_id) return resdict["network"]["id"] def delete_network(self, tenant_id, net_id): - """ Deletes Quantum network with specified UUID. """ + """Deletes Quantum network with specified UUID.""" self.client.delete_network(net_id, tenant=tenant_id) def network_exists(self, tenant_id, net_id): - """ Determine if a Quantum network exists for the - specified tenant. + """Determine if a Quantum network exists for the + specified tenant. """ try: self.client.show_network_details(net_id, tenant=tenant_id) @@ -76,9 +76,9 @@ class QuantumClientConnection(object): return False def create_and_attach_port(self, tenant_id, net_id, interface_id): - """ Creates a Quantum port on the specified network, sets - status to ACTIVE to enable traffic, and attaches the - vNIC with the specified interface-id. + """Creates a Quantum port on the specified network, sets + status to ACTIVE to enable traffic, and attaches the + vNIC with the specified interface-id. """ LOG.debug(_("Connecting interface %(interface_id)s to " "net %(net_id)s for %(tenant_id)s" % locals())) @@ -91,7 +91,7 @@ class QuantumClientConnection(object): tenant=tenant_id) def detach_and_delete_port(self, tenant_id, net_id, port_id): - """ Detach and delete the specified Quantum port. """ + """Detach and delete the specified Quantum port.""" LOG.debug(_("Deleting port %(port_id)s on net %(net_id)s" " for %(tenant_id)s" % locals())) @@ -99,8 +99,8 @@ class QuantumClientConnection(object): self.client.delete_port(net_id, port_id, tenant=tenant_id) def get_port_by_attachment(self, tenant_id, attachment_id): - """ Given a tenant, search for the Quantum network and port - UUID that has the specified interface-id attachment. + """Given a tenant, search for the Quantum network and port + UUID that has the specified interface-id attachment. """ # FIXME(danwent): this will be inefficient until the Quantum # API implements querying a port by the interface-id -- cgit From e5e3b306985a3b1fdd8a971f48b76eaf8f923f21 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 2 Sep 2011 13:24:38 -0700 Subject: fix pep8 violation --- nova/network/quantum/melange_connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py index 0c744f080..71ac9b5f1 100644 --- a/nova/network/quantum/melange_connection.py +++ b/nova/network/quantum/melange_connection.py @@ -35,6 +35,7 @@ flags.DEFINE_string('melange_port', json_content_type = {'Content-type': "application/json"} + # FIXME(danwent): talk to the Melange folks about creating a # client lib that we can import as a library, instead of # have to have all of the client code in here. -- cgit From fc0ee0d01320d81b5bb6cd1bc6cb23c90c8246a7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 2 Sep 2011 13:24:40 -0700 Subject: remove extra description stuff --- .../api/openstack/contrib/test_createserverext.py | 1 - nova/tests/api/openstack/test_servers.py | 19 +------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index a6da9abfd..0881efcfe 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -98,7 +98,6 @@ class CreateserverextTest(test.TestCase): 'uuid': FAKE_UUID, 'user_id': 'fake', 'project_id': 'fake', - 'display_description': 'fakedescription', 'created_at': "", 'updated_at': ""}] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 45ad6e5a8..a716af0e5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -156,7 +156,6 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, vm_state=None, task_state=None, reservation_id="", uuid=FAKE_UUID, image_ref="10", flavor_id="1", interfaces=None, name=None, key_name='', - description='fakedescription', access_ipv4=None, access_ipv6=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -211,7 +210,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": server_name, - "display_description": description, + "display_description": "", "locked": False, "metadata": metadata, "access_ip_v4": access_ipv4, @@ -354,7 +353,6 @@ class ServersTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "server1", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "", "accessIPv6": "", @@ -455,7 +453,6 @@ class ServersTest(test.TestCase): xmlns="http://docs.openstack.org/compute/api/v1.1" xmlns:atom="http://www.w3.org/2005/Atom" name="server1" - description="fakedescription" updated="%(expected_updated)s" created="%(expected_created)s" hostId="" @@ -528,7 +525,6 @@ class ServersTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "", "accessIPv6": "", @@ -626,7 +622,6 @@ class ServersTest(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "server1", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "", "accessIPv6": "", @@ -1488,7 +1483,6 @@ class ServersTest(test.TestCase): 'access_ip_v4': '1.2.3.4', 'access_ip_v6': 'fead::1234', 'image_ref': image_ref, - 'display_description': 'fakedescription', 'user_id': 'fake', 'project_id': 'fake', "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), @@ -3368,7 +3362,6 @@ class TestServerInstanceCreation(test.TestCase): return [{'id': '1234', 'display_name': 'fakeinstance', 'user_id': 'fake', 'project_id': 'fake', - 'display_description': 'fakedescription', 'uuid': FAKE_UUID}] def set_admin_password(self, *args, **kwargs): @@ -3686,7 +3679,6 @@ class ServersViewBuilderV11Test(test.TestCase): "terminated_at": utils.utcnow(), "availability_zone": "", "display_name": "test_server", - "display_description": "fakedescription", "locked": False, "metadata": [], "accessIPv4": "1.2.3.4", @@ -3775,7 +3767,6 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "", "accessIPv6": "", @@ -3833,7 +3824,6 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 100, "name": "test_server", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "", "accessIPv6": "", @@ -4011,7 +4001,6 @@ class ServersViewBuilderV11Test(test.TestCase): "created": "2010-10-10T12:00:00Z", "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "", "accessIPv6": "", @@ -4083,7 +4072,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "key_name": '', @@ -4222,7 +4210,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", @@ -4425,7 +4412,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", @@ -4483,7 +4469,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 100, "name": "test_server_2", - "description": "fakedescription", "status": "ACTIVE", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", @@ -4606,7 +4591,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "accessIPv4": "1.2.3.4", @@ -4744,7 +4728,6 @@ class ServerXMLSerializationTest(test.TestCase): 'updated': self.TIMESTAMP, "progress": 0, "name": "test_server", - "description": "fakedescription", "status": "BUILD", "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", -- cgit From 6eb28b5748a829d058fd35888f03f2ee1f26f5b5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 2 Sep 2011 13:31:19 -0700 Subject: default description to name --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 289f87921..9b2928bc8 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -164,7 +164,7 @@ class CreateInstanceHelper(object): # NOTE(vish): This is solely for compatibility with anything # that is using the display description field. metadata = server_dict.get('metadata') or {} - display_description = metadata.get('description') or '' + display_description = metadata.get('description') or name return (extra_values, create_method(context, inst_type, -- cgit From 494eb94192a971f64fa6aa78092074f8ed437a7f Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 2 Sep 2011 17:09:09 -0700 Subject: Added unit tests to check instance record is not inserted in db when security groups passed to the instances are not existing --- nova/tests/test_compute.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 766a7da9b..65fdffbd6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -161,6 +161,19 @@ class ComputeTestCase(test.TestCase): db.security_group_destroy(self.context, group['id']) db.instance_destroy(self.context, ref[0]['id']) + def test_create_instance_with_invalid_security_group_raises(self): + instance_type = instance_types.get_default_instance_type() + + pre_build_len = len(db.instance_get_all(context.get_admin_context())) + self.assertRaises(exception.SecurityGroupNotFoundForProject, + self.compute_api.create, + self.context, + instance_type=instance_type, + image_href=None, + security_group=['this_is_a_fake_sec_group']) + self.assertEqual(pre_build_len, + len(db.instance_get_all(context.get_admin_context()))) + def test_create_instance_associates_config_drive(self): """Make sure create associates a config drive.""" -- cgit From 864f3b24c6cd0753474ac152bce73c2df64bbdd1 Mon Sep 17 00:00:00 2001 From: Thuleau Édouard Date: Sat, 3 Sep 2011 09:36:06 +0200 Subject: Change non E ascii characte. --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 0beab16a4..f647a7c00 100644 --- a/Authors +++ b/Authors @@ -30,7 +30,7 @@ Devendra Modium Devin Carlen Donal Lafferty Ed Leafe -Édouard Thuleau +Edouard Thuleau Eldar Nugaev Eric Day Eric Windisch -- cgit From 0589ecebb5e610ba6a6787fada14e96af92361c2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 6 Sep 2011 14:26:02 -0500 Subject: Set flat_injected to False by default. --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index e6b30d1a0..6120137ce 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -74,7 +74,7 @@ flags.DEFINE_string('flat_network_bridge', None, 'Bridge for simple network instances') flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') -flags.DEFINE_bool('flat_injected', True, +flags.DEFINE_bool('flat_injected', False, 'Whether to attempt to inject network setup into guest') flags.DEFINE_string('flat_interface', None, 'FlatDhcp will bridge into this interface if set') -- cgit From 1f3856ffb92ab690b1d630deb6fa025ae74348f7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 6 Sep 2011 12:48:41 -0700 Subject: revert changes to display description --- nova/api/openstack/create_instance_helper.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 9b2928bc8..29e071609 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -161,10 +161,6 @@ class CreateInstanceHelper(object): 'config_drive': config_drive, 'password': password} - # NOTE(vish): This is solely for compatibility with anything - # that is using the display description field. - metadata = server_dict.get('metadata') or {} - display_description = metadata.get('description') or name return (extra_values, create_method(context, inst_type, @@ -172,9 +168,9 @@ class CreateInstanceHelper(object): kernel_id=kernel_id, ramdisk_id=ramdisk_id, display_name=name, - display_description=display_description, + display_description=name, key_name=key_name, - metadata=metadata, + metadata=server_dict.get('metadata', {}), access_ip_v4=server_dict.get('accessIPv4'), access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, -- cgit From 9b2885076d2ed438fb3189b8528f5bec6a2cda4d Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Tue, 6 Sep 2011 15:02:04 -0500 Subject: Fix a misspelling of "exception" --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index e6b30d1a0..050cec250 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -448,7 +448,7 @@ class NetworkManager(manager.SchedulerDependentManager): try: fixed_ips = kwargs.get('fixed_ips') or \ self.db.fixed_ip_get_by_instance(context, instance_id) - except exceptions.FixedIpNotFoundForInstance: + except exception.FixedIpNotFoundForInstance: fixed_ips = [] LOG.debug(_("network deallocation for instance |%s|"), instance_id, context=context) -- cgit From 41f3d157c917255683ae23313704f357e061911c Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 6 Sep 2011 16:41:35 -0500 Subject: Fixed unit test. --- nova/tests/test_xenapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 45dad3516..91b4161b0 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -494,6 +494,7 @@ class XenAPIVMTestCase(test.TestCase): self.check_vm_params_for_linux_with_external_kernel() def test_spawn_netinject_file(self): + self.flags(flat_injected=True) db_fakes.stub_out_db_instance_api(self.stubs, injected=True) self._tee_executed = False @@ -611,7 +612,6 @@ class XenAPIVMTestCase(test.TestCase): str(3 * 1024)) def test_rescue(self): - self.flags(flat_injected=False) instance = self._create_instance() conn = xenapi_conn.get_connection(False) conn.rescue(self.context, instance, None, []) -- cgit