From 357ffa7d3aa2124d9d7697a4bd492b7819646ba9 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Fri, 10 Aug 2012 11:29:52 -0700 Subject: remove nova code related to Quantum v1 API bug 1034692 Quantum team is removing all v1 support in Folsom, so this code will not be used or supported with Quantum Folsom release. Change-Id: I4c397d5238fcc3a15320e052bdc4735dfec1f176 --- nova/flags.py | 2 +- nova/network/manager.py | 2 +- nova/network/nova_ipam_lib.py | 101 ++++ nova/network/quantum/__init__.py | 16 - nova/network/quantum/client.py | 312 ------------ nova/network/quantum/fake_client.py | 171 ------- nova/network/quantum/manager.py | 762 ----------------------------- nova/network/quantum/melange_connection.py | 202 -------- nova/network/quantum/melange_ipam_lib.py | 261 ---------- nova/network/quantum/quantum_connection.py | 159 ------ nova/network/quantum/sg.py | 159 ------ nova/network/sg.py | 159 ++++++ nova/tests/fake_network.py | 2 +- nova/tests/network/test_quantum.py | 647 ------------------------ nova/tests/test_melange_ipam_lib.py | 243 --------- 15 files changed, 263 insertions(+), 2935 deletions(-) create mode 100644 nova/network/nova_ipam_lib.py delete mode 100644 nova/network/quantum/__init__.py delete mode 100644 nova/network/quantum/client.py delete mode 100644 nova/network/quantum/fake_client.py delete mode 100644 nova/network/quantum/manager.py delete mode 100644 nova/network/quantum/melange_connection.py delete mode 100644 nova/network/quantum/melange_ipam_lib.py delete mode 100644 nova/network/quantum/quantum_connection.py delete mode 100644 nova/network/quantum/sg.py create mode 100644 nova/network/sg.py delete mode 100644 nova/tests/network/test_quantum.py delete mode 100644 nova/tests/test_melange_ipam_lib.py (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 588ecfe5f..4960e9c35 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -410,7 +410,7 @@ global_opts = [ default='nova.volume.api.API', help='The full class name of the volume API class to use'), cfg.StrOpt('security_group_handler', - default='nova.network.quantum.sg.NullSecurityGroupHandler', + default='nova.network.sg.NullSecurityGroupHandler', help='The full class name of the security group handler class'), cfg.StrOpt('default_access_ip_network_name', default=None, diff --git a/nova/network/manager.py b/nova/network/manager.py index f81004fed..8171a2261 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -789,7 +789,7 @@ class NetworkManager(manager.SchedulerDependentManager): # NOTE(tr3buchet: unless manager subclassing NetworkManager has # already imported ipam, import nova ipam here if not hasattr(self, 'ipam'): - self._import_ipam_lib('nova.network.quantum.nova_ipam_lib') + self._import_ipam_lib('nova.network.nova_ipam_lib') l3_lib = kwargs.get("l3_lib", FLAGS.l3_lib) self.l3driver = importutils.import_object(l3_lib) diff --git a/nova/network/nova_ipam_lib.py b/nova/network/nova_ipam_lib.py new file mode 100644 index 000000000..be8858983 --- /dev/null +++ b/nova/network/nova_ipam_lib.py @@ -0,0 +1,101 @@ +# 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 ipv6 +from nova.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +def get_ipam_lib(net_man): + return QuantumNovaIPAMLib(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. + """ + + 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 get_subnets_by_net_id(self, context, tenant_id, net_id, _vif_id=None): + """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_v4 = { + 'network_id': n['uuid'], + 'cidr': n['cidr'], + 'gateway': n['gateway'], + 'broadcast': n['broadcast'], + 'netmask': n['netmask'], + 'version': 4, + 'dns1': n['dns1'], + 'dns2': n['dns2']} + #TODO(tr3buchet): I'm noticing we've assumed here that all dns is v4. + # this is probably bad as there is no way to add v6 + # dns to nova + subnet_v6 = { + 'network_id': n['uuid'], + 'cidr': n['cidr_v6'], + 'gateway': n['gateway_v6'], + 'broadcast': None, + 'netmask': n['netmask_v6'], + 'version': 6, + 'dns1': None, + 'dns2': None} + return [subnet_v4, subnet_v6] + + def get_routes_by_ip_block(self, context, block_id, project_id): + """Returns the list of routes for the IP block""" + return [] + + 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. + """ + # TODO(tr3buchet): link fixed_ips to vif by uuid so only 1 db call + vif_rec = db.virtual_interface_get_by_uuid(context, vif_id) + fixed_ips = db.fixed_ips_by_virtual_interface(context, + vif_rec['id']) + 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. + """ + 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) + if network['cidr_v6']: + ip = ipv6.to_global(network['cidr_v6'], + vif_rec['address'], + project_id) + return [ip] + return [] + + def get_floating_ips_by_fixed_address(self, context, fixed_address): + return db.floating_ip_get_by_fixed_address(context, fixed_address) diff --git a/nova/network/quantum/__init__.py b/nova/network/quantum/__init__.py deleted file mode 100644 index f7fbfb511..000000000 --- a/nova/network/quantum/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index 538a0b3a0..000000000 --- a/nova/network/quantum/client.py +++ /dev/null @@ -1,312 +0,0 @@ -# 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 socket -import urllib - -from nova.openstack.common import jsonutils - - -# 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 - the standard serializer from the quantum library. - """ - def serialize(self, data, content_type): - return jsonutils.dumps(data) - - def deserialize(self, data, content_type): - return jsonutils.loads(data) - - -# Quantum API v1.0 uses 420 + 430 for network + port not found -# Quantum API v1.1 uses 404 for network + port not found -NOT_FOUND_CODES = set((404, 420, 430)) - - -# 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 a not-found error code""" - 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): - self.func = func - - def __get__(self, instance, owner): - def with_params(*args, **kwargs): - """Temporarily set format and tenant for this request""" - (format, tenant) = (instance.format, instance.tenant) - - instance.format = kwargs.pop('format', instance.format) - instance.tenant = kwargs.pop('tenant', instance.tenant) - - ret = None - try: - ret = self.func(instance, *args, **kwargs) - 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.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", testing_stub=None, key_file=None, - cert_file=None, logger=None, timeout=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 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 - :param logger: logging object to be used by client library - """ - self.host = host - self.port = port - self.use_ssl = use_ssl - self.tenant = tenant - self.format = format - self.connection = None - self.testing_stub = testing_stub - self.key_file = key_file - self.cert_file = cert_file - self.logger = logger - self.timeout = timeout - - def get_connection_type(self): - """Returns the proper connection type""" - if self.testing_stub: - return self.testing_stub - elif 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 isinstance(params, 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 - kwargs = {} - if self.use_ssl: - if self.key_file: - kwargs['key_file'] = self.key_file - if self.cert_file: - kwargs['cert_file'] = self.cert_file - - if self.timeout: - kwargs['timeout'] = self.timeout - - c = connection_type(self.host, self.port, **kwargs) - - if self.logger: - self.logger.debug( - _("Quantum Client Request: %(method)s %(action)s"), - locals()) - 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 NOT_FOUND_CODES: - raise QuantumNotFoundException( - _("Quantum entity not found: %s"), data) - - if status_code in (httplib.OK, - httplib.CREATED, - httplib.ACCEPTED, - httplib.NO_CONTENT): - if data is not None and len(data): - return self.deserialize(data, status_code) - else: - raise QuantumServerException( - _("Server %(status_code)s error: %(data)s") - % locals()) - - except (socket.error, IOError), e: - 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 - 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 not data: - return None - elif isinstance(data, dict): - return JSONSerializer().serialize(data, self.content_type()) - else: - raise Exception(_("unable to deserialize object of type = '%s'") % - type(data)) - - def deserialize(self, data, status_code): - return JSONSerializer().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, filter_ops=None): - """Fetches a list of all networks for a tenant""" - return self.do_request("GET", self.networks_path, params=filter_ops) - - @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, filter_ops=None): - """Fetches a list of ports on a given network""" - return self.do_request("GET", self.ports_path % (network), - params=filter_ops) - - @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_client.py b/nova/network/quantum/fake_client.py deleted file mode 100644 index 2e0231e61..000000000 --- a/nova/network/quantum/fake_client.py +++ /dev/null @@ -1,171 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 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. -# @author: Dan Wendlandt Nicira Networks - -from nova.network.quantum import client -from nova import utils - -#TODO(danwent): would be nice to have these functions raise QuantumIOErrors -# to make sure such errors are caught and reported properly - - -# this is a fake quantum client that just stores all data in a nested dict -# -# example: -# -#{'': -# { '' : { 'name' : "t1-net1", -# 'ports' : [ { ' : -# {'state': 'Active', -# 'attachment': 'iface-id'}, -# { 'state': 'Down', -# 'attachment' : None}}] -# } -# } -# } - - -class FakeClient(object): - """A fake Quantum Client for testing""" - - def __init__(self, logger=None, **kwargs): - """Creates a new client to some service. - :param logger: logging object to be used by client library - """ - self.logger = logger - self.tenant_map = {} - - def _get_tenant_nets(self, tenant): - if tenant is None: - raise Exception("'tenant' cannot be None") - return self.tenant_map.setdefault(tenant, {}) - - def _verify_net(self, network, nets): - if network not in nets: - raise client.QuantumNotFoundException("no network with uuid %s" - % network) - - def _verify_net_and_port(self, network, port, nets): - if network not in nets: - raise client.QuantumNotFoundException("no network with uuid %s" - % network) - if port not in nets[network]['ports']: - raise client.QuantumNotFoundException("no port with uuid %s" - % port) - - def list_networks(self, tenant=None, filter_ops=None): - """Fetches a list of all networks for a tenant""" - nets = self._get_tenant_nets(tenant) - if filter_ops: - raise Exception("Need to implement filters %s in " - "quantum fake client" % filter_ops) - return {"networks": [{"id": uuid} for uuid in nets.keys()]} - - def show_network_details(self, network, tenant=None): - """Fetches the details of a certain network""" - nets = self._get_tenant_nets(tenant) - self._verify_net(network, nets) - return {"network": {"id": network, "name": nets[network]["name"]}} - - def create_network(self, body=None, tenant=None): - """Creates a new network""" - nets = self._get_tenant_nets(tenant) - uuid = str(utils.gen_uuid()) - name = body["network"]["name"] - nets[uuid] = {"ports": {}, "name": name} - return {"network": {"id": uuid}} - - def update_network(self, network, body=None, tenant=None): - """Updates a network""" - nets = self._get_tenant_nets(tenant) - self._verify_net(network, nets) - nets[network]['name'] = body["network"]["name"] - return {"network": {"id": network, "name": nets[networks]["name"]}} - - def delete_network(self, network, tenant=None): - """Deletes the specified network""" - nets = self._get_tenant_nets(tenant) - self._verify_net(network, nets) - del nets[network] - - def list_ports(self, network, filter_ops=None, tenant=None): - """Fetches a list of ports on a given network""" - nets = self._get_tenant_nets(tenant) - self._verify_net(network, nets) - - ports = nets[network]['ports'].items() - if filter_ops and 'attachment' in filter_ops: - a = filter_ops.pop('attachment') - ports = [p for p in ports if p[1]['attachment'] == a] - - if filter_ops: - raise Exception("Need to implement files %s in " - "quantum fake client" % filter_ops) - - return {"ports": [{'id': p[0]} for p in ports]} - - def show_port_details(self, network, port, tenant=None): - """Fetches the details of a certain port""" - nets = self._get_tenant_nets(tenant) - self._verify_net_and_port(network, port, nets) - p = nets[network]['ports'][port] - return {"port": {"state": p["state"], "id": port}} - - def create_port(self, network, body=None, tenant=None): - """Creates a new port on a given network""" - nets = self._get_tenant_nets(tenant) - self._verify_net(network, nets) - uuid = str(utils.gen_uuid()) - nets[network]['ports'][uuid] = {'attachment': None, - 'state': body['port']['state']} - return {"port": {"id": uuid}} - - def delete_port(self, network, port, tenant=None): - """Deletes the specified port from a network""" - nets = self._get_tenant_nets(tenant) - self._verify_net_and_port(network, port, nets) - del nets[network]['ports'][port] - - def set_port_state(self, network, port, body=None, tenant=None): - """Sets the state of the specified port""" - nets = self._get_tenant_nets(tenant) - self._verify_net_and_port(network, port, nets) - - new_state = body['port']['state'] - nets[network]['ports'][port]['state'] = new_state - return {"port": {"state": new_state}} - - def show_port_attachment(self, network, port, tenant=None): - """Fetches the attachment-id associated with the specified port""" - nets = self._get_tenant_nets(tenant) - self._verify_net_and_port(network, port, nets) - p = nets[network]['ports'][port] - if p['attachment'] is not None: - return {"attachment": {"id": p['attachment']}} - else: - return {"attachment": {}} - - def attach_resource(self, network, port, body=None, tenant=None): - nets = self._get_tenant_nets(tenant) - self._verify_net_and_port(network, port, nets) - nets[network]['ports'][port]['attachment'] = body['attachment']['id'] - - def detach_resource(self, network, port, tenant=None): - """Removes the attachment-id of the specified port""" - nets = self._get_tenant_nets(tenant) - self._verify_net_and_port(network, port, nets) - nets[network]['ports'][port]['attachment'] = None diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py deleted file mode 100644 index 0ee902f61..000000000 --- a/nova/network/quantum/manager.py +++ /dev/null @@ -1,762 +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. - -import time - -import netaddr - -from nova import context -from nova import db -from nova import exception -from nova import flags -from nova.network import manager -from nova.network.quantum import melange_ipam_lib -from nova.network.quantum import quantum_connection -from nova.openstack.common import cfg -from nova.openstack.common import log as logging -from nova.openstack.common import rpc -from nova import utils - -LOG = logging.getLogger(__name__) - -quantum_opts = [ - cfg.StrOpt('quantum_ipam_lib', - default='nova.network.quantum.nova_ipam_lib', - help="Indicates underlying IP address management library"), - # TODO(Vek): Eventually, this needs to mean more than just using - # Melange for assignment of MAC addresses (with an - # appropriate flag name change, of course), but this is all - # it does right now - cfg.BoolOpt('use_melange_mac_generation', - default=False, - help="Use Melange for assignment of MAC addresses"), - cfg.BoolOpt('quantum_use_dhcp', - default=False, - help='Whether or not to enable DHCP for networks'), - cfg.BoolOpt('quantum_use_port_security', - default=False, - help='Whether or not to enable port security'), - cfg.BoolOpt('quantum_port_security_include_link_local', - default=False, - help='Add the link local address to the port security list'), - ] - -FLAGS = flags.FLAGS -FLAGS.register_opts(quantum_opts) - - -class QuantumManager(manager.FloatingIP, 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. - """ - - DHCP = FLAGS.quantum_use_dhcp - - 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 not q_conn: - q_conn = quantum_connection.QuantumClientConnection() - self.q_conn = q_conn - - if not ipam_lib: - ipam_lib = FLAGS.quantum_ipam_lib - self._import_ipam_lib(ipam_lib) - - super(QuantumManager, self).__init__(*args, **kwargs) - - def init_host(self): - # Initialize general L3 networking - self.l3driver.initialize() - super(QuantumManager, self).init_host() - # Initialize floating ip support (only works for nova ipam currently) - if FLAGS.quantum_ipam_lib == 'nova.network.quantum.nova_ipam_lib': - LOG.debug("Initializing FloatingIP support") - self.init_host_floating_ips() - # Set up all the forwarding rules for any network that has a - # gateway set. - networks = self.get_all_networks(context.get_admin_context()) - cidrs = [] - for net in networks: - # Don't update host information for network that does not - # belong to you - if net['host'] != self.host: - continue - if net['gateway']: - LOG.debug("Initializing NAT: %s (cidr: %s, gw: %s)" % ( - net['label'], net['cidr'], net['gateway'])) - cidrs.append(net['cidr']) - self._update_network_host(context.get_admin_context(), - net['uuid']) - # .. and for each network - for c in cidrs: - self.l3driver.initialize_network(c) - - # Similar to FlatDHCPMananger, except we check for quantum_use_dhcp flag - # before we try to update_dhcp - def _setup_network_on_host(self, context, network): - """Sets up network on this host.""" - network['dhcp_server'] = self._get_dhcp_ip(context, network) - self.l3driver.initialize_gateway(network) - - if FLAGS.quantum_use_dhcp and not FLAGS.fake_network: - dev = self.driver.get_dev(network) - self.driver.update_dhcp(context, dev, network) - if FLAGS.use_ipv6: - self.driver.update_ra(context, dev, network) - gateway = utils.get_my_linklocal(dev) - self.db.network_update(context, network['id'], - {'gateway_v6': gateway}) - - def _update_network_host(self, context, net_uuid): - """Set the host column in the networks table: note that this won't - work with multi-host but QuantumManager doesn't support that - anyways. The floating IPs mixin required network['host'] to be - set.""" - entry = db.network_get_by_uuid(context.elevated(), net_uuid) - entry['host'] = self.host - db.network_update(context.elevated(), entry['id'], entry) - - def _get_nova_id(self, instance=None): - # When creating the network we need to pass in an identifier for - # this zone. Some Quantum plugins need this information in order - # to set up appropriate networking. - if instance and instance['availability_zone']: - return instance['availability_zone'] - else: - return FLAGS.node_availability_zone - - def get_all_networks(self, context): - networks = [] - networks.extend(self.ipam.get_global_networks(context)) - networks.extend(self.ipam.get_project_networks(context)) - return networks - - def create_networks(self, context, label, cidr, multi_host, num_networks, - network_size, cidr_v6, gateway, 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. - """ - # Enforce Configuration sanity. - # - # These flags are passed in from bin/nova-manage. The script - # collects the arguments and then passes them in through this - # function call. Note that in some cases, the script pre-processes - # the arguments, and sets them to a default value if a parameter's - # value was not specified on the command line. For pre-processed - # parameters, the most effective check to see if the user passed it - # in is to see if is different from the default value. (This - # does miss the use case where the user passes in the default value - # on the command line -- but it is unavoidable.) - if multi_host != FLAGS.multi_host: - # User specified it on the command line. - raise Exception(_("QuantumManager does not use 'multi_host'" - " parameter.")) - - if num_networks != 1: - raise Exception(_("QuantumManager requires that only one" - " network is created per call")) - - if network_size != int(FLAGS.network_size): - # User specified it on the command line. - LOG.warning("Ignoring unnecessary parameter 'network_size'") - - if kwargs.get('vlan_start', None): - if kwargs['vlan_start'] != int(FLAGS.vlan_start): - # User specified it on the command line. - LOG.warning(_("QuantumManager does not use 'vlan_start'" - " parameter.")) - - if kwargs.get('vpn_start', None): - if kwargs['vpn_start'] != int(FLAGS.vpn_start): - # User specified it on the command line. - LOG.warning(_("QuantumManager does not use 'vpn_start'" - " parameter.")) - - if bridge is not None and len(bridge) > 0: - LOG.warning(_("QuantumManager does not use 'bridge'" - " parameter.")) - - if bridge_interface is not None and len(bridge_interface) > 0: - LOG.warning(_("QuantumManager does not use 'bridge_interface'" - " parameter.")) - - if gateway is not None and len(gateway) > 0: - if gateway.split('.')[3] != '1': - raise Exception(_("QuantumManager requires a valid (.1)" - " gateway address.")) - - q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id - quantum_net_id = uuid - # If a uuid was specified with the network it should have already been - # created in Quantum, so make sure. - 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 '%(q_tenant_id)s' " - "with net-id '%(quantum_net_id)s'") % - locals()) - else: - nova_id = self._get_nova_id() - quantum_net_id = self.q_conn.create_network(q_tenant_id, label, - nova_id=nova_id) - - ipam_tenant_id = kwargs.get("project_id", None) - priority = kwargs.get("priority", 0) - # NOTE(tr3buchet): this call creates a nova network in the nova db - self.ipam.create_subnet(context, label, ipam_tenant_id, - quantum_net_id, priority, cidr, - gateway, gateway_v6, cidr_v6, dns1, dns2) - - self._update_network_host(context, quantum_net_id) - - # Initialize forwarding - self.l3driver.initialize_network(cidr) - - return [{'uuid': quantum_net_id}] - - def delete_network(self, context, fixed_range, uuid): - """Lookup network by uuid, delete both the IPAM - subnet and the corresponding Quantum network. - - The fixed_range parameter is kept here for interface compatibility - but is not used. - """ - net_ref = db.network_get_by_uuid(context.elevated(), uuid) - project_id = net_ref['project_id'] - q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - net_uuid = net_ref['uuid'] - - # Check for any attached ports on the network and fail the deletion if - # there is anything but the gateway port attached. If it is only the - # gateway port, unattach and delete it. - ports = self.q_conn.get_attached_ports(q_tenant_id, net_uuid) - num_ports = len(ports) - gw_interface_id = self.driver.get_dev(net_ref) - gw_port_uuid = None - if gw_interface_id is not None: - gw_port_uuid = self.q_conn.get_port_by_attachment(q_tenant_id, - net_uuid, gw_interface_id) - - if gw_port_uuid: - num_ports -= 1 - - if num_ports > 0: - raise exception.NetworkBusy(network=net_uuid) - - # only delete gw ports if we are going to finish deleting network - if gw_port_uuid: - self.q_conn.detach_and_delete_port(q_tenant_id, - net_uuid, - gw_port_uuid) - self.l3driver.remove_gateway(net_ref) - - # Now we can delete the network - self.q_conn.delete_network(q_tenant_id, net_uuid) - LOG.debug("Deleting network %s for tenant: %s" % - (net_uuid, q_tenant_id)) - self.ipam.delete_subnets_by_net_id(context, net_uuid, project_id) - # Get rid of dnsmasq - if FLAGS.quantum_use_dhcp: - if net_ref['host'] == self.host: - self.kill_dhcp(net_ref) - else: - topic = rpc.queue_get_for(context, - FLAGS.network_topic, - net_ref['host']) - - rpc.call(context, topic, {'method': 'kill_dhcp', - 'args': {'net_ref': net_ref}}) - - def kill_dhcp(self, net_ref): - dev = self.driver.get_dev(net_ref) - if self.driver._device_exists(dev): - self.driver.kill_dhcp(dev) - - 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['instance_id'] - rxtx_factor = kwargs['rxtx_factor'] - host = kwargs['host'] - project_id = kwargs['project_id'] - LOG.debug(_("network allocations for instance %s"), project_id) - requested_networks = kwargs.get('requested_networks') - instance = db.instance_get(context, instance_id) - - net_proj_pairs = self.ipam.get_project_and_global_net_ids(context, - project_id) - if requested_networks: - # need to figure out if a requested network is owned - # by the tenant, or by the provider - # Note: these are the only possible options, as the compute - # API already validated networks using validate_network() - proj_net_ids = set([p[0] for p in net_proj_pairs if p[1]]) - net_proj_pairs = [] - for net_id, _i in requested_networks: - if net_id in proj_net_ids: - net_proj_pairs.append((net_id, project_id)) - else: - net_proj_pairs.append((net_id, None)) - - # Create a port via quantum and attach the vif - for proj_pair in net_proj_pairs: - network = self.get_network(context, proj_pair) - - # TODO(tr3buchet): broken. Virtual interfaces require an integer - # network ID and it is not nullable - vif_rec = self.add_virtual_interface(context, - instance['uuid'], - network['id'], - project_id) - - # talk to Quantum API to create and attach port. - nova_id = self._get_nova_id(instance) - # Tell the ipam library to allocate an IP - ips = self.ipam.allocate_fixed_ips(context, project_id, - network['quantum_net_id'], network['net_tenant_id'], - vif_rec) - pairs = [] - # Set up port security if enabled - if FLAGS.quantum_use_port_security: - if FLAGS.quantum_port_security_include_link_local: - mac = netaddr.EUI(vif_rec['address']) - ips.append(str(mac.ipv6_link_local())) - - pairs = [{'mac_address': vif_rec['address'], - 'ip_address': ip} for ip in ips] - - self.q_conn.create_and_attach_port(network['net_tenant_id'], - network['quantum_net_id'], - vif_rec['uuid'], - vm_id=instance['uuid'], - rxtx_factor=rxtx_factor, - nova_id=nova_id, - allowed_address_pairs=pairs) - # Set up/start the dhcp server for this network if necessary - if FLAGS.quantum_use_dhcp: - if network['host'] == self.host: - self.enable_dhcp(context, network['quantum_net_id'], - network, vif_rec, network['net_tenant_id']) - else: - topic = rpc.queue_get_for(context, - FLAGS.network_topic, network['host']) - rpc.call(context, topic, {'method': 'enable_dhcp', - 'args': {'quantum_net_id': network['quantum_net_id'], - 'network_ref': network, - 'vif_rec': vif_rec, - 'project_id': network['net_tenant_id']}}) - - return self.get_instance_nw_info(context, instance_id, - instance['uuid'], - rxtx_factor, host, - project_id=project_id) - - def get_network(self, context, proj_pair): - (quantum_net_id, net_tenant_id) = proj_pair - - net_tenant_id = net_tenant_id or FLAGS.quantum_default_tenant_id - # 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. - admin_context = context.elevated() - - # We may not be able to get a network_ref here if this network - # isn't in the database (i.e. it came from Quantum). - network_ref = db.network_get_by_uuid(admin_context, - quantum_net_id) - - if network_ref is None: - network_ref = {} - network_ref = {"uuid": quantum_net_id, - "project_id": net_tenant_id, - # NOTE(bgh): We need to document this somewhere but since - # we don't know the priority of any networks we get from - # quantum we just give them a priority of 0. If its - # necessary to specify the order of the vifs and what - # network they map to then the user will have to use the - # OSCreateServer extension and specify them explicitly. - # - # In the future users will be able to tag quantum networks - # with a priority .. and at that point we can update the - # code here to reflect that. - "priority": 0, - "id": 'NULL', - "label": "quantum-net-%s" % quantum_net_id} - network_ref['net_tenant_id'] = net_tenant_id - network_ref['quantum_net_id'] = quantum_net_id - return network_ref - - @manager.wrap_check_policy - def get_instance_uuids_by_ip_filter(self, context, filters): - # This is not returning the instance IDs like the method name - # would make you think; it is matching the return format of - # the method it's overriding. - instance_ids = self.ipam.get_instance_ids_by_ip_address( - context, filters.get('ip')) - instances = [db.instance_get(context, id) for id in instance_ids] - return [{'instance_uuid':instance.uuid} for instance in instances] - - @utils.synchronized('quantum-enable-dhcp') - def enable_dhcp(self, context, quantum_net_id, network_ref, vif_rec, - project_id): - LOG.info("Using DHCP for network: %s" % network_ref['label']) - # Figure out the ipam tenant id for this subnet: We need to - # query for the tenant_id since the network could be created - # with the project_id as the tenant or the default tenant. - ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context, - quantum_net_id, vif_rec['uuid'], project_id) - # Figure out what subnets correspond to this network - subnets = self.ipam.get_subnets_by_net_id(context, - ipam_tenant_id, quantum_net_id, vif_rec['uuid']) - - # Set up (or find) the dhcp server for each of the subnets - # returned above (both v4 and v6). - for subnet in subnets: - if subnet is None or subnet['cidr'] is None: - continue - # Fill in some of the network fields that we would have - # previously gotten from the network table (they'll be - # passed to the linux_net functions). - network_ref['cidr'] = subnet['cidr'] - n = netaddr.IPNetwork(subnet['cidr']) - # NOTE(tr3buchet): should probably not always assume first+1 - network_ref['dhcp_server'] = netaddr.IPAddress(n.first + 1) - # TODO(bgh): Melange should probably track dhcp_start - # TODO(tr3buchet): melange should store dhcp_server as well - if (not 'dhcp_start' in network_ref or - network_ref['dhcp_start'] is None): - network_ref['dhcp_start'] = netaddr.IPAddress(n.first + 2) - network_ref['broadcast'] = netaddr.IPAddress(n.broadcast) - network_ref['gateway'] = subnet['gateway'] - # Construct the interface id that we'll use for the bridge - interface_id = self.driver.get_dev(network_ref) - network_ref['bridge'] = interface_id - # Query quantum to see if we've already created a port for - # the gateway device and attached the device to the port. - # If we haven't then we need to intiialize it and create - # it. This device will be the one serving dhcp via - # dnsmasq. - q_tenant_id = project_id or FLAGS.quantum_default_tenant_id - port = self.q_conn.get_port_by_attachment(q_tenant_id, - quantum_net_id, interface_id) - - if not port: # No dhcp server has been started - self.l3driver.initialize_gateway(network_ref) - LOG.debug("Intializing DHCP for network: %s" % - network_ref) - self.q_conn.create_and_attach_port(q_tenant_id, - quantum_net_id, interface_id) - - hosts = self.get_dhcp_hosts_text(context, - subnet['network_id'], project_id) - self.driver.update_dhcp_hostfile_with_text(interface_id, hosts) - self.driver.restart_dhcp(context, interface_id, network_ref) - - def add_virtual_interface(self, context, instance_uuid, network_id, - net_tenant_id): - # If we're not using melange, use the default means... - if FLAGS.use_melange_mac_generation: - return self._add_virtual_interface(context, instance_uuid, - network_id, net_tenant_id) - - return super(QuantumManager, self).add_virtual_interface(context, - instance_uuid, - network_id) - - def _add_virtual_interface(self, context, instance_uuid, network_id, - net_tenant_id): - vif = {'instance_uuid': instance_uuid, - 'network_id': network_id, - 'uuid': str(utils.gen_uuid())} - - # TODO(Vek): Ideally, we would have a VirtualInterface class - # that would take care of delegating to whoever it - # needs to get information from. We'll look at - # this after Trey's refactorings... - m_ipam = melange_ipam_lib.get_ipam_lib(self) - vif['address'] = m_ipam.create_vif(vif['uuid'], - vif['instance_uuid'], - net_tenant_id) - - return self.db.virtual_interface_create(context, vif) - - def get_instance_nw_info(self, context, instance_id, instance_uuid, - rxtx_factor, host, **kwargs): - """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. - """ - project_id = kwargs['project_id'] - vifs = db.virtual_interface_get_by_instance(context, instance_uuid) - - net_tenant_dict = dict((net_id, tenant_id) - for (net_id, tenant_id) - in self.ipam.get_project_and_global_net_ids( - context, project_id)) - networks = {} - for vif in vifs: - if vif.get('network_id') is not None: - network = db.network_get(context.elevated(), vif['network_id']) - net_tenant_id = net_tenant_dict[network['uuid']] - if net_tenant_id is None: - net_tenant_id = FLAGS.quantum_default_tenant_id - network = {'id': network['id'], - 'uuid': network['uuid'], - 'bridge': '', # Quantum ignores this field - 'label': network['label'], - 'injected': FLAGS.flat_injected, - 'project_id': net_tenant_id} - networks[vif['uuid']] = network - - # update instance network cache and return network_info - nw_info = self.build_network_info_model(context, vifs, networks, - rxtx_factor, host) - db.instance_info_cache_update(context, instance_uuid, - {'network_info': nw_info.json()}) - - return nw_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) - - admin_context = context.elevated() - instance = db.instance_get(context, instance_id) - vifs = db.virtual_interface_get_by_instance(admin_context, - instance['uuid']) - - for vif in vifs: - network = db.network_get(admin_context, vif['network_id']) - - self.deallocate_port(vif['uuid'], network['uuid'], project_id, - instance_id) - - ipam_tenant_id = self.deallocate_ip_address(context, - network['uuid'], project_id, vif, instance_id) - - if FLAGS.quantum_use_dhcp: - if network['host'] == self.host: - self.update_dhcp(context, ipam_tenant_id, network, - vif, project_id) - else: - topic = rpc.queue_get_for(context, - FLAGS.network_topic, network['host']) - rpc.call(context, topic, {'method': 'update_dhcp', - 'args': {'ipam_tenant_id': ipam_tenant_id, - 'network_ref': network, - 'vif_ref': vif, - 'project_id': network['project_id']}}) - - db.virtual_interface_delete(admin_context, vif['id']) - - def deallocate_port(self, interface_id, net_id, q_tenant_id, instance_id): - port_id = None - try: - port_id = self.q_conn.get_port_by_attachment(q_tenant_id, - net_id, interface_id) - if not port_id: - q_tenant_id = FLAGS.quantum_default_tenant_id - port_id = self.q_conn.get_port_by_attachment( - q_tenant_id, net_id, interface_id) - - if not port_id: - LOG.error("Unable to find port with attachment: %s" % - (interface_id)) - else: - self.q_conn.detach_and_delete_port(q_tenant_id, - net_id, port_id) - except Exception: - # except anything so the rest of deallocate can succeed - LOG.exception(_('port deallocation failed for instance: ' - '|%(instance_id)s|, port_id: |%(port_id)s|') % locals()) - - def deallocate_ip_address(self, context, net_id, - project_id, vif_ref, instance_id): - ipam_tenant_id = None - try: - ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context, - net_id, - vif_ref['uuid'], - project_id) - - self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, - net_id, vif_ref) - - except Exception: - # except anything so the rest of deallocate can succeed - vif_uuid = vif_ref['uuid'] - msg = _('ipam deallocation failed for instance: ' - '|%(instance_id)s|, vif_uuid: |%(vif_uuid)s|') % locals() - LOG.exception(msg) - return ipam_tenant_id - - # enable_dhcp() could also use this - # - # Use semaphore to : - # 1) avoid race between restart_dhcps - # 2) avoid race between update_dhcp_hostfile_with_texts - @utils.synchronized('quantum-restart-dhcp') - def _atomic_restart_dhcp(self, context, dev, hosts, network_ref): - self.driver.update_dhcp_hostfile_with_text(dev, hosts) - # Since we only update hosts file, explict kill_dhcp() isn't needed. - # restart_dhcp() will reload hostfile if dnsmasq is already running, - # or start new dnsmasq - self.driver.restart_dhcp(context, dev, network_ref) - - # TODO(bgh): At some point we should consider merging enable_dhcp() and - # update_dhcp() - # TODO(tr3buchet): agree, i'm curious why they differ even now.. - def update_dhcp(self, context, ipam_tenant_id, network_ref, vif_ref, - project_id): - # Figure out what subnet corresponds to this network/vif - subnets = self.ipam.get_subnets_by_net_id(context, - ipam_tenant_id, network_ref['uuid'], vif_ref['uuid']) - for subnet in subnets: - if subnet is None: - continue - # Fill in some of the network fields that we would have - # previously gotten from the network table (they'll be - # passed to the linux_net functions). - if subnet['cidr']: - network_ref['cidr'] = subnet['cidr'] - n = netaddr.IPNetwork(network_ref['cidr']) - network_ref['dhcp_server'] = netaddr.IPAddress(n.first + 1) - network_ref['dhcp_start'] = netaddr.IPAddress(n.first + 2) - network_ref['broadcast'] = netaddr.IPAddress(n.broadcast) - network_ref['gateway'] = netaddr.IPAddress(n.first + 1) - dev = self.driver.get_dev(network_ref) - # And remove the dhcp mappings for the subnet - hosts = self.get_dhcp_hosts_text(context, - subnet['network_id'], project_id) - - self._atomic_restart_dhcp(context, dev, hosts, network_ref) - - 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 - - project_id = context.project_id - for (net_id, _i) in networks: - # TODO(bgh): At some point we should figure out whether or - # not we want the verify_subnet_exists call to be optional. - if not self.ipam.verify_subnet_exists(context, project_id, - net_id): - raise exception.NetworkNotFound(network_id=net_id) - is_tenant_net = self.q_conn.network_exists(project_id, net_id) - is_provider_net = self.q_conn.network_exists( - FLAGS.quantum_default_tenant_id, - net_id) - if not (is_tenant_net or is_provider_net): - raise exception.NetworkNotFound(network_id=net_id) - - def get_dhcp_hosts_text(self, context, subnet_id, project_id=None): - ips = self.ipam.get_allocated_ips(context, subnet_id, project_id) - hosts_text = "" - admin_context = context.elevated() - for ip in ips: - address, vif_id = ip - vif = db.virtual_interface_get_by_uuid(admin_context, vif_id) - mac_address = vif['address'] - text = "%s,%s.%s,%s\n" % (mac_address, "host-" + address, - FLAGS.dhcp_domain, address) - hosts_text += text - LOG.debug("DHCP hosts: %s" % hosts_text) - return hosts_text - - def get_dhcp_leases(self, context, network_ref): - """Return a network's hosts config in dnsmasq leasefile format.""" - subnet_id = network_ref['uuid'] - project_id = network_ref['project_id'] - ips = self.ipam.get_allocated_ips(context, subnet_id, project_id) - leases_text = "" - admin_context = context.elevated() - for ip in ips: - address, vif_id = ip - vif = db.virtual_interface_get_by_uuid(admin_context, vif_id) - mac_address = vif['address'] - text = ("%s %s %s %s *\n" % (int(time.time()) - - FLAGS.dhcp_lease_time, - mac_address, address, '*')) - leases_text += text - LOG.debug("DHCP leases: %s" % leases_text) - return leases_text - - def setup_networks_on_host(self, *args, **kwargs): - # no host specific setup is needed in quantum manager - pass diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py deleted file mode 100644 index 342222909..000000000 --- a/nova/network/quantum/melange_connection.py +++ /dev/null @@ -1,202 +0,0 @@ -# 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 time -import urllib - -from nova import exception -from nova import flags -from nova.openstack.common import cfg -from nova.openstack.common import jsonutils -from nova.openstack.common import log as logging - - -melange_opts = [ - cfg.StrOpt('melange_host', - default='127.0.0.1', - help='HOST for connecting to melange'), - cfg.IntOpt('melange_port', - default=9898, - help='PORT for connecting to melange'), - cfg.IntOpt('melange_num_retries', - default=0, - help='Number retries when contacting melange'), - ] - -FLAGS = flags.FLAGS -FLAGS.register_opts(melange_opts) -LOG = logging.getLogger(__name__) - -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): - if host is None: - host = FLAGS.melange_host - if port is None: - port = FLAGS.melange_port - self.host = host - self.port = port - self.use_ssl = use_ssl - self.version = "v0.1" - - def get(self, path, params=None, headers=None): - return self.do_request("GET", path, params=params, headers=headers, - retries=FLAGS.melange_num_retries) - - def post(self, path, body=None, headers=None): - return self.do_request("POST", path, body=body, headers=headers) - - def delete(self, path, headers=None): - 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=None, params=None, - content_type=".json", retries=0): - headers = headers or {} - params = params or {} - - url = "/%s/%s%s" % (self.version, path, content_type) - if params: - url += "?%s" % urllib.urlencode(params) - for i in xrange(retries + 1): - connection = self._get_connection() - try: - 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: - LOG.exception(_('Connection error contacting melange' - ' service, retrying')) - - time.sleep(1) - - raise exception.MelangeConnectionFailed( - reason=_("Maximum attempts reached")) - - def allocate_ip(self, network_id, network_tenant_id, vif_id, - project_id=None, mac_address=None): - LOG.info(_("allocate IP on network |%(network_id)s| " - "belonging to |%(network_tenant_id)s| " - "to this vif |%(vif_id)s| with mac |%(mac_address)s| " - "belonging to |%(project_id)s| ") % locals()) - tenant_scope = "/tenants/%s" % (network_tenant_id - if network_tenant_id else "") - network_info = dict(network=dict(mac_address=mac_address, - tenant_id=project_id)) - request_body = jsonutils.dumps(network_info) 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 jsonutils.loads(response)['ip_addresses'] - - def create_block(self, network_id, cidr, - project_id=None, gateway=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', gateway=gateway, - dns1=dns1, dns2=dns2)) - self.post(url, - body=jsonutils.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 jsonutils.loads(response) - - def get_routes(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/ip_routes" % - locals()) - - response = self.get(url, headers=json_content_type) - return jsonutils.loads(response)['ip_routes'] - - 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 jsonutils.loads(response)['ip_addresses'] - - def get_allocated_ips_by_address(self, address): - url = "ipam/allocated_ip_addresses" - - response = self.get(url, params={'address': address}, - headers=json_content_type) - - return jsonutils.loads(response).get('ip_addresses', []) - - def get_allocated_ips_for_network(self, network_id, project_id=None): - tenant_scope = "/tenants/%s" % project_id if project_id else "" - url = ("ipam%(tenant_scope)s/allocated_ip_addresses" % locals()) - # TODO(bgh): This request fails if you add the ".json" to the end so - # it has to call do_request itself. Melange bug? - response = self.do_request("GET", url, content_type="") - return jsonutils.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) - - def create_vif(self, vif_id, instance_id, project_id=None): - url = "ipam/interfaces" - - request_body = dict(interface=dict(id=vif_id, tenant_id=project_id, - device_id=instance_id)) - - response = self.post(url, - body=jsonutils.dumps(request_body), - headers=json_content_type) - - return jsonutils.loads(response)['interface']['mac_address'] diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py deleted file mode 100644 index 44f4b3c69..000000000 --- a/nova/network/quantum/melange_ipam_lib.py +++ /dev/null @@ -1,261 +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. - -import netaddr - -from nova import db -from nova import exception -from nova import flags -from nova.network.quantum import melange_connection -from nova.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -FLAGS = flags.FLAGS - - -def get_ipam_lib(net_man): - return QuantumMelangeIPAMLib() - - -class QuantumMelangeIPAMLib(object): - """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=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. - - Also create an 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, - gateway=gateway, - dns1=dns1, dns2=dns2) - if cidr_v6: - self.m_conn.create_block(quantum_net_id, cidr_v6, - project_id=tenant_id, - gateway=gateway_v6, - dns1=dns1, dns2=dns2) - - net = {"uuid": quantum_net_id, - "project_id": tenant_id, - "priority": priority, - "label": label} - if FLAGS.quantum_use_dhcp: - if cidr: - n = netaddr.IPNetwork(cidr) - net['dhcp_start'] = netaddr.IPAddress(n.first + 2) - else: - net['dhcp_start'] = None - admin_context = context.elevated() - network = db.network_create_safe(admin_context, net) - - def allocate_fixed_ips(self, context, project_id, quantum_net_id, - network_tenant_id, vif_ref): - """Pass call to allocate fixed IP on to Melange""" - ips = self.m_conn.allocate_ip(quantum_net_id, network_tenant_id, - vif_ref['uuid'], project_id, - vif_ref['address']) - return [ip['address'] for ip in ips] - - 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) - 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_uuid(admin_context, net_id) - db.network_delete_safe(context, network['id']) - - def get_networks_by_tenant(self, admin_context, tenant_id): - nets = {} - 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) - nets[network_id] = network - return nets.values() - - def get_global_networks(self, admin_context): - return self.get_networks_by_tenant(admin_context, - FLAGS.quantum_default_tenant_id) - - def get_project_networks(self, admin_context): - try: - nets = db.network_get_all(admin_context.elevated()) - except exception.NoNetworksFound: - return [] - # only return networks with a project_id set - return [net for net in nets if net['project_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' (lowest integer value - is highest priority). - """ - if project_id is None: - 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): - nets = self.get_networks_by_tenant(admin_context, tenant_id) - for network in nets: - priority = network['priority'] - priority_nets.append((priority, network['uuid'], 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_tenant_id_by_net_id(self, context, net_id, vif_id, project_id): - ipam_tenant_id = None - tenant_ids = [FLAGS.quantum_default_tenant_id, project_id, None] - # This is confusing, if there are IPs for the given net, vif, - # tenant trifecta we assume that is the tenant for that network - for tid in tenant_ids: - try: - self.m_conn.get_allocated_ips(net_id, vif_id, tid) - except Exception: - continue - ipam_tenant_id = tid - break - return ipam_tenant_id - - # TODO(bgh): Rename this method .. it's now more of a - # "get_subnets_by_net_id_and_vif_id" method, but we could probably just - # call it "get_subnets". - def get_subnets_by_net_id(self, context, tenant_id, net_id, vif_id): - """Returns information about the IPv4 and IPv6 subnets - associated with a Quantum Network UUID. - """ - subnets = [] - ips = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id) - - for ip_address in ips: - block = ip_address['ip_block'] - subnet = {'network_id': block['network_id'], - 'id': block['id'], - 'cidr': block['cidr'], - 'gateway': block['gateway'], - 'broadcast': block['broadcast'], - 'netmask': block['netmask'], - 'dns1': block['dns1'], - 'dns2': block['dns2']} - if ip_address['version'] == 4: - subnet['version'] = 4 - else: - subnet['version'] = 6 - subnets.append(subnet) - return subnets - - def get_routes_by_ip_block(self, context, block_id, project_id): - """Returns the list of routes for the IP block""" - return self.m_conn.get_routes(block_id, project_id) - - 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. - """ - 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. - """ - 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. - """ - 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 netaddr.IPNetwork(ip['address']).version == ip_version] - - def get_instance_ids_by_ip_address(self, context, address): - ips = self.m_conn.get_allocated_ips_by_address(address) - # TODO(aaron.lee): melange should be storing & returning instance_uuid! - return [ip.get('used_by_device') for ip in ips] - - 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. - """ - # TODO(bgh): Would be nice if we could just do something like: - # GET /ipam/tenants/{tenant_id}/networks/{network_id}/ instead - # of searching through all the blocks. Checking for a 404 - # will then determine whether it exists. - 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'] == quantum_net_id: - return True - return False - - 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) - - def get_allocated_ips(self, context, subnet_id, project_id): - ips = self.m_conn.get_allocated_ips_for_network(subnet_id, project_id) - return [(ip['address'], ip['interface_id']) for ip in ips] - - def create_vif(self, vif_id, instance_id, project_id=None): - """Create a new vif with the specified information. - """ - tenant_id = project_id or FLAGS.quantum_default_tenant_id - return self.m_conn.create_vif(vif_id, instance_id, tenant_id) - - def get_floating_ips_by_fixed_address(self, context, fixed_address): - """This call is not supported in quantum yet""" - return [] diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py deleted file mode 100644 index 2161c7916..000000000 --- a/nova/network/quantum/quantum_connection.py +++ /dev/null @@ -1,159 +0,0 @@ -# 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.network.quantum import client as quantum_client -from nova.openstack.common import cfg -from nova.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - -quantum_opts = [ - cfg.StrOpt('quantum_connection_host', - default='127.0.0.1', - help='HOST for connecting to quantum'), - cfg.IntOpt('quantum_connection_port', - default=9696, - help='PORT for connecting to quantum'), - cfg.StrOpt('quantum_default_tenant_id', - default="default", - help='Default tenant id when creating quantum networks'), - cfg.IntOpt('quantum_request_timeout', - default=20, - help='Maximum amount of time to wait for quantum request'), - ] - -FLAGS = flags.FLAGS -FLAGS.register_opts(quantum_opts) - - -class QuantumClientConnection(object): - """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, client=None): - """Initialize Quantum client class based on flags.""" - if client: - self.client = client - else: - self.client = quantum_client.Client(FLAGS.quantum_connection_host, - FLAGS.quantum_connection_port, - timeout=FLAGS.quantum_request_timeout, - format="json", - logger=LOG) - - def create_network(self, tenant_id, network_name, **kwargs): - """Create network using specified name, return Quantum - network UUID. - """ - data = {'network': {'name': network_name}} - for kw in kwargs: - data['network'][kw] = kwargs[kw] - resdict = self.client.create_network(data, tenant=tenant_id) - return resdict["network"]["id"] - - def get_network_name(self, tenant_id, network_id): - net = self.client.show_network_details(network_id, tenant=tenant_id) - return net["network"]["name"] - - 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) - return True - except quantum_client.QuantumNotFoundException: - # Not really an error. Real errors will be propogated to caller - return False - - def get_networks(self, tenant_id): - """Retrieve all networks for this tenant""" - return self.client.list_networks(tenant=tenant_id) - - def create_and_attach_port(self, tenant_id, net_id, interface_id, - **kwargs): - """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()) - port_data = {'port': {'state': 'ACTIVE'}} - for kw in kwargs: - port_data['port'][kw] = kwargs[kw] - resdict = self.client.create_port(net_id, port_data, tenant=tenant_id) - port_id = resdict["port"]["id"] - - attach_data = {'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): - """Detach and delete the specified Quantum port.""" - 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) - - def get_port_by_attachment(self, tenant_id, net_id, attachment_id): - """Given a tenant and network, search for the port UUID that - has the specified interface-id attachment. - """ - port_list = [] - try: - port_list_resdict = self.client.list_ports(net_id, - tenant=tenant_id, - filter_ops={'attachment': attachment_id}) - port_list = port_list_resdict["ports"] - except quantum_client.QuantumNotFoundException: - return None - - port_list_len = len(port_list) - if port_list_len == 1: - return port_list[0]['id'] - elif port_list_len > 1: - raise Exception("Expected single port with attachment " - "%(attachment_id)s, found %(port_list_len)s" % locals()) - return None - - def get_attached_ports(self, tenant_id, network_id): - rv = [] - port_list = self.client.list_ports(network_id, tenant=tenant_id) - for p in port_list["ports"]: - try: - port_id = p["id"] - port = self.client.show_port_attachment(network_id, - port_id, tenant=tenant_id) - # Skip ports without an attachment - if "id" not in port["attachment"]: - continue - rv.append({'port-id': port_id, - 'attachment': port["attachment"]["id"]}) - except quantum_client.QuantumNotFoundException: - pass - return rv diff --git a/nova/network/quantum/sg.py b/nova/network/quantum/sg.py deleted file mode 100644 index aad9f1052..000000000 --- a/nova/network/quantum/sg.py +++ /dev/null @@ -1,159 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 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. - -'''Implement Security Groups abstraction and API. - -The nova security_group_handler flag specifies which class is to be used -to implement the security group calls. - -The NullSecurityGroupHandler provides a "no-op" plugin that is loaded -by default and has no impact on current system behavior. In the future, -special purposes classes that inherit from SecurityGroupHandlerBase -will provide enhanced functionality and will be loadable via the -security_group_handler flag. -''' - -from nova.openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - - -class SecurityGroupHandlerBase(object): - - def __init__(self): - raise NotImplementedError() - - def trigger_security_group_create_refresh(self, context, group): - '''Called when a security group is created - - :param context: the security context. - :param group: the new group added. group is a dictionary that contains - the following: user_id, project_id, name, description).''' - raise NotImplementedError() - - def trigger_security_group_destroy_refresh(self, context, - security_group_id): - '''Called when a security group is deleted - - :param context: the security context. - :param security_group_id: the security group identifier.''' - raise NotImplementedError() - - def trigger_security_group_rule_create_refresh(self, context, - rule_ids): - '''Called when a rule is added to a security_group. - - :param context: the security context. - :param rule_ids: a list of rule ids that have been affected.''' - raise NotImplementedError() - - def trigger_security_group_rule_destroy_refresh(self, context, - rule_ids): - '''Called when a rule is removed from a security_group. - - :param context: the security context. - :param rule_ids: a list of rule ids that have been affected.''' - raise NotImplementedError() - - def trigger_instance_add_security_group_refresh(self, context, instance, - group_name): - '''Called when a security group gains a new member. - - :param context: the security context. - :param instance: the instance to be associated. - :param group_name: the name of the security group to be associated.''' - raise NotImplementedError() - - def trigger_instance_remove_security_group_refresh(self, context, instance, - group_name): - '''Called when a security group loses a member. - - :param context: the security context. - :param instance: the instance to be associated. - :param group_name: the name of the security group to be associated.''' - raise NotImplementedError() - - def trigger_security_group_members_refresh(self, context, group_ids): - '''Called when a security group gains or loses a member. - - :param context: the security context. - :param group_ids: a list of security group identifiers.''' - raise NotImplementedError() - - -class NullSecurityGroupHandler(SecurityGroupHandlerBase): - - def __init__(self): - pass - - def trigger_security_group_create_refresh(self, context, group): - '''Called when a rule is added to a security_group. - - :param context: the security context. - :param group: the new group added. group is a dictionary that contains - the following: user_id, project_id, name, description).''' - pass - - def trigger_security_group_destroy_refresh(self, context, - security_group_id): - '''Called when a rule is added to a security_group. - - :param context: the security context. - :param security_group_id: the security group identifier.''' - pass - - def trigger_security_group_rule_create_refresh(self, context, - rule_ids): - '''Called when a rule is added to a security_group. - - :param context: the security context. - :param rule_ids: a list of rule ids that have been affected.''' - pass - - def trigger_security_group_rule_destroy_refresh(self, context, - rule_ids): - '''Called when a rule is removed from a security_group. - - :param context: the security context. - :param rule_ids: a list of rule ids that have been affected.''' - pass - - def trigger_instance_add_security_group_refresh(self, context, instance, - group_name): - '''Called when a security group gains a new member. - - :param context: the security context. - :param instance: the instance to be associated. - :param group_name: the name of the security group to be associated.''' - pass - - def trigger_instance_remove_security_group_refresh(self, context, instance, - group_name): - '''Called when a security group loses a member. - - :param context: the security context. - :param instance: the instance to be associated. - :param group_name: the name of the security group to be associated.''' - pass - - def trigger_security_group_members_refresh(self, context, group_ids): - '''Called when a security group gains or loses a member. - - :param context: the security context. - :param group_ids: a list of security group identifiers.''' - pass diff --git a/nova/network/sg.py b/nova/network/sg.py new file mode 100644 index 000000000..aad9f1052 --- /dev/null +++ b/nova/network/sg.py @@ -0,0 +1,159 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 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. + +'''Implement Security Groups abstraction and API. + +The nova security_group_handler flag specifies which class is to be used +to implement the security group calls. + +The NullSecurityGroupHandler provides a "no-op" plugin that is loaded +by default and has no impact on current system behavior. In the future, +special purposes classes that inherit from SecurityGroupHandlerBase +will provide enhanced functionality and will be loadable via the +security_group_handler flag. +''' + +from nova.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +class SecurityGroupHandlerBase(object): + + def __init__(self): + raise NotImplementedError() + + def trigger_security_group_create_refresh(self, context, group): + '''Called when a security group is created + + :param context: the security context. + :param group: the new group added. group is a dictionary that contains + the following: user_id, project_id, name, description).''' + raise NotImplementedError() + + def trigger_security_group_destroy_refresh(self, context, + security_group_id): + '''Called when a security group is deleted + + :param context: the security context. + :param security_group_id: the security group identifier.''' + raise NotImplementedError() + + def trigger_security_group_rule_create_refresh(self, context, + rule_ids): + '''Called when a rule is added to a security_group. + + :param context: the security context. + :param rule_ids: a list of rule ids that have been affected.''' + raise NotImplementedError() + + def trigger_security_group_rule_destroy_refresh(self, context, + rule_ids): + '''Called when a rule is removed from a security_group. + + :param context: the security context. + :param rule_ids: a list of rule ids that have been affected.''' + raise NotImplementedError() + + def trigger_instance_add_security_group_refresh(self, context, instance, + group_name): + '''Called when a security group gains a new member. + + :param context: the security context. + :param instance: the instance to be associated. + :param group_name: the name of the security group to be associated.''' + raise NotImplementedError() + + def trigger_instance_remove_security_group_refresh(self, context, instance, + group_name): + '''Called when a security group loses a member. + + :param context: the security context. + :param instance: the instance to be associated. + :param group_name: the name of the security group to be associated.''' + raise NotImplementedError() + + def trigger_security_group_members_refresh(self, context, group_ids): + '''Called when a security group gains or loses a member. + + :param context: the security context. + :param group_ids: a list of security group identifiers.''' + raise NotImplementedError() + + +class NullSecurityGroupHandler(SecurityGroupHandlerBase): + + def __init__(self): + pass + + def trigger_security_group_create_refresh(self, context, group): + '''Called when a rule is added to a security_group. + + :param context: the security context. + :param group: the new group added. group is a dictionary that contains + the following: user_id, project_id, name, description).''' + pass + + def trigger_security_group_destroy_refresh(self, context, + security_group_id): + '''Called when a rule is added to a security_group. + + :param context: the security context. + :param security_group_id: the security group identifier.''' + pass + + def trigger_security_group_rule_create_refresh(self, context, + rule_ids): + '''Called when a rule is added to a security_group. + + :param context: the security context. + :param rule_ids: a list of rule ids that have been affected.''' + pass + + def trigger_security_group_rule_destroy_refresh(self, context, + rule_ids): + '''Called when a rule is removed from a security_group. + + :param context: the security context. + :param rule_ids: a list of rule ids that have been affected.''' + pass + + def trigger_instance_add_security_group_refresh(self, context, instance, + group_name): + '''Called when a security group gains a new member. + + :param context: the security context. + :param instance: the instance to be associated. + :param group_name: the name of the security group to be associated.''' + pass + + def trigger_instance_remove_security_group_refresh(self, context, instance, + group_name): + '''Called when a security group loses a member. + + :param context: the security context. + :param instance: the instance to be associated. + :param group_name: the name of the security group to be associated.''' + pass + + def trigger_security_group_members_refresh(self, context, group_ids): + '''Called when a security group gains or loses a member. + + :param context: the security context. + :param group_ids: a list of security group identifiers.''' + pass diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 5c945fa75..476d7eab4 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -20,7 +20,7 @@ from nova import db from nova import exception from nova import flags from nova.network import manager as network_manager -from nova.network.quantum import nova_ipam_lib +from nova.network import nova_ipam_lib from nova import utils diff --git a/nova/tests/network/test_quantum.py b/nova/tests/network/test_quantum.py deleted file mode 100644 index b126994d0..000000000 --- a/nova/tests/network/test_quantum.py +++ /dev/null @@ -1,647 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011,2012 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. - -import mox - -from nova import context -from nova import db -from nova.db.sqlalchemy import models -from nova.db.sqlalchemy import session as sql_session -from nova import exception -from nova import flags -from nova.network.quantum import client as quantum_client -from nova.network.quantum import fake_client -from nova.network.quantum import manager as quantum_manager -from nova.network.quantum import melange_connection -from nova.network.quantum import melange_ipam_lib -from nova.network.quantum import quantum_connection -from nova.openstack.common import log as logging - -from nova import test -from nova import utils - -LOG = logging.getLogger(__name__) -FLAGS = flags.FLAGS - - -networks = [{'label': 'project1-net1', - 'injected': False, - 'multi_host': False, - 'cidr': '100.168.0.0/24', - 'cidr_v6': '100:1db8::/64', - 'gateway_v6': '100:1db8::1', - 'netmask_v6': '64', - 'netmask': '255.255.255.0', - 'bridge': None, - 'bridge_interface': None, - 'gateway': '100.168.0.1', - 'broadcast': '100.168.0.255', - 'dns1': '8.8.8.8', - 'vlan': None, - 'host': None, - 'vpn_public_address': None, - 'project_id': 'fake_project1', - 'priority': 1}, - {'label': 'project2-net1', - 'injected': False, - 'multi_host': False, - 'cidr': '101.168.1.0/24', - 'cidr_v6': '101:1db9::/64', - 'gateway_v6': '101:1db9::1', - 'netmask_v6': '64', - 'netmask': '255.255.255.0', - 'bridge': None, - 'bridge_interface': None, - 'gateway': '101.168.1.1', - 'broadcast': '101.168.1.255', - 'dns1': '8.8.8.8', - 'vlan': None, - 'host': None, - 'project_id': 'fake_project2', - 'priority': 1}, - {'label': "public", - 'injected': False, - 'multi_host': False, - 'cidr': '102.0.0.0/24', - 'cidr_v6': '102:1dba::/64', - 'gateway_v6': '102:1dba::1', - 'netmask_v6': '64', - 'netmask': '255.255.255.0', - 'bridge': None, - 'bridge_interface': None, - 'gateway': '102.0.0.1', - 'broadcast': '102.0.0.255', - 'dns1': '8.8.8.8', - 'vlan': None, - 'host': None, - 'project_id': None, - 'priority': 0}, - {'label': "project2-net2", - 'injected': False, - 'multi_host': False, - 'cidr': '103.0.0.0/24', - 'cidr_v6': '103:1dbb::/64', - 'gateway_v6': '103:1dbb::1', - 'netmask_v6': '64', - 'netmask': '255.255.255.0', - 'bridge': None, - 'bridge_interface': None, - 'gateway': '103.0.0.1', - 'broadcast': '103.0.0.255', - 'dns1': '8.8.8.8', - 'vlan': None, - 'host': None, - 'project_id': "fake_project2", - 'priority': 2}] - - -class QuantumConnectionTestCase(test.TestCase): - - def test_connection(self): - fc = fake_client.FakeClient(LOG) - qc = quantum_connection.QuantumClientConnection(client=fc) - t = "tenant1" - net1_name = "net1" - net1_uuid = qc.create_network(t, net1_name) - self.assertEquals(net1_name, qc.get_network_name(t, net1_uuid)) - self.assertTrue(qc.network_exists(t, net1_uuid)) - self.assertFalse(qc.network_exists(t, "fake-uuid")) - self.assertFalse(qc.network_exists("fake-tenant", net1_uuid)) - - nets = qc.get_networks(t)['networks'] - self.assertEquals(len(nets), 1) - self.assertEquals(nets[0]['id'], net1_uuid) - - num_ports = 10 - for i in range(0, num_ports): - qc.create_and_attach_port(t, net1_uuid, - 'iface' + str(i), state='ACTIVE') - - self.assertEquals(len(qc.get_attached_ports(t, net1_uuid)), num_ports) - - for i in range(0, num_ports): - port_uuid = qc.get_port_by_attachment(t, net1_uuid, - 'iface' + str(i)) - self.assertTrue(port_uuid) - qc.detach_and_delete_port(t, net1_uuid, port_uuid) - - self.assertEquals(len(qc.get_attached_ports(t, net1_uuid)), 0) - - # test port not found - qc.create_and_attach_port(t, net1_uuid, 'foo', state='ACTIVE') - port_uuid = qc.get_port_by_attachment(t, net1_uuid, 'foo') - qc.detach_and_delete_port(t, net1_uuid, port_uuid) - self.assertRaises(quantum_client.QuantumNotFoundException, - qc.detach_and_delete_port, t, - net1_uuid, port_uuid) - - qc.delete_network(t, net1_uuid) - self.assertFalse(qc.network_exists(t, net1_uuid)) - self.assertEquals(len(qc.get_networks(t)['networks']), 0) - - self.assertRaises(quantum_client.QuantumNotFoundException, - qc.get_network_name, t, net1_uuid) - - -# this is a base class to be used by other QuantumManager Test classes -class QuantumNovaTestCase(test.TestCase): - - def setUp(self): - super(QuantumNovaTestCase, self).setUp() - - self.flags(quantum_use_dhcp=True) - self.flags(l3_lib="nova.network.l3.LinuxNetL3") - linuxdrv = "nova.network.linux_net.LinuxOVSInterfaceDriver" - self.flags(linuxnet_interface_driver=linuxdrv) - fc = fake_client.FakeClient(LOG) - qc = quantum_connection.QuantumClientConnection(client=fc) - - self.net_man = quantum_manager.QuantumManager( - ipam_lib="nova.network.quantum.nova_ipam_lib", - q_conn=qc) - - def func(arg1, arg2): - pass - - def func2(arg1, arg2, arg3): - pass - - def func1(arg1): - pass - - self.net_man.driver.update_dhcp_hostfile_with_text = func - self.net_man.driver.restart_dhcp = func2 - self.net_man.driver.kill_dhcp = func1 - - # 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']) - - # 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 remove all existing fixed - # ips before starting. - session = sql_session.get_session() - result = session.query(models.FixedIp).all() - with session.begin(): - for fip_ref in result: - session.delete(fip_ref) - - self.net_man.init_host() - - def _create_network(self, n): - ctx = context.RequestContext('user1', n['project_id']) - nwks = 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=n['gateway'], - gateway_v6=n['gateway_v6'], bridge=None, - bridge_interface=None, dns1=n['dns1'], - project_id=n['project_id'], - priority=n['priority']) - n['uuid'] = nwks[0]['uuid'] - - -class QuantumAllocationTestCase(QuantumNovaTestCase): - def test_get_network_in_db(self): - context = self.mox.CreateMockAnything() - context.elevated().AndReturn('elevated') - self.mox.StubOutWithMock(db, 'network_get_by_uuid') - self.net_man.context = context - db.network_get_by_uuid('elevated', 'quantum_net_id').AndReturn( - {'uuid': 1}) - - self.mox.ReplayAll() - - network = self.net_man.get_network(context, ('quantum_net_id', - 'net_tenant_id')) - self.assertEquals(network['quantum_net_id'], 'quantum_net_id') - self.assertEquals(network['uuid'], 1) - - def test_get_network_not_in_db(self): - context = self.mox.CreateMockAnything() - context.elevated().AndReturn('elevated') - self.mox.StubOutWithMock(db, 'network_get_by_uuid') - self.net_man.context = context - db.network_get_by_uuid('elevated', 'quantum_net_id').AndReturn(None) - - self.mox.ReplayAll() - - network = self.net_man.get_network(context, ('quantum_net_id', - 'net_tenant_id')) - self.assertEquals(network['quantum_net_id'], 'quantum_net_id') - self.assertEquals(network['uuid'], 'quantum_net_id') - - -class QuantumDeallocationTestCase(QuantumNovaTestCase): - def test_deallocate_port(self): - quantum = self.mox.CreateMock( - quantum_connection.QuantumClientConnection) - quantum.get_port_by_attachment('q_tenant_id', 'net_id', - 'interface_id').AndReturn('port_id') - quantum.detach_and_delete_port('q_tenant_id', 'net_id', 'port_id') - self.net_man.q_conn = quantum - - self.mox.ReplayAll() - - self.net_man.deallocate_port('interface_id', 'net_id', 'q_tenant_id', - 'instance_id') - - def test_deallocate_port_logs_error(self): - quantum = self.mox.CreateMock( - quantum_connection.QuantumClientConnection) - quantum.get_port_by_attachment('q_tenant_id', 'net_id', - 'interface_id').AndRaise(Exception) - self.net_man.q_conn = quantum - - self.mox.StubOutWithMock(quantum_manager.LOG, 'exception') - quantum_manager.LOG.exception(mox.Regex(r'port deallocation failed')) - - self.mox.ReplayAll() - - self.net_man.deallocate_port('interface_id', 'net_id', 'q_tenant_id', - 'instance_id') - - def test_deallocate_ip_address(self): - ipam = self.mox.CreateMock(melange_ipam_lib.QuantumMelangeIPAMLib) - ipam.get_tenant_id_by_net_id('context', 'net_id', {'uuid': 1}, - 'project_id').AndReturn('ipam_tenant_id') - self.net_man.ipam = ipam - self.mox.ReplayAll() - self.net_man.deallocate_ip_address('context', 'net_id', 'project_id', - {'uuid': 1}, 'instance_id') - - def test_deallocate_ip_address_2(self): - ipam = self.mox.CreateMock(melange_ipam_lib.QuantumMelangeIPAMLib) - ipam.get_tenant_id_by_net_id('context', 'net_id', {'uuid': 1}, - 'project_id').AndRaise(Exception()) - self.net_man.ipam = ipam - - self.mox.StubOutWithMock(quantum_manager.LOG, 'exception') - quantum_manager.LOG.exception(mox.Regex(r'ipam deallocation failed')) - - self.mox.ReplayAll() - self.net_man.deallocate_ip_address('context', 'net_id', 'project_id', - {'uuid': 1}, 'instance_id') - - -class QuantumManagerTestCase(QuantumNovaTestCase): - def test_create_and_delete_nets(self): - self._create_nets() - self._delete_nets() - - def _create_nets(self): - for n in networks: - self._create_network(n) - - def _delete_nets(self): - for n in networks: - ctx = context.RequestContext('user1', n['project_id']) - self.net_man.delete_network(ctx, None, n['uuid']) - self.assertRaises(exception.NoNetworksFound, - db.network_get_all, ctx.elevated()) - - def _validate_nw_info(self, nw_info, expected_net_labels): - - self.assertEquals(len(nw_info), len(expected_net_labels)) - - ctx = context.RequestContext('user1', 'foo').elevated() - all_net_map = {} - for n in db.network_get_all(ctx): - all_net_map[n['label']] = n - - for i in range(0, len(nw_info)): - vif = nw_info[i] - net = all_net_map[expected_net_labels[i]] - - # simple test assumes that each starting prefix is unique - expected_v4_cidr_start = net['cidr'].split(".")[0].lower() - expected_v6_cidr_start = net['cidr_v6'].split(":")[0].lower() - - for subnet in vif['network']['subnets']: - addr = subnet['ips'][0]['address'] - if subnet['version'] == 4: - address_start = addr.split(".")[0].lower() - self.assertTrue(expected_v4_cidr_start, address_start) - else: - address_start = addr.split(":")[0].lower() - self.assertTrue(expected_v6_cidr_start, address_start) - - # confirm that there is a DHCP device on corresponding net - for l in expected_net_labels: - n = all_net_map[l] - tenant_id = (n['project_id'] or - FLAGS.quantum_default_tenant_id) - ports = self.net_man.q_conn.get_attached_ports( - tenant_id, n['uuid']) - self.assertEquals(len(ports), 2) # gw + instance VIF - - # make sure we aren't allowed to delete network with - # active port - self.assertRaises(exception.NetworkBusy, - self.net_man.delete_network, - ctx, None, n['uuid']) - - def _check_vifs(self, expect_num_vifs): - ctx = context.RequestContext('user1', "").elevated() - self.assertEqual(len(db.virtual_interface_get_all(ctx)), - expect_num_vifs) - - def _allocate_and_deallocate_instance(self, project_id, requested_networks, - expected_labels): - - ctx = context.RequestContext('user1', project_id) - self._check_vifs(0) - - instance_ref = db.instance_create(ctx, - {"project_id": project_id}) - - nw_info = self.net_man.allocate_for_instance(ctx.elevated(), - instance_id=instance_ref['id'], host="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - - self._check_vifs(len(nw_info)) - - self._validate_nw_info(nw_info, expected_labels) - - nw_info = self.net_man.get_instance_nw_info(ctx, instance_ref['id'], - instance_ref['uuid'], - instance_ref['instance_type_id'], "", - project_id=project_id) - - self._check_vifs(len(nw_info)) - self._validate_nw_info(nw_info, expected_labels) - - port_net_pairs = [] - for vif in nw_info: - nid = vif['network']['id'] - pid = self.net_man.q_conn.get_port_by_attachment( - project_id, nid, vif['id']) - if pid is None: - pid = self.net_man.q_conn.get_port_by_attachment( - FLAGS.quantum_default_tenant_id, - nid, vif['id']) - self.assertTrue(pid is not None) - port_net_pairs.append((pid, nid)) - - self.net_man.deallocate_for_instance(ctx, - instance_id=instance_ref['id'], - project_id=project_id) - - for pid, nid in port_net_pairs: - self.assertRaises(quantum_client.QuantumNotFoundException, - self.net_man.q_conn.detach_and_delete_port, - project_id, nid, pid) - self.assertRaises(quantum_client.QuantumNotFoundException, - self.net_man.q_conn.detach_and_delete_port, - FLAGS.quantum_default_tenant_id, nid, pid) - - self._check_vifs(0) - - def test_allocate_and_deallocate_instance_static(self): - self._create_nets() - self._allocate_and_deallocate_instance("fake_project1", None, - ['public', 'project1-net1']) - self._delete_nets() - - def test_allocate_and_deallocate_instance_dynamic(self): - - self._create_nets() - project_id = "fake_project2" - ctx = context.RequestContext('user1', project_id) - all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( - ctx, project_id) - requested_networks = [(n[0], None) for n in all_valid_networks] - - self.net_man.validate_networks(ctx, requested_networks) - - label_map = {} - for n in db.network_get_all(ctx.elevated()): - label_map[n['uuid']] = n['label'] - expected_labels = [label_map[uid] for uid, _i in requested_networks] - - self._allocate_and_deallocate_instance(project_id, requested_networks, - expected_labels) - 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)]) - - def test_create_net_external_uuid(self): - """Tests use case where network can be created directly via - Quantum API, then the UUID is passed in via nova-manage""" - project_id = "foo_project" - ctx = context.RequestContext('user1', project_id) - net_id = self.net_man.q_conn.create_network(project_id, 'net1') - self.net_man.create_networks( - ctx, - label='achtungbaby', - cidr="9.9.9.0/24", - multi_host=False, - num_networks=1, - network_size=256, - cidr_v6=None, - gateway="9.9.9.1", - gateway_v6=None, - bridge=None, - bridge_interface=None, - dns1="8.8.8.8", - project_id=project_id, - priority=9, - uuid=net_id) - net = db.network_get_by_uuid(ctx.elevated(), net_id) - self.assertTrue(net is not None) - self.assertEquals(net['uuid'], net_id) - - def test_create_net_external_uuid_and_host_is_set(self): - """Make sure network['host'] is set when creating a network via the - network manager""" - project_id = "foo_project" - ctx = context.RequestContext('user1', project_id) - net_id = self.net_man.q_conn.create_network(project_id, 'net2') - self.net_man.create_networks( - ctx, label='achtungbaby2', cidr="9.9.8.0/24", multi_host=False, - num_networks=1, network_size=256, cidr_v6=None, - gateway="9.9.8.1", gateway_v6=None, bridge=None, - bridge_interface=None, dns1="8.8.8.8", project_id=project_id, - priority=8, uuid=net_id) - net = db.network_get_by_uuid(ctx.elevated(), net_id) - self.assertTrue(net is not None) - self.assertEquals(net['uuid'], net_id) - self.assertTrue(net['host'] != None) - - -class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): - def test_local_mac_address_creation(self): - self.flags(use_melange_mac_generation=False) - fake_mac = "ab:cd:ef:ab:cd:ef" - self.stubs.Set(utils, "generate_mac_address", - lambda: fake_mac) - project_id = "fake_project1" - ctx = context.RequestContext('user1', project_id) - self._create_network(networks[0]) - - all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( - ctx, project_id) - requested_networks = [(n[0], None) for n in all_valid_networks] - - 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="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - self.assertEqual(nw_info[0]['address'], fake_mac) - - def test_melange_mac_address_creation(self): - self.flags(use_melange_mac_generation=True) - fake_mac = "ab:cd:ef:ab:cd:ef" - self.stubs.Set(melange_connection.MelangeConnection, "create_vif", - lambda w, x, y, z: fake_mac) - project_id = "fake_project1" - ctx = context.RequestContext('user1', project_id) - self._create_network(networks[0]) - - all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( - ctx, project_id) - requested_networks = [(n[0], None) for n in all_valid_networks] - - 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="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - self.assertEqual(nw_info[0]['address'], fake_mac) - - -class QuantumNovaPortSecurityTestCase(QuantumNovaTestCase): - def test_port_security(self): - self.flags(use_melange_mac_generation=True) - self.flags(quantum_use_port_security=True) - fake_mac = "ab:cd:ef:ab:cd:ef" - self.stubs.Set(melange_connection.MelangeConnection, "create_vif", - lambda w, x, y, z: fake_mac) - project_id = "fake_project1" - ctx = context.RequestContext('user1', project_id) - self._create_network(networks[0]) - - all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( - ctx, project_id) - requested_networks = [(n[0], None) for n in all_valid_networks] - - instance_ref = db.api.instance_create(ctx, - {"project_id": project_id}) - oldfunc = self.net_man.q_conn.create_and_attach_port - - # Make sure we get the appropriate mac set in allowed_address_pairs - # if port security is enabled. - def _instrumented_create_and_attach_port(tenant_id, net_id, - interface_id, **kwargs): - self.assertTrue('allowed_address_pairs' in kwargs.keys()) - pairs = kwargs['allowed_address_pairs'] - self.assertTrue(pairs[0]['mac_address'] == fake_mac) - self.net_man.q_conn.create_and_attach_port = oldfunc - return oldfunc(tenant_id, net_id, interface_id, **kwargs) - _port_attach = _instrumented_create_and_attach_port - self.net_man.q_conn.create_and_attach_port = _port_attach - nw_info = self.net_man.allocate_for_instance(ctx, - instance_id=instance_ref['id'], host="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - self.assertEqual(nw_info[0]['address'], fake_mac) - - def test_port_security_negative(self): - self.flags(use_melange_mac_generation=True) - self.flags(quantum_use_port_security=False) - fake_mac = "ab:cd:ef:ab:cd:ef" - self.stubs.Set(melange_connection.MelangeConnection, "create_vif", - lambda w, x, y, z: fake_mac) - project_id = "fake_project1" - ctx = context.RequestContext('user1', project_id) - self._create_network(networks[0]) - - all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( - ctx, project_id) - requested_networks = [(n[0], None) for n in all_valid_networks] - - instance_ref = db.api.instance_create(ctx, - {"project_id": project_id}) - oldfunc = self.net_man.q_conn.create_and_attach_port - - # Make sure no pairs are passed in if port security is turned off - def _instrumented_create_and_attach_port(tenant_id, net_id, - interface_id, **kwargs): - self.assertTrue('allowed_address_pairs' in kwargs.keys()) - pairs = kwargs['allowed_address_pairs'] - self.assertTrue(len(pairs) == 0) - self.net_man.q_conn.create_and_attach_port = oldfunc - return oldfunc(tenant_id, net_id, interface_id, **kwargs) - _port_attach = _instrumented_create_and_attach_port - self.net_man.q_conn.create_and_attach_port = _port_attach - nw_info = self.net_man.allocate_for_instance(ctx, - instance_id=instance_ref['id'], host="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - self.assertEqual(nw_info[0]['address'], fake_mac) - - -class QuantumMelangeTestCase(test.TestCase): - def setUp(self): - super(QuantumMelangeTestCase, self).setUp() - - fc = fake_client.FakeClient(LOG) - qc = quantum_connection.QuantumClientConnection(client=fc) - - self.net_man = quantum_manager.QuantumManager( - ipam_lib="nova.network.quantum.nova_ipam_lib", - q_conn=qc) - - def test_get_instance_uuids_by_ip_filter(self): - fake_context = context.RequestContext('user', 'project') - address = '1.2.3.4' - filters = {'ip': address} - - self.net_man.ipam = self.mox.CreateMockAnything() - self.net_man.ipam.get_instance_ids_by_ip_address(fake_context, - address).AndReturn(['instance_id']) - - instance = self.mox.CreateMockAnything() - instance.uuid = 'instance_uuid' - - self.mox.StubOutWithMock(db, 'instance_get') - db.instance_get(fake_context, 'instance_id').AndReturn(instance) - - self.mox.ReplayAll() - - uuids = self.net_man.get_instance_uuids_by_ip_filter(fake_context, - filters) - self.assertEquals(uuids, [{'instance_uuid':'instance_uuid'}]) diff --git a/nova/tests/test_melange_ipam_lib.py b/nova/tests/test_melange_ipam_lib.py deleted file mode 100644 index d752a5dee..000000000 --- a/nova/tests/test_melange_ipam_lib.py +++ /dev/null @@ -1,243 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 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. - -from nova import db -from nova import flags -from nova.network.quantum import melange_connection -from nova.network.quantum import melange_ipam_lib -from nova import test - -FLAGS = flags.FLAGS - - -class MelangeIpamLibTestCase(test.TestCase): - def setUp(self): - super(MelangeIpamLibTestCase, self).setUp() - self.m_conn = self.mox.CreateMock(melange_connection.MelangeConnection) - self.ipam = melange_ipam_lib.QuantumMelangeIPAMLib() - self.ipam.m_conn = self.m_conn - - def _block_list(self, id='id', cidr='cidr', network_id='network_id'): - return {'ip_blocks': [{'id': id, - 'cidr': cidr, - 'network_id': network_id}]} - - def test_allocate_fixed_ips_extracts_address(self): - self.m_conn.allocate_ip('quantum_net_id', 'network_tenant_id', - 'vif_ref_uuid', 'project_id', - 'vif_ref_address').AndReturn( - [{'address': 'ip_address'}]) - - self.mox.ReplayAll() - - ips = self.ipam.allocate_fixed_ips('context', - 'project_id', - 'quantum_net_id', - 'network_tenant_id', - {'uuid': 'vif_ref_uuid', - 'address': 'vif_ref_address'}) - self.assertEqual(ips[0], 'ip_address') - - def test_delete_subnets_by_net_id_deletes_block(self): - context = self.mox.CreateMockAnything() - context.elevated().AndReturn('elevated') - - self.m_conn.get_blocks('project_id').AndReturn( - self._block_list(id='block_id')) - self.m_conn.delete_block('block_id', 'project_id') - - self.mox.StubOutWithMock(db, 'network_get_by_uuid') - db.network_get_by_uuid('elevated', 'network_id').AndReturn( - {'id': 'network_id'}) - - self.mox.StubOutWithMock(db, 'network_delete_safe') - db.network_delete_safe(context, 'network_id') - - self.mox.ReplayAll() - self.ipam.delete_subnets_by_net_id(context, 'network_id', 'project_id') - - def test_get_networks_by_tenant_gets_all_networks(self): - block_list = self._block_list(network_id='net_1') - block_list['ip_blocks'] += self._block_list( - network_id='net_2')['ip_blocks'] - - self.m_conn.get_blocks('tenant_id').AndReturn(block_list) - - self.mox.StubOutWithMock(db, 'network_get_by_uuid') - db.network_get_by_uuid('admin_context', 'net_1').AndReturn('network1') - db.network_get_by_uuid('admin_context', 'net_2').AndReturn('network2') - - self.mox.ReplayAll() - values = self.ipam.get_networks_by_tenant('admin_context', 'tenant_id') - self.assertEquals(values, ['network1', 'network2']) - - def test_get_global_networks(self): - FLAGS.quantum_default_tenant_id = 'quantum_default_tenant_id' - self.mox.StubOutWithMock(self.ipam, 'get_networks_by_tenant') - self.ipam.get_networks_by_tenant('admin_context', - 'quantum_default_tenant_id') - - self.mox.ReplayAll() - - self.ipam.get_global_networks('admin_context') - - def test_get_project_networks(self): - context = self.mox.CreateMockAnything() - context.elevated().AndReturn('elevated') - - networks = [{'project_id': 1}, {'project_id': None}] - - self.mox.StubOutWithMock(db, 'network_get_all') - db.network_get_all('elevated').AndReturn(networks) - - self.mox.ReplayAll() - values = self.ipam.get_project_networks(context) - self.assertEquals(values, [networks[0]]) - - def test_get_project_and_global_net_ids__by_priority(self): - context = self.mox.CreateMockAnything() - context.elevated().AndReturn('elevated') - - FLAGS.quantum_default_tenant_id = 'default_tenant_id' - - net1 = {'uuid': 'net1_uuid', 'priority': 'net1_priority'} - net2 = {'uuid': 'net2_uuid', 'priority': 'net2_priority'} - - self.mox.StubOutWithMock(self.ipam, 'get_networks_by_tenant') - self.ipam.get_networks_by_tenant('elevated', - 'project_id').AndReturn([net1]) - self.ipam.get_networks_by_tenant('elevated', - 'default_tenant_id').AndReturn([net2]) - self.mox.ReplayAll() - self.ipam.get_project_and_global_net_ids(context, 'project_id') - - def test_get_tenant_id_by_net_id_returns_id(self): - FLAGS.quantum_default_tenant_id = 'qdti' - - self.m_conn.get_allocated_ips('net_id', 'vif_id', - 'qdti').AndReturn({}) - self.mox.ReplayAll() - value = self.ipam.get_tenant_id_by_net_id('context', 'net_id', - 'vif_id', 'project_id') - self.assertEqual(value, 'qdti') - - def test_get_tenant_id_by_net_id_returns_none_if_none_found(self): - FLAGS.quantum_default_tenant_id = 'qdti' - - self.m_conn.get_allocated_ips('net_id', 'vif_id', - 'qdti').AndRaise(KeyError()) - self.m_conn.get_allocated_ips('net_id', 'vif_id', - 'project_id').AndRaise(KeyError()) - self.m_conn.get_allocated_ips('net_id', 'vif_id', - None).AndRaise(KeyError()) - self.mox.ReplayAll() - value = self.ipam.get_tenant_id_by_net_id('context', 'net_id', - 'vif_id', 'project_id') - self.assertEqual(value, None) - - def test_get_subnets_by_net_id(self): - ips = [{'ip_block': {'network_id': 'network_id', - 'id': 'id', - 'cidr': 'cidr', - 'gateway': 'gateway', - 'broadcast': 'broadcast', - 'netmask': 'netmask', - 'dns1': 'dns1', - 'dns2': 'dns2'}, - 'version': 4}] - - self.m_conn.get_allocated_ips('net_id', 'vif_id', - 'tenant_id').AndReturn(ips) - - self.mox.ReplayAll() - value = self.ipam.get_subnets_by_net_id('context', 'tenant_id', - 'net_id', 'vif_id') - self.assertEquals(value[0]['cidr'], 'cidr') - - def test_get_routes_by_ip_block(self): - self.m_conn.get_routes('block_id', 'project_id') - self.mox.ReplayAll() - self.ipam.get_routes_by_ip_block('context', 'block_id', 'project_id') - - def test_get_v4_ips_by_interface(self): - self.mox.StubOutWithMock(self.ipam, '_get_ips_by_interface') - self.ipam._get_ips_by_interface('context', 'net_id', 'vif_id', - 'project_id', 4) - self.mox.ReplayAll() - self.ipam.get_v4_ips_by_interface('context', 'net_id', 'vif_id', - 'project_id') - - def test_get_v6_ips_by_interface(self): - self.mox.StubOutWithMock(self.ipam, '_get_ips_by_interface') - self.ipam._get_ips_by_interface('context', 'net_id', 'vif_id', - 'project_id', 6) - self.mox.ReplayAll() - self.ipam.get_v6_ips_by_interface('context', 'net_id', 'vif_id', - 'project_id') - - def test_get_ips_by_interface(self): - ips = [{'address': '10.10.10.10'}, {'address': '2001::CAFE'}] - self.m_conn.get_allocated_ips('net_id', 'vif_id', - 'tenant_id').AndReturn(ips) - self.m_conn.get_allocated_ips('net_id', 'vif_id', - 'tenant_id').AndReturn(ips) - self.mox.ReplayAll() - values = self.ipam._get_ips_by_interface('context', 'net_id', 'vif_id', - 'tenant_id', 4) - self.assertEquals(values, ["10.10.10.10"]) - values = self.ipam._get_ips_by_interface('context', 'net_id', 'vif_id', - 'tenant_id', 6) - self.assertEquals(values, ["2001::CAFE"]) - - def test_get_instance_ids_by_ip_address(self): - ips = [{'used_by_device': 'some_vif_uuid'}] - self.m_conn.get_allocated_ips_by_address('ip').AndReturn(ips) - - self.mox.ReplayAll() - uuid = self.ipam.get_instance_ids_by_ip_address('context', 'ip') - self.assertEqual(uuid, ['some_vif_uuid']) - - def test_verify_subnet_exists(self): - blocks = {'ip_blocks': [{'network_id': 'quantum_net_id'}]} - self.m_conn.get_blocks('tenant_id').AndReturn(blocks) - self.mox.ReplayAll() - value = self.ipam.verify_subnet_exists('context', 'tenant_id', - 'quantum_net_id') - self.assertEquals(value, True) - - def test_deallocate_ips_by_vif(self): - self.m_conn.deallocate_ips('net_id', 'uuid', 'tenant_id') - self.mox.ReplayAll() - self.ipam.deallocate_ips_by_vif('context', 'tenant_id', 'net_id', - {'uuid': 'uuid'}) - - def test_get_allocated_ips(self): - ips = [{'address': 'ip_address', 'interface_id': 'interface_id'}] - self.m_conn.get_allocated_ips_for_network('subnet_id', - 'project_id').AndReturn(ips) - self.mox.ReplayAll() - self.ipam.get_allocated_ips('context', 'subnet_id', 'project_id') - - def test_create_vif(self): - self.m_conn.create_vif('vif_id', 'instance_id', 'project_id') - self.mox.ReplayAll() - self.ipam.create_vif('vif_id', 'instance_id', 'project_id') - - def test_get_floating_ips_by_fixed_address(self): - value = self.ipam.get_floating_ips_by_fixed_address('context', - 'fixed_address') - self.assertEquals(value, []) -- cgit