diff --git a/TESTING b/TESTING new file mode 100644 index 0000000000..2b433d7eb2 --- /dev/null +++ b/TESTING @@ -0,0 +1,47 @@ +Testing Quantum +============================================================= + +Overview + + There are two types of tests in quantum: functional and unit. Their + respective directories are located in the tests/ directory. + + The functional tests are intended to be used when the service is running. + Their goal is to make sure the service is working end to end and also to + test any plugin for conformance and functionality. Also note that the + functional tests expect quantum to be running on the local machine. If it + isn't you will have to change the tests to point to your quuntum instance. + + The unit tests can be run without the service running. They are designed + to test the various pieces of the quantum tree to make sure any new + changes don't break existing functionality. + +Running tests + + All tests can be run via the run_tests.sh script, which will allow you to + run them in the standard environment or create a virtual environment to + run them in. All of the functional tests will fail if the service isn't + running. One current TODO item is to be able to specify whether you want + to run the functional or unit tests via run_tests.sh. + + After running all of the tests, run_test.sh will report any pep8 errors + found in the tree. + +Adding more tests + + Quantum is a pretty new code base at this point and there is plenty of + areas that need tests. The current blueprint and branch for adding tests + is located at: + https://code.launchpad.net/~netstack/quantum/quantum-unit-tests + + Also, there is a wiki page tracking the status of the test effort: + http://wiki.openstack.org/QuantumUnitTestStatus + +Development process + + It is expected that any new changes that are proposed for merge come with + unit tests for that feature or code area. Ideally any bugs fixes that are + submitted also have unit tests to prove that they stay fixed! :) In + addition, before proposing for merge, all of the current unit tests should + be passing. + diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index f00d7bcb2c..c5cac4bf74 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -25,6 +25,7 @@ import routes import webob.dec import webob.exc +from quantum import manager from quantum.api import faults from quantum.api import networks from quantum.api import ports @@ -41,41 +42,41 @@ class APIRouterV01(wsgi.Router): Routes requests on the Quantum API to the appropriate controller """ - def __init__(self, ext_mgr=None): + def __init__(self, options=None): mapper = routes.Mapper() - self._setup_routes(mapper) + self._setup_routes(mapper, options) super(APIRouterV01, self).__init__(mapper) - def _setup_routes(self, mapper): - + def _setup_routes(self, mapper, options): + # Loads the quantum plugin + plugin = manager.QuantumManager(options).get_plugin() uri_prefix = '/tenants/{tenant_id}/' mapper.resource('network', 'networks', - controller=networks.Controller(), + controller=networks.Controller(plugin), collection={'detail': 'GET'}, member={'detail': 'GET'}, path_prefix=uri_prefix) mapper.resource('port', 'ports', - controller=ports.Controller(), + controller=ports.Controller(plugin), parent_resource=dict(member_name='network', collection_name=uri_prefix +\ - 'networks')) - + 'networks')) mapper.connect("get_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', - controller=ports.Controller(), + controller=ports.Controller(plugin), action="get_resource", conditions=dict(method=['GET'])) mapper.connect("attach_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', - controller=ports.Controller(), + controller=ports.Controller(plugin), action="attach_resource", conditions=dict(method=['PUT'])) mapper.connect("detach_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', - controller=ports.Controller(), + controller=ports.Controller(plugin), action="detach_resource", conditions=dict(method=['DELETE'])) print "MAPPED ROUTES" diff --git a/quantum/api/api_common.py b/quantum/api/api_common.py index 4e01e55bf7..c588a83567 100644 --- a/quantum/api/api_common.py +++ b/quantum/api/api_common.py @@ -19,7 +19,6 @@ import logging from webob import exc -from quantum import manager from quantum.common import wsgi XML_NS_V01 = 'http://netstack.org/quantum/api/v0.1' @@ -30,8 +29,8 @@ LOG = logging.getLogger('quantum.api.api_common') class QuantumController(wsgi.Controller): """ Base controller class for Quantum API """ - def __init__(self, plugin_conf_file=None): - self._setup_network_manager() + def __init__(self, plugin): + self._plugin = plugin super(QuantumController, self).__init__() def _parse_request_params(self, req, params): @@ -64,6 +63,3 @@ class QuantumController(wsgi.Controller): raise exc.HTTPBadRequest(msg) results[param_name] = param_value or param.get('default-value') return results - - def _setup_network_manager(self): - self.network_manager = manager.QuantumManager().get_manager() diff --git a/quantum/api/networks.py b/quantum/api/networks.py index f9f5df4ed8..2a56d6bc64 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -29,7 +29,7 @@ class Controller(common.QuantumController): """ Network API controller for Quantum API """ _network_ops_param_list = [{ - 'param-name': 'network-name', + 'param-name': 'net-name', 'required': True}, ] _serialization_metadata = { @@ -41,9 +41,9 @@ class Controller(common.QuantumController): }, } - def __init__(self, plugin_conf_file=None): + def __init__(self, plugin): self._resource_name = 'network' - super(Controller, self).__init__() + super(Controller, self).__init__(plugin) def index(self, request, tenant_id): """ Returns a list of network ids """ @@ -60,7 +60,7 @@ class Controller(common.QuantumController): def _items(self, req, tenant_id, net_details=False, port_details=False): """ Returns a list of networks. """ - networks = self.network_manager.get_all_networks(tenant_id) + networks = self._plugin.get_all_networks(tenant_id) builder = networks_view.get_view_builder(req) result = [builder.build(network, net_details, port_details)['network'] for network in networks] @@ -85,6 +85,12 @@ class Controller(common.QuantumController): #do like show but with detaik return self._items(request, tenant_id, net_details=True, port_details=False) + network = self._plugin.get_network_details( + tenant_id, id) + builder = networks_view.get_view_builder(request) + #build response with details + result = builder.build(network, True) + return dict(networks=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) @@ -97,9 +103,9 @@ class Controller(common.QuantumController): self._network_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) - network = self.network_manager.\ + network = self._plugin.\ create_network(tenant_id, - request_params['network-name']) + request_params['net-name']) builder = networks_view.get_view_builder(request) result = builder.build(network) return dict(networks=result) @@ -113,19 +119,16 @@ class Controller(common.QuantumController): except exc.HTTPError as e: return faults.Fault(e) try: - network = self.network_manager.rename_network(tenant_id, - id, request_params['network-name']) - - builder = networks_view.get_view_builder(request) - result = builder.build(network, True) - return dict(networks=result) + self._plugin.rename_network(tenant_id, id, + request_params['net-name']) + return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) def delete(self, request, tenant_id, id): """ Destroys the network with the given id """ try: - self.network_manager.delete_network(tenant_id, id) + self._plugin.delete_network(tenant_id, id) return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index b92f33e9ef..ebe9ab843f 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -42,9 +42,9 @@ class Controller(common.QuantumController): "attributes": { "port": ["id", "state"], }, }, } - def __init__(self, plugin_conf_file=None): + def __init__(self, plugin): self._resource_name = 'port' - super(Controller, self).__init__() + super(Controller, self).__init__(plugin) def index(self, request, tenant_id, network_id): """ Returns a list of port ids for a given network """ @@ -53,7 +53,7 @@ class Controller(common.QuantumController): def _items(self, request, tenant_id, network_id, is_detail): """ Returns a list of networks. """ try: - ports = self.network_manager.get_all_ports(tenant_id, network_id) + ports = self._plugin.get_all_ports(tenant_id, network_id) builder = ports_view.get_view_builder(request) result = [builder.build(port, is_detail)['port'] for port in ports] @@ -64,7 +64,7 @@ class Controller(common.QuantumController): def show(self, request, tenant_id, network_id, id): """ Returns port details for given port and network """ try: - port = self.network_manager.get_port_details( + port = self._plugin.get_port_details( tenant_id, network_id, id) builder = ports_view.get_view_builder(request) #build response with details @@ -84,7 +84,7 @@ class Controller(common.QuantumController): except exc.HTTPError as e: return faults.Fault(e) try: - port = self.network_manager.create_port(tenant_id, + port = self._plugin.create_port(tenant_id, network_id, request_params['port-state']) builder = ports_view.get_view_builder(request) @@ -104,9 +104,8 @@ class Controller(common.QuantumController): except exc.HTTPError as e: return faults.Fault(e) try: - port = self.network_manager.\ - update_port(tenant_id, network_id, id, - request_params['port-state']) + port = self._plugin.update_port(tenant_id, network_id, id, + request_params['port-state']) builder = ports_view.get_view_builder(request) result = builder.build(port, True) return dict(ports=result) @@ -121,7 +120,7 @@ class Controller(common.QuantumController): """ Destroys the port with the given id """ #look for port state in request try: - self.network_manager.delete_port(tenant_id, network_id, id) + self._plugin.delete_port(tenant_id, network_id, id) return exc.HTTPAccepted() # TODO(salvatore-orlando): Handle portInUse error except exception.NetworkNotFound as e: @@ -133,8 +132,9 @@ class Controller(common.QuantumController): def get_resource(self, request, tenant_id, network_id, id): try: - result = self.network_manager.get_port_details( - tenant_id, network_id, id).get('attachment', None) + result = self._plugin.get_port_details( + tenant_id, network_id, id).get('attachment-id', + None) return dict(attachment=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) @@ -142,8 +142,6 @@ class Controller(common.QuantumController): return faults.Fault(faults.PortNotFound(e)) def attach_resource(self, request, tenant_id, network_id, id): - content_type = request.best_match_content_type() - print "Content type:%s" % content_type try: request_params = \ self._parse_request_params(request, @@ -151,7 +149,7 @@ class Controller(common.QuantumController): except exc.HTTPError as e: return faults.Fault(e) try: - self.network_manager.plug_interface(tenant_id, + self._plugin.plug_interface(tenant_id, network_id, id, request_params['attachment-id']) return exc.HTTPAccepted() @@ -166,8 +164,8 @@ class Controller(common.QuantumController): def detach_resource(self, request, tenant_id, network_id, id): try: - self.network_manager.unplug_interface(tenant_id, - network_id, id) + self._plugin.unplug_interface(tenant_id, + network_id, id) return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/cli.py b/quantum/cli.py index 8663d548b1..02bafbd09d 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -240,6 +240,7 @@ def api_create_port(client, *args): def delete_port(manager, *args): tid, nid, pid = args + manager.delete_port(tid, nid, pid) LOG.info("Deleted Virtual Port:%s " \ "on Virtual Network:%s" % (pid, nid)) @@ -299,7 +300,8 @@ def api_plug_iface(client, *args): LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid, pid, output)) return - print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, nid) + print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, + nid) def unplug_iface(manager, *args): @@ -318,8 +320,8 @@ def api_unplug_iface(client, *args): output = res.read() LOG.debug(output) if res.status != 202: - LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid, - pid, output)) + LOG.error("Failed to unplug iface from port \"%s\": %s" % \ + (pid, output)) return print "Unplugged interface from port:%s on network:%s" % (pid, nid) diff --git a/quantum/common/utils.py b/quantum/common/utils.py index 3a2455963f..ea6241e21b 100644 --- a/quantum/common/utils.py +++ b/quantum/common/utils.py @@ -190,7 +190,7 @@ def parse_isotime(timestr): return datetime.datetime.strptime(timestr, TIME_FORMAT) -def getPluginFromConfig(file="config.ini"): +def get_plugin_from_config(file="config.ini"): Config = ConfigParser.ConfigParser() Config.read(file) return Config.get("PLUGIN", "provider") diff --git a/quantum/db/api.py b/quantum/db/api.py index 2a296f2f07..6813d2096c 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -19,7 +19,8 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, exc -import models + +from quantum.db import models _ENGINE = None _MAKER = None @@ -42,6 +43,13 @@ def configure_db(options): register_models() +def clear_db(): + global _ENGINE + assert _ENGINE + for table in reversed(BASE.metadata.sorted_tables): + _ENGINE.execute(table.delete()) + + def get_session(autocommit=True, expire_on_commit=False): """Helper method to grab session""" global _MAKER, _ENGINE @@ -72,9 +80,10 @@ def network_create(tenant_id, name): net = None try: net = session.query(models.Network).\ - filter_by(name=name).\ + filter_by(tenant_id=tenant_id, name=name).\ one() - raise Exception("Network with name \"%s\" already exists" % name) + raise Exception("Network with name %(name)s already " \ + "exists for tenant %(tenant_id)s" % locals()) except exc.NoResultFound: with session.begin(): net = models.Network(tenant_id, name) @@ -104,7 +113,7 @@ def network_rename(net_id, tenant_id, new_name): session = get_session() try: res = session.query(models.Network).\ - filter_by(name=new_name).\ + filter_by(tenant_id=tenant_id, name=new_name).\ one() except exc.NoResultFound: net = network_get(net_id) @@ -128,10 +137,11 @@ def network_destroy(net_id): raise Exception("No network found with id = %s" % net_id) -def port_create(net_id): +def port_create(net_id, state=None): session = get_session() with session.begin(): port = models.Port(net_id) + port['state'] = state or 'DOWN' session.add(port) session.flush() return port @@ -154,15 +164,26 @@ def port_get(port_id): raise Exception("No port found with id = %s " % port_id) +def port_set_state(port_id, new_state): + port = port_get(port_id) + if port: + session = get_session() + port.state = new_state + session.merge(port) + session.flush() + return port + + def port_set_attachment(port_id, new_interface_id): session = get_session() - ports = None - try: - ports = session.query(models.Port).\ - filter_by(interface_id=new_interface_id).\ - all() - except exc.NoResultFound: - pass + ports = [] + if new_interface_id != "": + try: + ports = session.query(models.Port).\ + filter_by(interface_id=new_interface_id).\ + all() + except exc.NoResultFound: + pass if len(ports) == 0: port = port_get(port_id) port.interface_id = new_interface_id @@ -174,6 +195,14 @@ def port_set_attachment(port_id, new_interface_id): % (new_interface_id)) +def port_unset_attachment(port_id): + session = get_session() + port = port_get(port_id) + port.interface_id = None + session.merge(port) + session.flush + + def port_destroy(port_id): session = get_session() try: diff --git a/quantum/db/models.py b/quantum/db/models.py index 115ab282da..1a18e7e518 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -19,14 +19,49 @@ import uuid -from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy import Column, String, ForeignKey from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relation +from sqlalchemy.orm import relation, object_mapper BASE = declarative_base() -class Port(BASE): +class QuantumBase(object): + """Base class for Quantum Models.""" + + def __setitem__(self, key, value): + setattr(self, key, value) + + def __getitem__(self, key): + return getattr(self, key) + + def get(self, key, default=None): + return getattr(self, key, default) + + def __iter__(self): + self._i = iter(object_mapper(self).columns) + return self + + def next(self): + n = self._i.next().name + return n, getattr(self, n) + + def update(self, values): + """Make the model object behave like a dict""" + for k, v in values.iteritems(): + setattr(self, k, v) + + def iteritems(self): + """Make the model object behave like a dict. + Includes attributes from joins.""" + local = dict(self) + joined = dict([(k, v) for k, v in self.__dict__.iteritems() + if not k[0] == '_']) + local.update(joined) + return local.iteritems() + + +class Port(BASE, QuantumBase): """Represents a port on a quantum network""" __tablename__ = 'ports' @@ -34,17 +69,20 @@ class Port(BASE): network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False) interface_id = Column(String(255)) + # Port state - Hardcoding string value at the moment + state = Column(String(8)) def __init__(self, network_id): - self.uuid = uuid.uuid4() + self.uuid = str(uuid.uuid4()) self.network_id = network_id + self.state = "DOWN" def __repr__(self): - return "" % (self.uuid, self.network_id, - self.interface_id) + return "" % (self.uuid, self.network_id, + self.state, self.interface_id) -class Network(BASE): +class Network(BASE, QuantumBase): """Represents a quantum network""" __tablename__ = 'networks' @@ -54,7 +92,7 @@ class Network(BASE): ports = relation(Port, order_by=Port.uuid, backref="network") def __init__(self, tenant_id, name): - self.uuid = uuid.uuid4() + self.uuid = str(uuid.uuid4()) self.tenant_id = tenant_id self.name = name diff --git a/quantum/manager.py b/quantum/manager.py index 05762f3a59..3d244b1b27 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -24,15 +24,16 @@ class. The caller should make sure that QuantumManager is a singleton. """ import gettext +import logging import os -gettext.install('quantum', unicode=1) -import os +gettext.install('quantum', unicode=1) from common import utils from quantum_plugin_base import QuantumPluginBase CONFIG_FILE = "plugins.ini" +LOG = logging.getLogger('quantum.manager') def find_config(basepath): @@ -43,22 +44,27 @@ def find_config(basepath): class QuantumManager(object): - - def __init__(self, config=None): - if config == None: + def __init__(self, options=None, config_file=None): + if config_file == None: self.configuration_file = find_config( os.path.abspath(os.path.dirname(__file__))) else: - self.configuration_file = config - plugin_location = utils.getPluginFromConfig(self.configuration_file) - plugin_klass = utils.import_class(plugin_location) + self.configuration_file = config_file + # If no options have been provided, create an empty dict + if not options: + options = {} + if not 'plugin_provider' in options: + options['plugin_provider'] = \ + utils.get_plugin_from_config(self.configuration_file) + LOG.debug("Plugin location:%s", options['plugin_provider']) + plugin_klass = utils.import_class(options['plugin_provider']) if not issubclass(plugin_klass, QuantumPluginBase): raise Exception("Configured Quantum plug-in " \ "didn't pass compatibility test") else: - print("Successfully imported Quantum plug-in." \ - "All compatibility tests passed\n") + LOG.debug("Successfully imported Quantum plug-in." \ + "All compatibility tests passed") self.plugin = plugin_klass() - def get_manager(self): + def get_plugin(self): return self.plugin diff --git a/quantum/plugins.ini b/quantum/plugins.ini index 307d2b48d2..448cab59ea 100644 --- a/quantum/plugins.ini +++ b/quantum/plugins.ini @@ -1,3 +1,4 @@ [PLUGIN] # Quantum plugin provider module -provider = quantum.plugins.SamplePlugin.FakePlugin +#provider = quantum.plugins.SamplePlugin.FakePlugin +provider = quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPlugin diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 1f7c9f8dd4..46576389ca 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -14,8 +14,14 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Somik Behera, Nicira Networks, Inc. +# @author: Salvatore Orlando, Citrix + +import logging from quantum.common import exceptions as exc +from quantum.db import api as db + +LOG = logging.getLogger('quantum.plugins.SamplePlugin') class QuantumEchoPlugin(object): @@ -220,63 +226,41 @@ class FakePlugin(object): client/cli/api development """ - #static data for networks and ports - _port_dict_1 = { - 1: {'port-id': 1, - 'port-state': 'DOWN', - 'attachment': None}, - 2: {'port-id': 2, - 'port-state': 'ACTIVE', - 'attachment': None}} - _port_dict_2 = { - 1: {'port-id': 1, - 'port-state': 'ACTIVE', - 'attachment': 'SomeFormOfVIFID'}, - 2: {'port-id': 2, - 'port-state': 'DOWN', - 'attachment': None}} - _networks = {'001': - { - 'net-id': '001', - 'net-name': 'pippotest', - 'net-ports': _port_dict_1 - }, - '002': - { - 'net-id': '002', - 'net-name': 'cicciotest', - 'net-ports': _port_dict_2}} - def __init__(self): - FakePlugin._net_counter = len(FakePlugin._networks) + db.configure_db({'sql_connection': 'sqlite:///:memory:'}) + FakePlugin._net_counter = 0 def _get_network(self, tenant_id, network_id): - network = FakePlugin._networks.get(network_id) - if not network: + try: + network = db.network_get(network_id) + except: raise exc.NetworkNotFound(net_id=network_id) return network def _get_port(self, tenant_id, network_id, port_id): net = self._get_network(tenant_id, network_id) - port = net['net-ports'].get(int(port_id)) - if not port: + try: + port = db.port_get(port_id) + except: + raise exc.PortNotFound(net_id=network_id, port_id=port_id) + # Port must exist and belong to the appropriate network. + if port['network_id'] != net['uuid']: raise exc.PortNotFound(net_id=network_id, port_id=port_id) return port def _validate_port_state(self, port_state): - if port_state.upper() not in ('UP', 'DOWN'): + if port_state.upper() not in ('ACTIVE', 'DOWN'): raise exc.StateInvalid(port_state=port_state) return True def _validate_attachment(self, tenant_id, network_id, port_id, remote_interface_id): - network = self._get_network(tenant_id, network_id) - for port in network['net-ports'].values(): - if port['attachment'] == remote_interface_id: + for port in db.port_list(network_id): + if port['interface_id'] == remote_interface_id: raise exc.AlreadyAttached(net_id=network_id, port_id=port_id, - att_id=port['attachment'], - att_port_id=port['port-id']) + att_id=port['interface_id'], + att_port_id=port['uuid']) def get_all_networks(self, tenant_id): """ @@ -284,49 +268,47 @@ class FakePlugin(object): for the specified tenant. """ - print("get_all_networks() called\n") - return FakePlugin._networks.values() + LOG.debug("FakePlugin.get_all_networks() called") + nets = [] + for net in db.network_list(tenant_id): + net_item = {'net-id': str(net.uuid), + 'net-name': net.name} + nets.append(net_item) + return nets def get_network_details(self, tenant_id, net_id): """ retrieved a list of all the remote vifs that are attached to the network """ - print("get_network_details() called\n") - return self._get_network(tenant_id, net_id) + LOG.debug("FakePlugin.get_network_details() called") + net = self._get_network(tenant_id, net_id) + return {'net-id': str(net.uuid), + 'net-name': net.name} def create_network(self, tenant_id, net_name): """ Creates a new Virtual Network, and assigns it a symbolic name. """ - print("create_network() called\n") - FakePlugin._net_counter += 1 - new_net_id = ("0" * (3 - len(str(FakePlugin._net_counter)))) + \ - str(FakePlugin._net_counter) - print new_net_id - new_net_dict = {'net-id': new_net_id, - 'net-name': net_name, - 'net-ports': {}} - - FakePlugin._networks[new_net_id] = new_net_dict - # return network_id of the created network - return new_net_dict + LOG.debug("FakePlugin.create_network() called") + new_net = db.network_create(tenant_id, net_name) + # Return uuid for newly created network as net-id. + return {'net-id': new_net['uuid']} def delete_network(self, tenant_id, net_id): """ Deletes the network with the specified network identifier belonging to the specified tenant. """ - print("delete_network() called\n") - net = FakePlugin._networks.get(net_id) + LOG.debug("FakePlugin.delete_network() called") + net = self._get_network(tenant_id, net_id) # Verify that no attachments are plugged into the network if net: - if net['net-ports']: - for port in net['net-ports'].values(): - if port['attachment']: - raise exc.NetworkInUse(net_id=net_id) - FakePlugin._networks.pop(net_id) + for port in db.port_list(net_id): + if port['interface_id']: + raise exc.NetworkInUse(net_id=net_id) + db.network_destroy(net_id) return net # Network not found raise exc.NetworkNotFound(net_id=net_id) @@ -336,9 +318,12 @@ class FakePlugin(object): Updates the symbolic name belonging to a particular Virtual Network. """ - print("rename_network() called\n") + LOG.debug("FakePlugin.rename_network() called") + try: + db.network_rename(net_id, tenant_id, new_name) + except: + raise exc.NetworkNotFound(net_id=net_id) net = self._get_network(tenant_id, net_id) - net['net-name'] = new_name return net def get_all_ports(self, tenant_id, net_id): @@ -346,45 +331,49 @@ class FakePlugin(object): Retrieves all port identifiers belonging to the specified Virtual Network. """ - print("get_all_ports() called\n") - network = self._get_network(tenant_id, net_id) - ports_on_net = network['net-ports'].values() - return ports_on_net + LOG.debug("FakePlugin.get_all_ports() called") + port_ids = [] + ports = db.port_list(net_id) + for x in ports: + d = {'port-id': str(x.uuid)} + port_ids.append(d) + return port_ids def get_port_details(self, tenant_id, net_id, port_id): """ This method allows the user to retrieve a remote interface that is attached to this particular port. """ - print("get_port_details() called\n") - return self._get_port(tenant_id, net_id, port_id) + LOG.debug("FakePlugin.get_port_details() called") + port = self._get_port(tenant_id, net_id, port_id) + return {'port-id': str(port.uuid), + 'attachment-id': port.interface_id, + 'port-state': port.state} def create_port(self, tenant_id, net_id, port_state=None): """ Creates a port on the specified Virtual Network. """ - print("create_port() called\n") - net = self._get_network(tenant_id, net_id) - # check port state - # TODO(salvatore-orlando): Validate port state in API? - self._validate_port_state(port_state) - ports = net['net-ports'] - new_port_id = max(ports.keys()) + 1 - new_port_dict = {'port-id': new_port_id, - 'port-state': port_state, - 'attachment': None} - ports[new_port_id] = new_port_dict - return new_port_dict + LOG.debug("FakePlugin.create_port() called") + # verify net_id + self._get_network(tenant_id, net_id) + port = db.port_create(net_id, port_state) + port_item = {'port-id': str(port.uuid)} + return port_item - def update_port(self, tenant_id, net_id, port_id, port_state): + def update_port(self, tenant_id, net_id, port_id, new_state): """ Updates the state of a port on the specified Virtual Network. """ - print("create_port() called\n") - port = self._get_port(tenant_id, net_id, port_id) - self._validate_port_state(port_state) - port['port-state'] = port_state - return port + LOG.debug("FakePlugin.update_port() called") + #validate port and network ids + self._get_network(tenant_id, net_id) + self._get_port(tenant_id, net_id, port_id) + self._validate_port_state(new_state) + db.port_set_state(port_id, new_state) + port_item = {'port-id': port_id, + 'port-state': new_state} + return port_item def delete_port(self, tenant_id, net_id, port_id): """ @@ -393,39 +382,42 @@ class FakePlugin(object): the remote interface is first un-plugged and then the port is deleted. """ - print("delete_port() called\n") + LOG.debug("FakePlugin.delete_port() called") net = self._get_network(tenant_id, net_id) port = self._get_port(tenant_id, net_id, port_id) - if port['attachment']: + if port['interface_id']: raise exc.PortInUse(net_id=net_id, port_id=port_id, - att_id=port['attachment']) + att_id=port['interface_id']) try: - net['net-ports'].pop(int(port_id)) - except KeyError: - raise exc.PortNotFound(net_id=net_id, port_id=port_id) + port = db.port_destroy(port_id) + except Exception, e: + raise Exception("Failed to delete port: %s" % str(e)) + d = {} + d["port-id"] = str(port.uuid) + return d def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): """ Attaches a remote interface to the specified port on the specified Virtual Network. """ - print("plug_interface() called\n") + LOG.debug("FakePlugin.plug_interface() called") # Validate attachment self._validate_attachment(tenant_id, net_id, port_id, remote_interface_id) port = self._get_port(tenant_id, net_id, port_id) - if port['attachment']: + if port['interface_id']: raise exc.PortInUse(net_id=net_id, port_id=port_id, - att_id=port['attachment']) - port['attachment'] = remote_interface_id + att_id=port['interface_id']) + db.port_set_attachment(port_id, remote_interface_id) def unplug_interface(self, tenant_id, net_id, port_id): """ Detaches a remote interface from the specified port on the specified Virtual Network. """ - print("unplug_interface() called\n") - port = self._get_port(tenant_id, net_id, port_id) + LOG.debug("FakePlugin.unplug_interface() called") + self._get_port(tenant_id, net_id, port_id) # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? - port['attachment'] = None + db.port_unset_attachment(port_id) diff --git a/quantum/plugins/openvswitch/README b/quantum/plugins/openvswitch/README index b44a596724..f38a18c09d 100644 --- a/quantum/plugins/openvswitch/README +++ b/quantum/plugins/openvswitch/README @@ -62,20 +62,25 @@ mysql> FLUSH PRIVILEGES; distribution tarball (see below) and the agent will use the credentials here to access the database. -# -- Agent configuration +# -- XenServer Agent configuration - Create the agent distribution tarball $ make agent-dist - Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova compute node) -- Unpack the tarball and run install.sh. This will install all of the +- Unpack the tarball and run xenserver_install.sh. This will install all of the necessary pieces into /etc/xapi.d/plugins. It will also spit out the name of the integration bridge that you'll need for your nova configuration. Make sure to specify this in your nova flagfile as --flat_network_bridge. - Run the agent [on your hypervisor (dom0)]: $ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini +# -- KVM Agent configuration + +- Copy ovs_quantum_agent.py and ovs_quantum_plugin.ini to the Linux host and run: +$ python ovs_quantum_agent.py ovs_quantum_plugin.ini + # -- Getting quantum up and running - Start quantum [on the quantum service host]: diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index a5cd7948d7..5d2a66ca37 100755 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -130,40 +130,40 @@ class OVSBridge: def get_port_stats(self, port_name): return self.db_get_map("Interface", port_name, "statistics") + # this is a hack that should go away once nova properly reports bindings + # to quantum. We have this here for now as it lets us work with + # unmodified nova + def xapi_get_port(self, name): + external_ids = self.db_get_map("Interface", name, "external_ids") + if "attached-mac" not in external_ids: + return None + vm_uuid = external_ids.get("xs-vm-uuid", "") + if len(vm_uuid) == 0: + return None + LOG.debug("iface-id not set, got xs-vm-uuid: %s" % vm_uuid) + res = os.popen("xe vm-list uuid=%s params=name-label --minimal" \ + % vm_uuid).readline().strip() + if len(res) == 0: + return None + external_ids["iface-id"] = res + LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res)) + self.set_db_attribute("Interface", name, + "external-ids:iface-id", res) + ofport = self.db_get_val("Interface", name, "ofport") + return VifPort(name, ofport, external_ids["iface-id"], + external_ids["attached-mac"], self) + # returns a VIF object for each VIF port def get_vif_ports(self): edge_ports = [] port_names = self.get_port_name_list() for name in port_names: external_ids = self.db_get_map("Interface", name, "external_ids") - if "iface-id" in external_ids and "attached-mac" in external_ids: - ofport = self.db_get_val("Interface", name, "ofport") - p = VifPort(name, ofport, external_ids["iface-id"], - external_ids["attached-mac"], self) - edge_ports.append(p) - else: - # iface-id might not be set. See if we can figure it out and - # set it here. - external_ids = self.db_get_map("Interface", name, - "external_ids") - if "attached-mac" not in external_ids: - continue - vif_uuid = external_ids.get("xs-vif-uuid", "") - if len(vif_uuid) == 0: - continue - LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid) - res = os.popen("xe vif-param-get param-name=other-config " - "uuid=%s | grep nicira-iface-id | " - "awk '{print $2}'" - % vif_uuid).readline() - res = res.strip() - if len(res) == 0: - continue - external_ids["iface-id"] = res - LOG.info("Setting interface \"%s\" iface-id to \"%s\"" - % (name, res)) - self.set_db_attribute("Interface", name, - "external-ids:iface-id", res) + if "xs-vm-uuid" in external_ids: + p = xapi_get_port(name) + if p is not None: + edge_ports.append(p) + elif "iface-id" in external_ids and "attached-mac" in external_ids: ofport = self.db_get_val("Interface", name, "ofport") p = VifPort(name, ofport, external_ids["iface-id"], external_ids["attached-mac"], self) @@ -171,13 +171,15 @@ class OVSBridge: return edge_ports -class OVSNaaSPlugin: +class OVSQuantumAgent: + def __init__(self, integ_br): self.setup_integration_br(integ_br) def port_bound(self, port, vlan_id): self.int_br.set_db_attribute("Port", port.port_name, "tag", - str(vlan_id)) + str(vlan_id)) + self.int_br.delete_flows(match="in_port=%s" % port.ofport) def port_unbound(self, port, still_exists): if still_exists: @@ -186,13 +188,8 @@ class OVSNaaSPlugin: def setup_integration_br(self, integ_br): self.int_br = OVSBridge(integ_br) self.int_br.remove_all_flows() - # drop all traffic on the 'dead vlan' - self.int_br.add_flow(priority=2, match="dl_vlan=4095", actions="drop") - # switch all other traffic using L2 learning + # switch all traffic using L2 learning self.int_br.add_flow(priority=1, actions="normal") - # FIXME send broadcast everywhere, regardless of tenant - #int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff", - # actions="normal") def daemon_loop(self, conn): self.local_vlan_map = {} @@ -201,7 +198,7 @@ class OVSNaaSPlugin: while True: cursor = conn.cursor() - cursor.execute("SELECT * FROM network_bindings") + cursor.execute("SELECT * FROM ports") rows = cursor.fetchall() cursor.close() all_bindings = {} @@ -226,22 +223,26 @@ class OVSNaaSPlugin: else: # no binding, put him on the 'dead vlan' self.int_br.set_db_attribute("Port", p.port_name, "tag", - "4095") + "4095") + self.int_br.add_flow(priority=2, + match="in_port=%s" % p.ofport, actions="drop") + old_b = old_local_bindings.get(p.vif_id, None) new_b = new_local_bindings.get(p.vif_id, None) + if old_b != new_b: if old_b is not None: LOG.info("Removing binding to net-id = %s for %s" % (old_b, str(p))) self.port_unbound(p, True) if new_b is not None: - LOG.info("Adding binding to net-id = %s for %s" \ - % (new_b, str(p))) # If we don't have a binding we have to stick it on # the dead vlan vlan_id = vlan_bindings.get(all_bindings[p.vif_id], "4095") self.port_bound(p, vlan_id) + LOG.info("Adding binding to net-id = %s " \ + "for %s on vlan %s" % (new_b, str(p), vlan_id)) for vif_id in old_vif_ports.keys(): if vif_id not in new_vif_ports: LOG.info("Port Disappeared: %s" % vif_id) @@ -251,8 +252,6 @@ class OVSNaaSPlugin: old_vif_ports = new_vif_ports old_local_bindings = new_local_bindings - self.int_br.run_cmd(["bash", - "/etc/xapi.d/plugins/set_external_ids.sh"]) time.sleep(2) if __name__ == "__main__": @@ -291,7 +290,7 @@ if __name__ == "__main__": LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host)) conn = MySQLdb.connect(host=db_host, user=db_user, passwd=db_pass, db=db_name) - plugin = OVSNaaSPlugin(integ_br) + plugin = OVSQuantumAgent(integ_br) plugin.daemon_loop(conn) finally: if conn: diff --git a/quantum/plugins/openvswitch/agent/set_external_ids.sh b/quantum/plugins/openvswitch/agent/set_external_ids.sh deleted file mode 100755 index 2fae05f0a1..0000000000 --- a/quantum/plugins/openvswitch/agent/set_external_ids.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -VIFLIST=`xe vif-list params=uuid --minimal | sed s/,/" "/g` -for VIF_UUID in $VIFLIST; do -DEVICE_NUM=`xe vif-list params=device uuid=$VIF_UUID --minimal` - VM_NAME=`xe vif-list params=vm-name-label uuid=$VIF_UUID --minimal` - NAME="$VM_NAME-eth$DEVICE_NUM" - echo "Vif: $VIF_UUID is '$NAME'" - xe vif-param-set uuid=$VIF_UUID other-config:nicira-iface-id="$NAME" -done - -ps auxw | grep -v grep | grep ovs-xapi-sync > /dev/null 2>&1 -if [ $? -eq 0 ]; then - killall -HUP ovs-xapi-sync -fi - diff --git a/quantum/plugins/openvswitch/agent/install.sh b/quantum/plugins/openvswitch/agent/xenserver_install.sh similarity index 100% rename from quantum/plugins/openvswitch/agent/install.sh rename to quantum/plugins/openvswitch/agent/xenserver_install.sh diff --git a/quantum/plugins/openvswitch/ovs_db.py b/quantum/plugins/openvswitch/ovs_db.py index d72f9a89ce..d02aafd068 100644 --- a/quantum/plugins/openvswitch/ovs_db.py +++ b/quantum/plugins/openvswitch/ovs_db.py @@ -56,21 +56,3 @@ def remove_vlan_binding(netid): except exc.NoResultFound: pass session.flush() - - -def update_network_binding(netid, ifaceid): - session = db.get_session() - # Add to or delete from the bindings table - if ifaceid == None: - try: - binding = session.query(ovs_models.NetworkBinding).\ - filter_by(network_id=netid).\ - one() - session.delete(binding) - except exc.NoResultFound: - raise Exception("No binding found with network_id = %s" % netid) - else: - binding = ovs_models.NetworkBinding(netid, ifaceid) - session.add(binding) - - session.flush() diff --git a/quantum/plugins/openvswitch/ovs_models.py b/quantum/plugins/openvswitch/ovs_models.py index 5f529e55a3..4c2eed0627 100644 --- a/quantum/plugins/openvswitch/ovs_models.py +++ b/quantum/plugins/openvswitch/ovs_models.py @@ -26,23 +26,6 @@ from sqlalchemy.orm import relation from quantum.db.models import BASE -class NetworkBinding(BASE): - """Represents a binding of network_id, vif_id""" - __tablename__ = 'network_bindings' - - id = Column(Integer, primary_key=True, autoincrement=True) - network_id = Column(String(255)) - vif_id = Column(String(255)) - - def __init__(self, network_id, vif_id): - self.network_id = network_id - self.vif_id = vif_id - - def __repr__(self): - return "" % \ - (self.network_id, self.vif_id) - - class VlanBinding(BASE): """Represents a binding of network_id, vlan_id""" __tablename__ = 'vlan_bindings' diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini index 0c75b3fc48..66095d85d1 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -1,9 +1,9 @@ [DATABASE] -name = ovs_naas +name = ovs_quantum user = root -pass = foobar +pass = nova host = 127.0.0.1 port = 3306 [OVS] -integration-bridge = xapi1 +integration-bridge = br100 diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index b5d1bc68d1..2f23517ec7 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -200,11 +200,9 @@ class OVSQuantumPlugin(QuantumPluginBase): def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id): db.port_set_attachment(port_id, remote_iface_id) - ovs_db.update_network_binding(net_id, remote_iface_id) def unplug_interface(self, tenant_id, net_id, port_id): db.port_set_attachment(port_id, "") - ovs_db.update_network_binding(net_id, None) def get_interface_details(self, tenant_id, net_id, port_id): res = db.port_get(port_id) diff --git a/quantum/quantum_plugin_base.py b/quantum/quantum_plugin_base.py index 8e3f1f8e15..3228f868c7 100644 --- a/quantum/quantum_plugin_base.py +++ b/quantum/quantum_plugin_base.py @@ -22,6 +22,7 @@ QuantumPluginBase provides the definition of minimum set of methods that needs to be implemented by a Quantum Plug-in. """ +import inspect from abc import ABCMeta, abstractmethod @@ -242,7 +243,17 @@ class QuantumPluginBase(object): """ if cls is QuantumPluginBase: for method in cls.__abstractmethods__: - if any(method in base.__dict__ for base in klass.__mro__): + method_ok = False + for base in klass.__mro__: + if method in base.__dict__: + fn_obj = base.__dict__[method] + if inspect.isfunction(fn_obj): + abstract_fn_obj = cls.__dict__[method] + arg_count = fn_obj.func_code.co_argcount + expected_arg_count = \ + abstract_fn_obj.func_code.co_argcount + method_ok = arg_count == expected_arg_count + if method_ok: continue return NotImplemented return True diff --git a/run_tests.py b/run_tests.py index acc8260b81..d63cc34a42 100644 --- a/run_tests.py +++ b/run_tests.py @@ -63,6 +63,7 @@ To run a single functional test module:: """ import gettext +import logging import os import unittest import sys @@ -281,12 +282,19 @@ class QuantumTestRunner(core.TextTestRunner): if __name__ == '__main__': + # Set up test logger. + logger = logging.getLogger() + hdlr = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') + hdlr.setFormatter(formatter) + logger.addHandler(hdlr) + logger.setLevel(logging.DEBUG) + working_dir = os.path.abspath("tests") c = config.Config(stream=sys.stdout, env=os.environ, verbosity=3, workingDir=working_dir) - runner = QuantumTestRunner(stream=c.stream, verbosity=c.verbosity, config=c) diff --git a/run_tests.sh b/run_tests.sh index aa72cbe22d..9c603c9825 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -2,7 +2,7 @@ function usage { echo "Usage: $0 [OPTION]..." - echo "Run Melange's test suite(s)" + echo "Run Quantum's test suite(s)" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/functional/miniclient.py b/tests/functional/miniclient.py deleted file mode 100644 index be49867355..0000000000 --- a/tests/functional/miniclient.py +++ /dev/null @@ -1,99 +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. - -import httplib -import socket -import urllib - - -class MiniClient(object): - - """A base client class - derived from Glance.BaseClient""" - - action_prefix = '/v0.1/tenants/{tenant_id}' - - def __init__(self, host, port, use_ssl): - """ - Creates a new client to some service. - - :param host: The host where service resides - :param port: The port where service resides - :param use_ssl: Should we use HTTPS? - """ - self.host = host - self.port = port - self.use_ssl = use_ssl - self.connection = None - - def get_connection_type(self): - """ - Returns the proper connection type - """ - if self.use_ssl: - return httplib.HTTPSConnection - else: - return httplib.HTTPConnection - - def do_request(self, tenant, 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 - - """ - action = MiniClient.action_prefix + action - action = action.replace('{tenant_id}', tenant) - if type(params) is dict: - action += '?' + urllib.urlencode(params) - - try: - connection_type = self.get_connection_type() - headers = headers or {} - - # Open connection and send request - c = connection_type(self.host, self.port) - c.request(method, action, body, headers) - res = c.getresponse() - status_code = self.get_status_code(res) - if status_code in (httplib.OK, - httplib.CREATED, - httplib.ACCEPTED, - httplib.NO_CONTENT): - return res - else: - raise Exception("Server returned error: %s" % res.read()) - - except (socket.error, IOError), e: - raise Exception("Unable to connect to " - "server. Got error: %s" % e) - - def get_status_code(self, response): - """ - Returns the integer status code from the response, which - can be either a Webob.Response (used in testing) or httplib.Response - """ - if hasattr(response, 'status_int'): - return response.status_int - else: - return response.status diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py deleted file mode 100644 index ba4f61416b..0000000000 --- a/tests/functional/test_service.py +++ /dev/null @@ -1,137 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Citrix Systems -# 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. - -import gettext -import simplejson -import sys -import unittest - -gettext.install('quantum', unicode=1) - -from miniclient import MiniClient -from quantum.common.wsgi import Serializer - -HOST = '127.0.0.1' -PORT = 9696 -USE_SSL = False - -TENANT_ID = 'totore' -FORMAT = "json" - -test_network1_data = \ - {'network': {'network-name': 'test1'}} -test_network2_data = \ - {'network': {'network-name': 'test2'}} - - -def print_response(res): - content = res.read() - print "Status: %s" % res.status - print "Content: %s" % content - return content - - -class QuantumTest(unittest.TestCase): - def setUp(self): - self.client = MiniClient(HOST, PORT, USE_SSL) - - def create_network(self, data): - content_type = "application/" + FORMAT - body = Serializer().serialize(data, content_type) - res = self.client.do_request(TENANT_ID, 'POST', "/networks." + FORMAT, - body=body) - self.assertEqual(res.status, 200, "bad response: %s" % res.read()) - - def test_listNetworks(self): - self.create_network(test_network1_data) - self.create_network(test_network2_data) - res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - self.assertEqual(res.status, 200, "bad response: %s" % res.read()) - - def test_createNetwork(self): - self.create_network(test_network1_data) - - def test_createPort(self): - self.create_network(test_network1_data) - res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - resdict = simplejson.loads(res.read()) - for n in resdict["networks"]: - net_id = n["id"] - - # Step 1 - List Ports for network (should not find any) - res = self.client.do_request(TENANT_ID, 'GET', - "/networks/%s/ports.%s" % (net_id, FORMAT)) - self.assertEqual(res.status, 200, "Bad response: %s" % res.read()) - output = res.read() - self.assertTrue(len(output) == 0, - "Found unexpected ports: %s" % output) - - # Step 2 - Create Port for network - res = self.client.do_request(TENANT_ID, 'POST', - "/networks/%s/ports.%s" % (net_id, FORMAT)) - self.assertEqual(res.status, 200, "Bad response: %s" % output) - - # Step 3 - List Ports for network (again); should find one - res = self.client.do_request(TENANT_ID, 'GET', - "/networks/%s/ports.%s" % (net_id, FORMAT)) - output = res.read() - self.assertEqual(res.status, 200, "Bad response: %s" % output) - resdict = simplejson.loads(output) - ids = [] - for p in resdict["ports"]: - ids.append(p["id"]) - self.assertTrue(len(ids) == 1, - "Didn't find expected # of ports (1): %s" % ids) - - def test_renameNetwork(self): - self.create_network(test_network1_data) - res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - resdict = simplejson.loads(res.read()) - net_id = resdict["networks"][0]["id"] - - data = test_network1_data.copy() - data['network']['network-name'] = 'test_renamed' - content_type = "application/" + FORMAT - body = Serializer().serialize(data, content_type) - res = self.client.do_request(TENANT_ID, 'PUT', - "/networks/%s.%s" % (net_id, FORMAT), body=body) - resdict = simplejson.loads(res.read()) - self.assertTrue(resdict["networks"]["network"]["id"] == net_id, - "Network_rename: renamed network has a different uuid") - self.assertTrue( - resdict["networks"]["network"]["name"] == "test_renamed", - "Network rename didn't take effect") - - def delete_networks(self): - # Remove all the networks created on the tenant - res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - resdict = simplejson.loads(res.read()) - for n in resdict["networks"]: - net_id = n["id"] - res = self.client.do_request(TENANT_ID, 'DELETE', - "/networks/" + net_id + "." + FORMAT) - self.assertEqual(res.status, 202) - - def tearDown(self): - pass - #self.delete_networks() - -# Standard boilerplate to call the main() function. -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(QuantumTest) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py new file mode 100644 index 0000000000..c09af76990 --- /dev/null +++ b/tests/unit/test_api.py @@ -0,0 +1,831 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 ???? +# 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: Brad Hall, Nicira Networks +# @author: Salvatore Orlando, Citrix Systems + +import logging +import unittest + +import tests.unit.testlib_api as testlib + +from quantum import api as server +from quantum.db import api as db +from quantum.common.wsgi import Serializer + + +LOG = logging.getLogger('quantum.tests.test_api') + + +class APITest(unittest.TestCase): + + def _create_network(self, format, name=None, custom_req_body=None, + expected_res_status=200): + LOG.debug("Creating network") + content_type = "application/" + format + if name: + net_name = name + else: + net_name = self.network_name + network_req = testlib.new_network_request(self.tenant_id, + net_name, format, + custom_req_body) + network_res = network_req.get_response(self.api) + self.assertEqual(network_res.status_int, expected_res_status) + if expected_res_status == 200: + network_data = Serializer().deserialize(network_res.body, + content_type) + return network_data['networks']['network']['id'] + + def _create_port(self, network_id, port_state, format, + custom_req_body=None, expected_res_status=200): + LOG.debug("Creating port for network %s", network_id) + content_type = "application/%s" % format + port_req = testlib.new_port_request(self.tenant_id, network_id, + port_state, format, + custom_req_body) + port_res = port_req.get_response(self.api) + self.assertEqual(port_res.status_int, expected_res_status) + if expected_res_status == 200: + port_data = Serializer().deserialize(port_res.body, content_type) + return port_data['ports']['port']['id'] + + def _test_create_network(self, format): + LOG.debug("_test_create_network - format:%s - START", format) + content_type = "application/%s" % format + network_id = self._create_network(format) + show_network_req = testlib.show_network_request(self.tenant_id, + network_id, + format) + show_network_res = show_network_req.get_response(self.api) + self.assertEqual(show_network_res.status_int, 200) + network_data = Serializer().deserialize(show_network_res.body, + content_type) + self.assertEqual(network_id, + network_data['networks']['network']['id']) + LOG.debug("_test_create_network - format:%s - END", format) + + def _test_create_network_badrequest(self, format): + LOG.debug("_test_create_network_badrequest - format:%s - START", + format) + bad_body = {'network': {'bad-attribute': 'very-bad'}} + self._create_network(format, custom_req_body=bad_body, + expected_res_status=400) + LOG.debug("_test_create_network_badrequest - format:%s - END", + format) + + def _test_list_networks(self, format): + LOG.debug("_test_list_networks - format:%s - START", format) + content_type = "application/%s" % format + self._create_network(format, "net_1") + self._create_network(format, "net_2") + list_network_req = testlib.network_list_request(self.tenant_id, + format) + list_network_res = list_network_req.get_response(self.api) + self.assertEqual(list_network_res.status_int, 200) + network_data = Serializer().deserialize(list_network_res.body, + content_type) + # Check network count: should return 2 + self.assertEqual(len(network_data['networks']), 2) + LOG.debug("_test_list_networks - format:%s - END", format) + + def _test_show_network(self, format): + LOG.debug("_test_show_network - format:%s - START", format) + content_type = "application/%s" % format + network_id = self._create_network(format) + show_network_req = testlib.show_network_request(self.tenant_id, + network_id, + format) + show_network_res = show_network_req.get_response(self.api) + self.assertEqual(show_network_res.status_int, 200) + network_data = Serializer().deserialize(show_network_res.body, + content_type) + self.assertEqual({'id': network_id, 'name': self.network_name}, + network_data['networks']['network']) + LOG.debug("_test_show_network - format:%s - END", format) + + def _test_show_network_not_found(self, format): + LOG.debug("_test_show_network_not_found - format:%s - START", format) + show_network_req = testlib.show_network_request(self.tenant_id, + "A_BAD_ID", + format) + show_network_res = show_network_req.get_response(self.api) + self.assertEqual(show_network_res.status_int, 420) + LOG.debug("_test_show_network_not_found - format:%s - END", format) + + def _test_rename_network(self, format): + LOG.debug("_test_rename_network - format:%s - START", format) + content_type = "application/%s" % format + new_name = 'new_network_name' + network_id = self._create_network(format) + update_network_req = testlib.update_network_request(self.tenant_id, + network_id, + new_name, + format) + update_network_res = update_network_req.get_response(self.api) + self.assertEqual(update_network_res.status_int, 202) + show_network_req = testlib.show_network_request(self.tenant_id, + network_id, + format) + show_network_res = show_network_req.get_response(self.api) + self.assertEqual(show_network_res.status_int, 200) + network_data = Serializer().deserialize(show_network_res.body, + content_type) + self.assertEqual({'id': network_id, 'name': new_name}, + network_data['networks']['network']) + LOG.debug("_test_rename_network - format:%s - END", format) + + def _test_rename_network_badrequest(self, format): + LOG.debug("_test_rename_network_badrequest - format:%s - START", + format) + network_id = self._create_network(format) + bad_body = {'network': {'bad-attribute': 'very-bad'}} + update_network_req = testlib.\ + update_network_request(self.tenant_id, + network_id, format, + custom_req_body=bad_body) + update_network_res = update_network_req.get_response(self.api) + self.assertEqual(update_network_res.status_int, 400) + LOG.debug("_test_rename_network_badrequest - format:%s - END", + format) + + def _test_rename_network_not_found(self, format): + LOG.debug("_test_rename_network_not_found - format:%s - START", + format) + new_name = 'new_network_name' + update_network_req = testlib.update_network_request(self.tenant_id, + "A BAD ID", + new_name, + format) + update_network_res = update_network_req.get_response(self.api) + self.assertEqual(update_network_res.status_int, 420) + LOG.debug("_test_rename_network_not_found - format:%s - END", + format) + + def _test_delete_network(self, format): + LOG.debug("_test_delete_network - format:%s - START", format) + content_type = "application/%s" % format + network_id = self._create_network(format) + LOG.debug("Deleting network %(network_id)s"\ + " of tenant %(tenant_id)s", locals()) + delete_network_req = testlib.network_delete_request(self.tenant_id, + network_id, + format) + delete_network_res = delete_network_req.get_response(self.api) + self.assertEqual(delete_network_res.status_int, 202) + list_network_req = testlib.network_list_request(self.tenant_id, + format) + list_network_res = list_network_req.get_response(self.api) + network_list_data = Serializer().deserialize(list_network_res.body, + content_type) + network_count = len(network_list_data['networks']) + self.assertEqual(network_count, 0) + LOG.debug("_test_delete_network - format:%s - END", format) + + def _test_delete_network_in_use(self, format): + LOG.debug("_test_delete_network_in_use - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + attachment_id = "test_attachment" + network_id = self._create_network(format) + LOG.debug("Deleting network %(network_id)s"\ + " of tenant %(tenant_id)s", locals()) + port_id = self._create_port(network_id, port_state, format) + #plug an attachment into the port + LOG.debug("Putting attachment into port %s", port_id) + attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + attachment_id) + attachment_res = attachment_req.get_response(self.api) + self.assertEquals(attachment_res.status_int, 202) + + LOG.debug("Deleting network %(network_id)s"\ + " of tenant %(tenant_id)s", locals()) + delete_network_req = testlib.network_delete_request(self.tenant_id, + network_id, + format) + delete_network_res = delete_network_req.get_response(self.api) + self.assertEqual(delete_network_res.status_int, 421) + LOG.debug("_test_delete_network_in_use - format:%s - END", format) + + def _test_list_ports(self, format): + LOG.debug("_test_list_ports - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + self._create_port(network_id, port_state, format) + list_port_req = testlib.port_list_request(self.tenant_id, + network_id, format) + list_port_res = list_port_req.get_response(self.api) + self.assertEqual(list_port_res.status_int, 200) + port_data = Serializer().deserialize(list_port_res.body, + content_type) + # Check port count: should return 2 + self.assertEqual(len(port_data['ports']), 2) + LOG.debug("_test_list_ports - format:%s - END", format) + + def _test_show_port(self, format): + LOG.debug("_test_show_port - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + show_port_req = testlib.show_port_request(self.tenant_id, + network_id, port_id, + format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 200) + port_data = Serializer().deserialize(show_port_res.body, + content_type) + self.assertEqual({'id': port_id, 'state': port_state}, + port_data['ports']['port']) + LOG.debug("_test_show_port - format:%s - END", format) + + def _test_show_port_networknotfound(self, format): + LOG.debug("_test_show_port_networknotfound - format:%s - START", + format) + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + show_port_req = testlib.show_port_request(self.tenant_id, + "A_BAD_ID", port_id, + format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 420) + LOG.debug("_test_show_port_networknotfound - format:%s - END", + format) + + def _test_show_port_portnotfound(self, format): + LOG.debug("_test_show_port_portnotfound - format:%s - START", format) + network_id = self._create_network(format) + show_port_req = testlib.show_port_request(self.tenant_id, + network_id, + "A_BAD_ID", + format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 430) + LOG.debug("_test_show_port_portnotfound - format:%s - END", format) + + def _test_create_port(self, format): + LOG.debug("_test_create_port - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + show_port_req = testlib.show_port_request(self.tenant_id, + network_id, port_id, format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 200) + port_data = Serializer().deserialize(show_port_res.body, content_type) + self.assertEqual(port_id, port_data['ports']['port']['id']) + LOG.debug("_test_create_port - format:%s - END", format) + + def _test_create_port_networknotfound(self, format): + LOG.debug("_test_create_port_networknotfound - format:%s - START", + format) + port_state = "ACTIVE" + self._create_port("A_BAD_ID", port_state, format, + expected_res_status=420) + LOG.debug("_test_create_port_networknotfound - format:%s - END", + format) + + def _test_create_port_badrequest(self, format): + LOG.debug("_test_create_port_badrequest - format:%s - START", format) + bad_body = {'bad-resource': {'bad-attribute': 'bad-value'}} + network_id = self._create_network(format) + port_state = "ACTIVE" + self._create_port(network_id, port_state, format, + custom_req_body=bad_body, expected_res_status=400) + LOG.debug("_test_create_port_badrequest - format:%s - END", format) + + def _test_delete_port(self, format): + LOG.debug("_test_delete_port - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\ + " of tenant %(tenant_id)s", locals()) + delete_port_req = testlib.port_delete_request(self.tenant_id, + network_id, port_id, + format) + delete_port_res = delete_port_req.get_response(self.api) + self.assertEqual(delete_port_res.status_int, 202) + list_port_req = testlib.port_list_request(self.tenant_id, network_id, + format) + list_port_res = list_port_req.get_response(self.api) + port_list_data = Serializer().deserialize(list_port_res.body, + content_type) + port_count = len(port_list_data['ports']) + self.assertEqual(port_count, 0) + LOG.debug("_test_delete_port - format:%s - END", format) + + def _test_delete_port_in_use(self, format): + LOG.debug("_test_delete_port_in_use - format:%s - START", format) + content_type = "application/" + format + port_state = "ACTIVE" + attachment_id = "test_attachment" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + #plug an attachment into the port + LOG.debug("Putting attachment into port %s", port_id) + attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + attachment_id) + attachment_res = attachment_req.get_response(self.api) + self.assertEquals(attachment_res.status_int, 202) + LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\ + " of tenant %(tenant_id)s", locals()) + delete_port_req = testlib.port_delete_request(self.tenant_id, + network_id, port_id, + format) + delete_port_res = delete_port_req.get_response(self.api) + self.assertEqual(delete_port_res.status_int, 432) + LOG.debug("_test_delete_port_in_use - format:%s - END", format) + + def _test_delete_port_with_bad_id(self, format): + LOG.debug("_test_delete_port_with_bad_id - format:%s - START", + format) + port_state = "ACTIVE" + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + # Test for portnotfound + delete_port_req = testlib.port_delete_request(self.tenant_id, + network_id, "A_BAD_ID", + format) + delete_port_res = delete_port_req.get_response(self.api) + self.assertEqual(delete_port_res.status_int, 430) + LOG.debug("_test_delete_port_with_bad_id - format:%s - END", format) + + def _test_delete_port_networknotfound(self, format): + LOG.debug("_test_delete_port_networknotfound - format:%s - START", + format) + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + delete_port_req = testlib.port_delete_request(self.tenant_id, + "A_BAD_ID", port_id, + format) + delete_port_res = delete_port_req.get_response(self.api) + self.assertEqual(delete_port_res.status_int, 420) + LOG.debug("_test_delete_port_networknotfound - format:%s - END", + format) + + def _test_set_port_state(self, format): + LOG.debug("_test_set_port_state - format:%s - START", format) + content_type = "application/%s" % format + port_state = 'DOWN' + new_port_state = 'ACTIVE' + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + update_port_req = testlib.update_port_request(self.tenant_id, + network_id, port_id, + new_port_state, + format) + update_port_res = update_port_req.get_response(self.api) + self.assertEqual(update_port_res.status_int, 200) + show_port_req = testlib.show_port_request(self.tenant_id, + network_id, port_id, + format) + show_port_res = show_port_req.get_response(self.api) + self.assertEqual(show_port_res.status_int, 200) + network_data = Serializer().deserialize(show_port_res.body, + content_type) + self.assertEqual({'id': port_id, 'state': new_port_state}, + network_data['ports']['port']) + LOG.debug("_test_set_port_state - format:%s - END", format) + + def _test_set_port_state_networknotfound(self, format): + LOG.debug("_test_set_port_state_networknotfound - format:%s - START", + format) + port_state = 'DOWN' + new_port_state = 'ACTIVE' + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + update_port_req = testlib.update_port_request(self.tenant_id, + "A_BAD_ID", port_id, + new_port_state, + format) + update_port_res = update_port_req.get_response(self.api) + self.assertEqual(update_port_res.status_int, 420) + LOG.debug("_test_set_port_state_networknotfound - format:%s - END", + format) + + def _test_set_port_state_portnotfound(self, format): + LOG.debug("_test_set_port_state_portnotfound - format:%s - START", + format) + port_state = 'DOWN' + new_port_state = 'ACTIVE' + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + update_port_req = testlib.update_port_request(self.tenant_id, + network_id, + "A_BAD_ID", + new_port_state, + format) + update_port_res = update_port_req.get_response(self.api) + self.assertEqual(update_port_res.status_int, 430) + LOG.debug("_test_set_port_state_portnotfound - format:%s - END", + format) + + def _test_set_port_state_stateinvalid(self, format): + LOG.debug("_test_set_port_state_stateinvalid - format:%s - START", + format) + port_state = 'DOWN' + new_port_state = 'A_BAD_STATE' + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + update_port_req = testlib.update_port_request(self.tenant_id, + network_id, port_id, + new_port_state, + format) + update_port_res = update_port_req.get_response(self.api) + self.assertEqual(update_port_res.status_int, 431) + LOG.debug("_test_set_port_state_stateinvalid - format:%s - END", + format) + + def _test_show_attachment(self, format): + LOG.debug("_test_show_attachment - format:%s - START", format) + content_type = "application/%s" % format + port_state = "ACTIVE" + network_id = self._create_network(format) + interface_id = "test_interface" + port_id = self._create_port(network_id, port_state, format) + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + interface_id, + format) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, 202) + get_attachment_req = testlib.get_attachment_request(self.tenant_id, + network_id, + port_id, + format) + get_attachment_res = get_attachment_req.get_response(self.api) + attachment_data = Serializer().deserialize(get_attachment_res.body, + content_type) + self.assertEqual(attachment_data['attachment'], interface_id) + LOG.debug("_test_show_attachment - format:%s - END", format) + + def _test_show_attachment_networknotfound(self, format): + LOG.debug("_test_show_attachment_networknotfound - format:%s - START", + format) + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + get_attachment_req = testlib.get_attachment_request(self.tenant_id, + "A_BAD_ID", + port_id, + format) + get_attachment_res = get_attachment_req.get_response(self.api) + self.assertEqual(get_attachment_res.status_int, 420) + LOG.debug("_test_show_attachment_networknotfound - format:%s - END", + format) + + def _test_show_attachment_portnotfound(self, format): + LOG.debug("_test_show_attachment_portnotfound - format:%s - START", + format) + port_state = "ACTIVE" + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + get_attachment_req = testlib.get_attachment_request(self.tenant_id, + network_id, + "A_BAD_ID", + format) + get_attachment_res = get_attachment_req.get_response(self.api) + self.assertEqual(get_attachment_res.status_int, 430) + LOG.debug("_test_show_attachment_portnotfound - format:%s - END", + format) + + def _test_put_attachment(self, format): + LOG.debug("_test_put_attachment - format:%s - START", format) + port_state = "ACTIVE" + network_id = self._create_network(format) + interface_id = "test_interface" + port_id = self._create_port(network_id, port_state, format) + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + interface_id, + format) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, 202) + LOG.debug("_test_put_attachment - format:%s - END", format) + + def _test_put_attachment_networknotfound(self, format): + LOG.debug("_test_put_attachment_networknotfound - format:%s - START", + format) + port_state = 'DOWN' + interface_id = "test_interface" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + "A_BAD_ID", + port_id, + interface_id, + format) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, 420) + LOG.debug("_test_put_attachment_networknotfound - format:%s - END", + format) + + def _test_put_attachment_portnotfound(self, format): + LOG.debug("_test_put_attachment_portnotfound - format:%s - START", + format) + port_state = 'DOWN' + interface_id = "test_interface" + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + "A_BAD_ID", + interface_id, + format) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, 430) + LOG.debug("_test_put_attachment_portnotfound - format:%s - END", + format) + + def _test_delete_attachment(self, format): + LOG.debug("_test_delete_attachment - format:%s - START", format) + port_state = "ACTIVE" + network_id = self._create_network(format) + interface_id = "test_interface" + port_id = self._create_port(network_id, port_state, format) + put_attachment_req = testlib.put_attachment_request(self.tenant_id, + network_id, + port_id, + interface_id, + format) + put_attachment_res = put_attachment_req.get_response(self.api) + self.assertEqual(put_attachment_res.status_int, 202) + del_attachment_req = testlib.delete_attachment_request(self.tenant_id, + network_id, + port_id, + format) + del_attachment_res = del_attachment_req.get_response(self.api) + self.assertEqual(del_attachment_res.status_int, 202) + LOG.debug("_test_delete_attachment - format:%s - END", format) + + def _test_delete_attachment_networknotfound(self, format): + LOG.debug("_test_delete_attachment_networknotfound -" \ + " format:%s - START", format) + port_state = "ACTIVE" + network_id = self._create_network(format) + port_id = self._create_port(network_id, port_state, format) + del_attachment_req = testlib.delete_attachment_request(self.tenant_id, + "A_BAD_ID", + port_id, + format) + del_attachment_res = del_attachment_req.get_response(self.api) + self.assertEqual(del_attachment_res.status_int, 420) + LOG.debug("_test_delete_attachment_networknotfound -" \ + " format:%s - END", format) + + def _test_delete_attachment_portnotfound(self, format): + LOG.debug("_test_delete_attachment_portnotfound - " \ + " format:%s - START", format) + port_state = "ACTIVE" + network_id = self._create_network(format) + self._create_port(network_id, port_state, format) + del_attachment_req = testlib.delete_attachment_request(self.tenant_id, + network_id, + "A_BAD_ID", + format) + del_attachment_res = del_attachment_req.get_response(self.api) + self.assertEqual(del_attachment_res.status_int, 430) + LOG.debug("_test_delete_attachment_portnotfound - " \ + "format:%s - END", format) + + def setUp(self): + options = {} + options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin' + self.api = server.APIRouterV01(options) + self.tenant_id = "test_tenant" + self.network_name = "test_network" + + def tearDown(self): + """Clear the test environment""" + # Remove database contents + db.clear_db() + + def test_list_networks_json(self): + self._test_list_networks('json') + + def test_list_networks_xml(self): + self._test_list_networks('xml') + + def test_create_network_json(self): + self._test_create_network('json') + + def test_create_network_xml(self): + self._test_create_network('xml') + + def test_create_network_badrequest_json(self): + self._test_create_network_badrequest('json') + + def test_create_network_badreqyest_xml(self): + self._test_create_network_badrequest('xml') + + def test_show_network_not_found_json(self): + self._test_show_network_not_found('json') + + def test_show_network_not_found_xml(self): + self._test_show_network_not_found('xml') + + def test_show_network_json(self): + self._test_show_network('json') + + def test_show_network_xml(self): + self._test_show_network('xml') + + def test_delete_network_json(self): + self._test_delete_network('json') + + def test_delete_network_xml(self): + self._test_delete_network('xml') + + def test_rename_network_json(self): + self._test_rename_network('json') + + def test_rename_network_xml(self): + self._test_rename_network('xml') + + def test_rename_network_badrequest_json(self): + self._test_rename_network_badrequest('json') + + def test_rename_network_badrequest_xml(self): + self._test_rename_network_badrequest('xml') + + def test_rename_network_not_found_json(self): + self._test_rename_network_not_found('json') + + def test_rename_network_not_found_xml(self): + self._test_rename_network_not_found('xml') + + def test_delete_network_in_use_json(self): + self._test_delete_network_in_use('json') + + def test_delete_network_in_use_xml(self): + self._test_delete_network_in_use('xml') + + def test_list_ports_json(self): + self._test_list_ports('json') + + def test_list_ports_xml(self): + self._test_list_ports('xml') + + def test_show_port_json(self): + self._test_show_port('json') + + def test_show_port_xml(self): + self._test_show_port('xml') + + def test_show_port_networknotfound_json(self): + self._test_show_port_networknotfound('json') + + def test_show_port_networknotfound_xml(self): + self._test_show_port_networknotfound('xml') + + def test_show_port_portnotfound_json(self): + self._test_show_port_portnotfound('json') + + def test_show_port_portnotfound_xml(self): + self._test_show_port_portnotfound('xml') + + def test_create_port_json(self): + self._test_create_port('json') + + def test_create_port_xml(self): + self._test_create_port('xml') + + def test_create_port_networknotfound_json(self): + self._test_create_port_networknotfound('json') + + def test_create_port_networknotfound_xml(self): + self._test_create_port_networknotfound('xml') + + def test_create_port_badrequest_json(self): + self._test_create_port_badrequest('json') + + def test_create_port_badrequest_xml(self): + self._test_create_port_badrequest('xml') + + def test_delete_port_xml(self): + self._test_delete_port('xml') + + def test_delete_port_json(self): + self._test_delete_port('json') + + def test_delete_port_in_use_xml(self): + self._test_delete_port_in_use('xml') + + def test_delete_port_in_use_json(self): + self._test_delete_port_in_use('json') + + def test_delete_port_networknotfound_xml(self): + self._test_delete_port_networknotfound('xml') + + def test_delete_port_networknotfound_json(self): + self._test_delete_port_networknotfound('json') + + def test_delete_port_with_bad_id_xml(self): + self._test_delete_port_with_bad_id('xml') + + def test_delete_port_with_bad_id_json(self): + self._test_delete_port_with_bad_id('json') + + def test_set_port_state_xml(self): + self._test_set_port_state('xml') + + def test_set_port_state_json(self): + self._test_set_port_state('json') + + def test_set_port_state_networknotfound_xml(self): + self._test_set_port_state_networknotfound('xml') + + def test_set_port_state_networknotfound_json(self): + self._test_set_port_state_networknotfound('json') + + def test_set_port_state_portnotfound_xml(self): + self._test_set_port_state_portnotfound('xml') + + def test_set_port_state_portnotfound_json(self): + self._test_set_port_state_portnotfound('json') + + def test_set_port_state_stateinvalid_xml(self): + self._test_set_port_state_stateinvalid('xml') + + def test_set_port_state_stateinvalid_json(self): + self._test_set_port_state_stateinvalid('json') + + def test_show_attachment_xml(self): + self._test_show_attachment('xml') + + def test_show_attachment_json(self): + self._test_show_attachment('json') + + def test_show_attachment_networknotfound_xml(self): + self._test_show_attachment_networknotfound('xml') + + def test_show_attachment_networknotfound_json(self): + self._test_show_attachment_networknotfound('json') + + def test_show_attachment_portnotfound_xml(self): + self._test_show_attachment_portnotfound('xml') + + def test_show_attachment_portnotfound_json(self): + self._test_show_attachment_portnotfound('json') + + def test_put_attachment_xml(self): + self._test_put_attachment('xml') + + def test_put_attachment_json(self): + self._test_put_attachment('json') + + def test_put_attachment_networknotfound_xml(self): + self._test_put_attachment_networknotfound('xml') + + def test_put_attachment_networknotfound_json(self): + self._test_put_attachment_networknotfound('json') + + def test_put_attachment_portnotfound_xml(self): + self._test_put_attachment_portnotfound('xml') + + def test_put_attachment_portnotfound_json(self): + self._test_put_attachment_portnotfound('json') + + def test_delete_attachment_xml(self): + self._test_delete_attachment('xml') + + def test_delete_attachment_json(self): + self._test_delete_attachment('json') + + def test_delete_attachment_networknotfound_xml(self): + self._test_delete_attachment_networknotfound('xml') + + def test_delete_attachment_networknotfound_json(self): + self._test_delete_attachment_networknotfound('json') + + def test_delete_attachment_portnotfound_xml(self): + self._test_delete_attachment_portnotfound('xml') + + def test_delete_attachment_portnotfound_json(self): + self._test_delete_attachment_portnotfound('json') diff --git a/tests/unit/testlib_api.py b/tests/unit/testlib_api.py new file mode 100644 index 0000000000..1a731be154 --- /dev/null +++ b/tests/unit/testlib_api.py @@ -0,0 +1,130 @@ +import webob + +from quantum.common.wsgi import Serializer + + +def create_request(path, body, content_type, method='GET'): + req = webob.Request.blank(path) + req.method = method + req.headers = {} + req.headers['Accept'] = content_type + req.body = body + return req + + +def network_list_request(tenant_id, format='xml'): + method = 'GET' + path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def show_network_request(tenant_id, network_id, format='xml'): + method = 'GET' + path = "/tenants/%(tenant_id)s/networks" \ + "/%(network_id)s.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def new_network_request(tenant_id, network_name='new_name', + format='xml', custom_req_body=None): + method = 'POST' + path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals() + data = custom_req_body or {'network': {'net-name': '%s' % network_name}} + content_type = "application/%s" % format + body = Serializer().serialize(data, content_type) + return create_request(path, body, content_type, method) + + +def update_network_request(tenant_id, network_id, network_name, format='xml', + custom_req_body=None): + method = 'PUT' + path = "/tenants/%(tenant_id)s/networks" \ + "/%(network_id)s.%(format)s" % locals() + data = custom_req_body or {'network': {'net-name': '%s' % network_name}} + content_type = "application/%s" % format + body = Serializer().serialize(data, content_type) + return create_request(path, body, content_type, method) + + +def network_delete_request(tenant_id, network_id, format='xml'): + method = 'DELETE' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def port_list_request(tenant_id, network_id, format='xml'): + method = 'GET' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def show_port_request(tenant_id, network_id, port_id, format='xml'): + method = 'GET' + path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \ + "/ports/%(port_id)s.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def new_port_request(tenant_id, network_id, port_state, + format='xml', custom_req_body=None): + method = 'POST' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports.%(format)s" % locals() + data = custom_req_body or {'port': {'port-state': '%s' % port_state}} + content_type = "application/%s" % format + body = Serializer().serialize(data, content_type) + return create_request(path, body, content_type, method) + + +def port_delete_request(tenant_id, network_id, port_id, format='xml'): + method = 'DELETE' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports/%(port_id)s.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def update_port_request(tenant_id, network_id, port_id, port_state, + format='xml', custom_req_body=None): + method = 'PUT' + path = "/tenants/%(tenant_id)s/networks" \ + "/%(network_id)s/ports/%(port_id)s.%(format)s" % locals() + data = custom_req_body or {'port': {'port-state': '%s' % port_state}} + content_type = "application/%s" % format + body = Serializer().serialize(data, content_type) + return create_request(path, body, content_type, method) + + +def get_attachment_request(tenant_id, network_id, port_id, format='xml'): + method = 'GET' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) + + +def put_attachment_request(tenant_id, network_id, port_id, + attachment_id, format='xml'): + method = 'PUT' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals() + data = {'port': {'attachment-id': attachment_id}} + content_type = "application/%s" % format + body = Serializer().serialize(data, content_type) + return create_request(path, body, content_type, method) + + +def delete_attachment_request(tenant_id, network_id, port_id, + attachment_id, format='xml'): + method = 'DELETE' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals() + content_type = "application/%s" % format + return create_request(path, None, content_type, method) diff --git a/tools/batch_config.py b/tools/batch_config.py new file mode 100644 index 0000000000..63f4a5223d --- /dev/null +++ b/tools/batch_config.py @@ -0,0 +1,174 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Nicira Networks, Inc. +# +# 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, Inc. + +import httplib +import logging as LOG +import json +import socket +import sys +import urllib + +from quantum.manager import QuantumManager +from optparse import OptionParser +from quantum.common.wsgi import Serializer +from quantum.cli import MiniClient + +FORMAT = "json" +CONTENT_TYPE = "application/" + FORMAT + + +def delete_all_nets(client, tenant_id): + res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT) + resdict = json.loads(res.read()) + LOG.debug(resdict) + for n in resdict["networks"]: + nid = n["id"] + + res = client.do_request(tenant_id, 'GET', + "/networks/%s/ports.%s" % (nid, FORMAT)) + output = res.read() + if res.status != 200: + LOG.error("Failed to list ports: %s" % output) + continue + rd = json.loads(output) + LOG.debug(rd) + for port in rd["ports"]: + pid = port["id"] + + data = {'port': {'attachment-id': ''}} + body = Serializer().serialize(data, CONTENT_TYPE) + res = client.do_request(tenant_id, 'DELETE', + "/networks/%s/ports/%s/attachment.%s" % \ + (nid, pid, FORMAT), body=body) + output = res.read() + LOG.debug(output) + if res.status != 202: + LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid, + pid, output)) + continue + LOG.info("Unplugged interface from port:%s on network:%s" % (pid, + nid)) + + res = client.do_request(tenant_id, 'DELETE', + "/networks/%s/ports/%s.%s" % (nid, pid, FORMAT)) + output = res.read() + if res.status != 202: + LOG.error("Failed to delete port: %s" % output) + continue + print "Deleted Virtual Port:%s " \ + "on Virtual Network:%s" % (pid, nid) + + res = client.do_request(tenant_id, 'DELETE', + "/networks/" + nid + "." + FORMAT) + status = res.status + if status != 202: + Log.error("Failed to delete network: %s" % nid) + output = res.read() + print output + else: + print "Deleted Virtual Network with ID:%s" % nid + + +def create_net_with_attachments(net_name, iface_ids): + data = {'network': {'network-name': '%s' % net_name}} + body = Serializer().serialize(data, CONTENT_TYPE) + res = client.do_request(tenant_id, 'POST', + "/networks." + FORMAT, body=body) + rd = json.loads(res.read()) + LOG.debug(rd) + nid = rd["networks"]["network"]["id"] + print "Created a new Virtual Network %s with ID:%s" % (net_name, nid) + + for iface_id in iface_ids: + res = client.do_request(tenant_id, 'POST', + "/networks/%s/ports.%s" % (nid, FORMAT)) + output = res.read() + if res.status != 200: + LOG.error("Failed to create port: %s" % output) + continue + rd = json.loads(output) + new_port_id = rd["ports"]["port"]["id"] + print "Created Virtual Port:%s " \ + "on Virtual Network:%s" % (new_port_id, nid) + data = {'port': {'attachment-id': '%s' % iface_id}} + body = Serializer().serialize(data, CONTENT_TYPE) + res = client.do_request(tenant_id, 'PUT', + "/networks/%s/ports/%s/attachment.%s" %\ + (nid, new_port_id, FORMAT), body=body) + output = res.read() + LOG.debug(output) + if res.status != 202: + LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % \ + (iface_id, new_port_id, output)) + continue + print "Plugged interface \"%s\" to port:%s on network:%s" % \ + (iface_id, new_port_id, nid) + +if __name__ == "__main__": + usagestr = "Usage: %prog [OPTIONS] [args]\n" \ + "Example config-string: net1=instance-1,instance-2"\ + ":net2=instance-3,instance-4\n" \ + "This string would create two networks: \n" \ + "'net1' would have two ports, with iface-ids "\ + "instance-1 and instance-2 attached\n" \ + "'net2' would have two ports, with iface-ids"\ + " instance-3 and instance-4 attached\n" + parser = OptionParser(usage=usagestr) + parser.add_option("-H", "--host", dest="host", + type="string", default="127.0.0.1", help="ip address of api host") + parser.add_option("-p", "--port", dest="port", + type="int", default=9696, help="api poort") + parser.add_option("-s", "--ssl", dest="ssl", + action="store_true", default=False, help="use ssl") + parser.add_option("-v", "--verbose", dest="verbose", + action="store_true", default=False, help="turn on verbose logging") + parser.add_option("-d", "--delete", dest="delete", + action="store_true", default=False, \ + help="delete existing tenants networks") + + options, args = parser.parse_args() + + if options.verbose: + LOG.basicConfig(level=LOG.DEBUG) + else: + LOG.basicConfig(level=LOG.WARN) + + if len(args) < 1: + parser.print_help() + help() + sys.exit(1) + + nets = {} + tenant_id = args[0] + if len(args) > 1: + config_str = args[1] + for net_str in config_str.split(":"): + arr = net_str.split("=") + net_name = arr[0] + nets[net_name] = arr[1].split(",") + + print "nets: %s" % str(nets) + + client = MiniClient(options.host, options.port, options.ssl) + + if options.delete: + delete_all_nets(client, tenant_id) + + for net_name, iface_ids in nets.items(): + create_net_with_attachments(net_name, iface_ids) + + sys.exit(0)