diff --git a/bin/quantum b/bin/quantum index 0913c31c05..48abd18ae9 100755 --- a/bin/quantum +++ b/bin/quantum @@ -33,9 +33,10 @@ if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')): gettext.install('quantum', unicode=1) -from quantum import service +from quantum import service from quantum.common import config + def create_options(parser): """ Sets up the CLI and config-file options that may be @@ -58,4 +59,3 @@ if __name__ == '__main__': service.wait() except RuntimeError, e: sys.exit("ERROR: %s" % e) - diff --git a/etc/quantum.conf.test b/etc/quantum.conf.test index 3e532b04dd..b1c266246a 100644 --- a/etc/quantum.conf.test +++ b/etc/quantum.conf.test @@ -12,4 +12,4 @@ paste.app_factory = quantum.l2Network.service:app_factory bind_host = 0.0.0.0 # Port the bind the API server to -bind_port = 9696 \ No newline at end of file +bind_port = 9696 diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index 3665464c92..3bf7f113ad 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -54,8 +54,9 @@ class APIRouterV01(wsgi.Router): mapper.resource('port', 'ports', controller=ports.Controller(), parent_resource=dict(member_name='network', - collection_name=\ - uri_prefix + 'networks')) + collection_name=uri_prefix +\ + 'networks')) + mapper.connect("get_resource", uri_prefix + 'networks/{network_id}/' \ 'ports/{id}/attachment{.format}', diff --git a/quantum/api/faults.py b/quantum/api/faults.py index a10364df19..35f6c1073d 100644 --- a/quantum/api/faults.py +++ b/quantum/api/faults.py @@ -36,8 +36,7 @@ class Fault(webob.exc.HTTPException): 432: "portInUse", 440: "alreadyAttached", 470: "serviceUnavailable", - 471: "pluginFault" - } + 471: "pluginFault"} def __init__(self, exception): """Create a Fault for the given webob.exc.exception.""" @@ -52,7 +51,7 @@ class Fault(webob.exc.HTTPException): fault_data = { fault_name: { 'code': code, - 'message': self.wrapped_exc.explanation, + 'message': self.wrapped_exc.explanation, 'detail': self.wrapped_exc.detail}} # 'code' is an attribute on the fault tag itself metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} diff --git a/quantum/api/networks.py b/quantum/api/networks.py index 98dd5e305d..a24cf09ab5 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -44,63 +44,66 @@ class Controller(common.QuantumController): self._resource_name = 'network' super(Controller, self).__init__() - def index(self, req, tenant_id): + def index(self, request, tenant_id): """ Returns a list of network ids """ #TODO: this should be for a given tenant!!! - return self._items(req, tenant_id, is_detail=False) + return self._items(request, tenant_id, is_detail=False) - def _items(self, req, tenant_id, is_detail): + def _items(self, request, tenant_id, is_detail): """ Returns a list of networks. """ networks = self.network_manager.get_all_networks(tenant_id) - builder = networks_view.get_view_builder(req) + builder = networks_view.get_view_builder(request) result = [builder.build(network, is_detail)['network'] for network in networks] return dict(networks=result) - def show(self, req, tenant_id, id): + def show(self, request, tenant_id, id): """ Returns network details for the given network id """ try: network = self.network_manager.get_network_details( tenant_id, id) - builder = networks_view.get_view_builder(req) + 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)) - def create(self, req, tenant_id): + def create(self, request, tenant_id): """ Creates a new network for a given tenant """ #look for network name in request try: - req_params = \ - self._parse_request_params(req, self._network_ops_param_list) + request_params = \ + self._parse_request_params(request, + self._network_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) network = self.network_manager.\ - create_network(tenant_id,req_params['network-name']) - builder = networks_view.get_view_builder(req) + create_network(tenant_id, + request_params['network-name']) + builder = networks_view.get_view_builder(request) result = builder.build(network) return dict(networks=result) - def update(self, req, tenant_id, id): + def update(self, request, tenant_id, id): """ Updates the name for the network with the given id """ try: - req_params = \ - self._parse_request_params(req, self._network_ops_param_list) + request_params = \ + self._parse_request_params(request, + self._network_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) try: network = self.network_manager.rename_network(tenant_id, - id, req_params['network-name']) + id, request_params['network-name']) - builder = networks_view.get_view_builder(req) + builder = networks_view.get_view_builder(request) result = builder.build(network, True) return dict(networks=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) - def delete(self, req, tenant_id, id): + def delete(self, request, tenant_id, id): """ Destroys the network with the given id """ try: self.network_manager.delete_network(tenant_id, id) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index 8e01006346..c2de0d75fb 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -24,51 +24,49 @@ from quantum.common import exceptions as exception LOG = logging.getLogger('quantum.api.ports') + class Controller(common.QuantumController): """ Port API controller for Quantum API """ _port_ops_param_list = [{ 'param-name': 'port-state', 'default-value': 'DOWN', - 'required': False},] + 'required': False}, ] _attachment_ops_param_list = [{ 'param-name': 'attachment-id', - 'required': True},] + 'required': True}, ] _serialization_metadata = { "application/xml": { "attributes": { - "port": ["id","state"], - }, - }, - } + "port": ["id", "state"], }, }, } def __init__(self, plugin_conf_file=None): self._resource_name = 'port' super(Controller, self).__init__() - def index(self, req, tenant_id, network_id): + def index(self, request, tenant_id, network_id): """ Returns a list of port ids for a given network """ - return self._items(req, tenant_id, network_id, is_detail=False) + return self._items(request, tenant_id, network_id, is_detail=False) - def _items(self, req, tenant_id, network_id, is_detail): + def _items(self, request, tenant_id, network_id, is_detail): """ Returns a list of networks. """ - try : + try: ports = self.network_manager.get_all_ports(tenant_id, network_id) - builder = ports_view.get_view_builder(req) + builder = ports_view.get_view_builder(request) result = [builder.build(port, is_detail)['port'] for port in ports] return dict(ports=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) - def show(self, req, tenant_id, network_id, id): + 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( tenant_id, network_id, id) - builder = ports_view.get_view_builder(req) + builder = ports_view.get_view_builder(request) #build response with details result = builder.build(port, True) return dict(ports=result) @@ -77,19 +75,19 @@ class Controller(common.QuantumController): except exception.PortNotFound as e: return faults.Fault(faults.PortNotFound(e)) - def create(self, req, tenant_id, network_id): + def create(self, request, tenant_id, network_id): """ Creates a new port for a given network """ #look for port state in request try: - req_params = \ - self._parse_request_params(req, self._port_ops_param_list) + request_params = \ + self._parse_request_params(request, self._port_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) try: port = self.network_manager.create_port(tenant_id, - network_id, - req_params['port-state']) - builder = ports_view.get_view_builder(req) + network_id, + request_params['port-state']) + builder = ports_view.get_view_builder(request) result = builder.build(port) return dict(ports=result) except exception.NetworkNotFound as e: @@ -97,18 +95,18 @@ class Controller(common.QuantumController): except exception.StateInvalid as e: return faults.Fault(faults.RequestedStateInvalid(e)) - def update(self, req, tenant_id, network_id, id): + def update(self, request, tenant_id, network_id, id): """ Updates the state of a port for a given network """ #look for port state in request try: - req_params = \ - self._parse_request_params(req, self._port_ops_param_list) + request_params = \ + self._parse_request_params(request, self._port_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) try: - port = self.network_manager.update_port(tenant_id,network_id, id, - req_params['port-state']) - builder = ports_view.get_view_builder(req) + port = self.network_manager.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) except exception.NetworkNotFound as e: @@ -118,7 +116,7 @@ class Controller(common.QuantumController): except exception.StateInvalid as e: return faults.Fault(faults.RequestedStateInvalid(e)) - def delete(self, req, tenant_id, network_id, id): + def delete(self, request, tenant_id, network_id, id): """ Destroys the port with the given id """ #look for port state in request try: @@ -132,7 +130,7 @@ class Controller(common.QuantumController): except exception.PortInUse as e: return faults.Fault(faults.PortInUse(e)) - def get_resource(self,req,tenant_id, network_id, id): + def get_resource(self, request, tenant_id, network_id, id): try: result = self.network_manager.get_interface_details( tenant_id, network_id, id) @@ -143,19 +141,19 @@ class Controller(common.QuantumController): return faults.Fault(faults.PortNotFound(e)) #TODO - Complete implementation of these APIs - def attach_resource(self,req,tenant_id, network_id, id): - content_type = req.best_match_content_type() - print "Content type:%s" %content_type + def attach_resource(self, request, tenant_id, network_id, id): + content_type = request.best_match_content_type() + print "Content type:%s" % content_type try: - req_params = \ - self._parse_request_params(req, + request_params = \ + self._parse_request_params(request, self._attachment_ops_param_list) except exc.HTTPError as e: return faults.Fault(e) try: self.network_manager.plug_interface(tenant_id, - network_id,id, - req_params['attachment-id']) + network_id, id, + request_params['attachment-id']) return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) @@ -167,10 +165,10 @@ class Controller(common.QuantumController): return faults.Fault(faults.AlreadyAttached(e)) #TODO - Complete implementation of these APIs - def detach_resource(self,req,tenant_id, network_id, id): + def detach_resource(self, request, tenant_id, network_id, id): try: self.network_manager.unplug_interface(tenant_id, - network_id,id) + network_id, id) return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/api/views/__init__.py b/quantum/api/views/__init__.py index ea91034003..cd6a1d3e29 100644 --- a/quantum/api/views/__init__.py +++ b/quantum/api/views/__init__.py @@ -13,4 +13,4 @@ # 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: Somik Behera, Nicira Networks, Inc. \ No newline at end of file +# @author: Somik Behera, Nicira Networks, Inc. diff --git a/quantum/api/views/networks.py b/quantum/api/views/networks.py index 2a64d87f09..6630c6c39a 100644 --- a/quantum/api/views/networks.py +++ b/quantum/api/views/networks.py @@ -33,17 +33,17 @@ class ViewBuilder(object): def build(self, network_data, is_detail=False): """Generic method used to generate a network entity.""" - print "NETWORK-DATA:%s" %network_data + print "NETWORK-DATA:%s" % network_data if is_detail: network = self._build_detail(network_data) else: network = self._build_simple(network_data) return network - + def _build_simple(self, network_data): """Return a simple model of a server.""" return dict(network=dict(id=network_data['net-id'])) - + def _build_detail(self, network_data): """Return a simple model of a server.""" return dict(network=dict(id=network_data['net-id'], diff --git a/quantum/api/views/ports.py b/quantum/api/views/ports.py index 6077214b68..dabc7cf0f3 100644 --- a/quantum/api/views/ports.py +++ b/quantum/api/views/ports.py @@ -31,7 +31,7 @@ class ViewBuilder(object): def build(self, port_data, is_detail=False): """Generic method used to generate a port entity.""" - print "PORT-DATA:%s" %port_data + print "PORT-DATA:%s" % port_data if is_detail: port = self._build_detail(port_data) else: diff --git a/quantum/cli.py b/quantum/cli.py index 4c0ba4eee5..8663d548b1 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -31,25 +31,29 @@ from quantum.common.wsgi import Serializer FORMAT = "json" CONTENT_TYPE = "application/" + FORMAT + ### --- Miniclient (taking from the test directory) ### TODO(bgh): move this to a library within quantum 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): self.host = host self.port = port self.use_ssl = use_ssl self.connection = None + def get_connection_type(self): if self.use_ssl: return httplib.HTTPSConnection else: return httplib.HTTPConnection + def do_request(self, tenant, method, action, body=None, headers=None, params=None): action = MiniClient.action_prefix + action - action = action.replace('{tenant_id}',tenant) + action = action.replace('{tenant_id}', tenant) if type(params) is dict: action += '?' + urllib.urlencode(params) try: @@ -67,6 +71,7 @@ class MiniClient(object): 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): if hasattr(response, 'status_int'): return response.status_int @@ -76,6 +81,7 @@ class MiniClient(object): ### -- Core CLI functions + def list_nets(manager, *args): tenant_id = args[0] networks = manager.get_all_networks(tenant_id) @@ -85,6 +91,7 @@ def list_nets(manager, *args): name = net["net-name"] print "\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name) + def api_list_nets(client, *args): tenant_id = args[0] res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT) @@ -98,11 +105,13 @@ def api_list_nets(client, *args): # name = n["net-name"] # LOG.info("\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name)) + def create_net(manager, *args): tid, name = args new_net_id = manager.create_network(tid, name) print "Created a new Virtual Network with ID:%s\n" % new_net_id + def api_create_net(client, *args): tid, name = args data = {'network': {'network-name': '%s' % name}} @@ -119,11 +128,13 @@ def api_create_net(client, *args): return print "Created a new Virtual Network with ID:%s\n" % nid + def delete_net(manager, *args): tid, nid = args manager.delete_network(tid, nid) print "Deleted Virtual Network with ID:%s" % nid + def api_delete_net(client, *args): tid, nid = args res = client.do_request(tid, 'DELETE', "/networks/" + nid + "." + FORMAT) @@ -135,6 +146,7 @@ def api_delete_net(client, *args): else: print "Deleted Virtual Network with ID:%s" % nid + def detail_net(manager, *args): tid, nid = args iface_list = manager.get_network_details(tid, nid) @@ -142,6 +154,7 @@ def detail_net(manager, *args): for iface in iface_list: print "\tRemote interface:%s" % iface + def api_detail_net(client, *args): tid, nid = args res = client.do_request(tid, 'GET', @@ -163,11 +176,13 @@ def api_detail_net(client, *args): remote_iface = rd["attachment"] print "\tRemote interface:%s" % remote_iface + def rename_net(manager, *args): tid, nid, name = args manager.rename_network(tid, nid, name) print "Renamed Virtual Network with ID:%s" % nid + def api_rename_net(client, *args): tid, nid, name = args data = {'network': {'network-name': '%s' % name}} @@ -178,6 +193,7 @@ def api_rename_net(client, *args): LOG.debug(resdict) print "Renamed Virtual Network with ID:%s" % nid + def list_ports(manager, *args): tid, nid = args ports = manager.get_all_ports(tid, nid) @@ -185,6 +201,7 @@ def list_ports(manager, *args): for port in ports: print "\tVirtual Port:%s" % port["port-id"] + def api_list_ports(client, *args): tid, nid = args res = client.do_request(tid, 'GET', @@ -199,12 +216,14 @@ def api_list_ports(client, *args): for port in rd["ports"]: print "\tVirtual Port:%s" % port["id"] + def create_port(manager, *args): tid, nid = args new_port = manager.create_port(tid, nid) print "Created Virtual Port:%s " \ "on Virtual Network:%s" % (new_port, nid) + def api_create_port(client, *args): tid, nid = args res = client.do_request(tid, 'POST', @@ -218,11 +237,13 @@ def api_create_port(client, *args): print "Created Virtual Port:%s " \ "on Virtual Network:%s" % (new_port, nid) + def delete_port(manager, *args): tid, nid, pid = args LOG.info("Deleted Virtual Port:%s " \ "on Virtual Network:%s" % (pid, nid)) + def api_delete_port(client, *args): tid, nid, pid = args res = client.do_request(tid, 'DELETE', @@ -234,12 +255,14 @@ def api_delete_port(client, *args): LOG.info("Deleted Virtual Port:%s " \ "on Virtual Network:%s" % (pid, nid)) + def detail_port(manager, *args): tid, nid, pid = args port_detail = manager.get_port_details(tid, nid, pid) print "Virtual Port:%s on Virtual Network:%s " \ "contains remote interface:%s" % (pid, nid, port_detail) + def api_detail_port(client, *args): tid, nid, pid = args res = client.do_request(tid, 'GET', @@ -256,12 +279,14 @@ def api_detail_port(client, *args): print "Virtual Port:%s on Virtual Network:%s " \ "contains remote interface:%s" % (pid, nid, attachment) + def plug_iface(manager, *args): tid, nid, pid, vid = args manager.plug_interface(tid, nid, pid, vid) print "Plugged remote interface:%s " \ "into Virtual Network:%s" % (vid, nid) + def api_plug_iface(client, *args): tid, nid, pid, vid = args data = {'port': {'attachment-id': '%s' % vid}} @@ -276,12 +301,14 @@ def api_plug_iface(client, *args): return print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, nid) -def unplug_iface(manager, *args): + +def unplug_iface(manager, *args): tid, nid, pid = args manager.unplug_interface(tid, nid, pid) print "UnPlugged remote interface " \ "from Virtual Port:%s Virtual Network:%s" % (pid, nid) + def api_unplug_iface(client, *args): tid, nid, pid = args data = {'port': {'attachment-id': ''}} @@ -296,63 +323,53 @@ def api_unplug_iface(client, *args): return print "Unplugged interface from port:%s on network:%s" % (pid, nid) + commands = { "list_nets": { "func": list_nets, "api_func": api_list_nets, - "args": ["tenant-id"] - }, + "args": ["tenant-id"]}, "create_net": { "func": create_net, "api_func": api_create_net, - "args": ["tenant-id", "net-name"] - }, + "args": ["tenant-id", "net-name"]}, "delete_net": { "func": delete_net, "api_func": api_delete_net, - "args": ["tenant-id", "net-id"] - }, + "args": ["tenant-id", "net-id"]}, "detail_net": { "func": detail_net, "api_func": api_detail_net, - "args": ["tenant-id", "net-id"] - }, + "args": ["tenant-id", "net-id"]}, "rename_net": { "func": rename_net, "api_func": api_rename_net, - "args": ["tenant-id", "net-id", "new-name"] - }, + "args": ["tenant-id", "net-id", "new-name"]}, "list_ports": { "func": list_ports, "api_func": api_list_ports, - "args": ["tenant-id", "net-id"] - }, + "args": ["tenant-id", "net-id"]}, "create_port": { "func": create_port, "api_func": api_create_port, - "args": ["tenant-id", "net-id"] - }, + "args": ["tenant-id", "net-id"]}, "delete_port": { "func": delete_port, "api_func": api_delete_port, - "args": ["tenant-id", "net-id", "port-id"] - }, + "args": ["tenant-id", "net-id", "port-id"]}, "detail_port": { "func": detail_port, "api_func": api_detail_port, - "args": ["tenant-id", "net-id", "port-id"] - }, + "args": ["tenant-id", "net-id", "port-id"]}, "plug_iface": { "func": plug_iface, "api_func": api_plug_iface, - "args": ["tenant-id", "net-id", "port-id", "iface-id"] - }, + "args": ["tenant-id", "net-id", "port-id", "iface-id"]}, "unplug_iface": { "func": unplug_iface, "api_func": api_unplug_iface, - "args": ["tenant-id", "net-id", "port-id"] - }, - } + "args": ["tenant-id", "net-id", "port-id"]}, } + def help(): print "\nCommands:" @@ -360,6 +377,7 @@ def help(): print " %s %s" % (k, " ".join(["<%s>" % y for y in commands[k]["args"]])) + def build_args(cmd, cmdargs, arglist): args = [] orig_arglist = arglist[:] @@ -381,6 +399,7 @@ def build_args(cmd, cmdargs, arglist): return None return args + if __name__ == "__main__": usagestr = "Usage: %prog [OPTIONS] [args]" parser = OptionParser(usage=usagestr) @@ -420,7 +439,7 @@ if __name__ == "__main__": LOG.debug("Executing command \"%s\" with args: %s" % (cmd, args)) if not options.load_plugin: client = MiniClient(options.host, options.port, options.ssl) - if not commands[cmd].has_key("api_func"): + if "api_func" not in commands[cmd]: LOG.error("API version of \"%s\" is not yet implemented" % cmd) sys.exit(1) commands[cmd]["api_func"](client, *args) diff --git a/quantum/common/config.py b/quantum/common/config.py index cda7650781..f2daa417bb 100644 --- a/quantum/common/config.py +++ b/quantum/common/config.py @@ -33,6 +33,7 @@ from paste import deploy from quantum.common import flags from quantum.common import exceptions as exception +from quantum.common import extensions DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" @@ -209,7 +210,7 @@ def find_config_file(options, args): fix_path(os.path.join('~', '.quantum')), fix_path('~'), os.path.join(FLAGS.state_path, 'etc'), - os.path.join(FLAGS.state_path, 'etc','quantum'), + os.path.join(FLAGS.state_path, 'etc', 'quantum'), '/etc/quantum/', '/etc'] for cfg_dir in config_file_dirs: @@ -244,12 +245,10 @@ def load_paste_config(app_name, options, args): problem loading the configuration file. """ conf_file = find_config_file(options, args) - print "Conf_file:%s" %conf_file if not conf_file: raise RuntimeError("Unable to locate any configuration file. " "Cannot load application %s" % app_name) try: - print "App_name:%s" %app_name conf = deploy.appconfig("config:%s" % conf_file, name=app_name) return conf_file, conf except Exception, e: @@ -257,7 +256,7 @@ def load_paste_config(app_name, options, args): % (conf_file, e)) -def load_paste_app(conf_file, app_name): +def load_paste_app(app_name, options, args): """ Builds and returns a WSGI app from a paste config file. @@ -278,16 +277,15 @@ def load_paste_app(conf_file, app_name): :raises RuntimeError when config file cannot be located or application cannot be loaded from config file """ - #conf_file, conf = load_paste_config(app_name, options, args) + conf_file, conf = load_paste_config(app_name, options, args) try: - conf_file = os.path.abspath(conf_file) app = deploy.loadapp("config:%s" % conf_file, name=app_name) except (LookupError, ImportError), e: raise RuntimeError("Unable to load %(app_name)s from " "configuration file %(conf_file)s." "\nGot: %(e)r" % locals()) - return app + return conf, app def get_option(options, option, **kwargs): diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index 7b9784b921..4daf31762b 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -25,7 +25,7 @@ import logging class QuantumException(Exception): """Base Quantum Exception - + Taken from nova.exception.NovaException To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd @@ -45,6 +45,7 @@ class QuantumException(Exception): def __str__(self): return self._error_string + class ProcessExecutionError(IOError): def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, description=None): @@ -84,11 +85,11 @@ class NetworkNotFound(NotFound): class PortNotFound(NotFound): message = _("Port %(port_id)s could not be found " \ "on network %(net_id)s") - + class StateInvalid(QuantumException): message = _("Unsupported port state: %(port_state)s") - + class NetworkInUse(QuantumException): message = _("Unable to complete operation on network %(net_id)s. " \ @@ -100,11 +101,13 @@ class PortInUse(QuantumException): "for network %(net_id)s. The attachment '%(att_id)s" \ "is plugged into the logical port.") + class AlreadyAttached(QuantumException): message = _("Unable to plug the attachment %(att_id)s into port " \ "%(port_id)s for network %(net_id)s. The attachment is " \ "already plugged into port %(att_port_id)s") - + + class Duplicate(Error): pass diff --git a/quantum/common/flags.py b/quantum/common/flags.py index 947999d0f2..5adf4c3854 100644 --- a/quantum/common/flags.py +++ b/quantum/common/flags.py @@ -23,7 +23,7 @@ Global flags should be defined here, the rest are defined where they're used. """ import getopt -import os +import os import string import sys @@ -249,4 +249,3 @@ def DECLARE(name, module_string, flag_values=FLAGS): DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../../'), "Top-level directory for maintaining quantum's state") - diff --git a/quantum/common/utils.py b/quantum/common/utils.py index 7ab4615b2c..3a2455963f 100644 --- a/quantum/common/utils.py +++ b/quantum/common/utils.py @@ -37,6 +37,7 @@ from exceptions import ProcessExecutionError TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" FLAGS = flags.FLAGS + def int_from_bool_as_string(subject): """ Interpret a string as a boolean and return either 1 or 0. @@ -188,6 +189,7 @@ def isotime(at=None): def parse_isotime(timestr): return datetime.datetime.strptime(timestr, TIME_FORMAT) + def getPluginFromConfig(file="config.ini"): Config = ConfigParser.ConfigParser() Config.read(file) diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index 4caeab9abc..23e7c1c3d0 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -40,6 +40,7 @@ from quantum.common import exceptions as exception LOG = logging.getLogger('quantum.common.wsgi') + class WritableLogger(object): """A thin wrapper that responds to `write` and logs.""" @@ -126,7 +127,7 @@ class Request(webob.Request): """ parts = self.path.rsplit('.', 1) - LOG.debug("Request parts:%s",parts) + LOG.debug("Request parts:%s", parts) if len(parts) > 1: format = parts[1] if format in ['json', 'xml']: @@ -134,7 +135,7 @@ class Request(webob.Request): ctypes = ['application/json', 'application/xml'] bm = self.accept.best_match(ctypes) - LOG.debug("BM:%s",bm) + LOG.debug("BM:%s", bm) return bm or 'application/json' def get_content_type(self): @@ -336,21 +337,21 @@ class Controller(object): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) - LOG.debug("ARG_DICT:%s",arg_dict) - LOG.debug("Action:%s",action) - LOG.debug("Method:%s",method) + LOG.debug("ARG_DICT:%s", arg_dict) + LOG.debug("Action:%s", action) + LOG.debug("Method:%s", method) LOG.debug("%s %s" % (req.method, req.url)) del arg_dict['controller'] del arg_dict['action'] if 'format' in arg_dict: del arg_dict['format'] - arg_dict['req'] = req + arg_dict['request'] = req result = method(**arg_dict) if type(result) is dict: content_type = req.best_match_content_type() - LOG.debug("Content type:%s",content_type) - LOG.debug("Result:%s",result) + LOG.debug("Content type:%s", content_type) + LOG.debug("Result:%s", result) default_xmlns = self.get_default_xmlns(req) body = self._serialize(result, content_type, default_xmlns) @@ -497,7 +498,7 @@ class Serializer(object): xmlns = metadata.get('xmlns', None) if xmlns: result.setAttribute('xmlns', xmlns) - LOG.debug("DATA:%s",data) + LOG.debug("DATA:%s", data) if type(data) is list: LOG.debug("TYPE IS LIST") collections = metadata.get('list_collections', {}) @@ -538,8 +539,7 @@ class Serializer(object): result.appendChild(node) else: # Type is atom - LOG.debug("TYPE IS ATOM:%s",data) + LOG.debug("TYPE IS ATOM:%s", data) node = doc.createTextNode(str(data)) result.appendChild(node) return result - diff --git a/quantum/db/api.py b/quantum/db/api.py index 8a6ba305cc..2a296f2f07 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -25,6 +25,7 @@ _ENGINE = None _MAKER = None BASE = models.BASE + def configure_db(options): """ Establish the database, create an engine if needed, and @@ -40,6 +41,7 @@ def configure_db(options): pool_recycle=3600) register_models() + def get_session(autocommit=True, expire_on_commit=False): """Helper method to grab session""" global _MAKER, _ENGINE @@ -50,18 +52,21 @@ def get_session(autocommit=True, expire_on_commit=False): expire_on_commit=expire_on_commit) return _MAKER() + def register_models(): """Register Models and create properties""" global _ENGINE assert _ENGINE BASE.metadata.create_all(_ENGINE) + def unregister_models(): """Unregister Models, useful clearing out data before testing""" global _ENGINE assert _ENGINE BASE.metadata.drop_all(_ENGINE) + def network_create(tenant_id, name): session = get_session() net = None @@ -77,12 +82,14 @@ def network_create(tenant_id, name): session.flush() return net + def network_list(tenant_id): session = get_session() return session.query(models.Network).\ filter_by(tenant_id=tenant_id).\ all() + def network_get(net_id): session = get_session() try: @@ -92,6 +99,7 @@ def network_get(net_id): except exc.NoResultFound: raise Exception("No net found with id = %s" % net_id) + def network_rename(net_id, tenant_id, new_name): session = get_session() try: @@ -106,6 +114,7 @@ def network_rename(net_id, tenant_id, new_name): return net raise Exception("A network with name \"%s\" already exists" % new_name) + def network_destroy(net_id): session = get_session() try: @@ -118,6 +127,7 @@ def network_destroy(net_id): except exc.NoResultFound: raise Exception("No network found with id = %s" % net_id) + def port_create(net_id): session = get_session() with session.begin(): @@ -126,12 +136,14 @@ def port_create(net_id): session.flush() return port + def port_list(net_id): session = get_session() return session.query(models.Port).\ filter_by(network_id=net_id).\ all() + def port_get(port_id): session = get_session() try: @@ -141,6 +153,7 @@ def port_get(port_id): except exc.NoResultFound: raise Exception("No port found with id = %s " % port_id) + def port_set_attachment(port_id, new_interface_id): session = get_session() ports = None @@ -157,7 +170,9 @@ def port_set_attachment(port_id, new_interface_id): session.flush() return port else: - raise Exception("Port with attachment \"%s\" already exists" % (new_interface_id)) + raise Exception("Port with attachment \"%s\" already exists" + % (new_interface_id)) + def port_destroy(port_id): session = get_session() @@ -170,4 +185,3 @@ def port_destroy(port_id): return port except exc.NoResultFound: raise Exception("No port found with id = %s " % port_id) - diff --git a/quantum/db/models.py b/quantum/db/models.py index 28bc139b91..115ab282da 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -25,12 +25,14 @@ from sqlalchemy.orm import relation BASE = declarative_base() + class Port(BASE): """Represents a port on a quantum network""" __tablename__ = 'ports' uuid = Column(String(255), primary_key=True) - network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False) + network_id = Column(String(255), ForeignKey("networks.uuid"), + nullable=False) interface_id = Column(String(255)) def __init__(self, network_id): @@ -38,7 +40,9 @@ class Port(BASE): self.network_id = network_id def __repr__(self): - return "" % (self.uuid, self.network_id, self.interface_id) + return "" % (self.uuid, self.network_id, + self.interface_id) + class Network(BASE): """Represents a quantum network""" @@ -56,4 +60,4 @@ class Network(BASE): def __repr__(self): return "" % \ - (self.uuid,self.name,self.tenant_id) + (self.uuid, self.name, self.tenant_id) diff --git a/quantum/manager.py b/quantum/manager.py index 9932f17af0..a9662d8eb6 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -18,12 +18,14 @@ """ -Quantum's Manager class is responsible for parsing a config file and instantiating the correct -plugin that concretely implement quantum_plugin_base class +Quantum's Manager class is responsible for parsing a config file and +instantiating the correct plugin that concretely implement quantum_plugin_base +class. The caller should make sure that QuantumManager is a singleton. """ import gettext +import os gettext.install('quantum', unicode=1) import os @@ -33,16 +35,19 @@ from quantum_plugin_base import QuantumPluginBase CONFIG_FILE = "plugins.ini" + def find_config(basepath): for root, dirs, files in os.walk(basepath): if CONFIG_FILE in files: return os.path.join(root, CONFIG_FILE) return None + class QuantumManager(object): def __init__(self, config=None): if config == None: - self.configuration_file = find_config(os.path.abspath(os.path.dirname(__file__))) + self.configuration_file = find_config( + os.path.abspath(os.path.dirname(__file__))) else: self.configuration_file = config plugin_location = utils.getPluginFromConfig(self.configuration_file) @@ -58,4 +63,3 @@ class QuantumManager(object): def get_manager(self): return self.plugin - diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index f82397ebbb..376456a6c8 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -17,33 +17,32 @@ from quantum.common import exceptions as exc + class QuantumEchoPlugin(object): """ QuantumEchoPlugin is a demo plugin that doesn't do anything but demonstrated the concept of a concrete Quantum Plugin. Any call to this plugin - will result in just a "print" to std. out with + will result in just a "print" to std. out with the name of the method that was called. """ - + def get_all_networks(self, tenant_id): """ Returns a dictionary containing all for - the specified tenant. + the specified tenant. """ print("get_all_networks() called\n") - - + def create_network(self, tenant_id, net_name): """ Creates a new Virtual Network, and assigns it a symbolic name. """ print("create_network() called\n") - - + def delete_network(self, tenant_id, net_id): """ Deletes the network with the specified network identifier @@ -51,38 +50,33 @@ class QuantumEchoPlugin(object): """ print("delete_network() called\n") - def get_network_details(self, tenant_id, net_id): """ Deletes the Virtual Network belonging to a the spec """ print("get_network_details() called\n") - - + def rename_network(self, tenant_id, net_id, new_name): """ Updates the symbolic name belonging to a particular Virtual Network. """ print("rename_network() called\n") - - + def get_all_ports(self, tenant_id, net_id): """ Retrieves all port identifiers belonging to the specified Virtual Network. """ print("get_all_ports() called\n") - - + def create_port(self, tenant_id, net_id): """ Creates a port on the specified Virtual Network. """ print("create_port() called\n") - - + def delete_port(self, tenant_id, net_id, port_id): """ Deletes a port on a specified Virtual Network, @@ -97,24 +91,21 @@ class QuantumEchoPlugin(object): Updates the state of a port on the specified Virtual Network. """ print("update_port() called\n") - - + 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") - - + 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") - - + def unplug_interface(self, tenant_id, net_id, port_id): """ Detaches a remote interface from the specified port on the @@ -130,18 +121,17 @@ class DummyDataPlugin(object): hard-coded data structures to aid in quantum client/cli development """ - + def get_all_networks(self, tenant_id): """ Returns a dictionary containing all for - the specified tenant. + the specified tenant. """ - nets = {"001": "lNet1", "002": "lNet2" , "003": "lNet3"} + nets = {"001": "lNet1", "002": "lNet2", "003": "lNet3"} print("get_all_networks() called\n") return nets - - + def create_network(self, tenant_id, net_name): """ Creates a new Virtual Network, and assigns it @@ -150,8 +140,7 @@ class DummyDataPlugin(object): print("create_network() called\n") # return network_id of the created network return 101 - - + def delete_network(self, tenant_id, net_id): """ Deletes the network with the specified network identifier @@ -159,7 +148,6 @@ class DummyDataPlugin(object): """ print("delete_network() called\n") - def get_network_details(self, tenant_id, net_id): """ retrieved a list of all the remote vifs that @@ -168,16 +156,14 @@ class DummyDataPlugin(object): print("get_network_details() called\n") vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0"] return vifs_on_net - - + def rename_network(self, tenant_id, net_id, new_name): """ Updates the symbolic name belonging to a particular Virtual Network. """ print("rename_network() called\n") - - + def get_all_ports(self, tenant_id, net_id): """ Retrieves all port identifiers belonging to the @@ -186,8 +172,7 @@ class DummyDataPlugin(object): print("get_all_ports() called\n") port_ids_on_net = ["2", "3", "4"] return port_ids_on_net - - + def create_port(self, tenant_id, net_id): """ Creates a port on the specified Virtual Network. @@ -201,8 +186,7 @@ class DummyDataPlugin(object): Updates the state of a port on the specified Virtual Network. """ print("update_port() called\n") - - + def delete_port(self, tenant_id, net_id, port_id): """ Deletes a port on a specified Virtual Network, @@ -211,8 +195,7 @@ class DummyDataPlugin(object): is deleted. """ print("delete_port() called\n") - - + def get_port_details(self, tenant_id, net_id, port_id): """ This method allows the user to retrieve a remote interface @@ -221,24 +204,22 @@ class DummyDataPlugin(object): print("get_port_details() called\n") #returns the remote interface UUID return "/tenant1/networks/net_id/portid/vif2.1" - - + 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") - - + 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") - - + + class FakePlugin(object): """ FakePlugin is a demo plugin that provides @@ -248,72 +229,66 @@ class FakePlugin(object): #static data for networks and ports _port_dict_1 = { - 1 : {'port-id': 1, + 1: {'port-id': 1, 'port-state': 'DOWN', 'attachment': None}, - 2 : {'port-id': 2, - 'port-state':'UP', - 'attachment': None} - } + 2: {'port-id': 2, + 'port-state': 'UP', + 'attachment': None}} _port_dict_2 = { - 1 : {'port-id': 1, + 1: {'port-id': 1, 'port-state': 'UP', 'attachment': 'SomeFormOfVIFID'}, - 2 : {'port-id': 2, - 'port-state':'DOWN', - 'attachment': None} - } - _networks={'001': + 2: {'port-id': 2, + 'port-state': 'DOWN', + 'attachment': None}} + _networks = {'001': { - 'net-id':'001', - 'net-name':'pippotest', - 'net-ports': _port_dict_1 - }, + 'net-id': '001', + 'net-name': 'pippotest', + 'net-ports': _port_dict_1}, '002': { - 'net-id':'002', - 'net-name':'cicciotest', - 'net-ports': _port_dict_2 - }} - - + 'net-id': '002', + 'net-name': 'cicciotest', + 'net-ports': _port_dict_2}} + def __init__(self): - FakePlugin._net_counter=len(FakePlugin._networks) - + FakePlugin._net_counter = len(FakePlugin._networks) + def _get_network(self, tenant_id, network_id): network = FakePlugin._networks.get(network_id) if not network: 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: 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 ('UP', '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: - raise exc.AlreadyAttached(net_id = network_id, - port_id = port_id, - att_id = port['attachment'], - att_port_id = port['port-id']) - + raise exc.AlreadyAttached(net_id=network_id, + port_id=port_id, + att_id=port['attachment'], + att_port_id=port['port-id']) + def get_all_networks(self, tenant_id): """ Returns a dictionary containing all for - the specified tenant. + the specified tenant. """ print("get_all_networks() called\n") return FakePlugin._networks.values() @@ -333,16 +308,16 @@ class FakePlugin(object): """ print("create_network() called\n") FakePlugin._net_counter += 1 - new_net_id=("0" * (3 - len(str(FakePlugin._net_counter)))) + \ + 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, + new_net_dict = {'net-id': new_net_id, + 'net-name': net_name, 'net-ports': {}} - FakePlugin._networks[new_net_id]=new_net_dict + FakePlugin._networks[new_net_id] = new_net_dict # return network_id of the created network return new_net_dict - + def delete_network(self, tenant_id, net_id): """ Deletes the network with the specified network identifier @@ -360,7 +335,7 @@ class FakePlugin(object): return net # Network not found raise exc.NetworkNotFound(net_id=net_id) - + def rename_network(self, tenant_id, net_id, new_name): """ Updates the symbolic name belonging to a particular @@ -368,7 +343,7 @@ class FakePlugin(object): """ print("rename_network() called\n") net = self._get_network(tenant_id, net_id) - net['net-name']=new_name + net['net-name'] = new_name return net def get_all_ports(self, tenant_id, net_id): @@ -388,7 +363,7 @@ class FakePlugin(object): """ print("get_port_details() called\n") return self._get_port(tenant_id, net_id, port_id) - + def create_port(self, tenant_id, net_id, port_state=None): """ Creates a port on the specified Virtual Network. @@ -396,15 +371,15 @@ class FakePlugin(object): print("create_port() called\n") net = self._get_network(tenant_id, net_id) # check port state - # TODO(salvatore-orlando): Validate port state in API? + # 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, + 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 + return new_port_dict def update_port(self, tenant_id, net_id, port_id, port_state): """ @@ -414,8 +389,8 @@ class FakePlugin(object): port = self._get_port(tenant_id, net_id, port_id) self._validate_port_state(port_state) port['port-state'] = port_state - return port - + return port + def delete_port(self, tenant_id, net_id, port_id): """ Deletes a port on a specified Virtual Network, @@ -427,14 +402,13 @@ class FakePlugin(object): net = self._get_network(tenant_id, net_id) port = self._get_port(tenant_id, net_id, port_id) if port['attachment']: - raise exc.PortInUse(net_id=net_id,port_id=port_id, + raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port['attachment']) try: net['net-ports'].pop(int(port_id)) - except KeyError: + except KeyError: raise exc.PortNotFound(net_id=net_id, port_id=port_id) - def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): """ Attaches a remote interface to the specified port on the @@ -446,10 +420,10 @@ class FakePlugin(object): remote_interface_id) port = self._get_port(tenant_id, net_id, port_id) if port['attachment']: - raise exc.PortInUse(net_id=net_id,port_id=port_id, + raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port['attachment']) port['attachment'] = remote_interface_id - + def unplug_interface(self, tenant_id, net_id, port_id): """ Detaches a remote interface from the specified port on the @@ -460,5 +434,3 @@ class FakePlugin(object): # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? port['attachment'] = None - - \ No newline at end of file diff --git a/quantum/plugins/__init__.py b/quantum/plugins/__init__.py index df928bbf1c..7e695ff08d 100644 --- a/quantum/plugins/__init__.py +++ b/quantum/plugins/__init__.py @@ -13,4 +13,4 @@ # 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: Somik Behera, Nicira Networks, Inc. \ No newline at end of file +# @author: Somik Behera, Nicira Networks, Inc. diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index 34f28fbdd5..a5cd7948d7 100755 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -28,6 +28,7 @@ import time from optparse import OptionParser from subprocess import * + # A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac' # attributes set). class VifPort: @@ -37,11 +38,13 @@ class VifPort: self.vif_id = vif_id self.vif_mac = vif_mac self.switch = switch + def __str__(self): return "iface-id=" + self.vif_id + ", vif_mac=" + \ self.vif_mac + ", port_name=" + self.port_name + \ ", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name + class OVSBridge: def __init__(self, br_name): self.br_name = br_name @@ -51,27 +54,27 @@ class OVSBridge: return Popen(args, stdout=PIPE).communicate()[0] def run_vsctl(self, args): - full_args = ["ovs-vsctl" ] + args + full_args = ["ovs-vsctl"] + args return self.run_cmd(full_args) def reset_bridge(self): - self.run_vsctl([ "--" , "--if-exists", "del-br", self.br_name]) + self.run_vsctl(["--", "--if-exists", "del-br", self.br_name]) self.run_vsctl(["add-br", self.br_name]) def delete_port(self, port_name): - self.run_vsctl([ "--" , "--if-exists", "del-port", self.br_name, + self.run_vsctl(["--", "--if-exists", "del-port", self.br_name, port_name]) def set_db_attribute(self, table_name, record, column, value): - args = [ "set", table_name, record, "%s=%s" % (column,value) ] + args = ["set", table_name, record, "%s=%s" % (column, value)] self.run_vsctl(args) - def clear_db_attribute(self, table_name,record, column): - args = [ "clear", table_name, record, column ] + def clear_db_attribute(self, table_name, record, column): + args = ["clear", table_name, record, column] self.run_vsctl(args) def run_ofctl(self, cmd, args): - full_args = ["ovs-ofctl", cmd, self.br_name ] + args + full_args = ["ovs-ofctl", cmd, self.br_name] + args return self.run_cmd(full_args) def remove_all_flows(self): @@ -80,7 +83,7 @@ class OVSBridge: def get_port_ofport(self, port_name): return self.db_get_val("Interface", port_name, "ofport") - def add_flow(self,**dict): + def add_flow(self, **dict): if "actions" not in dict: raise Exception("must specify one or more actions") if "priority" not in dict: @@ -90,9 +93,9 @@ class OVSBridge: if "match" in dict: flow_str += "," + dict["match"] flow_str += ",actions=%s" % (dict["actions"]) - self.run_ofctl("add-flow", [ flow_str ] ) + self.run_ofctl("add-flow", [flow_str]) - def delete_flows(self,**dict): + def delete_flows(self, **dict): all_args = [] if "priority" in dict: all_args.append("priority=%s" % dict["priority"]) @@ -101,14 +104,14 @@ class OVSBridge: if "actions" in dict: all_args.append("actions=%s" % (dict["actions"])) flow_str = ",".join(all_args) - self.run_ofctl("del-flows", [ flow_str ] ) + self.run_ofctl("del-flows", [flow_str]) def db_get_map(self, table, record, column): - str = self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r") + str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r") return self.db_str_to_map(str) def db_get_val(self, table, record, column): - return self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r") + return self.run_vsctl(["get", table, record, column]).rstrip("\n\r") def db_str_to_map(self, full_str): list = full_str.strip("{}").split(", ") @@ -121,7 +124,7 @@ class OVSBridge: return ret def get_port_name_list(self): - res = self.run_vsctl([ "list-ports", self.br_name]) + res = self.run_vsctl(["list-ports", self.br_name]) return res.split("\n")[0:-1] def get_port_stats(self, port_name): @@ -132,47 +135,53 @@ class OVSBridge: edge_ports = [] port_names = self.get_port_name_list() for name in port_names: - external_ids = self.db_get_map("Interface",name,"external_ids") + 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") + 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") + 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 = 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)) + 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") + "external-ids:iface-id", res) + ofport = self.db_get_val("Interface", name, "ofport") p = VifPort(name, ofport, external_ids["iface-id"], - external_ids["attached-mac"], self) + external_ids["attached-mac"], self) edge_ports.append(p) return edge_ports + class OVSNaaSPlugin: 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", + self.int_br.set_db_attribute("Port", port.port_name, "tag", str(vlan_id)) def port_unbound(self, port, still_exists): if still_exists: - self.int_br.clear_db_attribute("Port", port.port_name,"tag") + self.int_br.clear_db_attribute("Port", port.port_name, "tag") def setup_integration_br(self, integ_br): self.int_br = OVSBridge(integ_br) @@ -182,7 +191,8 @@ class OVSNaaSPlugin: # switch all other 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") + #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 = {} @@ -216,9 +226,9 @@ class OVSNaaSPlugin: else: # no binding, put him on the 'dead vlan' self.int_br.set_db_attribute("Port", p.port_name, "tag", - "4095") - old_b = old_local_bindings.get(p.vif_id,None) - new_b = new_local_bindings.get(p.vif_id,None) + "4095") + 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" diff --git a/quantum/plugins/openvswitch/ovs_db.py b/quantum/plugins/openvswitch/ovs_db.py index a2a72ec8ce..d72f9a89ce 100644 --- a/quantum/plugins/openvswitch/ovs_db.py +++ b/quantum/plugins/openvswitch/ovs_db.py @@ -24,6 +24,7 @@ import quantum.db.api as db import quantum.db.models as models import ovs_models + def get_vlans(): session = db.get_session() try: @@ -33,9 +34,10 @@ def get_vlans(): return [] res = [] for x in bindings: - res.append((x.vlan_id, x.network_id)) + res.append((x.vlan_id, x.network_id)) return res + def add_vlan_binding(vlanid, netid): session = db.get_session() binding = ovs_models.VlanBinding(vlanid, netid) @@ -43,6 +45,7 @@ def add_vlan_binding(vlanid, netid): session.flush() return binding.vlan_id + def remove_vlan_binding(netid): session = db.get_session() try: @@ -54,6 +57,7 @@ def remove_vlan_binding(netid): pass session.flush() + def update_network_binding(netid, ifaceid): session = db.get_session() # Add to or delete from the bindings table diff --git a/quantum/plugins/openvswitch/ovs_models.py b/quantum/plugins/openvswitch/ovs_models.py index 610902a7ca..5f529e55a3 100644 --- a/quantum/plugins/openvswitch/ovs_models.py +++ b/quantum/plugins/openvswitch/ovs_models.py @@ -23,9 +23,9 @@ import uuid from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.ext.declarative import declarative_base 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' @@ -42,6 +42,7 @@ class NetworkBinding(BASE): 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.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 75619cae62..b5d1bc68d1 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -29,24 +29,29 @@ from optparse import OptionParser import quantum.db.api as db import ovs_db -CONF_FILE="ovs_quantum_plugin.ini" +CONF_FILE = "ovs_quantum_plugin.ini" LOG.basicConfig(level=LOG.WARN) LOG.getLogger("ovs_quantum_plugin") + def find_config(basepath): for root, dirs, files in os.walk(basepath): if CONF_FILE in files: return os.path.join(root, CONF_FILE) return None + class VlanMap(object): vlans = {} + def __init__(self): for x in xrange(2, 4094): self.vlans[x] = None + def set(self, vlan_id, network_id): self.vlans[vlan_id] = network_id + def acquire(self, network_id): for x in xrange(2, 4094): if self.vlans[x] == None: @@ -54,8 +59,10 @@ class VlanMap(object): # LOG.debug("VlanMap::acquire %s -> %s" % (x, network_id)) return x raise Exception("No free vlans..") + def get(self, vlan_id): return self.vlans[vlan_id] + def release(self, network_id): for x in self.vlans.keys(): if self.vlans[x] == network_id: @@ -64,14 +71,17 @@ class VlanMap(object): return LOG.error("No vlan found with network \"%s\"" % network_id) + class OVSQuantumPlugin(QuantumPluginBase): + def __init__(self, configfile=None): config = ConfigParser.ConfigParser() if configfile == None: if os.path.exists(CONF_FILE): configfile = CONF_FILE else: - configfile = find_config(os.path.abspath(os.path.dirname(__file__))) + configfile = find_config(os.path.abspath( + os.path.dirname(__file__))) if configfile == None: raise Exception("Configuration file \"%s\" doesn't exist" % (configfile)) @@ -93,7 +103,8 @@ class OVSQuantumPlugin(QuantumPluginBase): vlans = ovs_db.get_vlans() for x in vlans: vlan_id, network_id = x - # LOG.debug("Adding already populated vlan %s -> %s" % (vlan_id, network_id)) + # LOG.debug("Adding already populated vlan %s -> %s" + # % (vlan_id, network_id)) self.vmap.set(vlan_id, network_id) def get_all_networks(self, tenant_id): @@ -109,8 +120,8 @@ class OVSQuantumPlugin(QuantumPluginBase): def create_network(self, tenant_id, net_name): d = {} try: - res = db.network_create(tenant_id, net_name) - LOG.debug("Created newtork: %s" % res) + res = db.network_create(tenant_id, net_name) + LOG.debug("Created newtork: %s" % res) except Exception, e: LOG.error("Error: %s" % str(e)) return d @@ -199,21 +210,28 @@ class OVSQuantumPlugin(QuantumPluginBase): res = db.port_get(port_id) return res.interface_id + class VlanMapTest(unittest.TestCase): + def setUp(self): self.vmap = VlanMap() + def tearDown(self): pass + def testAddVlan(self): vlan_id = self.vmap.acquire("foobar") self.assertTrue(vlan_id == 2) + def testReleaseVlan(self): vlan_id = self.vmap.acquire("foobar") self.vmap.release("foobar") self.assertTrue(self.vmap.get(vlan_id) == None) + # TODO(bgh): Make the tests use a sqlite database instead of mysql class OVSPluginTest(unittest.TestCase): + def setUp(self): self.quantum = OVSQuantumPlugin() self.tenant_id = "testtenant" @@ -312,6 +330,7 @@ class OVSPluginTest(unittest.TestCase): self.quantum.delete_port(self.tenant_id, id, p["port-id"]) self.quantum.delete_network(self.tenant_id, id) + if __name__ == "__main__": usagestr = "Usage: %prog [OPTIONS] [args]" parser = OptionParser(usage=usagestr) diff --git a/tests/functional/miniclient.py b/tests/functional/miniclient.py index fb1ebc8fec..be49867355 100644 --- a/tests/functional/miniclient.py +++ b/tests/functional/miniclient.py @@ -19,6 +19,7 @@ import httplib import socket import urllib + class MiniClient(object): """A base client class - derived from Glance.BaseClient""" @@ -50,7 +51,7 @@ class MiniClient(object): def do_request(self, tenant, method, action, body=None, headers=None, params=None): """ - Connects to the server and issues a request. + Connects to the server and issues a request. Returns the result data, or raises an appropriate exception if HTTP status code is not 2xx @@ -62,14 +63,14 @@ class MiniClient(object): """ action = MiniClient.action_prefix + action - action = action.replace('{tenant_id}',tenant) + 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) @@ -95,4 +96,4 @@ class MiniClient(object): if hasattr(response, 'status_int'): return response.status_int else: - return response.status \ No newline at end of file + return response.status diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py index f9cd77418b..6ed728394a 100644 --- a/tests/functional/test_service.py +++ b/tests/functional/test_service.py @@ -34,16 +34,18 @@ TENANT_ID = 'totore' FORMAT = "json" test_network1_data = \ - {'network': {'network-name': 'test1' }} + {'network': {'network-name': 'test1'}} test_network2_data = \ - {'network': {'network-name': 'test2' }} + {'network': {'network-name': 'test2'}} + def print_response(res): content = res.read() - print "Status: %s" %res.status - print "Content: %s" %content + 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) @@ -58,7 +60,7 @@ class QuantumTest(unittest.TestCase): 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) + res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) self.assertEqual(res.status, 200, "bad response: %s" % res.read()) def test_createNetwork(self): @@ -111,8 +113,9 @@ class QuantumTest(unittest.TestCase): 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") + 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