From b5ff0823378844fe89b529eee2308a8e879f3fc4 Mon Sep 17 00:00:00 2001 From: Santhosh Date: Thu, 23 Jun 2011 18:03:59 +0530 Subject: [PATCH 01/76] Santhosh/Vinkesh | Added extensions framework --- etc/quantum.conf | 13 +- etc/quantum.conf.sample | 25 +- etc/quantum.conf.test | 19 +- extensions/__init__.py | 15 + quantum/common/extensions.py | 440 ++++++++++++++++++++++++++++ tests/unit/extensions/__init__.py | 15 + tests/unit/extensions/foxinsocks.py | 97 ++++++ tests/unit/test_extensions.py | 229 +++++++++++++++ 8 files changed, 843 insertions(+), 10 deletions(-) create mode 100644 extensions/__init__.py create mode 100644 quantum/common/extensions.py create mode 100644 tests/unit/extensions/__init__.py create mode 100644 tests/unit/extensions/foxinsocks.py create mode 100644 tests/unit/test_extensions.py diff --git a/etc/quantum.conf b/etc/quantum.conf index ba96a9a275f..d527c83870c 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -11,15 +11,22 @@ bind_host = 0.0.0.0 # Port the bind the API server to bind_port = 9696 +# Path to the extensions +api_extensions_path = extensions + [composite:quantum] use = egg:Paste#urlmap /: quantumversions /v0.1: quantumapi +[pipeline:quantumapi] +pipeline = extensions quantumapiapp + +[filter:extensions] +paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory + [app:quantumversions] paste.app_factory = quantum.api.versions:Versions.factory -[app:quantumapi] +[app:quantumapiapp] paste.app_factory = quantum.api:APIRouterV01.factory - - diff --git a/etc/quantum.conf.sample b/etc/quantum.conf.sample index 85d6282b504..502503468fe 100644 --- a/etc/quantum.conf.sample +++ b/etc/quantum.conf.sample @@ -5,11 +5,28 @@ verbose = True # Show debugging output in logs (sets DEBUG log level output) debug = False -[app:quantum] -paste.app_factory = quantum.service:app_factory - # Address to bind the API server 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 + +# Path to the extensions +api_extensions_path = extensions + +[composite:quantum] +use = egg:Paste#urlmap +/: quantumversions +/v0.1: quantumapi + +[pipeline:quantumapi] +pipeline = extensions quantumapiapp + +[filter:extensions] +paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory + +[app:quantumversions] +paste.app_factory = quantum.api.versions:Versions.factory + +[app:quantumapiapp] +paste.app_factory = quantum.api:APIRouterV01.factory diff --git a/etc/quantum.conf.test b/etc/quantum.conf.test index b1c266246af..5e1e3412bcc 100644 --- a/etc/quantum.conf.test +++ b/etc/quantum.conf.test @@ -5,11 +5,24 @@ verbose = True # Show debugging output in logs (sets DEBUG log level output) debug = False -[app:quantum] -paste.app_factory = quantum.l2Network.service:app_factory - # Address to bind the API server bind_host = 0.0.0.0 # Port the bind the API server to bind_port = 9696 + +# Path to the extensions +api_extensions_path = unit/extensions + +[pipeline:extensions_app_with_filter] +pipeline = extensions extensions_test_app + +[filter:extensions] +paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory + +[app:extensions_test_app] +paste.app_factory = tests.unit.test_extensions:app_factory + +[app:quantum] +paste.app_factory = quantum.l2Network.service:app_factory + diff --git a/extensions/__init__.py b/extensions/__init__.py new file mode 100644 index 00000000000..848908a953a --- /dev/null +++ b/extensions/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py new file mode 100644 index 00000000000..1a88d1febf6 --- /dev/null +++ b/quantum/common/extensions.py @@ -0,0 +1,440 @@ + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# Copyright 2011 Justin Santa Barbara +# 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 imp +import os +import routes +import logging +import webob.dec +import webob.exc + +from quantum.common import exceptions +from quantum.common import wsgi +from gettext import gettext as _ + +LOG = logging.getLogger('quantum.common.extensions') + + +class ExtensionDescriptor(object): + """Base class that defines the contract for extensions. + + Note that you don't have to derive from this class to have a valid + extension; it is purely a convenience. + + """ + + def get_name(self): + """The name of the extension. + + e.g. 'Fox In Socks' + + """ + raise NotImplementedError() + + def get_alias(self): + """The alias for the extension. + + e.g. 'FOXNSOX' + + """ + raise NotImplementedError() + + def get_description(self): + """Friendly description for the extension. + + e.g. 'The Fox In Socks Extension' + + """ + raise NotImplementedError() + + def get_namespace(self): + """The XML namespace for the extension. + + e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0' + + """ + raise NotImplementedError() + + def get_updated(self): + """The timestamp when the extension was last updated. + + e.g. '2011-01-22T13:25:27-06:00' + + """ + # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS + raise NotImplementedError() + + def get_resources(self): + """List of extensions.ResourceExtension extension objects. + + Resources define new nouns, and are accessible through URLs. + + """ + resources = [] + return resources + + def get_actions(self): + """List of extensions.ActionExtension extension objects. + + Actions are verbs callable from the API. + + """ + actions = [] + return actions + + def get_request_extensions(self): + """List of extensions.RequestException extension objects. + + Request extensions are used to handle custom request data. + + """ + request_exts = [] + return request_exts + + +class ActionExtensionController(wsgi.Controller): + + def __init__(self, application): + + self.application = application + self.action_handlers = {} + + def add_action(self, action_name, handler): + self.action_handlers[action_name] = handler + + def action(self, request, id): + + input_dict = self._deserialize(request.body, + request.get_content_type()) + for action_name, handler in self.action_handlers.iteritems(): + if action_name in input_dict: + return handler(input_dict, request, id) + # no action handler found (bump to downstream application) + response = self.application + return response + + +class RequestExtensionController(wsgi.Controller): + + def __init__(self, application): + self.application = application + self.handlers = [] + + def add_handler(self, handler): + self.handlers.append(handler) + + def process(self, request, *args, **kwargs): + res = request.get_response(self.application) + # currently request handlers are un-ordered + for handler in self.handlers: + response = handler(request, res) + return response + + +class ExtensionController(wsgi.Controller): + + def __init__(self, extension_manager): + self.extension_manager = extension_manager + + def _translate(self, ext): + ext_data = {} + ext_data['name'] = ext.get_name() + ext_data['alias'] = ext.get_alias() + ext_data['description'] = ext.get_description() + ext_data['namespace'] = ext.get_namespace() + ext_data['updated'] = ext.get_updated() + ext_data['links'] = [] # TODO(dprince): implement extension links + return ext_data + + def index(self, request): + extensions = [] + for _alias, ext in self.extension_manager.extensions.iteritems(): + extensions.append(self._translate(ext)) + return dict(extensions=extensions) + + def show(self, request, id): + # NOTE(dprince): the extensions alias is used as the 'id' for show + ext = self.extension_manager.extensions[id] + return self._translate(ext) + + def delete(self, request, id): + raise webob.exc.HTTPNotFound() + + def create(self, request): + raise webob.exc.HTTPNotFound() + + +class ExtensionMiddleware(wsgi.Middleware): + """Extensions middleware for WSGI.""" + @classmethod + def factory(cls, global_config, **local_config): + """Paste factory.""" + def _factory(app): + return cls(app, global_config, **local_config) + return _factory + + def _action_ext_controllers(self, application, ext_mgr, mapper): + """Return a dict of ActionExtensionController-s by collection.""" + action_controllers = {} + for action in ext_mgr.get_actions(): + if not action.collection in action_controllers.keys(): + controller = ActionExtensionController(application) + mapper.connect("/%s/:(id)/action.:(format)" % + action.collection, + action='action', + controller=controller, + conditions=dict(method=['POST'])) + mapper.connect("/%s/:(id)/action" % action.collection, + action='action', + controller=controller, + conditions=dict(method=['POST'])) + action_controllers[action.collection] = controller + + return action_controllers + + def _request_ext_controllers(self, application, ext_mgr, mapper): + """Returns a dict of RequestExtensionController-s by collection.""" + request_ext_controllers = {} + for req_ext in ext_mgr.get_request_extensions(): + if not req_ext.key in request_ext_controllers.keys(): + controller = RequestExtensionController(application) + mapper.connect(req_ext.url_route + '.:(format)', + action='process', + controller=controller, + conditions=req_ext.conditions) + + mapper.connect(req_ext.url_route, + action='process', + controller=controller, + conditions=req_ext.conditions) + request_ext_controllers[req_ext.key] = controller + + return request_ext_controllers + + def __init__(self, application, config_params, + ext_mgr=None): + + self.ext_mgr = (ext_mgr + or ExtensionManager(config_params.get('api_extensions_path', + ''))) + + mapper = routes.Mapper() + + # extended resources + for resource in self.ext_mgr.get_resources(): + LOG.debug(_('Extended resource: %s'), + resource.collection) + mapper.resource(resource.collection, resource.collection, + controller=resource.controller, + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) + + # extended actions + action_controllers = self._action_ext_controllers(application, + self.ext_mgr, mapper) + for action in self.ext_mgr.get_actions(): + LOG.debug(_('Extended action: %s'), action.action_name) + controller = action_controllers[action.collection] + controller.add_action(action.action_name, action.handler) + + # extended requests + req_controllers = self._request_ext_controllers(application, + self.ext_mgr, mapper) + for request_ext in self.ext_mgr.get_request_extensions(): + LOG.debug(_('Extended request: %s'), request_ext.key) + controller = req_controllers[request_ext.key] + controller.add_handler(request_ext.handler) + + self._router = routes.middleware.RoutesMiddleware(self._dispatch, + mapper) + + super(ExtensionMiddleware, self).__init__(application) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + """Route the incoming request with router.""" + req.environ['extended.app'] = self.application + return self._router + + @staticmethod + @webob.dec.wsgify(RequestClass=wsgi.Request) + def _dispatch(req): + """Dispatch the request. + + Returns the routed WSGI app's response or defers to the extended + application. + + """ + match = req.environ['wsgiorg.routing_args'][1] + if not match: + return req.environ['extended.app'] + app = match['controller'] + return app + + +class ExtensionManager(object): + """Load extensions from the configured extension path. + + See tests/unit/extensions/foxinsocks.py for an + example extension implementation. + + """ + + def __init__(self, path): + LOG.info(_('Initializing extension manager.')) + + self.path = path + self.extensions = {} + self._load_all_extensions() + + def get_resources(self): + """Returns a list of ResourceExtension objects.""" + resources = [] + resources.append(ResourceExtension('extensions', + ExtensionController(self))) + for alias, ext in self.extensions.iteritems(): + try: + resources.extend(ext.get_resources()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have resource + # extensions + pass + return resources + + def get_actions(self): + """Returns a list of ActionExtension objects.""" + actions = [] + for alias, ext in self.extensions.iteritems(): + try: + actions.extend(ext.get_actions()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have action + # extensions + pass + return actions + + def get_request_extensions(self): + """Returns a list of RequestExtension objects.""" + request_exts = [] + for alias, ext in self.extensions.iteritems(): + try: + request_exts.extend(ext.get_request_extensions()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have request + # extensions + pass + return request_exts + + def _check_extension(self, extension): + """Checks for required methods in extension objects.""" + try: + LOG.debug(_('Ext name: %s'), extension.get_name()) + LOG.debug(_('Ext alias: %s'), extension.get_alias()) + LOG.debug(_('Ext description: %s'), extension.get_description()) + LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) + LOG.debug(_('Ext updated: %s'), extension.get_updated()) + except AttributeError as ex: + LOG.exception(_("Exception loading extension: %s"), unicode(ex)) + + def _load_all_extensions(self): + """Load extensions from the configured path. + + Load extensions from the configured path. The extension name is + constructed from the module_name. If your extension module was named + widgets.py the extension class within that module should be + 'Widgets'. + + In addition, extensions are loaded from the 'contrib' directory. + + See tests/unit/extensions/foxinsocks.py for an example + extension implementation. + + """ + if os.path.exists(self.path): + self._load_all_extensions_from_path(self.path) + + contrib_path = os.path.join(os.path.dirname(__file__), "contrib") + if os.path.exists(contrib_path): + self._load_all_extensions_from_path(contrib_path) + + def _load_all_extensions_from_path(self, path): + for f in os.listdir(path): + LOG.info(_('Loading extension file: %s'), f) + mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) + ext_path = os.path.join(path, f) + if file_ext.lower() == '.py' and not mod_name.startswith('_'): + mod = imp.load_source(mod_name, ext_path) + ext_name = mod_name[0].upper() + mod_name[1:] + new_ext_class = getattr(mod, ext_name, None) + if not new_ext_class: + LOG.warn(_('Did not find expected name ' + '"%(ext_name)s" in %(file)s'), + {'ext_name': ext_name, + 'file': ext_path}) + continue + new_ext = new_ext_class() + self._check_extension(new_ext) + self._add_extension(new_ext) + + def _add_extension(self, ext): + alias = ext.get_alias() + LOG.info(_('Loaded extension: %s'), alias) + + self._check_extension(ext) + + if alias in self.extensions: + raise exception.Error("Found duplicate extension: %s" + % alias) + self.extensions[alias] = ext + + +class RequestExtension(object): + """Extend requests and responses of core Quantum OpenStack API controllers. + + Provide a way to add data to responses and handle custom request data + that is sent to core Quantum OpenStack API controllers. + + """ + def __init__(self, method, url_route, handler): + self.url_route = url_route + self.handler = handler + self.conditions = dict(method=[method]) + self.key = "%s-%s" % (method, url_route) + + +class ActionExtension(object): + """Add custom actions to core Quantum OpenStack API controllers.""" + + def __init__(self, collection, action_name, handler): + self.collection = collection + self.action_name = action_name + self.handler = handler + + +class ResourceExtension(object): + """Add top level resources to the OpenStack API in Quantum.""" + + def __init__(self, collection, controller, parent=None, + collection_actions={}, member_actions={}): + self.collection = collection + self.controller = controller + self.parent = parent + self.collection_actions = collection_actions + self.member_actions = member_actions diff --git a/tests/unit/extensions/__init__.py b/tests/unit/extensions/__init__.py new file mode 100644 index 00000000000..848908a953a --- /dev/null +++ b/tests/unit/extensions/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/tests/unit/extensions/foxinsocks.py b/tests/unit/extensions/foxinsocks.py new file mode 100644 index 00000000000..648225ce6fc --- /dev/null +++ b/tests/unit/extensions/foxinsocks.py @@ -0,0 +1,97 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from quantum.common import wsgi +from quantum.common import extensions + + +class FoxInSocksController(wsgi.Controller): + + def index(self, request): + return "Try to say this Mr. Knox, sir..." + + +class Foxinsocks(object): + + def __init__(self): + pass + + def get_name(self): + return "Fox In Socks" + + def get_alias(self): + return "FOXNSOX" + + def get_description(self): + return "The Fox In Socks Extension" + + def get_namespace(self): + return "http://www.fox.in.socks/api/ext/pie/v1.0" + + def get_updated(self): + return "2011-01-22T13:25:27-06:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('foxnsocks', + FoxInSocksController()) + resources.append(resource) + return resources + + def get_actions(self): + return [extensions.ActionExtension('dummy_resources', 'add_tweedle', + self._add_tweedle), + extensions.ActionExtension('dummy_resources', + 'delete_tweedle', self._delete_tweedle)] + + def get_request_extensions(self): + request_exts = [] + + def _goose_handler(req, res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['googoose'] = req.GET.get('chewing') + res.body = json.dumps(data) + return res + + req_ext1 = extensions.RequestExtension('GET', '/dummy_resources/:(id)', + _goose_handler) + request_exts.append(req_ext1) + + def _bands_handler(req, res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['big_bands'] = 'Pig Bands!' + res.body = json.dumps(data) + return res + + req_ext2 = extensions.RequestExtension('GET', '/dummy_resources/:(id)', + _bands_handler) + request_exts.append(req_ext2) + return request_exts + + def _add_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Added." + + def _delete_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Deleted." diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py new file mode 100644 index 00000000000..9da8d8a821b --- /dev/null +++ b/tests/unit/test_extensions.py @@ -0,0 +1,229 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import json +import unittest +import routes +import os.path +from tests.unit import BaseTest + +from webtest import TestApp +from quantum.common import extensions +from quantum.common import wsgi +from quantum.common import config + + +response_body = "Try to say this Mr. Knox, sir..." +test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, + os.pardir, 'etc', 'quantum.conf.test') + + +class ExtensionControllerTest(unittest.TestCase): + + def setUp(self): + super(ExtensionControllerTest, self).setUp() + self.test_app = setup_extensions_test_app() + + def test_index(self): + response = self.test_app.get("/extensions") + self.assertEqual(200, response.status_int) + + def test_get_by_alias(self): + response = self.test_app.get("/extensions/FOXNSOX") + self.assertEqual(200, response.status_int) + + +class ResourceExtensionTest(unittest.TestCase): + + def test_no_extension_present(self): + test_app = setup_extensions_test_app(StubExtensionManager(None)) + response = test_app.get("/blah", status='*') + self.assertEqual(404, response.status_int) + + def test_get_resources(self): + res_ext = extensions.ResourceExtension('tweedles', + StubController(response_body)) + test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) + + response = test_app.get("/tweedles") + self.assertEqual(200, response.status_int) + self.assertEqual(response_body, response.body) + + +class ExtensionManagerTest(unittest.TestCase): + + def test_get_resources(self): + test_app = setup_extensions_test_app() + response = test_app.get('/foxnsocks') + + self.assertEqual(200, response.status_int) + self.assertEqual(response_body, response.body) + + +class ActionExtensionTest(unittest.TestCase): + + def setUp(self): + super(ActionExtensionTest, self).setUp() + self.test_app = setup_extensions_test_app() + + def _send_server_action_request(self, url, body): + return self.test_app.post(url, json.dumps(body), + content_type='application/json', status='*') + + def test_extended_action(self): + body = json.dumps(dict(add_tweedle=dict(name="test"))) + response = self.test_app.post('/dummy_resources/1/action', body, + content_type='application/json') + self.assertEqual("Tweedle Beetle Added.", response.body) + + body = json.dumps(dict(delete_tweedle=dict(name="test"))) + response = self.test_app.post("/dummy_resources/1/action", body, + content_type='application/json') + + self.assertEqual(200, response.status_int) + self.assertEqual("Tweedle Beetle Deleted.", response.body) + + def test_invalid_action_body(self): + body = json.dumps(dict(blah=dict(name="test"))) # Doesn't exist + response = self.test_app.post("/dummy_resources/1/action", body, + content_type='application/json', + status='*') + self.assertEqual(404, response.status_int) + + def test_invalid_action(self): + body = json.dumps(dict(blah=dict(name="test"))) + response = self.test_app.post("/asdf/1/action", + body, content_type='application/json', + status='*') + self.assertEqual(404, response.status_int) + + +class RequestExtensionTest(BaseTest): + + def test_get_resources_with_stub_mgr(self): + + def _req_handler(req, res): + # only handle JSON responses + data = json.loads(res.body) + data['googoose'] = req.GET.get('chewing') + res.body = json.dumps(data) + return res + + req_ext = extensions.RequestExtension('GET', + '/dummy_resources/:(id)', + _req_handler) + + manager = StubExtensionManager(None, None, req_ext) + app = setup_extensions_test_app(manager) + + response = app.get("/dummy_resources/1?chewing=bluegoos", + extra_environ={'api.version': '1.1'}) + + self.assertEqual(200, response.status_int) + response_data = json.loads(response.body) + self.assertEqual('bluegoos', response_data['googoose']) + self.assertEqual('knox', response_data['fort']) + + def test_get_resources_with_mgr(self): + app = setup_extensions_test_app() + + response = app.get("/dummy_resources/1?" + "chewing=newblue", status='*') + + self.assertEqual(200, response.status_int) + response_data = json.loads(response.body) + self.assertEqual('newblue', response_data['googoose']) + self.assertEqual("Pig Bands!", response_data['big_bands']) + + +class TestExtensionMiddlewareFactory(unittest.TestCase): + + def test_app_configured_with_extensions_as_filter(self): + conf, quantum_app = config.load_paste_app('extensions_app_with_filter', + {"config_file": test_conf_file}, + None) + + response = TestApp(quantum_app).get("/extensions") + self.assertEqual(response.status_int, 200) + + +class ExtensionsTestApp(wsgi.Router): + + def __init__(self, options={}): + mapper = routes.Mapper() + controller = StubController(response_body) + mapper.resource("dummy_resource", "/dummy_resources", + controller=controller) + super(ExtensionsTestApp, self).__init__(mapper) + + +class StubController(wsgi.Controller): + + def __init__(self, body): + self.body = body + + def index(self, request): + return self.body + + def show(self, request, id): + return {'fort': 'knox'} + + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return ExtensionsTestApp(conf) + + +def setup_extensions_test_app(extension_manager=None): + options = {'config_file': test_conf_file} + conf, app = config.load_paste_app('extensions_test_app', options, None) + extended_app = extensions.ExtensionMiddleware(app, conf, extension_manager) + return TestApp(extended_app) + + +class StubExtensionManager(object): + + def __init__(self, resource_ext=None, action_ext=None, request_ext=None): + self.resource_ext = resource_ext + self.action_ext = action_ext + self.request_ext = request_ext + + def get_name(self): + return "Tweedle Beetle Extension" + + def get_alias(self): + return "TWDLBETL" + + def get_description(self): + return "Provides access to Tweedle Beetles" + + def get_resources(self): + resource_exts = [] + if self.resource_ext: + resource_exts.append(self.resource_ext) + return resource_exts + + def get_actions(self): + action_exts = [] + if self.action_ext: + action_exts.append(self.action_ext) + return action_exts + + def get_request_extensions(self): + request_extensions = [] + if self.request_ext: + request_extensions.append(self.request_ext) + return request_extensions From b45f213e78592935922c9be12ec358bb9d83119a Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Wed, 6 Jul 2011 19:15:54 +0530 Subject: [PATCH 02/76] Rajaram/Vinkesh | Added tests to confirm extensions can edit previously uneditable field. --- tests/unit/test_extensions.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 9da8d8a821b..7ec6eb38c9f 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -38,6 +38,10 @@ class ExtensionControllerTest(unittest.TestCase): def test_index(self): response = self.test_app.get("/extensions") + foxnsox = response.json["extensions"][0] + self.assertEqual(foxnsox["alias"], "FOXNSOX") + self.assertEqual(foxnsox["namespace"], + "http://www.fox.in.socks/api/ext/pie/v1.0") self.assertEqual(200, response.status_int) def test_get_by_alias(self): @@ -147,6 +151,30 @@ class RequestExtensionTest(BaseTest): self.assertEqual('newblue', response_data['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) + def test_edit_previously_uneditable_field(self): + + def _update_handler(req, res): + data = json.loads(res.body) + data['uneditable'] = req.params['uneditable'] + res.body = json.dumps(data) + return res + + conf, app = config.load_paste_app('extensions_test_app', + {'config_file': test_conf_file}, None) + base_app = TestApp(app) + response = base_app.put("/dummy_resources/1", {'uneditable': "new_value"}) + self.assertEqual(response.json['uneditable'], "original_value") + + + req_ext = extensions.RequestExtension('PUT', + '/dummy_resources/:(id)', + _update_handler) + + manager = StubExtensionManager(None, None, req_ext) + extended_app = setup_extensions_test_app(manager) + response = extended_app.put("/dummy_resources/1", {'uneditable': "new_value"}) + self.assertEqual(response.json['uneditable'], "new_value") + class TestExtensionMiddlewareFactory(unittest.TestCase): @@ -180,6 +208,9 @@ class StubController(wsgi.Controller): def show(self, request, id): return {'fort': 'knox'} + def update(self, request, id): + return {'uneditable': 'original_value'} + def app_factory(global_conf, **local_conf): conf = global_conf.copy() From de610f66fc79768640d99b40042d575744014b4c Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Thu, 7 Jul 2011 12:58:20 +0530 Subject: [PATCH 03/76] Santosh/Rajaram| added extenstion test to show header extensibility --- tests/unit/test_extensions.py | 40 +++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 7ec6eb38c9f..40bc4805d66 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -116,8 +116,18 @@ class ActionExtensionTest(unittest.TestCase): class RequestExtensionTest(BaseTest): - def test_get_resources_with_stub_mgr(self): + def test_headers_extension(self): + def _req_header_handler(req, res): + ext_header = req.headers['X-rax-fox'] + res.headers['X-got-header'] = ext_header + return res + app = self._setup_app_with_request_handler(_req_header_handler, + 'GET') + response = app.get("/dummy_resources/1", headers={'X-rax-fox': "sox"}) + self.assertEqual(response.headers['X-got-header'], "sox") + + def test_get_resources_with_stub_mgr(self): def _req_handler(req, res): # only handle JSON responses data = json.loads(res.body) @@ -160,28 +170,32 @@ class RequestExtensionTest(BaseTest): return res conf, app = config.load_paste_app('extensions_test_app', - {'config_file': test_conf_file}, None) + {'config_file': test_conf_file}, None) base_app = TestApp(app) - response = base_app.put("/dummy_resources/1", {'uneditable': "new_value"}) - self.assertEqual(response.json['uneditable'], "original_value") - - req_ext = extensions.RequestExtension('PUT', - '/dummy_resources/:(id)', - _update_handler) + response = base_app.put("/dummy_resources/1", + {'uneditable': "new_value"}) + self.assertEqual(response.json['uneditable'], "original_value") + + ext_app = self._setup_app_with_request_handler(_update_handler, + 'PUT') + ext_response = ext_app.put("/dummy_resources/1", + {'uneditable': "new_value"}) + self.assertEqual(ext_response.json['uneditable'], "new_value") + + def _setup_app_with_request_handler(self, handler, verb): + req_ext = extensions.RequestExtension(verb, + '/dummy_resources/:(id)', handler) manager = StubExtensionManager(None, None, req_ext) - extended_app = setup_extensions_test_app(manager) - response = extended_app.put("/dummy_resources/1", {'uneditable': "new_value"}) - self.assertEqual(response.json['uneditable'], "new_value") + return setup_extensions_test_app(manager) class TestExtensionMiddlewareFactory(unittest.TestCase): def test_app_configured_with_extensions_as_filter(self): conf, quantum_app = config.load_paste_app('extensions_app_with_filter', - {"config_file": test_conf_file}, - None) + {"config_file": test_conf_file}, None) response = TestApp(quantum_app).get("/extensions") self.assertEqual(response.status_int, 200) From c723fada9ae5229f3ba8a262a9cc18781095120c Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Thu, 7 Jul 2011 17:35:14 +0530 Subject: [PATCH 04/76] Rajaram/Santosh|misc readablity improvements to extension tests --- quantum/common/extensions.py | 16 ++- tests/unit/extensions/foxinsocks.py | 15 ++- tests/unit/test_extensions.py | 188 ++++++++++++++++------------ 3 files changed, 126 insertions(+), 93 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 1a88d1febf6..7ad802fa969 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -352,6 +352,8 @@ class ExtensionManager(object): LOG.debug(_('Ext updated: %s'), extension.get_updated()) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) + return False + return True def _load_all_extensions(self): """Load extensions from the configured path. @@ -391,16 +393,18 @@ class ExtensionManager(object): continue new_ext = new_ext_class() self._check_extension(new_ext) - self._add_extension(new_ext) + self.add_extension(new_ext) + + def add_extension(self, ext): + # Do nothing if the extension doesn't check out + if not self._check_extension(ext): + return - def _add_extension(self, ext): alias = ext.get_alias() - LOG.info(_('Loaded extension: %s'), alias) - - self._check_extension(ext) + LOG.warn(_('Loaded extension: %s'), alias) if alias in self.extensions: - raise exception.Error("Found duplicate extension: %s" + raise exceptions.Error("Found duplicate extension: %s" % alias) self.extensions[alias] = ext diff --git a/tests/unit/extensions/foxinsocks.py b/tests/unit/extensions/foxinsocks.py index 648225ce6fc..2c224af8292 100644 --- a/tests/unit/extensions/foxinsocks.py +++ b/tests/unit/extensions/foxinsocks.py @@ -56,9 +56,9 @@ class Foxinsocks(object): def get_actions(self): return [extensions.ActionExtension('dummy_resources', 'add_tweedle', - self._add_tweedle), + self._add_tweedle_handler), extensions.ActionExtension('dummy_resources', - 'delete_tweedle', self._delete_tweedle)] + 'delete_tweedle', self._delete_tweedle_handler)] def get_request_extensions(self): request_exts = [] @@ -88,10 +88,9 @@ class Foxinsocks(object): request_exts.append(req_ext2) return request_exts - def _add_tweedle(self, input_dict, req, id): + def _add_tweedle_handler(self, input_dict, req, id): + return "Tweedle {0} Added.".format(input_dict['add_tweedle']['name']) - return "Tweedle Beetle Added." - - def _delete_tweedle(self, input_dict, req, id): - - return "Tweedle Beetle Deleted." + def _delete_tweedle_handler(self, input_dict, req, id): + return "Tweedle {0} Deleted.".format( + input_dict['delete_tweedle']['name']) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 40bc4805d66..9579a0d7201 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -25,7 +25,7 @@ from quantum.common import wsgi from quantum.common import config -response_body = "Try to say this Mr. Knox, sir..." +extension_index_response = "Try to say this Mr. Knox, sir..." test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'etc', 'quantum.conf.test') @@ -36,127 +36,152 @@ class ExtensionControllerTest(unittest.TestCase): super(ExtensionControllerTest, self).setUp() self.test_app = setup_extensions_test_app() - def test_index(self): + def test_index_gets_all_registerd_extensions(self): response = self.test_app.get("/extensions") foxnsox = response.json["extensions"][0] + self.assertEqual(foxnsox["alias"], "FOXNSOX") self.assertEqual(foxnsox["namespace"], "http://www.fox.in.socks/api/ext/pie/v1.0") - self.assertEqual(200, response.status_int) - def test_get_by_alias(self): - response = self.test_app.get("/extensions/FOXNSOX") - self.assertEqual(200, response.status_int) + def test_extension_can_be_accessed_by_alias(self): + foxnsox_extension = self.test_app.get("/extensions/FOXNSOX").json + + self.assertEqual(foxnsox_extension["alias"], "FOXNSOX") + self.assertEqual(foxnsox_extension["namespace"], + "http://www.fox.in.socks/api/ext/pie/v1.0") class ResourceExtensionTest(unittest.TestCase): - def test_no_extension_present(self): - test_app = setup_extensions_test_app(StubExtensionManager(None)) - response = test_app.get("/blah", status='*') - self.assertEqual(404, response.status_int) - - def test_get_resources(self): - res_ext = extensions.ResourceExtension('tweedles', - StubController(response_body)) + def test_resource_extension(self): + res_ext = extensions.ResourceExtension('tweedles', StubController( + extension_index_response)) test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) response = test_app.get("/tweedles") self.assertEqual(200, response.status_int) - self.assertEqual(response_body, response.body) + self.assertEqual(extension_index_response, response.body) + + def test_returns_404_for_non_existant_extension(self): + test_app = setup_extensions_test_app(StubExtensionManager(None)) + + response = test_app.get("/non_extistant_extension", status='*') + + self.assertEqual(404, response.status_int) class ExtensionManagerTest(unittest.TestCase): - def test_get_resources(self): - test_app = setup_extensions_test_app() - response = test_app.get('/foxnsocks') + def test_invalid_extensions_are_not_registered(self): - self.assertEqual(200, response.status_int) - self.assertEqual(response_body, response.body) + class ValidExtension(object): + + def get_name(self): + return "Valid Extension" + + def get_alias(self): + return "valid_extension" + + def get_description(self): + return "" + + def get_namespace(self): + return "" + + def get_updated(self): + return "" + + class InvalidExtension(object): + def get_alias(self): + return "invalid_extension" + + extended_app = setup_extensions_middleware() + ext_mgr = extended_app.ext_mgr + ext_mgr.add_extension(InvalidExtension()) + ext_mgr.add_extension(ValidExtension()) + self.assertTrue('valid_extension' in ext_mgr.extensions) + self.assertFalse('invalid_extension' in ext_mgr.extensions) class ActionExtensionTest(unittest.TestCase): def setUp(self): super(ActionExtensionTest, self).setUp() - self.test_app = setup_extensions_test_app() + self.extension_app = setup_extensions_test_app() - def _send_server_action_request(self, url, body): - return self.test_app.post(url, json.dumps(body), - content_type='application/json', status='*') - - def test_extended_action(self): - body = json.dumps(dict(add_tweedle=dict(name="test"))) - response = self.test_app.post('/dummy_resources/1/action', body, - content_type='application/json') + def test_extended_action_for_adding_extra_data(self): + action_name = 'add_tweedle' + action_params = dict(name='Beetle') + req_body = json.dumps({action_name: action_params}) + response = self.extension_app.post('/dummy_resources/1/action', + req_body, content_type='application/json') self.assertEqual("Tweedle Beetle Added.", response.body) - body = json.dumps(dict(delete_tweedle=dict(name="test"))) - response = self.test_app.post("/dummy_resources/1/action", body, - content_type='application/json') + def test_extended_action_for_deleting_extra_data(self): + action_name = 'delete_tweedle' + action_params = dict(name='Bailey') + req_body = json.dumps({action_name: action_params}) + response = self.extension_app.post("/dummy_resources/1/action", + req_body, content_type='application/json') + self.assertEqual("Tweedle Bailey Deleted.", response.body) - self.assertEqual(200, response.status_int) - self.assertEqual("Tweedle Beetle Deleted.", response.body) + def test_returns_404_for_non_existant_action(self): + non_existant_action = 'blah_action' + action_params = dict(name="test") + req_body = json.dumps({non_existant_action: action_params}) + + response = self.extension_app.post("/dummy_resources/1/action", + req_body, content_type='application/json', + status='*') - def test_invalid_action_body(self): - body = json.dumps(dict(blah=dict(name="test"))) # Doesn't exist - response = self.test_app.post("/dummy_resources/1/action", body, - content_type='application/json', - status='*') self.assertEqual(404, response.status_int) - def test_invalid_action(self): - body = json.dumps(dict(blah=dict(name="test"))) - response = self.test_app.post("/asdf/1/action", - body, content_type='application/json', - status='*') + def test_returns_404_for_non_existant_resource(self): + action_name = 'add_tweedle' + action_params = dict(name='Beetle') + req_body = json.dumps({action_name: action_params}) + + response = self.extension_app.post("/asdf/1/action", req_body, + content_type='application/json', status='*') self.assertEqual(404, response.status_int) class RequestExtensionTest(BaseTest): - def test_headers_extension(self): - def _req_header_handler(req, res): - ext_header = req.headers['X-rax-fox'] - res.headers['X-got-header'] = ext_header + def test_headers_can_be_extended(self): + def extend_headers(req, res): + assert req.headers['X-NEW-REQUEST-HEADER'] == "sox" + res.headers['X-NEW-RESPONSE-HEADER'] = "response_header_data" return res - app = self._setup_app_with_request_handler(_req_header_handler, - 'GET') - response = app.get("/dummy_resources/1", headers={'X-rax-fox': "sox"}) - self.assertEqual(response.headers['X-got-header'], "sox") + app = self._setup_app_with_request_handler(extend_headers, 'GET') + response = app.get("/dummy_resources/1", + headers={'X-NEW-REQUEST-HEADER': "sox"}) - def test_get_resources_with_stub_mgr(self): - def _req_handler(req, res): - # only handle JSON responses + self.assertEqual(response.headers['X-NEW-RESPONSE-HEADER'], + "response_header_data") + + def test_extend_get_resource_response(self): + def extend_response_data(req, res): data = json.loads(res.body) - data['googoose'] = req.GET.get('chewing') + data['extended_key'] = req.GET.get('extended_key') res.body = json.dumps(data) return res - req_ext = extensions.RequestExtension('GET', - '/dummy_resources/:(id)', - _req_handler) - - manager = StubExtensionManager(None, None, req_ext) - app = setup_extensions_test_app(manager) - - response = app.get("/dummy_resources/1?chewing=bluegoos", - extra_environ={'api.version': '1.1'}) + app = self._setup_app_with_request_handler(extend_response_data, 'GET') + response = app.get("/dummy_resources/1?extended_key=extended_data") self.assertEqual(200, response.status_int) response_data = json.loads(response.body) - self.assertEqual('bluegoos', response_data['googoose']) + self.assertEqual('extended_data', response_data['extended_key']) self.assertEqual('knox', response_data['fort']) - def test_get_resources_with_mgr(self): + def test_get_resources(self): app = setup_extensions_test_app() - response = app.get("/dummy_resources/1?" - "chewing=newblue", status='*') + response = app.get("/dummy_resources/1?chewing=newblue") - self.assertEqual(200, response.status_int) response_data = json.loads(response.body) self.assertEqual('newblue', response_data['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) @@ -169,10 +194,7 @@ class RequestExtensionTest(BaseTest): res.body = json.dumps(data) return res - conf, app = config.load_paste_app('extensions_test_app', - {'config_file': test_conf_file}, None) - base_app = TestApp(app) - + base_app = TestApp(setup_base_app()) response = base_app.put("/dummy_resources/1", {'uneditable': "new_value"}) self.assertEqual(response.json['uneditable'], "original_value") @@ -186,7 +208,6 @@ class RequestExtensionTest(BaseTest): def _setup_app_with_request_handler(self, handler, verb): req_ext = extensions.RequestExtension(verb, '/dummy_resources/:(id)', handler) - manager = StubExtensionManager(None, None, req_ext) return setup_extensions_test_app(manager) @@ -205,7 +226,7 @@ class ExtensionsTestApp(wsgi.Router): def __init__(self, options={}): mapper = routes.Mapper() - controller = StubController(response_body) + controller = StubController(extension_index_response) mapper.resource("dummy_resource", "/dummy_resources", controller=controller) super(ExtensionsTestApp, self).__init__(mapper) @@ -232,11 +253,20 @@ def app_factory(global_conf, **local_conf): return ExtensionsTestApp(conf) -def setup_extensions_test_app(extension_manager=None): +def setup_base_app(): options = {'config_file': test_conf_file} conf, app = config.load_paste_app('extensions_test_app', options, None) - extended_app = extensions.ExtensionMiddleware(app, conf, extension_manager) - return TestApp(extended_app) + return app + + +def setup_extensions_middleware(extension_manager=None): + options = {'config_file': test_conf_file} + conf, app = config.load_paste_app('extensions_test_app', options, None) + return extensions.ExtensionMiddleware(app, conf, extension_manager) + + +def setup_extensions_test_app(extension_manager=None): + return TestApp(setup_extensions_middleware(extension_manager)) class StubExtensionManager(object): From d365ae3ac15dc575013ed8f7adfa0f6c8d524d09 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 8 Jul 2011 09:34:04 -0700 Subject: [PATCH 05/76] Initial checkin for the L2-Network Plugin with all the associated modules and artifacts. --- quantum/plugins.ini | 2 +- quantum/plugins/cisco/README | 78 ++++ quantum/plugins/cisco/cisco_configuration.py | 87 +++++ quantum/plugins/cisco/cisco_constants.py | 46 +++ quantum/plugins/cisco/cisco_credentials.py | 73 ++++ quantum/plugins/cisco/cisco_exceptions.py | 52 +++ quantum/plugins/cisco/cisco_nexus_plugin.py | 152 ++++++++ quantum/plugins/cisco/cisco_ucs.py | 102 +++++ .../plugins/cisco/cisco_ucs_network_driver.py | 256 +++++++++++++ quantum/plugins/cisco/cisco_ucs_plugin.py | 294 +++++++++++++++ quantum/plugins/cisco/cisco_utils.py | 59 +++ quantum/plugins/cisco/get-vif.sh | 15 + quantum/plugins/cisco/l2network_plugin.py | 348 ++++++++++++++++++ 13 files changed, 1563 insertions(+), 1 deletion(-) create mode 100644 quantum/plugins/cisco/README create mode 100644 quantum/plugins/cisco/cisco_configuration.py create mode 100644 quantum/plugins/cisco/cisco_constants.py create mode 100644 quantum/plugins/cisco/cisco_credentials.py create mode 100644 quantum/plugins/cisco/cisco_exceptions.py create mode 100644 quantum/plugins/cisco/cisco_nexus_plugin.py create mode 100644 quantum/plugins/cisco/cisco_ucs.py create mode 100644 quantum/plugins/cisco/cisco_ucs_network_driver.py create mode 100644 quantum/plugins/cisco/cisco_ucs_plugin.py create mode 100644 quantum/plugins/cisco/cisco_utils.py create mode 100755 quantum/plugins/cisco/get-vif.sh create mode 100644 quantum/plugins/cisco/l2network_plugin.py diff --git a/quantum/plugins.ini b/quantum/plugins.ini index 307d2b48d2c..60db782d0c4 100644 --- a/quantum/plugins.ini +++ b/quantum/plugins.ini @@ -1,3 +1,3 @@ [PLUGIN] # Quantum plugin provider module -provider = quantum.plugins.SamplePlugin.FakePlugin +provider = quantum.plugins.cisco.l2network_plugin.L2Network diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README new file mode 100644 index 00000000000..fcc50018f94 --- /dev/null +++ b/quantum/plugins/cisco/README @@ -0,0 +1,78 @@ + L2 Network Plugin +================== + +*** Current support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh *** + +** Pre-requisities +* UCS B200 series blades with M81KR VIC installed. +* UCSM 2.0 (Capitola) Build 230 +* RHEL 6.1 +* UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at: +http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM +* To run Quantum on RHEL, you will need to have the correct version of python-routes (version 1.12.3 or later). The RHEL 6.1 package contains an older version. Do the following and check your python-routes version: +rpm -qav | grep "python-routes" + +If it's an older version, you will need to upgrade to 1.12.3 or later. One quick way to do it as by adding the following to your /etc/yum.repos.d/openstack.repo (assuming that you had installed OpenStack on this host, and hence had this repo; else you could add to any other operational repo config), and then update the python-routes package. That should get you the python-routes-1.12.3-2.el6.noarch package. + +[openstack-deps] +name=OpenStack Nova Compute Dependencies +baseurl=http://yum.griddynamics.net/yum/cactus/deps +enabled=1 +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK + + +** Plugin Installation Instructions: +* Make a backup copy of quantum/quantum/plugins.ini, and edit the file to remove all exisiting entries and add the following entry: +provider = quantum.plugins.cisco.l2network_plugin.L2Network +* You should have the following files in quantum/quantum/plugins/cisco directory (if you have pulled the Cisco Quantum branch, you will already have them): +l2network_plugin.py +cisco_configuration.py +cisco_constants.py +cisco_credentials.py +cisco_exceptions.py +cisco_nexus_network_driver.py +cisco_ucs_network_driver.py +cisco_ucs_plugin.py +cisco_utils.py +__init__.py +get-vif.sh +* Configure the L2 Network Pllugin: + + In cisco_configuration.py, + - change the UCSM IP in the following statement to your UCSM IP + flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of UCSM') + - change the Nova MySQL DB IP if you are running Quantum on a different host than the OpenStack Cloud Controller (in other words you do not need to change the IP if Quantum is running on the same host on which the Nova DB is running). DB IP is changed in the following statement: + flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server') + - change the hostname of the OpenStack Cloud Controller below + flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud controller hostname') + - change the name of the OpenStack project + flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova') + - change the start range of the VLAN (if you are not sure about this number, leave this unchanged) + flags.DEFINE_string('vlan_start', "100", 'This is the start value of the allowable VLANs') + - change the end range of the VLAN (if you are not sure about this number, leave this unchanged) + flags.DEFINE_string('vlan_end', "3000", 'This is the end value of the allowable VLANs') + - unless you have VLANs created in UCSM which start with the name "q-", you do not need to change the following property. If you do need to change it, change "q-" to some other string. Do not use more than 6 characters. + flags.DEFINE_string('vlan_name_prefix', "q-", 'Prefix of the name given to the VLAN') + - unless you have Port Profiles created in UCSM which start with the name "q-", you do not need to change the following property. If you do need to change it, change "q-" to some other string. Do not use more than 6 characters. + flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name given to the port profile') + - Change the path to reflect the location of the get-vif.sh script, if you have followed the instructions in this README, this location should be the same as that of your other plugin modules + flags.DEFINE_string('get_next_vif', "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", 'This is the location of the script to get the next available dynamic nic') + + In cisco_credentials.py, + - Change the following stucture to reflect the correct UCS and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack + _creds_dictionary = { + 'UCSM_IP_ADDRESS':["UCSM_USERNAME", "UCSM_PASSWORD"], + 'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"] + } +* Start the Quantum service + +** Additional installation required on Nova Compute: +* Create DB Table in Nova DB (On the Cloud Controller) +mysql -uroot -p nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));' + +* Replace the following files with the files from the Cisco Nova branch: +/usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py + +* Add the following files from the Cisco Nova branch: +/usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py + +* Restart nova-compute service diff --git a/quantum/plugins/cisco/cisco_configuration.py b/quantum/plugins/cisco/cisco_configuration.py new file mode 100644 index 00000000000..5ab2aaf336c --- /dev/null +++ b/quantum/plugins/cisco/cisco_configuration.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# +from quantum.common import flags + +# Note: All configuration values defined here are strings +FLAGS = flags.FLAGS +# +# TODO (Sumit): The following are defaults, but we also need to add to config +# file +# +flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of \ + UCSM') +flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB \ + server') +flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud \ + controller hostname') + +flags.DEFINE_string('db_name', "nova", 'DB name') +flags.DEFINE_string('vlan_name_prefix', "q-", 'Prefix of the name given \ + to the VLAN') +flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name \ + given to the port profile') +flags.DEFINE_string('vlan_start', "100", 'This is the start value of the \ + allowable VLANs') +flags.DEFINE_string('vlan_end', "3000", 'This is the end value of the \ + allowable VLANs') +flags.DEFINE_string('default_vlan_name', "default", 'This is the name of \ + the VLAN which will be associated with the port profile \ + when it is created, by default the VMs will be on this \ + VLAN, until attach is called') +flags.DEFINE_string('default_vlan_id', "1", 'This is the name of the VLAN \ + which will be associated with the port profile when it \ + is created, by default the VMs will be on this VLAN, \ + until attach is called') +flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova') +# +# TODO (Sumit): SAVBU to provide the accurate number below +# +flags.DEFINE_string('max_ucsm_port_profiles', "1024", 'This is the maximum \ + number port profiles that can be handled by one UCSM.') +flags.DEFINE_string('max_port_profiles', "65568", 'This is the maximum \ + number port profiles that can be handled by Cisco \ + plugin. Currently this is just an arbitrary number.') +flags.DEFINE_string('max_networks', "65568", 'This is the maximum number \ + of networks that can be handled by Cisco plugin. \ + Currently this is just an arbitrary number.') + +flags.DEFINE_string('get_next_vif', + "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", + 'This is the location of the script to get the next \ + next available dynamic nic') + +# Inventory items +UCSM_IP_ADDRESS = FLAGS.ucsm_ip_address +DB_SERVER_IP = FLAGS.db_server_ip +NOVA_HOST_NAME = FLAGS.nova_host_name + +# General configuration items +DB_NAME = FLAGS.db_name +VLAN_NAME_PREFIX = FLAGS.vlan_name_prefix +PROFILE_NAME_PREFIX = FLAGS.profile_name_prefix +VLAN_START = FLAGS.vlan_start +VLAN_END = FLAGS.vlan_end +DEFAULT_VLAN_NAME = FLAGS.default_vlan_name +DEFAULT_VLAN_ID = FLAGS.default_vlan_id +NOVA_PROJ_NAME = FLAGS.nova_proj_name +MAX_UCSM_PORT_PROFILES = FLAGS.max_ucsm_port_profiles +MAX_PORT_PROFILES = FLAGS.max_port_profiles +MAX_NETWORKS = FLAGS.max_networks + +GET_NEXT_VIF_SCRIPT = FLAGS.get_next_vif diff --git a/quantum/plugins/cisco/cisco_constants.py b/quantum/plugins/cisco/cisco_constants.py new file mode 100644 index 00000000000..115addf4237 --- /dev/null +++ b/quantum/plugins/cisco/cisco_constants.py @@ -0,0 +1,46 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +PORT_STATE = 'port-state' +PORT_UP = "UP" +PORT_DOWN = "DOWN" + +ATTACHMENT = 'attachment' +PORT_ID = 'port-id' + +NET_ID = 'net-id' +NET_NAME = 'net-name' +NET_PORTS = 'net-ports' +NET_VLAN_NAME = 'net-vlan-name' +NET_VLAN_ID = 'net-vlan-id' +NET_TENANTS = 'net-tenants' + +TENANT_ID = 'tenant-id' +TENANT_NETWORKS = 'tenant-networks' +TENANT_NAME = 'tenant-name' +TENANT_PORTPROFILES = 'tenant-portprofiles' + +PORT_PROFILE = 'port-profile' +PROFILE_ID = 'profile-id' +PROFILE_NAME = 'profile-name' +PROFILE_VLAN_NAME = 'profile-vlan-name' +PROFILE_VLAN_ID = 'profile-vlan-id' +PROFILE_QOS = 'profile-qos' + +LOGGER_COMPONENT_NAME = "cisco_plugin" diff --git a/quantum/plugins/cisco/cisco_credentials.py b/quantum/plugins/cisco/cisco_credentials.py new file mode 100644 index 00000000000..c37d00f1abe --- /dev/null +++ b/quantum/plugins/cisco/cisco_credentials.py @@ -0,0 +1,73 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import logging as LOG + +from quantum.plugins.cisco import cisco_constants as const + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + +_creds_dictionary = { + '172.20.231.27': ["admin", "c3l12345"], + '127.0.0.1': ["root", "nova"] +} + + +class Store(object): + # The format for this store is {"ip-address" :{"username", "password"}} + def __init__(self): + pass + + @staticmethod + def putId(id): + _creds_dictionary[id] = [] + + @staticmethod + def putUsername(id, username): + creds = _creds_dictionary.get(id) + creds.insert(0, username) + + @staticmethod + def putPassword(id, password): + creds = _creds_dictionary.get(id) + creds.insert(1, password) + + @staticmethod + def getUsername(id): + creds = _creds_dictionary.get(id) + return creds[0] + + @staticmethod + def getPassword(id): + creds = _creds_dictionary.get(id) + return creds[1] + + +def main(): + LOG.debug("username %s\n" % Store.getUsername("172.20.231.27")) + LOG.debug("password %s\n" % Store.getPassword("172.20.231.27")) + Store.putId("192.168.1.1") + Store.putUsername("192.168.1.1", "guest-username") + Store.putPassword("192.168.1.1", "guest-password") + LOG.debug("username %s\n" % Store.getUsername("192.168.1.1")) + LOG.debug("password %s\n" % Store.getPassword("192.168.1.1")) + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/cisco_exceptions.py b/quantum/plugins/cisco/cisco_exceptions.py new file mode 100644 index 00000000000..2829329c859 --- /dev/null +++ b/quantum/plugins/cisco/cisco_exceptions.py @@ -0,0 +1,52 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +""" +Exceptions used by the Cisco plugin +""" + +from quantum.common import exceptions + + +class NoMoreNics(exceptions.QuantumException): + message = _("Unable to complete operation on port %(port_id)s " \ + "for network %(net_id)s. No more dynamic nics are available" \ + "in the system.") + + +class PortProfileLimit(exceptions.QuantumException): + message = _("Unable to complete operation on port %(port_id)s " \ + "for network %(net_id)s. The system has reached the maximum" \ + "limit of allowed port profiles.") + + +class UCSMPortProfileLimit(exceptions.QuantumException): + message = _("Unable to complete operation on port %(port_id)s " \ + "for network %(net_id)s. The system has reached the maximum" \ + "limit of allowed UCSM port profiles.") + + +class NetworksLimit(exceptions.QuantumException): + message = _("Unable to create new network. Number of networks" \ + "for the system has exceeded the limit") + + +class PortProfileNotFound(exceptions.QuantumException): + message = _("Port profile %(port_id)s could not be found " \ + "for tenant %(tenant_id)s") diff --git a/quantum/plugins/cisco/cisco_nexus_plugin.py b/quantum/plugins/cisco/cisco_nexus_plugin.py new file mode 100644 index 00000000000..545499fe9b4 --- /dev/null +++ b/quantum/plugins/cisco/cisco_nexus_plugin.py @@ -0,0 +1,152 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# +import logging as LOG + +from quantum.common import exceptions as exc +from quantum.plugins.cisco import cisco_configuration as conf +from quantum.plugins.cisco import cisco_constants as const +from quantum.plugins.cisco import cisco_credentials as cred +from quantum.plugins.cisco import cisco_exceptions as cexc +from quantum.plugins.cisco import cisco_utils as cutil + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class NexusPlugin(object): + _networks = {} + + def __init__(self): + """ + Initialize the Nexus driver here + """ + pass + + def get_all_networks(self, tenant_id): + """ + Returns a dictionary containing all + for + the specified tenant. + """ + LOG.debug("NexusPlugin:get_all_networks() called\n") + return self._networks.values() + + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id): + """ + Create a VLAN in the switch, and configure the appropriate interfaces + for this VLAN + """ + LOG.debug("NexusPlugin:create_network() called\n") + # TODO (Sumit): Call the nexus driver here to create the VLAN, and + # configure the appropriate interfaces + new_net_dict = {const.NET_ID: net_id, + const.NET_NAME: net_name, + const.NET_PORTS: {}, + const.NET_VLAN_NAME: vlan_name, + const.NET_VLAN_ID: vlan_id} + self._networks[net_id] = new_net_dict + return new_net_dict + + def delete_network(self, tenant_id, net_id): + """ + Deletes a VLAN in the switch, and removes the VLAN configuration + from the relevant interfaces + """ + LOG.debug("NexusPlugin:delete_network() called\n") + net = self._networks.get(net_id) + if net: + # TODO (Sumit): Call the nexus driver here to create the VLAN, + # and configure the appropriate interfaces + self._networks.pop(net_id) + return net + # Network not found + raise exc.NetworkNotFound(net_id=net_id) + + def get_network_details(self, tenant_id, net_id): + """ + Returns the details of a particular network + """ + LOG.debug("NexusPlugin:get_network_details() called\n") + network = self._get_network(tenant_id, net_id) + return network + + def rename_network(self, tenant_id, net_id, new_name): + """ + Updates the symbolic name belonging to a particular + Virtual Network. + """ + LOG.debug("NexusPlugin:rename_network() called\n") + network = self._get_network(tenant_id, net_id) + network[const.NET_NAME] = new_name + return network + + def get_all_ports(self, tenant_id, net_id): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:get_all_ports() called\n") + + def create_port(self, tenant_id, net_id, port_state, port_id): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:create_port() called\n") + + def delete_port(self, tenant_id, net_id, port_id): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:delete_port() called\n") + + def update_port(self, tenant_id, net_id, port_id, port_state): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:update_port() called\n") + + def get_port_details(self, tenant_id, net_id, port_id): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:get_port_details() called\n") + + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:plug_interface() called\n") + + def unplug_interface(self, tenant_id, net_id, port_id): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:unplug_interface() called\n") + + def _get_network(self, tenant_id, network_id): + network = self._networks.get(network_id) + if not network: + raise exc.NetworkNotFound(net_id=network_id) + return network diff --git a/quantum/plugins/cisco/cisco_ucs.py b/quantum/plugins/cisco/cisco_ucs.py new file mode 100644 index 00000000000..0723da9f0a0 --- /dev/null +++ b/quantum/plugins/cisco/cisco_ucs.py @@ -0,0 +1,102 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# +# + +import MySQLdb +import sys, traceback + +from nova import flags +from nova import log as logging + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.virt.libvirt_conn') +# +# TODO (Sumit): The following are defaults, but we might need to make it conf file driven as well +# + +flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server') +flags.DEFINE_string('db_username', "root", 'DB username') +flags.DEFINE_string('db_password', "nova", 'DB paswwprd') +flags.DEFINE_string('db_name', "nova", 'DB name') +flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova') +flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud controller hostname') + +class CiscoUCSComputeDriver(object): + def __init__(self): + pass + + def _get_db_connection(self): + self.db = MySQLdb.connect(FLAGS.db_server_ip, FLAGS.db_username, FLAGS.db_password, FLAGS.db_name) + return self.db + + def _execute_db_query(self, sql_query): + db = self._get_db_connection() + cursor = db.cursor() + try: + cursor.execute(sql_query) + results = cursor.fetchall() + db.commit() + print "DB query execution succeeded: %s" % sql_query + except: + db.rollback() + print "DB query execution failed: %s" % sql_query + traceback.print_exc() + db.close() + return results + + def reserve_port(self, instance_name, instance_nic_name): + sql_query = "SELECT * from ports WHERE used='0'" + results = self._execute_db_query(sql_query) + if len(results) == 0: + print "No ports available/n" + return 0 + else: + for row in results: + port_id = row[0]; + sql_query = "UPDATE ports SET instance_name = '%s', instance_nic_name = '%s' WHERE port_id = '%s'" % (instance_name, instance_nic_name, port_id) + results = self._execute_db_query(sql_query) + return port_id; + return 0 + + def get_port_details(self, port_id): + port_details = {} + sql_query = "SELECT * from ports WHERE port_id='%s'" % (port_id) + results = self._execute_db_query(sql_query) + if len(results) == 0: + print "Could not fetch port from DB for port_id = %s/n" % port_id + return + else: + for row in results: + profile_name = row[1]; + dynamic_vnic = row[2]; + sql_query = "UPDATE ports SET used = %d WHERE port_id = '%s'" % (1, port_id) + results = self._execute_db_query(sql_query) + port_details = {'profile_name':profile_name, 'dynamic_vnic':dynamic_vnic} + return port_details; + + def release_port(self, instance_name, instance_nic_name): + sql_query = "SELECT * from ports WHERE instance_name='%s' and instance_nic_name='%s'" % (instance_name, instance_nic_name) + results = self._execute_db_query(sql_query) + if len(results) == 0: + print "No matching ports found for releasing/n" + return 0 + else: + for row in results: + port_id = row[0]; + sql_query = "UPDATE ports SET instance_name = NULL, instance_nic_name = NULL, used = 0 WHERE port_id = '%s'" % (port_id) + results = self._execute_db_query(sql_query) + return port_id; + return 0 + +def main(): + client = CiscoUCSComputeDriver() + port_id = client.reserve_port("instance-1", "eth1") + port_details = client.get_port_details(port_id) + print "profile_name %s dynamic_vnic %s\n" % (port_details['profile_name'], port_details['dynamic_vnic']) + port_id = client.release_port("instance-1", "eth1") + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/cisco_ucs_network_driver.py b/quantum/plugins/cisco/cisco_ucs_network_driver.py new file mode 100644 index 00000000000..ca912edf92b --- /dev/null +++ b/quantum/plugins/cisco/cisco_ucs_network_driver.py @@ -0,0 +1,256 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems Inc. +# +""" +Implements a UCSM XML API Client +""" + +import httplib +import logging as LOG +import string +import subprocess +from xml.etree import ElementTree as et +import urllib + +from quantum.plugins.cisco import cisco_configuration as conf +from quantum.plugins.cisco import cisco_constants as const +from quantum.plugins.cisco import cisco_exceptions as cexc + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + +COOKIE_VALUE = "cookie_placeholder" +PROFILE_NAME = "profilename_placeholder" +PROFILE_CLIENT = "profileclient_placeholder" +VLAN_NAME = "vlanname_placeholder" +VLAN_ID = "vlanid_placeholder" +OLD_VLAN_NAME = "old_vlanname_placeholder" +DYNAMIC_NIC_PREFIX = "eth" + +# The following are standard strings, messages used to communicate with UCSM, +#only place holder values change for each message +HEADERS = {"Content-Type": "text/xml"} +METHOD = "POST" +URL = "/nuova" + +CREATE_VLAN = " " \ +" " \ +" " + +CREATE_PROFILE = " " \ +" " \ +" " \ +" " + +ASSOCIATE_PROFILE = " " \ +" " + +CHANGE_VLAN_IN_PROFILE = " " \ +" " \ +" " \ +" " + +DELETE_VLAN = " " \ +" " \ +"" + +DELETE_PROFILE = " " \ +" " \ +" " + + +class CiscoUCSMDriver(): + + def __init__(self): + pass + + def _post_data(self, ucsm_ip, ucsm_username, ucsm_password, data): + conn = httplib.HTTPConnection(ucsm_ip) + login_data = "" + conn.request(METHOD, URL, login_data, HEADERS) + response = conn.getresponse() + response_data = response.read() + LOG.debug(response.status) + LOG.debug(response.reason) + LOG.debug(response_data) + # TODO (Sumit): If login is not successful, throw exception + xmlTree = et.XML(response_data) + cookie = xmlTree.attrib["outCookie"] + + data = data.replace(COOKIE_VALUE, cookie) + LOG.debug("POST: %s" % data) + conn.request(METHOD, URL, data, HEADERS) + response = conn.getresponse() + response_data = response.read() + LOG.debug(response.status) + LOG.debug(response.reason) + LOG.debug("UCSM Response: %s" % response_data) + + logout_data = "" + conn.request(METHOD, URL, logout_data, HEADERS) + response = conn.getresponse() + response_data = response.read() + LOG.debug(response.status) + LOG.debug(response.reason) + LOG.debug(response_data) + + def _create_vlan_post_data(self, vlan_name, vlan_id): + data = CREATE_VLAN.replace(VLAN_NAME, vlan_name) + data = data.replace(VLAN_ID, vlan_id) + return data + + def _create_profile_post_data(self, profile_name, vlan_name): + data = CREATE_PROFILE.replace(PROFILE_NAME, profile_name) + data = data.replace(VLAN_NAME, vlan_name) + return data + + def _create_profile_client_post_data(self, profile_name): + data = ASSOCIATE_PROFILE.replace(PROFILE_NAME, profile_name) + data = data.replace(PROFILE_CLIENT, profile_name) + return data + + def _change_vlan_in_profile_post_data(self, profile_name, old_vlan_name, + new_vlan_name): + data = CHANGE_VLAN_IN_PROFILE.replace(PROFILE_NAME, profile_name) + data = data.replace(OLD_VLAN_NAME, old_vlan_name) + data = data.replace(VLAN_NAME, new_vlan_name) + return data + + def _delete_vlan_post_data(self, vlan_name): + data = DELETE_VLAN.replace(VLAN_NAME, vlan_name) + return data + + def _delete_profile_post_data(self, profile_name): + data = DELETE_PROFILE.replace(PROFILE_NAME, profile_name) + return data + + def _get_next_dynamic_nic(self): + # TODO (Sumit): following should be a call to a python module + # (which will in turn eliminate the reference to the path and script) + dynamic_nic_id = string.strip(subprocess.Popen( + conf.GET_NEXT_VIF_SCRIPT, + stdout=subprocess.PIPE).communicate()[0]) + if len(dynamic_nic_id) > 0: + return dynamic_nic_id + else: + raise cisco_exceptions.NoMoreNics(net_id=net_id, port_id=port_id) + + def create_vlan(self, vlan_name, vlan_id, ucsm_ip, ucsm_username, + ucsm_password): + data = self._create_vlan_post_data(vlan_name, vlan_id) + self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + + def create_profile(self, profile_name, vlan_name, ucsm_ip, ucsm_username, + ucsm_password): + data = self._create_profile_post_data(profile_name, vlan_name) + self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + data = self._create_profile_client_post_data(profile_name) + self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + + def change_vlan_in_profile(self, profile_name, old_vlan_name, + new_vlan_name, ucsm_ip, ucsm_username, + ucsm_password): + data = self._change_vlan_in_profile_post_data(profile_name, + old_vlan_name, + new_vlan_name) + self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + + def get_dynamic_nic(self, host): + # TODO (Sumit): Check availability per host + # TODO (Sumit): If not available raise exception + # TODO (Sumit): This simple logic assumes that create-port and + # spawn-VM happens in lock-step + # But we should support multiple create-port calls, + # followed by spawn-VM calls + # That would require managing a pool of available + # dynamic vnics per host + dynamic_nic_name = self._get_next_dynamic_nic() + LOG.debug("Reserving dynamic nic %s" % dynamic_nic_name) + return dynamic_nic_name + + def delete_vlan(self, vlan_name, ucsm_ip, ucsm_username, ucsm_password): + data = self._delete_vlan_post_data(vlan_name) + self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + + def delete_profile(self, profile_name, ucsm_ip, ucsm_username, + ucsm_password): + data = self._delete_profile_post_data(profile_name) + self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) + + def release_dynamic_nic(self, host): + # TODO (Sumit): Release on a specific host + pass + + +def main(): + client = CiscoUCSMDriver() + #client.create_vlan("quantum-vlan-3", "3","172.20.231.27","admin", + # "c3l12345") + #client.create_profile("q-prof-3", "quantum-vlan-3","172.20.231.27", + # "admin", "c3l12345") + #client.get_dynamic_nic("dummy") + #client.get_dynamic_nic("dummy") + #client.release_dynamic_nic("dummy") + #client.get_dynamic_nic("dummy") + #client.change_vlan_in_profile("br100", "default", "test-2", + # "172.20.231.27","admin", + # "c3l12345") + client.change_vlan_in_profile("br100", "test-2", "default", + "172.20.231.27", "admin", "c3l12345") + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/cisco_ucs_plugin.py b/quantum/plugins/cisco/cisco_ucs_plugin.py new file mode 100644 index 00000000000..52195a55521 --- /dev/null +++ b/quantum/plugins/cisco/cisco_ucs_plugin.py @@ -0,0 +1,294 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import logging as LOG + +from quantum.common import exceptions as exc +from quantum.plugins.cisco import cisco_configuration as conf +from quantum.plugins.cisco import cisco_constants as const +from quantum.plugins.cisco import cisco_credentials as cred +from quantum.plugins.cisco import cisco_exceptions as cexc +from quantum.plugins.cisco import cisco_ucs_network_driver +from quantum.plugins.cisco import cisco_utils as cutil + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class UCSVICPlugin(object): + _networks = {} + + def __init__(self): + self._client = cisco_ucs_network_driver.CiscoUCSMDriver() + self._utils = cutil.DBUtils() + # TODO (Sumit) This is for now, when using only one chassis + self._ucsm_ip = conf.UCSM_IP_ADDRESS + self._ucsm_username = cred.Store.getUsername(conf.UCSM_IP_ADDRESS) + self._ucsm_password = cred.Store.getPassword(conf.UCSM_IP_ADDRESS) + # TODO (Sumit) Make the counter per UCSM + self._port_profile_counter = 0 + + def get_all_networks(self, tenant_id): + """ + Returns a dictionary containing all + for + the specified tenant. + """ + LOG.debug("UCSVICPlugin:get_all_networks() called\n") + return self._networks.values() + + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id): + """ + Creates a new Virtual Network, and assigns it + a symbolic name. + """ + LOG.debug("UCSVICPlugin:create_network() called\n") + self._client.create_vlan(vlan_name, str(vlan_id), self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + new_net_dict = {const.NET_ID: net_id, + const.NET_NAME: net_name, + const.NET_PORTS: {}, + const.NET_VLAN_NAME: vlan_name, + const.NET_VLAN_ID: vlan_id} + self._networks[net_id] = new_net_dict + return new_net_dict + + def delete_network(self, tenant_id, net_id): + """ + Deletes the network with the specified network identifier + belonging to the specified tenant. + """ + LOG.debug("UCSVICPlugin:delete_network() called\n") + net = self._networks.get(net_id) + # TODO (Sumit) : Verify that no attachments are plugged into the + # network + if net: + # TODO (Sumit) : Before deleting the network, make sure all the + # ports associated with this network are also deleted + self._client.delete_vlan(net[const.NET_VLAN_NAME], self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + self._networks.pop(net_id) + return net + raise exc.NetworkNotFound(net_id=net_id) + + def get_network_details(self, tenant_id, net_id): + """ + Deletes the Virtual Network belonging to a the + spec + """ + LOG.debug("UCSVICPlugin:get_network_details() called\n") + network = self._get_network(tenant_id, net_id) + return network + + def rename_network(self, tenant_id, net_id, new_name): + """ + Updates the symbolic name belonging to a particular + Virtual Network. + """ + LOG.debug("UCSVICPlugin:rename_network() called\n") + network = self._get_network(tenant_id, net_id) + network[const.NET_NAME] = new_name + return network + + def get_all_ports(self, tenant_id, net_id): + """ + Retrieves all port identifiers belonging to the + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:get_all_ports() called\n") + network = self._get_network(tenant_id, net_id) + ports_on_net = network[const.NET_PORTS].values() + return ports_on_net + + def create_port(self, tenant_id, net_id, port_state, port_id): + """ + Creates a port on the specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:create_port() called\n") + net = self._get_network(tenant_id, net_id) + ports = net[const.NET_PORTS] + # TODO (Sumit): This works on a single host deployment, + # in multi-host environment, dummy needs to be replaced with the + # hostname + dynamic_nic_name = self._client.get_dynamic_nic("dummy") + new_port_profile = self._create_port_profile(tenant_id, net_id, + port_id, + conf.DEFAULT_VLAN_NAME, + conf.DEFAULT_VLAN_ID) + profile_name = new_port_profile[const.PROFILE_NAME] + sql_query = "INSERT INTO ports (port_id, profile_name, dynamic_vnic," \ + "host, instance_name, instance_nic_name, used) VALUES" \ + "('%s', '%s', '%s', 'dummy', NULL, NULL, 0)" % \ + (port_id, profile_name, dynamic_nic_name) + self._utils.execute_db_query(sql_query) + new_port_dict = {const.PORT_ID: port_id, + const.PORT_STATE: const.PORT_UP, + const.ATTACHMENT: None, + const.PORT_PROFILE: new_port_profile} + ports[port_id] = new_port_dict + return new_port_dict + + def delete_port(self, tenant_id, net_id, port_id): + """ + Deletes a port on a specified Virtual Network, + if the port contains a remote interface attachment, + the remote interface should first be un-plugged and + then the port can be deleted. + """ + LOG.debug("UCSVICPlugin:delete_port() called\n") + port = self._get_port(tenant_id, net_id, port_id) + if port[const.ATTACHMENT]: + raise exc.PortInUse(net_id=net_id, port_id=port_id, + att_id=port[const.ATTACHMENT]) + try: + #TODO (Sumit): Before deleting port profile make sure that there + # is no VM using this port profile + self._client.release_dynamic_nic("dummy") + port_profile = port[const.PORT_PROFILE] + self._delete_port_profile(port_id, + port_profile[const.PROFILE_NAME]) + sql_query = "delete from ports where port_id = \"%s\"" % \ + (port[const.PORT_ID]) + self._utils.execute_db_query(sql_query) + net = self._get_network(tenant_id, net_id) + net[const.NET_PORTS].pop(port_id) + except KeyError: + raise exc.PortNotFound(net_id=net_id, port_id=port_id) + + def update_port(self, tenant_id, net_id, port_id, port_state): + """ + Updates the state of a port on the specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:update_port() called\n") + port = self._get_port(tenant_id, net_id, port_id) + self._validate_port_state(port_state) + port[const.PORT_STATE] = port_state + return port + + 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. + """ + LOG.debug("UCSVICPlugin:get_port_details() called\n") + return self._get_port(tenant_id, net_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 + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:plug_interface() called\n") + self._validate_attachment(tenant_id, net_id, port_id, + remote_interface_id) + port = self._get_port(tenant_id, net_id, port_id) + if port[const.ATTACHMENT]: + raise exc.PortInUse(net_id=net_id, port_id=port_id, + att_id=port[const.ATTACHMENT]) + port[const.ATTACHMENT] = remote_interface_id + port_profile = port[const.PORT_PROFILE] + profile_name = port_profile[const.PROFILE_NAME] + old_vlan_name = port_profile[const.PROFILE_VLAN_NAME] + new_vlan_name = self._get_vlan_name_for_network(tenant_id, net_id) + new_vlan_id = self._get_vlan_id_for_network(tenant_id, net_id) + self._client.change_vlan_in_profile(profile_name, old_vlan_name, + new_vlan_name, self._ucsm_ip, + self._ucsm_username, + self._ucsm_password) + port_profile[const.PROFILE_VLAN_NAME] = new_vlan_name + port_profile[const.PROFILE_VLAN_ID] = new_vlan_id + + def unplug_interface(self, tenant_id, net_id, port_id): + """ + Detaches a remote interface from the specified port on the + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:unplug_interface() called\n") + port = self._get_port(tenant_id, net_id, port_id) + port[const.ATTACHMENT] = None + port_profile = port[const.PORT_PROFILE] + profile_name = port_profile[const.PROFILE_NAME] + old_vlan_name = port_profile[const.PROFILE_VLAN_NAME] + new_vlan_name = conf.DEFAULT_VLAN_NAME + self._client.change_vlan_in_profile(profile_name, old_vlan_name, + new_vlan_name, self._ucsm_ip, + self._ucsm_username, + self._ucsm_password) + port_profile[const.PROFILE_VLAN_NAME] = conf.DEFAULT_VLAN_NAME + port_profile[const.PROFILE_VLAN_ID] = conf.DEFAULT_VLAN_ID + + def _get_profile_name(self, port_id): + profile_name = conf.PROFILE_NAME_PREFIX + port_id + return profile_name + + def _validate_port_state(self, port_state): + if port_state.upper() not in (const.PORT_UP, const.PORT_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[const.NET_PORTS].values(): + if port[const.ATTACHMENT] == remote_interface_id: + raise exc.AlreadyAttached(net_id=network_id, + port_id=port_id, + att_id=port[const.ATTACHMENT], + att_port_id=port[const.PORT_ID]) + + def _get_network(self, tenant_id, network_id): + network = self._networks.get(network_id) + if not network: + raise exc.NetworkNotFound(net_id=network_id) + return network + + def _get_vlan_name_for_network(self, tenant_id, network_id): + net = self._get_network(tenant_id, network_id) + vlan_name = net[const.NET_VLAN_NAME] + return vlan_name + + def _get_vlan_id_for_network(self, tenant_id, network_id): + net = self._get_network(tenant_id, network_id) + vlan_id = net[const.NET_VLAN_ID] + return vlan_id + + def _get_port(self, tenant_id, network_id, port_id): + net = self._get_network(tenant_id, network_id) + port = net[const.NET_PORTS].get(port_id) + if not port: + raise exc.PortNotFound(net_id=network_id, port_id=port_id) + return port + + def _create_port_profile(self, tenant_id, net_id, port_id, vlan_name, + vlan_id): + if self._port_profile_counter >= int(conf.MAX_UCSM_PORT_PROFILES): + raise cexc.UCSMPortProfileLimit(net_id=net_id, port_id=port_id) + profile_name = self._get_profile_name(port_id) + self._client.create_profile(profile_name, vlan_name, self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + self._port_profile_counter += 1 + new_port_profile = {const.PROFILE_NAME: profile_name, + const.PROFILE_VLAN_NAME: vlan_name, + const.PROFILE_VLAN_ID: vlan_id} + return new_port_profile + + def _delete_port_profile(self, port_id, profile_name): + self._client.delete_profile(profile_name, self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + self._port_profile_counter -= 1 diff --git a/quantum/plugins/cisco/cisco_utils.py b/quantum/plugins/cisco/cisco_utils.py new file mode 100644 index 00000000000..bd0b257d549 --- /dev/null +++ b/quantum/plugins/cisco/cisco_utils.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import MySQLdb +import logging as LOG +import sys +import traceback + +from quantum.common import exceptions as exc +from quantum.plugins.cisco import cisco_configuration as conf +from quantum.plugins.cisco import cisco_constants as const +from quantum.plugins.cisco import cisco_credentials as cred + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class DBUtils(object): + + def __init__(self): + pass + + def _get_db_connection(self): + db_ip = conf.DB_SERVER_IP + db_username = cred.Store.getUsername(db_ip) + db_password = cred.Store.getPassword(db_ip) + self.db = MySQLdb.connect(db_ip, db_username, db_password, + conf.DB_NAME) + return self.db + + def execute_db_query(self, sql_query): + db = self._get_db_connection() + cursor = db.cursor() + try: + cursor.execute(sql_query) + results = cursor.fetchall() + db.commit() + LOG.debug("DB query execution succeeded: %s" % sql_query) + except: + db.rollback() + LOG.debug("DB query execution failed: %s" % sql_query) + traceback.print_exc() + db.close() diff --git a/quantum/plugins/cisco/get-vif.sh b/quantum/plugins/cisco/get-vif.sh new file mode 100755 index 00000000000..d424b5fea28 --- /dev/null +++ b/quantum/plugins/cisco/get-vif.sh @@ -0,0 +1,15 @@ +#!/bin/bash +eths=`ifconfig -a | grep eth | cut -f1 -d " "` +for eth in $eths; do + bdf=`ethtool -i $eth | grep bus-info | cut -f2 -d " "` + deviceid=`lspci -n -s $bdf | cut -f4 -d ":" | cut -f1 -d " "` + if [ $deviceid = "0044" ]; then + used=`/sbin/ip link show $eth | grep "UP"` + avail=$? + if [ $avail -eq 1 ]; then + echo $eth + exit + fi + fi +done + diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py new file mode 100644 index 00000000000..e98344e098a --- /dev/null +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -0,0 +1,348 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import logging as LOG + +from quantum.common import exceptions as exc +from quantum.plugins.cisco import cisco_configuration as conf +from quantum.plugins.cisco import cisco_constants as const +from quantum.plugins.cisco import cisco_credentials as cred +from quantum.plugins.cisco import cisco_exceptions as cexc +from quantum.plugins.cisco import cisco_nexus_plugin +from quantum.plugins.cisco import cisco_ucs_plugin +from quantum.plugins.cisco import cisco_utils as cutil + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class L2Network(object): + _networks = {} + _tenants = {} + _portprofiles = {} + + def __init__(self): + self._net_counter = 0 + self._portprofile_counter = 0 + self._vlan_counter = int(conf.VLAN_START) - 1 + self._ucs_plugin = cisco_ucs_plugin.UCSVICPlugin() + self._nexus_plugin = cisco_nexus_plugin.NexusPlugin() + + """ + Core API implementation + """ + def get_all_networks(self, tenant_id): + """ + Returns a dictionary containing all + for + the specified tenant. + """ + LOG.debug("get_all_networks() called\n") + return self._networks.values() + + def create_network(self, tenant_id, net_name): + """ + Creates a new Virtual Network, and assigns it + a symbolic name. + """ + LOG.debug("create_network() called\n") + new_net_id = self._get_unique_net_id(tenant_id) + vlan_id = self._get_vlan_for_tenant(tenant_id, net_name) + vlan_name = self._get_vlan_name(new_net_id, str(vlan_id)) + self._nexus_plugin.create_network(tenant_id, net_name, new_net_id, + vlan_name, vlan_id) + self._ucs_plugin.create_network(tenant_id, net_name, new_net_id, + vlan_name, vlan_id) + new_net_dict = {const.NET_ID: new_net_id, + const.NET_NAME: net_name, + const.NET_PORTS: {}, + const.NET_VLAN_NAME: vlan_name, + const.NET_VLAN_ID: vlan_id, + const.NET_TENANTS: [tenant_id]} + self._networks[new_net_id] = new_net_dict + tenant = self._get_tenant(tenant_id) + tenant_networks = tenant[const.TENANT_NETWORKS] + tenant_networks[new_net_id] = new_net_dict + return new_net_dict + + def delete_network(self, tenant_id, net_id): + """ + Deletes the network with the specified network identifier + belonging to the specified tenant. + """ + LOG.debug("delete_network() called\n") + net = self._networks.get(net_id) + # TODO (Sumit) : Verify that no attachments are plugged into the + # network + if net: + # TODO (Sumit) : Before deleting the network, make sure all the + # ports associated with this network are also deleted + self._nexus_plugin.delete_network(tenant_id, net_id) + self._ucs_plugin.delete_network(tenant_id, net_id) + self._networks.pop(net_id) + tenant = self._get_tenant(tenant_id) + tenant_networks = tenant[const.TENANT_NETWORKS] + tenant_networks.pop(net_id) + return net + # Network not found + raise exc.NetworkNotFound(net_id=net_id) + + def get_network_details(self, tenant_id, net_id): + """ + Deletes the Virtual Network belonging to a the + spec + """ + LOG.debug("get_network_details() called\n") + network = self._get_network(tenant_id, net_id) + return network + + def rename_network(self, tenant_id, net_id, new_name): + """ + Updates the symbolic name belonging to a particular + Virtual Network. + """ + LOG.debug("rename_network() called\n") + self._nexus_plugin.rename_network(tenant_id, net_id) + self._ucs_plugin.rename_network(tenant_id, net_id) + network = self._get_network(tenant_id, net_id) + network[const.NET_NAME] = new_name + return network + + def get_all_ports(self, tenant_id, net_id): + """ + Retrieves all port identifiers belonging to the + specified Virtual Network. + """ + LOG.debug("get_all_ports() called\n") + network = self._get_network(tenant_id, net_id) + ports_on_net = network[const.NET_PORTS].values() + return ports_on_net + + def create_port(self, tenant_id, net_id, port_state=None): + """ + Creates a port on the specified Virtual Network. + """ + LOG.debug("create_port() called\n") + net = self._get_network(tenant_id, net_id) + ports = net[const.NET_PORTS] + unique_port_id_string = self._get_unique_port_id(tenant_id, net_id) + self._ucs_plugin.create_port(tenant_id, net_id, port_state, + unique_port_id_string) + new_port_dict = {const.PORT_ID: unique_port_id_string, + const.PORT_STATE: const.PORT_UP, + const.ATTACHMENT: None} + ports[unique_port_id_string] = new_port_dict + return new_port_dict + + def delete_port(self, tenant_id, net_id, port_id): + """ + Deletes a port on a specified Virtual Network, + if the port contains a remote interface attachment, + the remote interface should first be un-plugged and + then the port can be deleted. + """ + LOG.debug("delete_port() called\n") + port = self._get_port(tenant_id, net_id, port_id) + if port[const.ATTACHMENT]: + raise exc.PortInUse(net_id=net_id, port_id=port_id, + att_id=port[const.ATTACHMENT]) + try: + #TODO (Sumit): Before deleting port profile make sure that there + # is no VM using this port profile + self._ucs_plugin.delete_port(tenant_id, net_id, port_id) + net = self._get_network(tenant_id, net_id) + net[const.NET_PORTS].pop(port_id) + except KeyError: + raise exc.PortNotFound(net_id=net_id, port_id=port_id) + + def update_port(self, tenant_id, net_id, port_id, port_state): + """ + Updates the state of a port on the specified Virtual Network. + """ + LOG.debug("update_port() called\n") + port = self._get_port(tenant_id, net_id, port_id) + self._validate_port_state(port_state) + port[const.PORT_STATE] = port_state + return port + + 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. + """ + LOG.debug("get_port_details() called\n") + return self._get_port(tenant_id, net_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 + specified Virtual Network. + """ + LOG.debug("plug_interface() called\n") + self._validate_attachment(tenant_id, net_id, port_id, + remote_interface_id) + port = self._get_port(tenant_id, net_id, port_id) + if port[const.ATTACHMENT]: + raise exc.PortInUse(net_id=net_id, port_id=port_id, + att_id=port[const.ATTACHMENT]) + self._ucs_plugin.plug_interface(tenant_id, net_id, port_id, + remote_interface_id) + port[const.ATTACHMENT] = 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. + """ + LOG.debug("unplug_interface() called\n") + port = self._get_port(tenant_id, net_id, port_id) + self._ucs_plugin.unplug_interface(tenant_id, net_id, + port_id) + port[const.ATTACHMENT] = None + + """ + Extension API implementation + """ + def get_all_portprofiles(self, tenant_id): + return self._portprofiles.values() + + def get_portprofile_details(self, tenant_id, profile_id): + return self._get_portprofile(tenant_id, profile_id) + + def create_portprofile(self, tenant_id, profile_name, vlan_id): + profile_id = self._get_unique_profile_id(tenant_id) + new_port_profile_dict = {const.PROFILE_ID: profile_id, + const.PROFILE_NAME: profile_name, + const.PROFILE_VLAN_ID: vlan_id, + const.PROFILE_QOS: None} + self._portprofiles[profile_id] = new_port_profile_dict + tenant = self._get_tenant(tenant_id) + portprofiles = tenant[const.TENANT_PORTPROFILES] + portprofiles[profile_id] = new_port_profile_dict + return new_profile_dict + + def delete_portprofile(self, tenant_id, profile_id): + portprofile = self._get_portprofile(tenant_id, profile_id) + self._portprofile.pop(profile_id) + tenant = self._get_tenant(tenant_id) + tenant[const.TENANT_PORTPROFILES].pop(profile_id) + + def rename_portprofile(self, tenant_id, profile_id, new_name): + portprofile = self._get_portprofile(tenant_id, profile_id) + portprofile[const.PROFILE_NAME] = new_name + return portprofile + + """ + Private functions + """ + def _get_vlan_for_tenant(self, tenant_id, net_name): + # TODO (Sumit): + # The VLAN ID for a tenant might need to be obtained from + # somewhere (from Donabe/Melange?) + # Also need to make sure that the VLAN ID is not being used already + # Currently, just a wrap-around counter ranging from VLAN_START to + # VLAN_END + self._vlan_counter += 1 + self._vlan_counter %= int(conf.VLAN_END) + if self._vlan_counter < int(conf.VLAN_START): + self._vlan_counter = int(conf.VLAN_START) + return self._vlan_counter + + def _get_vlan_name(self, net_id, vlan): + vlan_name = conf.VLAN_NAME_PREFIX + net_id + "-" + vlan + return vlan_name + + def _validate_port_state(self, port_state): + if port_state.upper() not in (const.PORT_UP, const.PORT_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[const.NET_PORTS].values(): + if port[const.ATTACHMENT] == remote_interface_id: + raise exc.AlreadyAttached(net_id=network_id, + port_id=port_id, + att_id=port[const.ATTACHMENT], + att_port_id=port[const.PORT_ID]) + + def _get_network(self, tenant_id, network_id): + network = self._networks.get(network_id) + if not network: + raise exc.NetworkNotFound(net_id=network_id) + return network + + def _get_tenant(self, tenant_id): + tenant = self._tenants.get(tenant_id) + if not tenant: + LOG.debug("Creating new tenant record with tenant id %s\n" % + tenant_id) + tenant = {const.TENANT_ID: tenant_id, + const.TENANT_NAME: tenant_id, + const.TENANT_NETWORKS: {}, + const.TENANT_PORTPROFILES: {}} + self._tenants[tenant_id] = tenant + return tenant + + def _get_port(self, tenant_id, network_id, port_id): + net = self._get_network(tenant_id, network_id) + port = net[const.NET_PORTS].get(port_id) + if not port: + raise exc.PortNotFound(net_id=network_id, port_id=port_id) + return port + + def _get_portprofile(self, tenant_id, portprofile_id): + portprofile = self._portprofiles.get(portprofile_id) + if not portprofile: + raise cexc.PortProfileNotFound(tenant_id=tenant_id, + profile_id=portprofile_id) + return portprofile + + def _get_unique_net_id(self, tenant_id): + self._net_counter += 1 + self._net_counter %= int(conf.MAX_NETWORKS) + id = tenant_id[:3] + \ + "-n-" + ("0" * (6 - len(str(self._net_counter)))) + \ + str(self._net_counter) + # TODO (Sumit): Need to check if the ID has already been allocated + return id + + def _get_unique_port_id(self, tenant_id, net_id): + net = self._get_network(tenant_id, net_id) + ports = net[const.NET_PORTS] + if len(ports) == 0: + new_port_id = 1 + else: + new_port_id = max(ports.keys()) + 1 + id = net_id + "-p-" + str(new_port_id) + # TODO (Sumit): Need to check if the ID has already been allocated + return id + + def _get_unique_profile_id(self, tenant_id): + self._portprofile_counter += 1 + self._portprofile_counter %= int(conf.MAX_PORT_PROFILES) + id = tenant_id[:3] + "-pp-" + \ + ("0" * (6 - len(str(self._net_counter)))) + str(self._net_counter) + # TODO (Sumit): Need to check if the ID has already been allocated + return id + +# TODO (Sumit): + # (1) Persistent storage From c644206e43a9cc198833f5ae544174c8250bffd1 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 8 Jul 2011 14:29:45 -0700 Subject: [PATCH 06/76] This file is not required. --- quantum/plugins/cisco/cisco_ucs.py | 102 ----------------------------- 1 file changed, 102 deletions(-) delete mode 100644 quantum/plugins/cisco/cisco_ucs.py diff --git a/quantum/plugins/cisco/cisco_ucs.py b/quantum/plugins/cisco/cisco_ucs.py deleted file mode 100644 index 0723da9f0a0..00000000000 --- a/quantum/plugins/cisco/cisco_ucs.py +++ /dev/null @@ -1,102 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# -# @author: Sumit Naiksatam, Cisco Systems, Inc. -# -# - -import MySQLdb -import sys, traceback - -from nova import flags -from nova import log as logging - -FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.virt.libvirt_conn') -# -# TODO (Sumit): The following are defaults, but we might need to make it conf file driven as well -# - -flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server') -flags.DEFINE_string('db_username', "root", 'DB username') -flags.DEFINE_string('db_password', "nova", 'DB paswwprd') -flags.DEFINE_string('db_name', "nova", 'DB name') -flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova') -flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud controller hostname') - -class CiscoUCSComputeDriver(object): - def __init__(self): - pass - - def _get_db_connection(self): - self.db = MySQLdb.connect(FLAGS.db_server_ip, FLAGS.db_username, FLAGS.db_password, FLAGS.db_name) - return self.db - - def _execute_db_query(self, sql_query): - db = self._get_db_connection() - cursor = db.cursor() - try: - cursor.execute(sql_query) - results = cursor.fetchall() - db.commit() - print "DB query execution succeeded: %s" % sql_query - except: - db.rollback() - print "DB query execution failed: %s" % sql_query - traceback.print_exc() - db.close() - return results - - def reserve_port(self, instance_name, instance_nic_name): - sql_query = "SELECT * from ports WHERE used='0'" - results = self._execute_db_query(sql_query) - if len(results) == 0: - print "No ports available/n" - return 0 - else: - for row in results: - port_id = row[0]; - sql_query = "UPDATE ports SET instance_name = '%s', instance_nic_name = '%s' WHERE port_id = '%s'" % (instance_name, instance_nic_name, port_id) - results = self._execute_db_query(sql_query) - return port_id; - return 0 - - def get_port_details(self, port_id): - port_details = {} - sql_query = "SELECT * from ports WHERE port_id='%s'" % (port_id) - results = self._execute_db_query(sql_query) - if len(results) == 0: - print "Could not fetch port from DB for port_id = %s/n" % port_id - return - else: - for row in results: - profile_name = row[1]; - dynamic_vnic = row[2]; - sql_query = "UPDATE ports SET used = %d WHERE port_id = '%s'" % (1, port_id) - results = self._execute_db_query(sql_query) - port_details = {'profile_name':profile_name, 'dynamic_vnic':dynamic_vnic} - return port_details; - - def release_port(self, instance_name, instance_nic_name): - sql_query = "SELECT * from ports WHERE instance_name='%s' and instance_nic_name='%s'" % (instance_name, instance_nic_name) - results = self._execute_db_query(sql_query) - if len(results) == 0: - print "No matching ports found for releasing/n" - return 0 - else: - for row in results: - port_id = row[0]; - sql_query = "UPDATE ports SET instance_name = NULL, instance_nic_name = NULL, used = 0 WHERE port_id = '%s'" % (port_id) - results = self._execute_db_query(sql_query) - return port_id; - return 0 - -def main(): - client = CiscoUCSComputeDriver() - port_id = client.reserve_port("instance-1", "eth1") - port_details = client.get_port_details(port_id) - print "profile_name %s dynamic_vnic %s\n" % (port_details['profile_name'], port_details['dynamic_vnic']) - port_id = client.release_port("instance-1", "eth1") - -if __name__ == '__main__': - main() From 5c1bca06ff247c712b8df587a083945a9725c183 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 8 Jul 2011 14:40:56 -0700 Subject: [PATCH 07/76] Changed some credentials (does not affect functionality). --- quantum/plugins/cisco/cisco_credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/cisco_credentials.py b/quantum/plugins/cisco/cisco_credentials.py index c37d00f1abe..4c8761f6b7f 100644 --- a/quantum/plugins/cisco/cisco_credentials.py +++ b/quantum/plugins/cisco/cisco_credentials.py @@ -25,7 +25,7 @@ LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) _creds_dictionary = { - '172.20.231.27': ["admin", "c3l12345"], + '10.10.10.10': ["username", "password"], '127.0.0.1': ["root", "nova"] } From 632b5bb8003ccfb2f5c9687b31738e081407647e Mon Sep 17 00:00:00 2001 From: Rick Clark Date: Fri, 8 Jul 2011 17:46:20 -0500 Subject: [PATCH 08/76] minor pep8 fix. --- quantum/plugins/cisco/cisco_credentials.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/quantum/plugins/cisco/cisco_credentials.py b/quantum/plugins/cisco/cisco_credentials.py index 4c8761f6b7f..c0cd282530c 100644 --- a/quantum/plugins/cisco/cisco_credentials.py +++ b/quantum/plugins/cisco/cisco_credentials.py @@ -24,10 +24,8 @@ from quantum.plugins.cisco import cisco_constants as const LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) -_creds_dictionary = { - '10.10.10.10': ["username", "password"], - '127.0.0.1': ["root", "nova"] -} +_creds_dictionary = {'10.10.10.10': ["username", "password"], + '127.0.0.1': ["root", "nova"]} class Store(object): From 617e1b5ab83c5e31cfe0d2e7c14aa3fb6dffc896 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Tue, 12 Jul 2011 14:50:49 -0700 Subject: [PATCH 09/76] Required for recognizing the "cisco" package. Missed in the initial checkin. --- quantum/plugins/cisco/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 quantum/plugins/cisco/__init__.py diff --git a/quantum/plugins/cisco/__init__.py b/quantum/plugins/cisco/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 7fb19f22ce31c1023568cdd6ba9e5b35b15f5cf9 Mon Sep 17 00:00:00 2001 From: "rohitagarwalla roagarwa@cisco.com" <> Date: Wed, 13 Jul 2011 12:39:09 -0700 Subject: [PATCH 10/76] Porting shell script get-vif.sh to python module get-vif.py for cisco ucsm module --- quantum/plugins/cisco/get-vif.py | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 quantum/plugins/cisco/get-vif.py diff --git a/quantum/plugins/cisco/get-vif.py b/quantum/plugins/cisco/get-vif.py new file mode 100644 index 00000000000..0512ecb0dd5 --- /dev/null +++ b/quantum/plugins/cisco/get-vif.py @@ -0,0 +1,37 @@ +import sys +import subprocess + + +def get_next_dynic(argv=[]): + cmd = ["ifconfig", "-a"] + f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\ + communicate()[0] + eths = [lines.split(' ')[0] for lines in f_cmd_output.splitlines() \ + if "eth" in lines] + #print eths + for eth in eths: + cmd = ["ethtool", "-i", eth] + f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\ + communicate()[0] + bdf = [lines.split(' ')[1] for lines in f_cmd_output.splitlines() \ + if "bus-info" in lines] + #print bdf + cmd = ["lspci", "-n", "-s", bdf[0]] + f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\ + communicate()[0] + deviceid = [(lines.split(':')[3]).split(' ')[0] \ + for lines in f_cmd_output.splitlines()] + #print deviceid + if deviceid[0] == "0044": + cmd = ["/usr/sbin/ip", "link", "show", eth] + f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\ + communicate()[0] + used = [lines for lines in f_cmd_output.splitlines() \ + if "UP" in lines] + if not used: + break + return eth + +if __name__ == '__main__': + nic = get_next_dynic(sys.argv) + print nic From 0d9bf52025ca3044c9ee1e02a9f6dbe30e6666bc Mon Sep 17 00:00:00 2001 From: Debo Date: Thu, 14 Jul 2011 17:11:55 -0700 Subject: [PATCH 11/76] Very initial version of the nxos driver .... lets call it ver 0.0.1! yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy added: quantum/plugins/cisco/nxosapi.py --- quantum/plugins/cisco/nxosapi.py | 172 +++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 quantum/plugins/cisco/nxosapi.py diff --git a/quantum/plugins/cisco/nxosapi.py b/quantum/plugins/cisco/nxosapi.py new file mode 100644 index 00000000000..0f0ebe04f01 --- /dev/null +++ b/quantum/plugins/cisco/nxosapi.py @@ -0,0 +1,172 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 Cisco Systems Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# @author: Debojyoti Dutta, Cisco Systems, Inc. + +import sys +import os +import warnings +warnings.simplefilter("ignore", DeprecationWarning) +from ncclient import manager +from ncclient import NCClientError +from ncclient.transport.errors import * + +exec_conf_prefix = """ + + + <__XML__MODE__exec_configure> +""" + + +exec_conf_postfix = """ + + + +""" + + +cmd_vlan_conf_snippet = """ + + + <__XML__PARAM_value>%s + <__XML__MODE_vlan> + + %s + + + active + + + + + + + +""" + +cmd_no_vlan_conf_snippet = """ + + + + <__XML__PARAM_value>%s + + + +""" + +cmd_vlan_int_snippet = """ + + + %s + <__XML__MODE_if-ethernet-switch> + + + + + + <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> + %s + + + + + + + + +""" + + +cmd_no_vlan_int_snippet = """ + + + %s + <__XML__MODE_if-ethernet-switch> + + + + + + + <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> + %s + + + + + + + + + +""" + + +filter_show_vlan_brief_snippet = """ + + + + + """ + + +def nxos_connect(host, port, user, password): + try: + m = manager.connect(host=host, port=port, username=user, + password=password) + return m + except SSHUnknownHostError: + sys.stderr.write('SSH unknown host error\n') + exit() + + +def enable_vlan(mgr, vlanid, vlanname): + confstr = cmd_vlan_conf_snippet % (vlanid, vlanname) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + mgr.edit_config(target='running', config=confstr) + + +def disable_vlan(mgr, vlanid): + confstr = cmd_no_vlan_conf_snippet % vlanid + confstr = exec_conf_prefix + confstr + exec_conf_postfix + mgr.edit_config(target='running', config=confstr) + + +def enable_vlan_on_trunk_int(mgr, interface, vlanid): + confstr = cmd_vlan_int_snippet % (interface, vlanid) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + print confstr + mgr.edit_config(target='running', config=confstr) + + +def disable_vlan_on_trunk_int(mgr, interface, vlanid): + confstr = cmd_no_vlan_int_snippet % (interface, vlanid) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + print confstr + mgr.edit_config(target='running', config=confstr) + + +def test_nxos_api(host, user, password): + with nxos_connect(host, port=22, user=user, password=password) as m: + enable_vlan(m, '100', 'ccn1') + enable_vlan_on_trunk_int(m, '2/1', '100') + disable_vlan_on_trunk_int(m, '2/1', '100') + disable_vlan(m, '100') + result = m.get(("subtree", filter_show_vlan_brief_snippet)) + #print result + + +if __name__ == '__main__': + test_nxos_api(sys.argv[1], sys.argv[2], sys.argv[3]) From 62f0f53ddd48721e2bdcddaca27b0dcb59a071b1 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Thu, 14 Jul 2011 18:24:39 -0700 Subject: [PATCH 12/76] Changes to support port-profile extension. Fixed an error in the README file. --- quantum/plugins/cisco/README | 2 +- quantum/plugins/cisco/cisco_constants.py | 3 ++- quantum/plugins/cisco/cisco_exceptions.py | 7 ++++- quantum/plugins/cisco/l2network_plugin.py | 32 +++++++++++++++++++---- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index fcc50018f94..6c2bc0cdcdd 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -31,7 +31,7 @@ cisco_configuration.py cisco_constants.py cisco_credentials.py cisco_exceptions.py -cisco_nexus_network_driver.py +cisco_nexus_plugin.py cisco_ucs_network_driver.py cisco_ucs_plugin.py cisco_utils.py diff --git a/quantum/plugins/cisco/cisco_constants.py b/quantum/plugins/cisco/cisco_constants.py index 115addf4237..7f2367d7947 100644 --- a/quantum/plugins/cisco/cisco_constants.py +++ b/quantum/plugins/cisco/cisco_constants.py @@ -40,7 +40,8 @@ PORT_PROFILE = 'port-profile' PROFILE_ID = 'profile-id' PROFILE_NAME = 'profile-name' PROFILE_VLAN_NAME = 'profile-vlan-name' -PROFILE_VLAN_ID = 'profile-vlan-id' +PROFILE_VLAN_ID = 'vlan-id' PROFILE_QOS = 'profile-qos' +PROFILE_ASSOCIATIONS = 'assignment' LOGGER_COMPONENT_NAME = "cisco_plugin" diff --git a/quantum/plugins/cisco/cisco_exceptions.py b/quantum/plugins/cisco/cisco_exceptions.py index 2829329c859..06e5e53639e 100644 --- a/quantum/plugins/cisco/cisco_exceptions.py +++ b/quantum/plugins/cisco/cisco_exceptions.py @@ -48,5 +48,10 @@ class NetworksLimit(exceptions.QuantumException): class PortProfileNotFound(exceptions.QuantumException): - message = _("Port profile %(port_id)s could not be found " \ + message = _("Port profile %(portprofile_id)s could not be found " \ "for tenant %(tenant_id)s") + + +class PortProfileInvalidDelete(exceptions.QuantumException): + message = _("Port profile %(profile_id)s could not be deleted " \ + "for tenant %(tenant_id)s since port associations exist") diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index e98344e098a..47f63874eed 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -230,25 +230,47 @@ class L2Network(object): profile_id = self._get_unique_profile_id(tenant_id) new_port_profile_dict = {const.PROFILE_ID: profile_id, const.PROFILE_NAME: profile_name, + const.PROFILE_ASSOCIATIONS: [], const.PROFILE_VLAN_ID: vlan_id, const.PROFILE_QOS: None} self._portprofiles[profile_id] = new_port_profile_dict tenant = self._get_tenant(tenant_id) portprofiles = tenant[const.TENANT_PORTPROFILES] portprofiles[profile_id] = new_port_profile_dict - return new_profile_dict + return new_port_profile_dict def delete_portprofile(self, tenant_id, profile_id): portprofile = self._get_portprofile(tenant_id, profile_id) - self._portprofile.pop(profile_id) - tenant = self._get_tenant(tenant_id) - tenant[const.TENANT_PORTPROFILES].pop(profile_id) + associations = portprofile[const.PROFILE_ASSOCIATIONS] + if len(associations) > 0: + raise cexc.PortProfileInvalidDelete(tenant_id=tenant_id, + profile_id=profile_id) + else: + self._portprofiles.pop(profile_id) + tenant = self._get_tenant(tenant_id) + tenant[const.TENANT_PORTPROFILES].pop(profile_id) def rename_portprofile(self, tenant_id, profile_id, new_name): portprofile = self._get_portprofile(tenant_id, profile_id) portprofile[const.PROFILE_NAME] = new_name return portprofile + def associate_portprofile(self, tenant_id, net_id, + port_id, portprofile_id): + portprofile = self._get_portprofile(tenant_id, portprofile_id) + associations = portprofile[const.PROFILE_ASSOCIATIONS] + associations.append(port_id) + + def disassociate_portprofile(self, tenant_id, net_id, + port_id, portprofile_id): + portprofile = self._get_portprofile(tenant_id, portprofile_id) + associations = portprofile[const.PROFILE_ASSOCIATIONS] + associations.remove(port_id) + + def create_defaultPProfile(self, tenant_id, network_id, profile_name, + vlan_id): + pass + """ Private functions """ @@ -313,7 +335,7 @@ class L2Network(object): portprofile = self._portprofiles.get(portprofile_id) if not portprofile: raise cexc.PortProfileNotFound(tenant_id=tenant_id, - profile_id=portprofile_id) + portprofile_id=portprofile_id) return portprofile def _get_unique_net_id(self, tenant_id): From a8915ac3bc0e593c3ba7bc9b56f96e60813a2c0c Mon Sep 17 00:00:00 2001 From: Ying Liu Date: Fri, 15 Jul 2011 17:33:36 -0700 Subject: [PATCH 13/76] add api extensions (including portprofiles resources and associate/disassociate actions.) --- etc/quantum.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etc/quantum.conf b/etc/quantum.conf index ba96a9a275f..6bd962790c2 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -15,6 +15,7 @@ bind_port = 9696 use = egg:Paste#urlmap /: quantumversions /v0.1: quantumapi +/v0.1/extensions:cisco_extensions [app:quantumversions] paste.app_factory = quantum.api.versions:Versions.factory @@ -22,4 +23,6 @@ paste.app_factory = quantum.api.versions:Versions.factory [app:quantumapi] paste.app_factory = quantum.api:APIRouterV01.factory +[app:cisco_extensions] +paste.app_factory = cisco_extensions:ExtRouterV01.factory From 6d25812ace7585c8f9187d6daf0c6db91268aabd Mon Sep 17 00:00:00 2001 From: Ying Liu Date: Fri, 15 Jul 2011 17:40:33 -0700 Subject: [PATCH 14/76] add extension code in.(last push does not include this directory.) --- cisco_extensions/__init__.py | 71 ++++++++++++ cisco_extensions/exceptions.py | 148 +++++++++++++++++++++++++ cisco_extensions/extensions.py | 42 ++++++++ cisco_extensions/faults.py | 111 +++++++++++++++++++ cisco_extensions/portprofiles.py | 180 +++++++++++++++++++++++++++++++ cisco_extensions/pprofiles.py | 45 ++++++++ 6 files changed, 597 insertions(+) create mode 100644 cisco_extensions/__init__.py create mode 100644 cisco_extensions/exceptions.py create mode 100644 cisco_extensions/extensions.py create mode 100644 cisco_extensions/faults.py create mode 100644 cisco_extensions/portprofiles.py create mode 100644 cisco_extensions/pprofiles.py diff --git a/cisco_extensions/__init__.py b/cisco_extensions/__init__.py new file mode 100644 index 00000000000..5fc5d889193 --- /dev/null +++ b/cisco_extensions/__init__.py @@ -0,0 +1,71 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ying Liu, Cisco Systems, Inc. +# + +import logging +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 +from quantum.common import flags +from quantum.common import wsgi +from cisco_extensions import portprofiles +from cisco_extensions import extensions + + +LOG = logging.getLogger('quantum_extension.api') +FLAGS = flags.FLAGS + + +class ExtRouterV01(wsgi.Router): + """ + Routes requests on the Quantum API to the appropriate controller + """ + + def __init__(self, ext_mgr=None): + uri_prefix = '/tenants/{tenant_id}/' + + mapper = routes.Mapper() + plugin = manager.QuantumManager().get_plugin() + controller = portprofiles.Controller(plugin) + ext_controller = extensions.Controller(plugin) + mapper.connect("home", "/", controller=ext_controller, + action="list_extension", + conditions=dict(method=['GET'])) + #mapper.redirect("/", "www.google.com") + mapper.resource("portprofiles", "portprofiles", + controller=controller, + path_prefix=uri_prefix) + mapper.connect("associate_portprofile", + uri_prefix + + 'portprofiles/{portprofile_id}/assignment{.format}', + controller=controller, + action="associate_portprofile", + conditions=dict(method=['PUT'])) + mapper.connect("disassociate_portprofile", + uri_prefix + + 'portprofiles/{portprofile_id}/assignment{.format}', + controller=controller, + action="disassociate_portprofile", + conditions=dict(method=['DELETE'])) + + super(ExtRouterV01, self).__init__(mapper) diff --git a/cisco_extensions/exceptions.py b/cisco_extensions/exceptions.py new file mode 100644 index 00000000000..415731e3851 --- /dev/null +++ b/cisco_extensions/exceptions.py @@ -0,0 +1,148 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ying Liu, Cisco Systems, Inc. +# +import logging + + +class ExtensionException(Exception): + """Quantum Cisco api 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 + with the keyword arguments provided to the constructor. + + """ + message = _("An unknown exception occurred.") + + def __init__(self, **kwargs): + try: + self._error_string = self.message % kwargs + + except Exception: + # at least get the core message out if something happened + self._error_string = self.message + + def __str__(self): + return self._error_string + + +class ProcessExecutionError(IOError): + def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, + description=None): + if description is None: + description = "Unexpected error while running command." + if exit_code is None: + exit_code = '-' + message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( + description, cmd, exit_code, stdout, stderr) + IOError.__init__(self, message) + + +class Error(Exception): + def __init__(self, message=None): + super(Error, self).__init__(message) + + +class ApiError(Error): + def __init__(self, message='Unknown', code='Unknown'): + self.message = message + self.code = code + super(ApiError, self).__init__('%s: %s' % (code, message)) + + +class NotFound(ExtensionException): + pass + + +class ClassNotFound(NotFound): + message = _("Class %(class_name)s could not be found") + + +class PortprofileNotFound(NotFound): + message = _("Portprofile %(_id)s could not be found") + + +class PortNotFound(NotFound): + message = _("Port %(port_id)s could not be found " \ + "on Network %(net_id)s") + + +""" + + +class PortprofileInUse(ExtensionException): + message = _("Unable to complete operation on Portprofile %(net_id)s. " \ + "There is one or more attachments plugged into its ports.") + + +class PortInUse(ExtensionException): + message = _("Unable to complete operation on port %(port_id)s " \ + "for Portprofile %(net_id)s. The attachment '%(att_id)s" \ + "is plugged into the logical port.") + +class AlreadyAttached(ExtensionException): + message = _("Unable to plug the attachment %(att_id)s into port " \ + "%(port_id)s for Portprofile %(net_id)s. The attachment is " \ + "already plugged into port %(att_port_id)s") + +""" + + +class Duplicate(Error): + pass + + +class NotAuthorized(Error): + pass + + +class NotEmpty(Error): + pass + + +class Invalid(Error): + pass + + +class InvalidContentType(Invalid): + message = _("Invalid content type %(content_type)s.") + + +class BadInputError(Exception): + """Error resulting from a client sending bad input to a server""" + pass + + +class MissingArgumentError(Error): + pass + + +def wrap_exception(f): + def _wrap(*args, **kw): + try: + return f(*args, **kw) + except Exception, e: + if not isinstance(e, Error): + #exc_type, exc_value, exc_traceback = sys.exc_info() + logging.exception('Uncaught exception') + #logging.error(traceback.extract_stack(exc_traceback)) + raise Error(str(e)) + raise + _wrap.func_name = f.func_name + return _wrap diff --git a/cisco_extensions/extensions.py b/cisco_extensions/extensions.py new file mode 100644 index 00000000000..34bc37e814a --- /dev/null +++ b/cisco_extensions/extensions.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ying Liu, Cisco Systems, Inc. +# +import logging +import webob.dec + +from quantum.common import wsgi +from quantum.api import api_common as common + + +LOG = logging.getLogger('quantum.api.cisco_extension.extensions') + + +class Controller(common.QuantumController): + + def __init__(self, plugin): + #self._plugin = plugin + #super(QuantumController, self).__init__() + self._resource_name = 'extensions' + super(Controller, self).__init__(plugin) + + def list_extension(self, req): + """Respond to a request for listing all extension api.""" + response = "extensions api list" + return response + + \ No newline at end of file diff --git a/cisco_extensions/faults.py b/cisco_extensions/faults.py new file mode 100644 index 00000000000..c965f731dc6 --- /dev/null +++ b/cisco_extensions/faults.py @@ -0,0 +1,111 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ying Liu, Cisco Systems, Inc. +# +import webob.dec +import webob.exc + +from quantum.api import api_common as common +from quantum.common import wsgi + + +class Fault(webob.exc.HTTPException): + """Error codes for API faults""" + + _fault_names = { + 400: "malformedRequest", + 401: "unauthorized", + 420: "networkNotFound", + 421: "PortprofileInUse", + 430: "portNotFound", + 431: "requestedStateInvalid", + 432: "portInUse", + 440: "alreadyAttached", + 450: "PortprofileNotFound", + 470: "serviceUnavailable", + 471: "pluginFault"} + + def __init__(self, exception): + """Create a Fault for the given webob.exc.exception.""" + self.wrapped_exc = exception + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + """Generate a WSGI response based on the exception passed to ctor.""" + #print ("*********TEST2") + # Replace the body with fault details. + code = self.wrapped_exc.status_int + fault_name = self._fault_names.get(code, "quantumServiceFault") + fault_data = { + fault_name: { + 'code': code, + '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'}}} + default_xmlns = common.XML_NS_V10 + serializer = wsgi.Serializer(metadata, default_xmlns) + content_type = req.best_match_content_type() + self.wrapped_exc.body = serializer.serialize(fault_data, content_type) + self.wrapped_exc.content_type = content_type + return self.wrapped_exc + + +class PortprofileNotFound(webob.exc.HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server did not find the Portprofile specified + in the HTTP request + + code: 450, title: Portprofile not Found + """ + #print ("*********TEST1") + code = 450 + title = 'Portprofile Not Found' + explanation = ('Unable to find a Portprofile with' + + ' the specified identifier.') + + +class PortNotFound(webob.exc.HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server did not find the port specified + in the HTTP request for a given network + + code: 430, title: Port not Found + """ + code = 430 + title = 'Port not Found' + explanation = ('Unable to find a port with the specified identifier.') + + +class RequestedStateInvalid(webob.exc.HTTPClientError): + """ + subclass of :class:`~HTTPClientError` + + This indicates that the server could not update the port state to + to the request value + + code: 431, title: Requested State Invalid + """ + code = 431 + title = 'Requested State Invalid' + explanation = ('Unable to update port state with specified value.') + + diff --git a/cisco_extensions/portprofiles.py b/cisco_extensions/portprofiles.py new file mode 100644 index 00000000000..5d195528f87 --- /dev/null +++ b/cisco_extensions/portprofiles.py @@ -0,0 +1,180 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ying Liu, Cisco Systems, Inc. +# + +import logging +import webob.dec +from quantum.common import wsgi +from webob import exc + +from quantum.api import api_common as common + +from cisco_extensions import pprofiles as pprofiles_view +from cisco_extensions import exceptions as exception +from cisco_extensions import faults as faults + +LOG = logging.getLogger('quantum.api.portprofiles') + + +class Controller(common.QuantumController): + """ portprofile API controller + based on QuantumController """ + + _portprofile_ops_param_list = [{ + 'param-name': 'portprofile-name', + 'required': True}, { + 'param-name': 'vlan-id', + 'required': True}, { + 'param-name': 'assignment', + 'required': False}] + + _assignprofile_ops_param_list = [{ + 'param-name': 'network-id', + 'required': True}, { + 'param-name': 'port-id', + 'required': True}] + + _serialization_metadata = { + "application/xml": { + "attributes": { + "portprofile": ["id", "name"], + }, + }, + } + + def __init__(self, plugin): + self._resource_name = 'portprofile' + super(Controller, self).__init__(plugin) + + def index(self, request, tenant_id): + """ Returns a list of portprofile ids """ + #TODO: this should be for a given tenant!!! + return self._items(request, tenant_id, is_detail=False) + + def _items(self, request, tenant_id, is_detail): + """ Returns a list of portprofiles. """ + portprofiles = self._plugin.get_all_portprofiles(tenant_id) + builder = pprofiles_view.get_view_builder(request) + result = [builder.build(portprofile, is_detail)['portprofile'] + for portprofile in portprofiles] + return dict(portprofiles=result) + + def show(self, request, tenant_id, id): + """ Returns portprofile details for the given portprofile id """ + try: + portprofile = self._plugin.get_portprofile_details( + tenant_id, id) + builder = pprofiles_view.get_view_builder(request) + #build response with details + result = builder.build(portprofile, True) + return dict(portprofiles=result) + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + #return faults.Fault(e) + + def create(self, request, tenant_id): + """ Creates a new portprofile for a given tenant """ + #look for portprofile name in request + try: + req_params = \ + self._parse_request_params(request, + self._portprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + portprofile = self._plugin.\ + create_portprofile(tenant_id, + req_params['portprofile-name'], + req_params['vlan-id']) + builder = pprofiles_view.get_view_builder(request) + result = builder.build(portprofile) + return dict(portprofiles=result) + + def update(self, request, tenant_id, id): + """ Updates the name for the portprofile with the given id """ + try: + req_params = \ + self._parse_request_params(request, + self._portprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + try: + portprofile = self._plugin.\ + rename_portprofile(tenant_id, + id, req_params['portprofile-name']) + + builder = pprofiles_view.get_view_builder(request) + result = builder.build(portprofile, True) + return dict(portprofiles=result) + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + + def delete(self, request, tenant_id, id): + """ Destroys the portprofile with the given id """ + try: + self._plugin.delete_portprofile(tenant_id, id) + return exc.HTTPAccepted() + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + + #added for cisco's extension + def associate_portprofile(self, request, tenant_id, portprofile_id): + content_type = request.best_match_content_type() + print "Content type:%s" % content_type + + try: + req_params = \ + self._parse_request_params(request, + self._assignprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + net_id = req_params['network-id'].strip() + #print "*****net id "+net_id + port_id = req_params['port-id'].strip() + try: + self._plugin.associate_portprofile(tenant_id, + net_id, port_id, + portprofile_id) + return exc.HTTPAccepted() + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) + + #added for Cisco extension + def disassociate_portprofile(self, request, tenant_id, portprofile_id): + content_type = request.best_match_content_type() + print "Content type:%s" % content_type + + try: + req_params = \ + self._parse_request_params(request, + self._assignprofile_ops_param_list) + except exc.HTTPError as e: + return faults.Fault(e) + net_id = req_params['network-id'].strip() + #print "*****net id "+net_id + port_id = req_params['port-id'].strip() + try: + self._plugin. \ + disassociate_portprofile(tenant_id, + net_id, port_id, portprofile_id) + return exc.HTTPAccepted() + except exception.PortprofileNotFound as e: + return faults.Fault(faults.PortprofileNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) diff --git a/cisco_extensions/pprofiles.py b/cisco_extensions/pprofiles.py new file mode 100644 index 00000000000..ba7f8a328bb --- /dev/null +++ b/cisco_extensions/pprofiles.py @@ -0,0 +1,45 @@ + + +import os + + +def get_view_builder(req): + base_url = req.application_url + return ViewBuilder(base_url) + + +class ViewBuilder(object): + """ + ViewBuilder for Portprofile, + derived from quantum.views.networks + """ + def __init__(self, base_url): + """ + :param base_url: url of the root wsgi application + """ + self.base_url = base_url + + def build(self, portprofile_data, is_detail=False): + """Generic method used to generate a portprofile entity.""" + print "portprofile-DATA:%s" %portprofile_data + if is_detail: + portprofile = self._build_detail(portprofile_data) + else: + portprofile = self._build_simple(portprofile_data) + return portprofile + + def _build_simple(self, portprofile_data): + """Return a simple model of a server.""" + return dict(portprofile=dict(id=portprofile_data['profile-id'])) + + def _build_detail(self, portprofile_data): + """Return a simple model of a server.""" + if (portprofile_data['assignment']==None): + return dict(portprofile=dict(id=portprofile_data['profile-id'], + name=portprofile_data['profile-name'], + vlan_id=portprofile_data['vlan-id'])) + else: + return dict(portprofile=dict(id=portprofile_data['profile-id'], + name=portprofile_data['profile-name'], + vlan_id=portprofile_data['vlan-id'], + assignment=portprofile_data['assignment'])) From fbc19d8158f359dd1899daadb898a331be4aa7de Mon Sep 17 00:00:00 2001 From: Deepak N Date: Mon, 18 Jul 2011 16:11:20 +0530 Subject: [PATCH 15/76] Santhosh/deepak| Load extensions supported by plugin --- quantum/common/extensions.py | 13 ++++--- quantum/manager.py | 6 ++- quantum/plugins/SamplePlugin.py | 3 ++ quantum/quantum_plugin_base.py | 9 +++++ tests/unit/test_extensions.py | 67 ++++++++++++++++++++++----------- 5 files changed, 69 insertions(+), 29 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 7ad802fa969..2682aa92993 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -24,6 +24,7 @@ import logging import webob.dec import webob.exc +from quantum.manager import QuantumManager from quantum.common import exceptions from quantum.common import wsgi from gettext import gettext as _ @@ -231,8 +232,9 @@ class ExtensionMiddleware(wsgi.Middleware): ext_mgr=None): self.ext_mgr = (ext_mgr - or ExtensionManager(config_params.get('api_extensions_path', - ''))) + or ExtensionManager( + config_params.get('api_extensions_path', + ''), QuantumManager().plugin)) mapper = routes.Mapper() @@ -296,11 +298,11 @@ class ExtensionManager(object): example extension implementation. """ - - def __init__(self, path): + def __init__(self, path, plugin): LOG.info(_('Initializing extension manager.')) self.path = path + self.plugin = plugin self.extensions = {} self._load_all_extensions() @@ -350,10 +352,10 @@ class ExtensionManager(object): LOG.debug(_('Ext description: %s'), extension.get_description()) LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) LOG.debug(_('Ext updated: %s'), extension.get_updated()) + return self.plugin.supports_extension(extension) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) return False - return True def _load_all_extensions(self): """Load extensions from the configured path. @@ -392,7 +394,6 @@ class ExtensionManager(object): 'file': ext_path}) continue new_ext = new_ext_class() - self._check_extension(new_ext) self.add_extension(new_ext) def add_extension(self, ext): diff --git a/quantum/manager.py b/quantum/manager.py index a9662d8eb6a..00aefc69878 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -26,6 +26,7 @@ The caller should make sure that QuantumManager is a singleton. """ import gettext import os +import logging gettext.install('quantum', unicode=1) import os @@ -33,6 +34,7 @@ import os from common import utils from quantum_plugin_base import QuantumPluginBase +LOG = logging.getLogger('quantum.manager') CONFIG_FILE = "plugins.ini" @@ -51,13 +53,13 @@ class QuantumManager(object): else: self.configuration_file = config plugin_location = utils.getPluginFromConfig(self.configuration_file) - print "PLUGIN LOCATION:%s" % plugin_location + LOG.debug("PLUGIN LOCATION:%s" % plugin_location) plugin_klass = utils.import_class(plugin_location) if not issubclass(plugin_klass, QuantumPluginBase): raise Exception("Configured Quantum plug-in " \ "didn't pass compatibility test") else: - print("Successfully imported Quantum plug-in." \ + LOG.debug("Successfully imported Quantum plug-in." \ "All compatibility tests passed\n") self.plugin = plugin_klass() diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 376456a6c81..fa8da29d95d 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -434,3 +434,6 @@ class FakePlugin(object): # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? port['attachment'] = None + + def supports_extension(self, extension): + return True diff --git a/quantum/quantum_plugin_base.py b/quantum/quantum_plugin_base.py index 8e3f1f8e152..52947aa7ec3 100644 --- a/quantum/quantum_plugin_base.py +++ b/quantum/quantum_plugin_base.py @@ -230,6 +230,15 @@ class QuantumPluginBase(object): """ pass + @abstractmethod + def supports_extension(self, extension): + """ + Returns if the extension is suppoorted + + :returns: True or False + """ + pass + @classmethod def __subclasshook__(cls, klass): """ diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 9579a0d7201..7a02cdeb479 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -23,7 +23,7 @@ from webtest import TestApp from quantum.common import extensions from quantum.common import wsgi from quantum.common import config - +from quantum.common.extensions import ExtensionManager extension_index_response = "Try to say this Mr. Knox, sir..." test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, @@ -71,38 +71,63 @@ class ResourceExtensionTest(unittest.TestCase): self.assertEqual(404, response.status_int) +class StubExtension(object): + + def __init__(self, alias="stub_extension"): + self.alias = alias + + def get_name(self): + return "Stub Extension" + + def get_alias(self): + return self.alias + + def get_description(self): + return "" + + def get_namespace(self): + return "" + + def get_updated(self): + return "" + + +class StubPlugin(object): + + def __init__(self, supported_extensions=[]): + self.supported_extensions = supported_extensions + + def supports_extension(self, extension): + return extension.get_alias() in self.supported_extensions + + class ExtensionManagerTest(unittest.TestCase): def test_invalid_extensions_are_not_registered(self): - class ValidExtension(object): - - def get_name(self): - return "Valid Extension" - - def get_alias(self): - return "valid_extension" - - def get_description(self): - return "" - - def get_namespace(self): - return "" - - def get_updated(self): - return "" - class InvalidExtension(object): def get_alias(self): return "invalid_extension" - extended_app = setup_extensions_middleware() - ext_mgr = extended_app.ext_mgr + ext_mgr = setup_extensions_middleware().ext_mgr ext_mgr.add_extension(InvalidExtension()) - ext_mgr.add_extension(ValidExtension()) + ext_mgr.add_extension(StubExtension("valid_extension")) + self.assertTrue('valid_extension' in ext_mgr.extensions) self.assertFalse('invalid_extension' in ext_mgr.extensions) + def test_unsupported_extensions_are_not_loaded(self): + ext_mgr = setup_extensions_middleware().ext_mgr + ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) + + ext_mgr.add_extension(StubExtension("e1")) + ext_mgr.add_extension(StubExtension("e2")) + ext_mgr.add_extension(StubExtension("e3")) + + self.assertTrue("e1" in ext_mgr.extensions) + self.assertFalse("e2" in ext_mgr.extensions) + self.assertTrue("e3" in ext_mgr.extensions) + class ActionExtensionTest(unittest.TestCase): From 61e481146837265f245680303293af123622c33f Mon Sep 17 00:00:00 2001 From: Deepak N Date: Mon, 18 Jul 2011 17:24:26 +0530 Subject: [PATCH 16/76] Santhosh/Deepak | Made supports_extension method optional for plugin, plugin will be loaded only once --- quantum/api/__init__.py | 2 +- quantum/common/extensions.py | 8 ++++++-- quantum/manager.py | 10 ++++++++-- quantum/quantum_plugin_base.py | 4 ++-- tests/__init__.py | 19 +++++++++++++++++++ tests/unit/test_extensions.py | 15 +++++++++++++++ 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index fc347673338..87df7673f06 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -48,7 +48,7 @@ class APIRouterV01(wsgi.Router): def _setup_routes(self, mapper): # Loads the quantum plugin - plugin = manager.QuantumManager().get_plugin() + plugin = manager.QuantumManager.get_plugin() uri_prefix = '/tenants/{tenant_id}/' mapper.resource('network', 'networks', controller=networks.Controller(plugin), diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 2682aa92993..44d52a8b88f 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -234,7 +234,7 @@ class ExtensionMiddleware(wsgi.Middleware): self.ext_mgr = (ext_mgr or ExtensionManager( config_params.get('api_extensions_path', - ''), QuantumManager().plugin)) + ''), QuantumManager.get_plugin())) mapper = routes.Mapper() @@ -352,11 +352,15 @@ class ExtensionManager(object): LOG.debug(_('Ext description: %s'), extension.get_description()) LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) LOG.debug(_('Ext updated: %s'), extension.get_updated()) - return self.plugin.supports_extension(extension) + return self._plugin_supports(extension) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) return False + def _plugin_supports(self, extension): + return (hasattr(self.plugin, "supports_extension") and + self.plugin.supports_extension(extension)) + def _load_all_extensions(self): """Load extensions from the configured path. diff --git a/quantum/manager.py b/quantum/manager.py index d639ec1d3c9..33eb77e5904 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -44,6 +44,9 @@ def find_config(basepath): class QuantumManager(object): + + _instance = None + def __init__(self, config=None): if config == None: self.configuration_file = find_config( @@ -60,5 +63,8 @@ class QuantumManager(object): "All compatibility tests passed\n") self.plugin = plugin_klass() - def get_plugin(self): - return self.plugin + @classmethod + def get_plugin(cls): + if(cls._instance is None): + cls._instance = QuantumManager() + return cls._instance.plugin diff --git a/quantum/quantum_plugin_base.py b/quantum/quantum_plugin_base.py index 52947aa7ec3..a43b8c0cc51 100644 --- a/quantum/quantum_plugin_base.py +++ b/quantum/quantum_plugin_base.py @@ -230,10 +230,10 @@ class QuantumPluginBase(object): """ pass - @abstractmethod def supports_extension(self, extension): """ - Returns if the extension is suppoorted + Returns if the extension is supported. + If this method is not implemented, extensions will not be loaded. :returns: True or False """ diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb2d..9578283b2d6 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +logging.basicConfig() diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 7a02cdeb479..0fe557f0dbc 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -128,6 +128,21 @@ class ExtensionManagerTest(unittest.TestCase): self.assertFalse("e2" in ext_mgr.extensions) self.assertTrue("e3" in ext_mgr.extensions) + def test_extensions_are_not_loaded_for_extensions_unaware_plugins(self): + class ExtensionUnawarePlugin(object): + """ + This plugin does not implement supports_extension method. + Extensions will not be loaded when this plugin is used. + """ + pass + + ext_mgr = setup_extensions_middleware().ext_mgr + ext_mgr.plugin = ExtensionUnawarePlugin() + + ext_mgr.add_extension(StubExtension("e1")) + + self.assertFalse("e1" in ext_mgr.extensions) + class ActionExtensionTest(unittest.TestCase): From 2392ba00664054a49933c6efbfac9d9868129110 Mon Sep 17 00:00:00 2001 From: Deepak N Date: Mon, 18 Jul 2011 19:45:24 +0530 Subject: [PATCH 17/76] Deepak/Santhosh | ExtensionManager verifies that plugin implements the interface expected by the extension --- quantum/common/extensions.py | 16 +++++- tests/unit/test_extensions.py | 92 ++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 44d52a8b88f..7d467269953 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -352,7 +352,8 @@ class ExtensionManager(object): LOG.debug(_('Ext description: %s'), extension.get_description()) LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) LOG.debug(_('Ext updated: %s'), extension.get_updated()) - return self._plugin_supports(extension) + return (self._plugin_supports(extension) and + self._plugin_implements_interface(extension)) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) return False @@ -361,6 +362,19 @@ class ExtensionManager(object): return (hasattr(self.plugin, "supports_extension") and self.plugin.supports_extension(extension)) + def _plugin_implements_interface(self, extension): + if not hasattr(extension, "get_plugin_interface"): + return True + interface = extension.get_plugin_interface() + expected_methods = self._get_public_methods(interface) + implemented_methods = self._get_public_methods(self.plugin.__class__) + missing_methods = set(expected_methods) - set(implemented_methods) + return len(missing_methods) == 0 + + def _get_public_methods(self, klass): + return filter(lambda name: not(name.startswith("_")), + klass.__dict__.keys()) + def _load_all_extensions(self): """Load extensions from the configured path. diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 0fe557f0dbc..d806469a01b 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -101,34 +101,48 @@ class StubPlugin(object): return extension.get_alias() in self.supported_extensions +class ExtensionExpectingPluginInterface(StubExtension): + + def get_plugin_interface(self): + return PluginInterface + + +class PluginInterface(object): + + def get_foo(self, bar=None): + pass + + class ExtensionManagerTest(unittest.TestCase): + def setUp(self): + self.ext_mgr = setup_extensions_middleware().ext_mgr + super(ExtensionManagerTest, self).setUp() + def test_invalid_extensions_are_not_registered(self): class InvalidExtension(object): def get_alias(self): return "invalid_extension" - ext_mgr = setup_extensions_middleware().ext_mgr - ext_mgr.add_extension(InvalidExtension()) - ext_mgr.add_extension(StubExtension("valid_extension")) + self.ext_mgr.add_extension(InvalidExtension()) + self.ext_mgr.add_extension(StubExtension("valid_extension")) - self.assertTrue('valid_extension' in ext_mgr.extensions) - self.assertFalse('invalid_extension' in ext_mgr.extensions) + self.assertTrue('valid_extension' in self.ext_mgr.extensions) + self.assertFalse('invalid_extension' in self.ext_mgr.extensions) def test_unsupported_extensions_are_not_loaded(self): - ext_mgr = setup_extensions_middleware().ext_mgr - ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) + self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) - ext_mgr.add_extension(StubExtension("e1")) - ext_mgr.add_extension(StubExtension("e2")) - ext_mgr.add_extension(StubExtension("e3")) + self.ext_mgr.add_extension(StubExtension("e1")) + self.ext_mgr.add_extension(StubExtension("e2")) + self.ext_mgr.add_extension(StubExtension("e3")) - self.assertTrue("e1" in ext_mgr.extensions) - self.assertFalse("e2" in ext_mgr.extensions) - self.assertTrue("e3" in ext_mgr.extensions) + self.assertTrue("e1" in self.ext_mgr.extensions) + self.assertFalse("e2" in self.ext_mgr.extensions) + self.assertTrue("e3" in self.ext_mgr.extensions) - def test_extensions_are_not_loaded_for_extensions_unaware_plugins(self): + def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self): class ExtensionUnawarePlugin(object): """ This plugin does not implement supports_extension method. @@ -136,12 +150,54 @@ class ExtensionManagerTest(unittest.TestCase): """ pass - ext_mgr = setup_extensions_middleware().ext_mgr - ext_mgr.plugin = ExtensionUnawarePlugin() + self.ext_mgr.plugin = ExtensionUnawarePlugin() + self.ext_mgr.add_extension(StubExtension("e1")) - ext_mgr.add_extension(StubExtension("e1")) + self.assertFalse("e1" in self.ext_mgr.extensions) - self.assertFalse("e1" in ext_mgr.extensions) + def test_extensions_not_loaded_for_plugin_without_expected_interface(self): + + class PluginWithoutExpectedInterface(object): + """ + Plugin does not implement get_foo method as expected by extension + """ + def supports_extension(self, true): + return true + + self.ext_mgr.plugin = PluginWithoutExpectedInterface() + self.ext_mgr.add_extension(ExtensionExpectingPluginInterface("e1")) + + self.assertFalse("e1" in self.ext_mgr.extensions) + + def test_extensions_are_loaded_for_plugin_with_expected_interface(self): + + class PluginWithExpectedInterface(object): + """ + This Plugin implements get_foo method as expected by extension + """ + def supports_extension(self, true): + return true + + def get_foo(self, bar=None): + pass + + self.ext_mgr.plugin = PluginWithExpectedInterface() + self.ext_mgr.add_extension(ExtensionExpectingPluginInterface("e1")) + + self.assertTrue("e1" in self.ext_mgr.extensions) + + def test_extensions_expecting_quantum_plugin_interface_are_loaded(self): + class ExtensionForQuamtumPluginInterface(StubExtension): + """ + This Extension does not implement get_plugin_interface method. + This will work with any plugin implementing QuantumPluginBase + """ + pass + + self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) + self.ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) + + self.assertTrue("e1" in self.ext_mgr.extensions) class ActionExtensionTest(unittest.TestCase): From 0781498b8459e5df2d12e61e07cfac8236fc6349 Mon Sep 17 00:00:00 2001 From: Deepak N Date: Tue, 19 Jul 2011 10:20:48 +0530 Subject: [PATCH 18/76] Vinkesh/Deepak| Added doc and small refactoring --- quantum/common/extensions.py | 2 +- tests/unit/test_extensions.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 7d467269953..1d9d38bbb82 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -369,7 +369,7 @@ class ExtensionManager(object): expected_methods = self._get_public_methods(interface) implemented_methods = self._get_public_methods(self.plugin.__class__) missing_methods = set(expected_methods) - set(implemented_methods) - return len(missing_methods) == 0 + return not missing_methods def _get_public_methods(self, klass): return filter(lambda name: not(name.startswith("_")), diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index d806469a01b..591a4941696 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -102,6 +102,10 @@ class StubPlugin(object): class ExtensionExpectingPluginInterface(StubExtension): + """ + This extension expects plugin to implement all the methods defined + in PluginInterface + """ def get_plugin_interface(self): return PluginInterface @@ -122,6 +126,10 @@ class ExtensionManagerTest(unittest.TestCase): def test_invalid_extensions_are_not_registered(self): class InvalidExtension(object): + """ + This Extension doesn't implement extension methods : + get_name, get_description, get_namespace and get_updated + """ def get_alias(self): return "invalid_extension" From 2de192bb4621601f75172378340be057b9673554 Mon Sep 17 00:00:00 2001 From: Deepak N Date: Tue, 19 Jul 2011 11:25:31 +0530 Subject: [PATCH 19/76] Deepak/Vinkesh | Added an base abstract class which can be inherited by PluginInterface class which defines the contract expected by extension. --- quantum/common/extensions.py | 40 +++++++++++++++++++++++++++++------ tests/unit/test_extensions.py | 22 ++++++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 1d9d38bbb82..5d24a3d816c 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -23,15 +23,36 @@ import routes import logging import webob.dec import webob.exc +from gettext import gettext as _ +from abc import ABCMeta, abstractmethod from quantum.manager import QuantumManager from quantum.common import exceptions from quantum.common import wsgi -from gettext import gettext as _ LOG = logging.getLogger('quantum.common.extensions') +class PluginInterface(object): + __metaclass__ = ABCMeta + + @classmethod + def __subclasshook__(cls, klass): + """ + The __subclasshook__ method is a class method + that will be called everytime a class is tested + using issubclass(klass, PluginInterface). + In that case, it will check that every method + marked with the abstractmethod decorator is + provided by the plugin class. + """ + for method in cls.__abstractmethods__: + if any(method in base.__dict__ for base in klass.__mro__): + continue + return NotImplemented + return True + + class ExtensionDescriptor(object): """Base class that defines the contract for extensions. @@ -108,6 +129,14 @@ class ExtensionDescriptor(object): request_exts = [] return request_exts + def get_plugin_interface(self): + """ + Returns an abstract class which defines contract for the plugin. + The abstract class should inherit from extesnions.PluginInterface, + Methods in this abstract class should be decorated as abstractmethod + """ + return None + class ActionExtensionController(wsgi.Controller): @@ -363,13 +392,10 @@ class ExtensionManager(object): self.plugin.supports_extension(extension)) def _plugin_implements_interface(self, extension): - if not hasattr(extension, "get_plugin_interface"): + if(not hasattr(extension, "get_plugin_interface") or + extension.get_plugin_interface() is None): return True - interface = extension.get_plugin_interface() - expected_methods = self._get_public_methods(interface) - implemented_methods = self._get_public_methods(self.plugin.__class__) - missing_methods = set(expected_methods) - set(implemented_methods) - return not missing_methods + return isinstance(self.plugin, extension.get_plugin_interface()) def _get_public_methods(self, klass): return filter(lambda name: not(name.startswith("_")), diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 591a4941696..4472d2377a4 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -18,6 +18,7 @@ import unittest import routes import os.path from tests.unit import BaseTest +from abc import abstractmethod from webtest import TestApp from quantum.common import extensions @@ -104,15 +105,16 @@ class StubPlugin(object): class ExtensionExpectingPluginInterface(StubExtension): """ This extension expects plugin to implement all the methods defined - in PluginInterface + in StubPluginInterface """ def get_plugin_interface(self): - return PluginInterface + return StubPluginInterface -class PluginInterface(object): +class StubPluginInterface(extensions.PluginInterface): + @abstractmethod def get_foo(self, bar=None): pass @@ -207,6 +209,20 @@ class ExtensionManagerTest(unittest.TestCase): self.assertTrue("e1" in self.ext_mgr.extensions) + def test_extensions_without_need_for__plugin_interface_are_loaded(self): + class ExtensionWithNoNeedForPluginInterface(StubExtension): + """ + This Extension does not need any plugin interface. + This will work with any plugin implementing QuantumPluginBase + """ + def get_plugin_interface(self): + return None + + self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) + self.ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) + + self.assertTrue("e1" in self.ext_mgr.extensions) + class ActionExtensionTest(unittest.TestCase): From 4779f609f3900307a028d72ebff85923d286b82e Mon Sep 17 00:00:00 2001 From: Deepak N Date: Tue, 19 Jul 2011 11:53:57 +0530 Subject: [PATCH 20/76] Vinkesh/Deepak | Moved plugin related checks in ExtensionManager code to PluginAwareExtensionManager --- quantum/common/extensions.py | 51 ++++++++++++++++++++--------------- tests/unit/test_extensions.py | 22 ++++++++------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 5d24a3d816c..772beaaafe2 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -261,9 +261,8 @@ class ExtensionMiddleware(wsgi.Middleware): ext_mgr=None): self.ext_mgr = (ext_mgr - or ExtensionManager( - config_params.get('api_extensions_path', - ''), QuantumManager.get_plugin())) + or PluginAwareExtensionManager( + config_params.get('api_extensions_path', ''))) mapper = routes.Mapper() @@ -327,11 +326,9 @@ class ExtensionManager(object): example extension implementation. """ - def __init__(self, path, plugin): + def __init__(self, path): LOG.info(_('Initializing extension manager.')) - self.path = path - self.plugin = plugin self.extensions = {} self._load_all_extensions() @@ -381,25 +378,10 @@ class ExtensionManager(object): LOG.debug(_('Ext description: %s'), extension.get_description()) LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) LOG.debug(_('Ext updated: %s'), extension.get_updated()) - return (self._plugin_supports(extension) and - self._plugin_implements_interface(extension)) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) return False - - def _plugin_supports(self, extension): - return (hasattr(self.plugin, "supports_extension") and - self.plugin.supports_extension(extension)) - - def _plugin_implements_interface(self, extension): - if(not hasattr(extension, "get_plugin_interface") or - extension.get_plugin_interface() is None): - return True - return isinstance(self.plugin, extension.get_plugin_interface()) - - def _get_public_methods(self, klass): - return filter(lambda name: not(name.startswith("_")), - klass.__dict__.keys()) + return True def _load_all_extensions(self): """Load extensions from the configured path. @@ -454,6 +436,31 @@ class ExtensionManager(object): self.extensions[alias] = ext +class PluginAwareExtensionManager(ExtensionManager): + + def __init__(self, path): + self.plugin = QuantumManager.get_plugin() + super(PluginAwareExtensionManager, self).__init__(path) + + def _check_extension(self, extension): + """Checks if plugin supports extension and implements the contract.""" + extension_is_valid = super(PluginAwareExtensionManager, + self)._check_extension(extension) + return (extension_is_valid and + self._plugin_supports(extension) and + self._plugin_implements_interface(extension)) + + def _plugin_supports(self, extension): + return (hasattr(self.plugin, "supports_extension") and + self.plugin.supports_extension(extension)) + + def _plugin_implements_interface(self, extension): + if(not hasattr(extension, "get_plugin_interface") or + extension.get_plugin_interface() is None): + return True + return isinstance(self.plugin, extension.get_plugin_interface()) + + class RequestExtension(object): """Extend requests and responses of core Quantum OpenStack API controllers. diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 4472d2377a4..75080238136 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -24,7 +24,8 @@ from webtest import TestApp from quantum.common import extensions from quantum.common import wsgi from quantum.common import config -from quantum.common.extensions import ExtensionManager +from quantum.common.extensions import (ExtensionManager, + PluginAwareExtensionManager) extension_index_response = "Try to say this Mr. Knox, sir..." test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, @@ -121,10 +122,6 @@ class StubPluginInterface(extensions.PluginInterface): class ExtensionManagerTest(unittest.TestCase): - def setUp(self): - self.ext_mgr = setup_extensions_middleware().ext_mgr - super(ExtensionManagerTest, self).setUp() - def test_invalid_extensions_are_not_registered(self): class InvalidExtension(object): @@ -135,11 +132,18 @@ class ExtensionManagerTest(unittest.TestCase): def get_alias(self): return "invalid_extension" - self.ext_mgr.add_extension(InvalidExtension()) - self.ext_mgr.add_extension(StubExtension("valid_extension")) + ext_mgr = ExtensionManager('') + ext_mgr.add_extension(InvalidExtension()) + ext_mgr.add_extension(StubExtension("valid_extension")) - self.assertTrue('valid_extension' in self.ext_mgr.extensions) - self.assertFalse('invalid_extension' in self.ext_mgr.extensions) + self.assertTrue('valid_extension' in ext_mgr.extensions) + self.assertFalse('invalid_extension' in ext_mgr.extensions) + + +class PluginAwareExtensionManagerTest(unittest.TestCase): + + def setUp(self): + self.ext_mgr = PluginAwareExtensionManager('') def test_unsupported_extensions_are_not_loaded(self): self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) From bb4c1a764072d06f9d650d8c3287da3b1d26379d Mon Sep 17 00:00:00 2001 From: vinkesh banka Date: Wed, 20 Jul 2011 19:08:16 +0530 Subject: [PATCH 21/76] Vinkesh/Santhosh | Added tests to check the member and collection custom actions of ResourceExtensions --- tests/unit/test_extensions.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 75080238136..2e9bd05ce24 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -65,6 +65,28 @@ class ResourceExtensionTest(unittest.TestCase): self.assertEqual(200, response.status_int) self.assertEqual(extension_index_response, response.body) + def test_resource_extension_with_custom_member_action(self): + controller = StubController(extension_index_response) + member = {'custom_member_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + member_actions=member) + test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) + + response = test_app.get("/tweedles/some_id/custom_member_action") + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['member_action'], "value") + + def test_resource_extension_with_custom_collection_action(self): + controller = StubController(extension_index_response) + collections = {'custom_collection_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) + + response = test_app.get("/tweedles/custom_collection_action") + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") + def test_returns_404_for_non_existant_extension(self): test_app = setup_extensions_test_app(StubExtensionManager(None)) @@ -370,6 +392,12 @@ class StubController(wsgi.Controller): def update(self, request, id): return {'uneditable': 'original_value'} + def custom_member_action(self, request, id): + return {'member_action': 'value'} + + def custom_collection_action(self, request): + return {'collection': 'value'} + def app_factory(global_conf, **local_conf): conf = global_conf.copy() From c651950e26f69f612c2514a87edd45126f652803 Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Fri, 22 Jul 2011 12:51:38 +0530 Subject: [PATCH 22/76] Rajaram/Vinkesh | Plugins advertise which extensions it supports. --- quantum/common/extensions.py | 85 +++---- quantum/plugins/SamplePlugin.py | 5 +- quantum/quantum_plugin_base.py | 9 - tests/unit/test_extensions.py | 381 ++++++++++++++++---------------- 4 files changed, 233 insertions(+), 247 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 772beaaafe2..80d43fd0852 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -212,6 +212,46 @@ class ExtensionController(wsgi.Controller): class ExtensionMiddleware(wsgi.Middleware): """Extensions middleware for WSGI.""" + def __init__(self, application, config_params, + ext_mgr=None): + + self.ext_mgr = (ext_mgr + or PluginAwareExtensionManager( + config_params.get('api_extensions_path', ''))) + + mapper = routes.Mapper() + + # extended resources + for resource in self.ext_mgr.get_resources(): + LOG.debug(_('Extended resource: %s'), + resource.collection) + mapper.resource(resource.collection, resource.collection, + controller=resource.controller, + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) + + # extended actions + action_controllers = self._action_ext_controllers(application, + self.ext_mgr, mapper) + for action in self.ext_mgr.get_actions(): + LOG.debug(_('Extended action: %s'), action.action_name) + controller = action_controllers[action.collection] + controller.add_action(action.action_name, action.handler) + + # extended requests + req_controllers = self._request_ext_controllers(application, + self.ext_mgr, mapper) + for request_ext in self.ext_mgr.get_request_extensions(): + LOG.debug(_('Extended request: %s'), request_ext.key) + controller = req_controllers[request_ext.key] + controller.add_handler(request_ext.handler) + + self._router = routes.middleware.RoutesMiddleware(self._dispatch, + mapper) + + super(ExtensionMiddleware, self).__init__(application) + @classmethod def factory(cls, global_config, **local_config): """Paste factory.""" @@ -257,46 +297,6 @@ class ExtensionMiddleware(wsgi.Middleware): return request_ext_controllers - def __init__(self, application, config_params, - ext_mgr=None): - - self.ext_mgr = (ext_mgr - or PluginAwareExtensionManager( - config_params.get('api_extensions_path', ''))) - - mapper = routes.Mapper() - - # extended resources - for resource in self.ext_mgr.get_resources(): - LOG.debug(_('Extended resource: %s'), - resource.collection) - mapper.resource(resource.collection, resource.collection, - controller=resource.controller, - collection=resource.collection_actions, - member=resource.member_actions, - parent_resource=resource.parent) - - # extended actions - action_controllers = self._action_ext_controllers(application, - self.ext_mgr, mapper) - for action in self.ext_mgr.get_actions(): - LOG.debug(_('Extended action: %s'), action.action_name) - controller = action_controllers[action.collection] - controller.add_action(action.action_name, action.handler) - - # extended requests - req_controllers = self._request_ext_controllers(application, - self.ext_mgr, mapper) - for request_ext in self.ext_mgr.get_request_extensions(): - LOG.debug(_('Extended request: %s'), request_ext.key) - controller = req_controllers[request_ext.key] - controller.add_handler(request_ext.handler) - - self._router = routes.middleware.RoutesMiddleware(self._dispatch, - mapper) - - super(ExtensionMiddleware, self).__init__(application) - @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): """Route the incoming request with router.""" @@ -451,8 +451,9 @@ class PluginAwareExtensionManager(ExtensionManager): self._plugin_implements_interface(extension)) def _plugin_supports(self, extension): - return (hasattr(self.plugin, "supports_extension") and - self.plugin.supports_extension(extension)) + alias = extension.get_alias() + return (hasattr(self.plugin, "supported_extension_aliases") and + alias in self.plugin.supported_extension_aliases) def _plugin_implements_interface(self, extension): if(not hasattr(extension, "get_plugin_interface") or diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index fa8da29d95d..5b4e9c12655 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -253,6 +253,8 @@ class FakePlugin(object): 'net-name': 'cicciotest', 'net-ports': _port_dict_2}} + supported_extension_aliases = ["FOXNSOX"] + def __init__(self): FakePlugin._net_counter = len(FakePlugin._networks) @@ -434,6 +436,3 @@ class FakePlugin(object): # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? port['attachment'] = None - - def supports_extension(self, extension): - return True diff --git a/quantum/quantum_plugin_base.py b/quantum/quantum_plugin_base.py index a43b8c0cc51..8e3f1f8e152 100644 --- a/quantum/quantum_plugin_base.py +++ b/quantum/quantum_plugin_base.py @@ -230,15 +230,6 @@ class QuantumPluginBase(object): """ pass - def supports_extension(self, extension): - """ - Returns if the extension is supported. - If this method is not implemented, extensions will not be loaded. - - :returns: True or False - """ - pass - @classmethod def __subclasshook__(cls, klass): """ diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 2e9bd05ce24..8511a13a144 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -27,74 +27,10 @@ from quantum.common import config from quantum.common.extensions import (ExtensionManager, PluginAwareExtensionManager) -extension_index_response = "Try to say this Mr. Knox, sir..." test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'etc', 'quantum.conf.test') -class ExtensionControllerTest(unittest.TestCase): - - def setUp(self): - super(ExtensionControllerTest, self).setUp() - self.test_app = setup_extensions_test_app() - - def test_index_gets_all_registerd_extensions(self): - response = self.test_app.get("/extensions") - foxnsox = response.json["extensions"][0] - - self.assertEqual(foxnsox["alias"], "FOXNSOX") - self.assertEqual(foxnsox["namespace"], - "http://www.fox.in.socks/api/ext/pie/v1.0") - - def test_extension_can_be_accessed_by_alias(self): - foxnsox_extension = self.test_app.get("/extensions/FOXNSOX").json - - self.assertEqual(foxnsox_extension["alias"], "FOXNSOX") - self.assertEqual(foxnsox_extension["namespace"], - "http://www.fox.in.socks/api/ext/pie/v1.0") - - -class ResourceExtensionTest(unittest.TestCase): - - def test_resource_extension(self): - res_ext = extensions.ResourceExtension('tweedles', StubController( - extension_index_response)) - test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) - - response = test_app.get("/tweedles") - self.assertEqual(200, response.status_int) - self.assertEqual(extension_index_response, response.body) - - def test_resource_extension_with_custom_member_action(self): - controller = StubController(extension_index_response) - member = {'custom_member_action': "GET"} - res_ext = extensions.ResourceExtension('tweedles', controller, - member_actions=member) - test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) - - response = test_app.get("/tweedles/some_id/custom_member_action") - self.assertEqual(200, response.status_int) - self.assertEqual(json.loads(response.body)['member_action'], "value") - - def test_resource_extension_with_custom_collection_action(self): - controller = StubController(extension_index_response) - collections = {'custom_collection_action': "GET"} - res_ext = extensions.ResourceExtension('tweedles', controller, - collection_actions=collections) - test_app = setup_extensions_test_app(StubExtensionManager(res_ext)) - - response = test_app.get("/tweedles/custom_collection_action") - self.assertEqual(200, response.status_int) - self.assertEqual(json.loads(response.body)['collection'], "value") - - def test_returns_404_for_non_existant_extension(self): - test_app = setup_extensions_test_app(StubExtensionManager(None)) - - response = test_app.get("/non_extistant_extension", status='*') - - self.assertEqual(404, response.status_int) - - class StubExtension(object): def __init__(self, alias="stub_extension"): @@ -119,10 +55,7 @@ class StubExtension(object): class StubPlugin(object): def __init__(self, supported_extensions=[]): - self.supported_extensions = supported_extensions - - def supports_extension(self, extension): - return extension.get_alias() in self.supported_extensions + self.supported_extension_aliases = supported_extensions class ExtensionExpectingPluginInterface(StubExtension): @@ -142,112 +75,84 @@ class StubPluginInterface(extensions.PluginInterface): pass -class ExtensionManagerTest(unittest.TestCase): +class StubBaseAppController(wsgi.Controller): - def test_invalid_extensions_are_not_registered(self): + def index(self, request): + return "base app index" - class InvalidExtension(object): - """ - This Extension doesn't implement extension methods : - get_name, get_description, get_namespace and get_updated - """ - def get_alias(self): - return "invalid_extension" + def show(self, request, id): + return {'fort': 'knox'} - ext_mgr = ExtensionManager('') - ext_mgr.add_extension(InvalidExtension()) - ext_mgr.add_extension(StubExtension("valid_extension")) - - self.assertTrue('valid_extension' in ext_mgr.extensions) - self.assertFalse('invalid_extension' in ext_mgr.extensions) + def update(self, request, id): + return {'uneditable': 'original_value'} -class PluginAwareExtensionManagerTest(unittest.TestCase): +class ExtensionsTestApp(wsgi.Router): - def setUp(self): - self.ext_mgr = PluginAwareExtensionManager('') + def __init__(self, options={}): + mapper = routes.Mapper() + controller = StubBaseAppController() + mapper.resource("dummy_resource", "/dummy_resources", + controller=controller) + super(ExtensionsTestApp, self).__init__(mapper) - def test_unsupported_extensions_are_not_loaded(self): - self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) - self.ext_mgr.add_extension(StubExtension("e1")) - self.ext_mgr.add_extension(StubExtension("e2")) - self.ext_mgr.add_extension(StubExtension("e3")) +class ResourceExtensionTest(unittest.TestCase): - self.assertTrue("e1" in self.ext_mgr.extensions) - self.assertFalse("e2" in self.ext_mgr.extensions) - self.assertTrue("e3" in self.ext_mgr.extensions) + class ResourceExtensionController(wsgi.Controller): - def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self): - class ExtensionUnawarePlugin(object): - """ - This plugin does not implement supports_extension method. - Extensions will not be loaded when this plugin is used. - """ - pass + def index(self, request): + return "resource index" - self.ext_mgr.plugin = ExtensionUnawarePlugin() - self.ext_mgr.add_extension(StubExtension("e1")) + def show(self, request, id): + return {'data': {'id': id}} - self.assertFalse("e1" in self.ext_mgr.extensions) + def custom_member_action(self, request, id): + return {'member_action': 'value'} - def test_extensions_not_loaded_for_plugin_without_expected_interface(self): + def custom_collection_action(self, request): + return {'collection': 'value'} - class PluginWithoutExpectedInterface(object): - """ - Plugin does not implement get_foo method as expected by extension - """ - def supports_extension(self, true): - return true + def test_resource_can_be_added_as_extension(self): + res_ext = extensions.ResourceExtension('tweedles', + self.ResourceExtensionController()) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) - self.ext_mgr.plugin = PluginWithoutExpectedInterface() - self.ext_mgr.add_extension(ExtensionExpectingPluginInterface("e1")) + index_response = test_app.get("/tweedles") + self.assertEqual(200, index_response.status_int) + self.assertEqual("resource index", index_response.body) - self.assertFalse("e1" in self.ext_mgr.extensions) + show_response = test_app.get("/tweedles/25266") + self.assertEqual({'data': {'id': "25266"}}, show_response.json) - def test_extensions_are_loaded_for_plugin_with_expected_interface(self): + def test_resource_extension_with_custom_member_action(self): + controller = self.ResourceExtensionController() + member = {'custom_member_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + member_actions=member) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) - class PluginWithExpectedInterface(object): - """ - This Plugin implements get_foo method as expected by extension - """ - def supports_extension(self, true): - return true + response = test_app.get("/tweedles/some_id/custom_member_action") + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['member_action'], "value") - def get_foo(self, bar=None): - pass + def test_resource_extension_with_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) - self.ext_mgr.plugin = PluginWithExpectedInterface() - self.ext_mgr.add_extension(ExtensionExpectingPluginInterface("e1")) + response = test_app.get("/tweedles/custom_collection_action") + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") - self.assertTrue("e1" in self.ext_mgr.extensions) + def test_returns_404_for_non_existant_extension(self): + test_app = setup_extensions_test_app(SimpleExtensionManager(None)) - def test_extensions_expecting_quantum_plugin_interface_are_loaded(self): - class ExtensionForQuamtumPluginInterface(StubExtension): - """ - This Extension does not implement get_plugin_interface method. - This will work with any plugin implementing QuantumPluginBase - """ - pass + response = test_app.get("/non_extistant_extension", status='*') - self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) - self.ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) - - self.assertTrue("e1" in self.ext_mgr.extensions) - - def test_extensions_without_need_for__plugin_interface_are_loaded(self): - class ExtensionWithNoNeedForPluginInterface(StubExtension): - """ - This Extension does not need any plugin interface. - This will work with any plugin implementing QuantumPluginBase - """ - def get_plugin_interface(self): - return None - - self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) - self.ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) - - self.assertTrue("e1" in self.ext_mgr.extensions) + self.assertEqual(404, response.status_int) class ActionExtensionTest(unittest.TestCase): @@ -354,10 +259,140 @@ class RequestExtensionTest(BaseTest): def _setup_app_with_request_handler(self, handler, verb): req_ext = extensions.RequestExtension(verb, '/dummy_resources/:(id)', handler) - manager = StubExtensionManager(None, None, req_ext) + manager = SimpleExtensionManager(None, None, req_ext) return setup_extensions_test_app(manager) +class ExtensionManagerTest(unittest.TestCase): + + def test_invalid_extensions_are_not_registered(self): + + class InvalidExtension(object): + """ + This Extension doesn't implement extension methods : + get_name, get_description, get_namespace and get_updated + """ + def get_alias(self): + return "invalid_extension" + + ext_mgr = ExtensionManager('') + ext_mgr.add_extension(InvalidExtension()) + ext_mgr.add_extension(StubExtension("valid_extension")) + + self.assertTrue('valid_extension' in ext_mgr.extensions) + self.assertFalse('invalid_extension' in ext_mgr.extensions) + + +class PluginAwareExtensionManagerTest(unittest.TestCase): + + def setUp(self): + self.ext_mgr = PluginAwareExtensionManager('') + + def test_unsupported_extensions_are_not_loaded(self): + self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) + + self.ext_mgr.add_extension(StubExtension("e1")) + self.ext_mgr.add_extension(StubExtension("e2")) + self.ext_mgr.add_extension(StubExtension("e3")) + + self.assertTrue("e1" in self.ext_mgr.extensions) + self.assertFalse("e2" in self.ext_mgr.extensions) + self.assertTrue("e3" in self.ext_mgr.extensions) + + def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self): + class ExtensionUnawarePlugin(object): + """ + This plugin does not implement supports_extension method. + Extensions will not be loaded when this plugin is used. + """ + pass + + self.ext_mgr.plugin = ExtensionUnawarePlugin() + self.ext_mgr.add_extension(StubExtension("e1")) + + self.assertFalse("e1" in self.ext_mgr.extensions) + + def test_extensions_not_loaded_for_plugin_without_expected_interface(self): + + class PluginWithoutExpectedInterface(object): + """ + Plugin does not implement get_foo method as expected by extension + """ + supported_extension_aliases = ["supported_extension"] + + self.ext_mgr.plugin = PluginWithoutExpectedInterface() + self.ext_mgr.add_extension( + ExtensionExpectingPluginInterface("supported_extension")) + + self.assertFalse("e1" in self.ext_mgr.extensions) + + def test_extensions_are_loaded_for_plugin_with_expected_interface(self): + + class PluginWithExpectedInterface(object): + """ + This Plugin implements get_foo method as expected by extension + """ + supported_extension_aliases = ["supported_extension"] + + def get_foo(self, bar=None): + pass + + self.ext_mgr.plugin = PluginWithExpectedInterface() + self.ext_mgr.add_extension( + ExtensionExpectingPluginInterface("supported_extension")) + + self.assertTrue("supported_extension" in self.ext_mgr.extensions) + + def test_extensions_expecting_quantum_plugin_interface_are_loaded(self): + class ExtensionForQuamtumPluginInterface(StubExtension): + """ + This Extension does not implement get_plugin_interface method. + This will work with any plugin implementing QuantumPluginBase + """ + pass + + self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) + self.ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) + + self.assertTrue("e1" in self.ext_mgr.extensions) + + def test_extensions_without_need_for__plugin_interface_are_loaded(self): + class ExtensionWithNoNeedForPluginInterface(StubExtension): + """ + This Extension does not need any plugin interface. + This will work with any plugin implementing QuantumPluginBase + """ + def get_plugin_interface(self): + return None + + self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) + self.ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) + + self.assertTrue("e1" in self.ext_mgr.extensions) + + +class ExtensionControllerTest(unittest.TestCase): + + def setUp(self): + super(ExtensionControllerTest, self).setUp() + self.test_app = setup_extensions_test_app() + + def test_index_gets_all_registerd_extensions(self): + response = self.test_app.get("/extensions") + foxnsox = response.json["extensions"][0] + + self.assertEqual(foxnsox["alias"], "FOXNSOX") + self.assertEqual(foxnsox["namespace"], + "http://www.fox.in.socks/api/ext/pie/v1.0") + + def test_extension_can_be_accessed_by_alias(self): + foxnsox_extension = self.test_app.get("/extensions/FOXNSOX").json + + self.assertEqual(foxnsox_extension["alias"], "FOXNSOX") + self.assertEqual(foxnsox_extension["namespace"], + "http://www.fox.in.socks/api/ext/pie/v1.0") + + class TestExtensionMiddlewareFactory(unittest.TestCase): def test_app_configured_with_extensions_as_filter(self): @@ -368,37 +403,6 @@ class TestExtensionMiddlewareFactory(unittest.TestCase): self.assertEqual(response.status_int, 200) -class ExtensionsTestApp(wsgi.Router): - - def __init__(self, options={}): - mapper = routes.Mapper() - controller = StubController(extension_index_response) - mapper.resource("dummy_resource", "/dummy_resources", - controller=controller) - super(ExtensionsTestApp, self).__init__(mapper) - - -class StubController(wsgi.Controller): - - def __init__(self, body): - self.body = body - - def index(self, request): - return self.body - - def show(self, request, id): - return {'fort': 'knox'} - - def update(self, request, id): - return {'uneditable': 'original_value'} - - def custom_member_action(self, request, id): - return {'member_action': 'value'} - - def custom_collection_action(self, request): - return {'collection': 'value'} - - def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) @@ -421,22 +425,13 @@ def setup_extensions_test_app(extension_manager=None): return TestApp(setup_extensions_middleware(extension_manager)) -class StubExtensionManager(object): +class SimpleExtensionManager(object): def __init__(self, resource_ext=None, action_ext=None, request_ext=None): self.resource_ext = resource_ext self.action_ext = action_ext self.request_ext = request_ext - def get_name(self): - return "Tweedle Beetle Extension" - - def get_alias(self): - return "TWDLBETL" - - def get_description(self): - return "Provides access to Tweedle Beetles" - def get_resources(self): resource_exts = [] if self.resource_ext: From 71189050545ee7c5b8885adebc37f8ba66a8a9c1 Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Mon, 25 Jul 2011 18:45:21 +0530 Subject: [PATCH 23/76] Rajaram/Santhosh|quantum manager loads plugin only once, even though both extension middleware and APIRouter calls it --- etc/quantum.conf | 2 +- etc/quantum.conf.sample | 2 +- etc/quantum.conf.test | 19 +---- quantum/api/__init__.py | 2 +- quantum/common/extensions.py | 25 ++++--- quantum/manager.py | 9 ++- .../openvswitch/ovs_quantum_plugin.ini | 2 +- tests/unit/test_extensions.py | 69 ++++++++----------- 8 files changed, 53 insertions(+), 77 deletions(-) diff --git a/etc/quantum.conf b/etc/quantum.conf index d527c83870c..e4d910b400c 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -23,7 +23,7 @@ use = egg:Paste#urlmap pipeline = extensions quantumapiapp [filter:extensions] -paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory +paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory [app:quantumversions] paste.app_factory = quantum.api.versions:Versions.factory diff --git a/etc/quantum.conf.sample b/etc/quantum.conf.sample index 502503468fe..eccde5f0595 100644 --- a/etc/quantum.conf.sample +++ b/etc/quantum.conf.sample @@ -23,7 +23,7 @@ use = egg:Paste#urlmap pipeline = extensions quantumapiapp [filter:extensions] -paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory +paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory [app:quantumversions] paste.app_factory = quantum.api.versions:Versions.factory diff --git a/etc/quantum.conf.test b/etc/quantum.conf.test index f3199cd888f..a7134d2848f 100644 --- a/etc/quantum.conf.test +++ b/etc/quantum.conf.test @@ -18,24 +18,7 @@ api_extensions_path = unit/extensions pipeline = extensions extensions_test_app [filter:extensions] -paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory +paste.filter_factory = quantum.common.extensions:plugin_aware_extension_middleware_factory [app:extensions_test_app] paste.app_factory = tests.unit.test_extensions:app_factory - -[composite:quantum] -use = egg:Paste#urlmap -/: quantumversions -/v0.1: quantumapi - -[pipeline:quantumapi] -pipeline = extensions quantumapiapp - -[filter:extensions] -paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory - -[app:quantumversions] -paste.app_factory = quantum.api.versions:Versions.factory - -[app:quantumapiapp] -paste.app_factory = quantum.api:APIRouterV01.factory diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index e0f0d0101d0..e2b0013868b 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -48,7 +48,7 @@ class APIRouterV01(wsgi.Router): def _setup_routes(self, mapper, options): # Loads the quantum plugin - plugin = manager.QuantumManager(options).get_plugin() + plugin = manager.QuantumManager.get_plugin(options) uri_prefix = '/tenants/{tenant_id}/' mapper.resource('network', 'networks', diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 521543a40d3..79d3cfb153e 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -318,16 +318,14 @@ class ExtensionMiddleware(wsgi.Middleware): return app -class PluginAwareExtensionMiddleware(ExtensionMiddleware): - - def __init__(self, application, config_params, ext_mgr=None, - plugin_options=None): - plugin_aware_extension_mgr = PluginAwareExtensionManager( - config_params.get('api_extensions_path', ''), - plugin_options) - ext_mgr = (ext_mgr or plugin_aware_extension_mgr) - super(PluginAwareExtensionMiddleware, self).__init__( - application, config_params, ext_mgr) +def plugin_aware_extension_middleware_factory(global_config, **local_config): + """Paste factory.""" + def _factory(app): + extensions_path = global_config.get('api_extensions_path', '') + ext_mgr = PluginAwareExtensionManager(extensions_path, + QuantumManager().get_plugin()) + return ExtensionMiddleware(app, global_config, ext_mgr=ext_mgr) + return _factory class ExtensionManager(object): @@ -449,8 +447,8 @@ class ExtensionManager(object): class PluginAwareExtensionManager(ExtensionManager): - def __init__(self, path, plugin_options=None): - self.plugin = QuantumManager(plugin_options).get_plugin() + def __init__(self, path, plugin): + self.plugin = plugin super(PluginAwareExtensionManager, self).__init__(path) def _check_extension(self, extension): @@ -470,7 +468,8 @@ class PluginAwareExtensionManager(ExtensionManager): if(not hasattr(extension, "get_plugin_interface") or extension.get_plugin_interface() is None): return True - return isinstance(self.plugin, extension.get_plugin_interface()) + return isinstance(self.plugin, + extension.get_plugin_interface()) class RequestExtension(object): diff --git a/quantum/manager.py b/quantum/manager.py index 4c890d7f750..388324a8db9 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -47,6 +47,8 @@ def find_config(basepath): class QuantumManager(object): + _instance = None + def __init__(self, options=None, config_file=None): if config_file == None: self.configuration_file = find_config( @@ -69,5 +71,8 @@ class QuantumManager(object): "All compatibility tests passed") self.plugin = plugin_klass() - def get_plugin(self): - return self.plugin + @classmethod + def get_plugin(cls, options=None, config_file=None): + if cls._instance is None: + cls._instance = cls(options, config_file) + return cls._instance.plugin diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini index 66095d85d1b..3fd52e0c9c1 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -1,7 +1,7 @@ [DATABASE] name = ovs_quantum user = root -pass = nova +pass = host = 127.0.0.1 port = 3306 diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index e9f01e3ebca..0478e3a3775 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -26,7 +26,7 @@ from quantum.common import wsgi from quantum.common import config from quantum.common.extensions import (ExtensionManager, PluginAwareExtensionManager, - PluginAwareExtensionMiddleware) + ExtensionMiddleware) test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'etc', 'quantum.conf.test') @@ -288,19 +288,17 @@ class ExtensionManagerTest(unittest.TestCase): class PluginAwareExtensionManagerTest(unittest.TestCase): - def setUp(self): - self.ext_mgr = PluginAwareExtensionManager('', plugin_options) - def test_unsupported_extensions_are_not_loaded(self): - self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"]) + stub_plugin = StubPlugin(supported_extensions=["e1", "e3"]) + ext_mgr = PluginAwareExtensionManager('', stub_plugin) - self.ext_mgr.add_extension(StubExtension("e1")) - self.ext_mgr.add_extension(StubExtension("e2")) - self.ext_mgr.add_extension(StubExtension("e3")) + ext_mgr.add_extension(StubExtension("e1")) + ext_mgr.add_extension(StubExtension("e2")) + ext_mgr.add_extension(StubExtension("e3")) - self.assertTrue("e1" in self.ext_mgr.extensions) - self.assertFalse("e2" in self.ext_mgr.extensions) - self.assertTrue("e3" in self.ext_mgr.extensions) + self.assertTrue("e1" in ext_mgr.extensions) + self.assertFalse("e2" in ext_mgr.extensions) + self.assertTrue("e3" in ext_mgr.extensions) def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self): class ExtensionUnawarePlugin(object): @@ -310,10 +308,10 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): """ pass - self.ext_mgr.plugin = ExtensionUnawarePlugin() - self.ext_mgr.add_extension(StubExtension("e1")) + ext_mgr = PluginAwareExtensionManager('', ExtensionUnawarePlugin()) + ext_mgr.add_extension(StubExtension("e1")) - self.assertFalse("e1" in self.ext_mgr.extensions) + self.assertFalse("e1" in ext_mgr.extensions) def test_extensions_not_loaded_for_plugin_without_expected_interface(self): @@ -323,11 +321,12 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): """ supported_extension_aliases = ["supported_extension"] - self.ext_mgr.plugin = PluginWithoutExpectedInterface() - self.ext_mgr.add_extension( + ext_mgr = PluginAwareExtensionManager('', + PluginWithoutExpectedInterface()) + ext_mgr.add_extension( ExtensionExpectingPluginInterface("supported_extension")) - self.assertFalse("e1" in self.ext_mgr.extensions) + self.assertFalse("e1" in ext_mgr.extensions) def test_extensions_are_loaded_for_plugin_with_expected_interface(self): @@ -339,12 +338,12 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): def get_foo(self, bar=None): pass - - self.ext_mgr.plugin = PluginWithExpectedInterface() - self.ext_mgr.add_extension( + ext_mgr = PluginAwareExtensionManager('', + PluginWithExpectedInterface()) + ext_mgr.add_extension( ExtensionExpectingPluginInterface("supported_extension")) - self.assertTrue("supported_extension" in self.ext_mgr.extensions) + self.assertTrue("supported_extension" in ext_mgr.extensions) def test_extensions_expecting_quantum_plugin_interface_are_loaded(self): class ExtensionForQuamtumPluginInterface(StubExtension): @@ -353,11 +352,11 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): This will work with any plugin implementing QuantumPluginBase """ pass + stub_plugin = StubPlugin(supported_extensions=["e1"]) + ext_mgr = PluginAwareExtensionManager('', stub_plugin) + ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) - self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) - self.ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) - - self.assertTrue("e1" in self.ext_mgr.extensions) + self.assertTrue("e1" in ext_mgr.extensions) def test_extensions_without_need_for__plugin_interface_are_loaded(self): class ExtensionWithNoNeedForPluginInterface(StubExtension): @@ -368,10 +367,11 @@ class PluginAwareExtensionManagerTest(unittest.TestCase): def get_plugin_interface(self): return None - self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"]) - self.ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) + stub_plugin = StubPlugin(supported_extensions=["e1"]) + ext_mgr = PluginAwareExtensionManager('', stub_plugin) + ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) - self.assertTrue("e1" in self.ext_mgr.extensions) + self.assertTrue("e1" in ext_mgr.extensions) class ExtensionControllerTest(unittest.TestCase): @@ -396,16 +396,6 @@ class ExtensionControllerTest(unittest.TestCase): "http://www.fox.in.socks/api/ext/pie/v1.0") -class TestExtensionMiddlewareFactory(unittest.TestCase): - - def test_app_configured_with_extensions_as_filter(self): - conf, quantum_app = config.load_paste_app('extensions_app_with_filter', - {"config_file": test_conf_file}, None) - - response = TestApp(quantum_app).get("/extensions") - self.assertEqual(response.status_int, 200) - - def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) @@ -421,8 +411,7 @@ def setup_base_app(): def setup_extensions_middleware(extension_manager=None): options = {'config_file': test_conf_file} conf, app = config.load_paste_app('extensions_test_app', options, None) - return PluginAwareExtensionMiddleware(app, conf, ext_mgr=extension_manager, - plugin_options=plugin_options) + return ExtensionMiddleware(app, conf, ext_mgr=extension_manager) def setup_extensions_test_app(extension_manager=None): From 01eafb56be207c3660cbe125e28598ac83cef097 Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Tue, 26 Jul 2011 10:57:24 +0530 Subject: [PATCH 24/76] Rajaram/Santhosh|Added plugin interface in foxinsox and Updated README --- README | 25 +++++++++++++++++++++++++ quantum/common/extensions.py | 2 +- quantum/plugins/SamplePlugin.py | 7 +++++-- tests/unit/extensions/foxinsocks.py | 11 +++++++++++ tests/unit/test_extensions.py | 7 +++++-- 5 files changed, 47 insertions(+), 5 deletions(-) diff --git a/README b/README index f3e973faacd..5c59970840e 100644 --- a/README +++ b/README @@ -105,4 +105,29 @@ There are a few requirements to writing your own plugin: 4) Launch the Quantum Service, and your plug-in is configured and ready to manage a Cloud Networking Fabric. +# -- Extensions +1) Creating Extensions: + An example extension exists in ./tests/unit/extensions/foxinsocks.py + The unit tests in ./tests/unit/test_extensions.py document the complete + set of extension features supported +2) Loading Extension: + a) The extension file should have a class with same name as the filename. + This class should implement the contract required by the extension framework. + See ExtensionDescriptor class in ./quantum/common/extensions.py for details + For an example look at Foxinsocks class in foxinsocks.py + b) The extension file should be deployed in the ./extensions folder. + If the filename starts with an "_", it will not be treated as an extension. +3) Plugins advertizing extension support: + A Plugin can advertize all the extensions it supports through the + 'supported_extension_aliases' attribute. Eg: + + class SomePlugin: + ... + supported_extension_aliases = ['extension1_alias', + 'extension2_alias', + 'extension3_alias'] +4) Standardizing extensions: + An extension might be supported by multiple plugins. In such cases, the extension + can mandate an interface that all plugins have to support for that extension. + For an example see the FoxInSocksPluginInterface in foxinsocks.py and the QuantumEchoPlugin diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 79d3cfb153e..485fbc2a5ae 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -24,7 +24,7 @@ import logging import webob.dec import webob.exc from gettext import gettext as _ -from abc import ABCMeta, abstractmethod +from abc import ABCMeta from quantum.manager import QuantumManager from quantum.common import exceptions diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index ff08bb37010..68cd8e54e7e 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -119,6 +119,11 @@ class QuantumEchoPlugin(object): """ print("unplug_interface() called\n") + supported_extension_aliases = ["FOXNSOX"] + + def method_to_support_foxnsox_extension(self): + print("method_to_support_foxnsox_extension() called\n") + class DummyDataPlugin(object): @@ -237,8 +242,6 @@ class FakePlugin(object): db.configure_db({'sql_connection': 'sqlite:///:memory:'}) FakePlugin._net_counter = 0 - supported_extension_aliases = ["FOXNSOX"] - def _get_network(self, tenant_id, network_id): try: network = db.network_get(network_id) diff --git a/tests/unit/extensions/foxinsocks.py b/tests/unit/extensions/foxinsocks.py index 2c224af8292..5bdefde95fd 100644 --- a/tests/unit/extensions/foxinsocks.py +++ b/tests/unit/extensions/foxinsocks.py @@ -19,6 +19,7 @@ import json from quantum.common import wsgi from quantum.common import extensions +from abc import abstractmethod class FoxInSocksController(wsgi.Controller): @@ -27,11 +28,21 @@ class FoxInSocksController(wsgi.Controller): return "Try to say this Mr. Knox, sir..." +class FoxInSocksPluginInterface(extensions.PluginInterface): + + @abstractmethod + def method_to_support_foxnsox_extension(self): + pass + + class Foxinsocks(object): def __init__(self): pass + def get_plugin_interface(self): + return FoxInSocksPluginInterface + def get_name(self): return "Fox In Socks" diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 0478e3a3775..9772f89e9ff 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -27,11 +27,11 @@ from quantum.common import config from quantum.common.extensions import (ExtensionManager, PluginAwareExtensionManager, ExtensionMiddleware) +from quantum.plugins.SamplePlugin import QuantumEchoPlugin test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'etc', 'quantum.conf.test') - -plugin_options = {'plugin_provider': "quantum.plugins.SamplePlugin.FakePlugin"} +extensions_path = os.path.join(os.path.dirname(__file__), "extensions") class StubExtension(object): @@ -409,6 +409,9 @@ def setup_base_app(): def setup_extensions_middleware(extension_manager=None): + extension_manager = (extension_manager or + PluginAwareExtensionManager(extensions_path, + QuantumEchoPlugin())) options = {'config_file': test_conf_file} conf, app = config.load_paste_app('extensions_test_app', options, None) return ExtensionMiddleware(app, conf, ext_mgr=extension_manager) From 01ecfb96c82ece8be94c725ed608dc0c5392daf4 Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Tue, 26 Jul 2011 12:32:27 +0530 Subject: [PATCH 25/76] Rajaram/Santhosh | Added logging to the PluginAwareExtensionManager failures --- quantum/common/extensions.py | 53 ++++++++++++------- .../openvswitch/ovs_quantum_plugin.ini | 2 +- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 485fbc2a5ae..9a2cbe63308 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -415,21 +415,25 @@ class ExtensionManager(object): def _load_all_extensions_from_path(self, path): for f in os.listdir(path): - LOG.info(_('Loading extension file: %s'), f) - mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) - ext_path = os.path.join(path, f) - if file_ext.lower() == '.py' and not mod_name.startswith('_'): - mod = imp.load_source(mod_name, ext_path) - ext_name = mod_name[0].upper() + mod_name[1:] - new_ext_class = getattr(mod, ext_name, None) - if not new_ext_class: - LOG.warn(_('Did not find expected name ' - '"%(ext_name)s" in %(file)s'), - {'ext_name': ext_name, - 'file': ext_path}) - continue - new_ext = new_ext_class() + try: + LOG.info(_('Loading extension file: %s'), f) + mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) + ext_path = os.path.join(path, f) + if file_ext.lower() == '.py' and not mod_name.startswith('_'): + mod = imp.load_source(mod_name, ext_path) + ext_name = mod_name[0].upper() + mod_name[1:] + new_ext_class = getattr(mod, ext_name, None) + if not new_ext_class: + LOG.warn(_('Did not find expected name ' + '"%(ext_name)s" in %(file)s'), + {'ext_name': ext_name, + 'file': ext_path}) + continue + new_ext = new_ext_class() self.add_extension(new_ext) + except Exception as exception: + LOG.warn("extension file %s wasnt loaded due to %s", + f, exception) def add_extension(self, ext): # Do nothing if the extension doesn't check out @@ -452,7 +456,8 @@ class PluginAwareExtensionManager(ExtensionManager): super(PluginAwareExtensionManager, self).__init__(path) def _check_extension(self, extension): - """Checks if plugin supports extension and implements the contract.""" + """Checks if plugin supports extension and implements the + extension contract.""" extension_is_valid = super(PluginAwareExtensionManager, self)._check_extension(extension) return (extension_is_valid and @@ -461,15 +466,25 @@ class PluginAwareExtensionManager(ExtensionManager): def _plugin_supports(self, extension): alias = extension.get_alias() - return (hasattr(self.plugin, "supported_extension_aliases") and - alias in self.plugin.supported_extension_aliases) + supports_extension = (hasattr(self.plugin, + "supported_extension_aliases") and + alias in self.plugin.supported_extension_aliases) + if not supports_extension: + LOG.warn("extension %s not supported by plugin %s", + alias, self.plugin) + return supports_extension def _plugin_implements_interface(self, extension): if(not hasattr(extension, "get_plugin_interface") or extension.get_plugin_interface() is None): return True - return isinstance(self.plugin, - extension.get_plugin_interface()) + plugin_has_interface = isinstance(self.plugin, + extension.get_plugin_interface()) + if not plugin_has_interface: + LOG.warn("plugin %s does not implement extension's" + "plugin interface %s" % (self.plugin, + extension.get_alias())) + return plugin_has_interface class RequestExtension(object): diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini index 3fd52e0c9c1..66095d85d1b 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -1,7 +1,7 @@ [DATABASE] name = ovs_quantum user = root -pass = +pass = nova host = 127.0.0.1 port = 3306 From c7a04be9f788909e6491e00faaaadaa6a3457875 Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Tue, 26 Jul 2011 14:10:54 +0530 Subject: [PATCH 26/76] Santhosh/Rajaram|modified extensions section in README --- README | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/README b/README index 5c59970840e..c7877ef6f6d 100644 --- a/README +++ b/README @@ -108,26 +108,32 @@ There are a few requirements to writing your own plugin: # -- Extensions 1) Creating Extensions: - An example extension exists in ./tests/unit/extensions/foxinsocks.py - The unit tests in ./tests/unit/test_extensions.py document the complete - set of extension features supported -2) Loading Extension: - a) The extension file should have a class with same name as the filename. + a) Extension files should be placed under ./extensions folder. + b) The extension file should have a class with the same name as the filename. This class should implement the contract required by the extension framework. See ExtensionDescriptor class in ./quantum/common/extensions.py for details - For an example look at Foxinsocks class in foxinsocks.py - b) The extension file should be deployed in the ./extensions folder. - If the filename starts with an "_", it will not be treated as an extension. -3) Plugins advertizing extension support: - A Plugin can advertize all the extensions it supports through the - 'supported_extension_aliases' attribute. Eg: + c) To stop a file in ./extensions folder from being loaded as an extension, + the filename should start with an "_" + For an example of an extension file look at Foxinsocks class in + ./tests/unit/extensions/foxinsocks.py + The unit tests in ./tests/unit/test_extensions.py document all the ways in + which you can use extensions + +2) Associating plugins with extensions: + a) A Plugin can advertize all the extensions it supports through the + 'supported_extension_aliases' attribute. Eg: - class SomePlugin: - ... - supported_extension_aliases = ['extension1_alias', + class SomePlugin: + ... + supported_extension_aliases = ['extension1_alias', 'extension2_alias', 'extension3_alias'] -4) Standardizing extensions: - An extension might be supported by multiple plugins. In such cases, the extension - can mandate an interface that all plugins have to support for that extension. - For an example see the FoxInSocksPluginInterface in foxinsocks.py and the QuantumEchoPlugin + Any extension not in this list will not be loaded for the plugin + + b) Extension Interfaces for plugins (optional) + The extension can mandate an interface that plugins have to support with the + 'get_plugin_interface' method in the extension. + For an example see the FoxInSocksPluginInterface in foxinsocks.py. + + The QuantumEchoPlugin lists foxinsox in its supported_extension_aliases + and implements the method from FoxInSocksPluginInterface. From 5e01fb7fbaa1c05b77bc294ef5b2dc064bcb8960 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Wed, 27 Jul 2011 12:05:20 +0530 Subject: [PATCH 27/76] Vinkesh/Santhosh | Removed loading extensions from 'contrib' and fixed an indentation bug while loading extensions --- quantum/common/extensions.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 9a2cbe63308..430c0c94de7 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -400,8 +400,6 @@ class ExtensionManager(object): widgets.py the extension class within that module should be 'Widgets'. - In addition, extensions are loaded from the 'contrib' directory. - See tests/unit/extensions/foxinsocks.py for an example extension implementation. @@ -409,10 +407,6 @@ class ExtensionManager(object): if os.path.exists(self.path): self._load_all_extensions_from_path(self.path) - contrib_path = os.path.join(os.path.dirname(__file__), "contrib") - if os.path.exists(contrib_path): - self._load_all_extensions_from_path(contrib_path) - def _load_all_extensions_from_path(self, path): for f in os.listdir(path): try: @@ -430,7 +424,7 @@ class ExtensionManager(object): 'file': ext_path}) continue new_ext = new_ext_class() - self.add_extension(new_ext) + self.add_extension(new_ext) except Exception as exception: LOG.warn("extension file %s wasnt loaded due to %s", f, exception) From 6244bccdbada53f90f8a5557d28ef6bff1ac9bb1 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Thu, 28 Jul 2011 18:28:07 -0700 Subject: [PATCH 28/76] Changed the param name "network-name" to "net-name" since the Quantum service expects the later. --- quantum/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantum/cli.py b/quantum/cli.py index 02bafbd09dc..0f6a0c27e0b 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -114,7 +114,7 @@ def create_net(manager, *args): def api_create_net(client, *args): tid, name = args - data = {'network': {'network-name': '%s' % name}} + data = {'network': {'net-name': '%s' % name}} body = Serializer().serialize(data, CONTENT_TYPE) res = client.do_request(tid, 'POST', "/networks." + FORMAT, body=body) rd = json.loads(res.read()) @@ -185,7 +185,7 @@ def rename_net(manager, *args): def api_rename_net(client, *args): tid, nid, name = args - data = {'network': {'network-name': '%s' % name}} + data = {'network': {'net-name': '%s' % name}} body = Serializer().serialize(data, CONTENT_TYPE) res = client.do_request(tid, 'PUT', "/networks/%s.%s" % (nid, FORMAT), body=body) From 5796e43bd9c4021996112c14aaa3be6537d52187 Mon Sep 17 00:00:00 2001 From: rohitagarwalla Date: Fri, 29 Jul 2011 20:48:41 -0700 Subject: [PATCH 29/76] persistence of l2network & ucs plugins using mysql - db_conn.ini - configuration details of making a connection to the database - db_test_plugin.py - contains abstraction methods for storing database values in a dict and unit test cases for DB testing - l2network_db.py - db methods for l2network models - l2network_models.py - class definitions for the l2 network tables - ucs_db.py - db methods for ucs models - ucs_models.py - class definition for the ucs tables dynamic loading of the 2nd layer plugin db's based on passed arguments Create, Delete, Get, Getall, Update database methods at - Quantum, L2Network and Ucs Unit test cases for create, delete, getall and update operations for L2Network and Ucs plugins pep8 checks done branch based off revision 34 plugin-framework --- quantum/plugins/cisco/db_conn.ini | 5 + quantum/plugins/cisco/db_test_plugin.py | 1046 +++++++++++++++++++++ quantum/plugins/cisco/l2network_db.py | 239 +++++ quantum/plugins/cisco/l2network_models.py | 86 ++ quantum/plugins/cisco/ucs_db.py | 314 +++++++ quantum/plugins/cisco/ucs_models.py | 113 +++ 6 files changed, 1803 insertions(+) create mode 100644 quantum/plugins/cisco/db_conn.ini create mode 100644 quantum/plugins/cisco/db_test_plugin.py create mode 100644 quantum/plugins/cisco/l2network_db.py create mode 100644 quantum/plugins/cisco/l2network_models.py create mode 100644 quantum/plugins/cisco/ucs_db.py create mode 100644 quantum/plugins/cisco/ucs_models.py diff --git a/quantum/plugins/cisco/db_conn.ini b/quantum/plugins/cisco/db_conn.ini new file mode 100644 index 00000000000..29c5c95369b --- /dev/null +++ b/quantum/plugins/cisco/db_conn.ini @@ -0,0 +1,5 @@ +[DATABASE] +name = cisco_naas +user = root +pass = nova +host = 127.0.0.1 diff --git a/quantum/plugins/cisco/db_test_plugin.py b/quantum/plugins/cisco/db_test_plugin.py new file mode 100644 index 00000000000..3e39399774a --- /dev/null +++ b/quantum/plugins/cisco/db_test_plugin.py @@ -0,0 +1,1046 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. + +import ConfigParser +import os +import logging as LOG +import unittest +from optparse import OptionParser + +import quantum.db.api as db +import quantum.plugins.cisco.l2network_db as l2network_db +import quantum.db.models +import quantum.plugins.cisco.l2network_models + +CONF_FILE = "db_conn.ini" + + +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 + + +def db_conf(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__))) + if configfile == None: + raise Exception("Configuration file \"%s\" doesn't exist" % + (configfile)) + LOG.debug("Using configuration file: %s" % configfile) + config.read(configfile) + + DB_NAME = config.get("DATABASE", "name") + DB_USER = config.get("DATABASE", "user") + DB_PASS = config.get("DATABASE", "pass") + DB_HOST = config.get("DATABASE", "host") + options = {"sql_connection": "mysql://%s:%s@%s/%s" % (DB_USER, + DB_PASS, DB_HOST, DB_NAME)} + db.configure_db(options) + + +class UcsDB(object): + def get_all_ucsmbindings(self): + bindings = [] + try: + for x in ucs_db.get_all_ucsmbinding(): + LOG.debug("Getting ucsm binding : %s" % x.ucsm_ip) + bind_dict = {} + bind_dict["ucsm-ip"] = str(x.ucsm_ip) + bind_dict["network-id"] = str(x.network_id) + bindings.append(bind_dict) + except Exception, e: + LOG.error("Failed to get all bindings: %s" % str(e)) + return bindings + + def get_ucsmbinding(self, ucsm_ip): + binding = [] + try: + for x in ucs_db.get_ucsmbinding(ucsm_ip): + LOG.debug("Getting ucsm binding : %s" % x.ucsm_ip) + bind_dict = {} + bind_dict["ucsm-ip"] = str(res.ucsm_ip) + bind_dict["network-id"] = str(res.network_id) + binding.append(bind_dict) + except Exception, e: + LOG.error("Failed to get binding: %s" % str(e)) + return binding + + def create_ucsmbinding(self, ucsm_ip, networ_id): + bind_dict = {} + try: + res = ucs_db.add_ucsmbinding(ucsm_ip, networ_id) + LOG.debug("Created ucsm binding: %s" % res.ucsm_ip) + bind_dict["ucsm-ip"] = str(res.ucsm_ip) + bind_dict["network-id"] = str(res.network_id) + return bind_dict + except Exception, e: + LOG.error("Failed to create ucsm binding: %s" % str(e)) + + def delete_ucsmbinding(self, ucsm_ip): + try: + res = ucs_db.remove_ucsmbinding(ucsm_ip) + LOG.debug("Deleted ucsm binding : %s" % res.ucsm_ip) + bind_dict = {} + bind_dict["ucsm-ip"] = str(res.ucsm_ip) + return bind_dict + except Exception, e: + raise Exception("Failed to delete dynamic vnic: %s" % str(e)) + + def update_ucsmbinding(self, ucsm_ip, network_id): + try: + res = ucs_db.update_ucsmbinding(ucsm_ip, network_id) + LOG.debug("Updating ucsm binding : %s" % res.ucsm_ip) + bind_dict = {} + bind_dict["ucsm-ip"] = str(res.ucsm_ip) + bind_dict["network-id"] = str(res.network_id) + return bind_dict + except Exception, e: + raise Exception("Failed to update dynamic vnic: %s" % str(e)) + + def get_all_dynamicvnics(self): + vnics = [] + try: + for x in ucs_db.get_all_dynamicvnics(): + LOG.debug("Getting dynamic vnic : %s" % x.uuid) + vnic_dict = {} + vnic_dict["vnic-id"] = str(x.uuid) + vnic_dict["device-name"] = x.device_name + vnic_dict["blade-id"] = str(x.blade_id) + vnics.append(vnic_dict) + except Exception, e: + LOG.error("Failed to get all dynamic vnics: %s" % str(e)) + return vnics + + def get_dynamicvnic(self, vnic_id): + vnic = [] + try: + for x in ucs_db.get_dynamicvnic(vnic_id): + LOG.debug("Getting dynamic vnic : %s" % x.uuid) + vnic_dict = {} + vnic_dict["vnic-id"] = str(x.uuid) + vnic_dict["device-name"] = x.device_name + vnic_dict["blade-id"] = str(x.blade_id) + vnic.append(vnic_dict) + except Exception, e: + LOG.error("Failed to get dynamic vnic: %s" % str(e)) + return vnic + + def create_dynamicvnic(self, device_name, blade_id): + vnic_dict = {} + try: + res = ucs_db.add_dynamicvnic(device_name, blade_id) + LOG.debug("Created dynamic vnic: %s" % res.uuid) + vnic_dict["vnic-id"] = str(res.uuid) + vnic_dict["device-name"] = res.device_name + vnic_dict["blade-id"] = str(res.blade_id) + return vnic_dict + except Exception, e: + LOG.error("Failed to create dynamic vnic: %s" % str(e)) + + def delete_dynamicvnic(self, vnic_id): + try: + res = ucs_db.remove_dynamicvnic(vnic_id) + LOG.debug("Deleted dynamic vnic : %s" % res.uuid) + vnic_dict = {} + vnic_dict["vnic-id"] = str(res.uuid) + return vnic_dict + except Exception, e: + raise Exception("Failed to delete dynamic vnic: %s" % str(e)) + + def update_dynamicvnic(self, vnic_id, device_name=None, blade_id=None): + try: + res = ucs_db.update_dynamicvnic(vnic_id, device_name, blade_id) + LOG.debug("Updating dynamic vnic : %s" % res.uuid) + vnic_dict = {} + vnic_dict["vnic-id"] = str(res.uuid) + vnic_dict["device-name"] = res.device_name + vnic_dict["blade-id"] = str(res.blade_id) + return vnic_dict + except Exception, e: + raise Exception("Failed to update dynamic vnic: %s" % str(e)) + + def get_all_blades(self): + blades = [] + try: + for x in ucs_db.get_all_blades(): + LOG.debug("Getting blade : %s" % x.uuid) + blade_dict = {} + blade_dict["blade-id"] = str(x.uuid) + blade_dict["mgmt-ip"] = str(x.mgmt_ip) + blade_dict["mac-addr"] = str(x.mac_addr) + blade_dict["chassis-id"] = str(x.chassis_id) + blade_dict["ucsm-ip"] = str(x.ucsm_ip) + blades.append(blade_dict) + except Exception, e: + LOG.error("Failed to get all blades: %s" % str(e)) + return blades + + def get_blade(self, blade_id): + blade = [] + try: + for x in ucs_db.get_blade(blade_id): + LOG.debug("Getting blade : %s" % x.uuid) + blade_dict = {} + blade_dict["blade-id"] = str(x.uuid) + blade_dict["mgmt-ip"] = str(x.mgmt_ip) + blade_dict["mac-addr"] = str(x.mac_addr) + blade_dict["chassis-id"] = str(x.chassis_id) + blade_dict["ucsm-ip"] = str(x.ucsm_ip) + blade.append(blade_dict) + except Exception, e: + LOG.error("Failed to get all blades: %s" % str(e)) + return blade + + def create_blade(self, mgmt_ip, mac_addr, chassis_id, ucsm_ip): + blade_dict = {} + try: + res = ucs_db.add_blade(mgmt_ip, mac_addr, chassis_id, ucsm_ip) + LOG.debug("Created blade: %s" % res.uuid) + blade_dict["blade-id"] = str(res.uuid) + blade_dict["mgmt-ip"] = str(res.mgmt_ip) + blade_dict["mac-addr"] = str(res.mac_addr) + blade_dict["chassis-id"] = str(res.chassis_id) + blade_dict["ucsm-ip"] = str(res.ucsm_ip) + return blade_dict + except Exception, e: + LOG.error("Failed to create blade: %s" % str(e)) + + def delete_blade(self, blade_id): + try: + res = ucs_db.remove_blade(blade_id) + LOG.debug("Deleted blade : %s" % res.uuid) + blade_dict = {} + blade_dict["blade-id"] = str(res.uuid) + return blade_dict + except Exception, e: + raise Exception("Failed to delete blade: %s" % str(e)) + + def update_blade(self, blade_id, mgmt_ip=None, mac_addr=None,\ + chassis_id=None, ucsm_ip=None): + try: + res = ucs_db.update_blade(blade_id, mgmt_ip, mac_addr, \ + chassis_id, ucsm_ip) + LOG.debug("Updating blade : %s" % res.uuid) + blade_dict = {} + blade_dict["blade-id"] = str(res.uuid) + blade_dict["mgmt-ip"] = str(res.mgmt_ip) + blade_dict["mac-addr"] = str(res.mac_addr) + blade_dict["chassis-id"] = str(res.chassis_id) + blade_dict["ucsm-ip"] = str(res.ucsm_ip) + return blade_dict + except Exception, e: + raise Exception("Failed to update blade: %s" % str(e)) + + def get_all_port_bindings(self): + port_bindings = [] + try: + for x in ucs_db.get_all_portbindings(): + LOG.debug("Getting port binding for port: %s" % x.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = x.port_id + port_bind_dict["dynamic-vnic-id"] = str(x.dynamic_vnic_id) + port_bind_dict["portprofile-name"] = x.portprofile_name + port_bind_dict["vlan-name"] = x.vlan_name + port_bind_dict["vlan-id"] = str(x.vlan_id) + port_bind_dict["qos"] = x.qos + port_bindings.append(port_bind_dict) + except Exception, e: + LOG.error("Failed to get all port bindings: %s" % str(e)) + return port_bindings + + def get_port_binding(self): + port_binding = [] + try: + for x in ucs_db.get_portbinding(port_id): + LOG.debug("Getting port binding for port: %s" % x.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = x.port_id + port_bind_dict["dynamic-vnic-id"] = str(x.dynamic_vnic_id) + port_bind_dict["portprofile-name"] = x.portprofile_name + port_bind_dict["vlan-name"] = x.vlan_name + port_bind_dict["vlan-id"] = str(x.vlan_id) + port_bind_dict["qos"] = x.qos + port_bindings.append(port_bind_dict) + except Exception, e: + LOG.error("Failed to get port binding: %s" % str(e)) + return port_binding + + def create_port_binding(self, port_id, dynamic_vnic_id, portprofile_name, \ + vlan_name, vlan_id, qos): + port_bind_dict = {} + try: + res = ucs_db.add_portbinding(port_id, dynamic_vnic_id, \ + portprofile_name, vlan_name, vlan_id, qos) + LOG.debug("Created port binding: %s" % res.port_id) + port_bind_dict["port-id"] = res.port_id + port_bind_dict["dynamic-vnic-id"] = str(res.dynamic_vnic_id) + port_bind_dict["portprofile-name"] = res.portprofile_name + port_bind_dict["vlan-name"] = res.vlan_name + port_bind_dict["vlan-id"] = str(res.vlan_id) + port_bind_dict["qos"] = res.qos + return port_bind_dict + except Exception, e: + LOG.error("Failed to create port binding: %s" % str(e)) + + def delete_port_binding(self, port_id): + try: + res = ucs_db.remove_portbinding(port_id) + LOG.debug("Deleted port binding : %s" % res.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = res.port_id + return port_bind_dict + except Exception, e: + raise Exception("Failed to delete port profile: %s" % str(e)) + + def update_port_binding(self, port_id, dynamic_vnic_id, \ + portprofile_name, vlan_name, vlan_id, qos): + try: + res = ucs_db.update_portbinding(port_id, dynamic_vnic_id, \ + portprofile_name, vlan_name, vlan_id, qos) + LOG.debug("Updating port binding: %s" % res.port_id) + port_bind_dict = {} + port_bind_dict["port-id"] = res.port_id + port_bind_dict["dynamic-vnic-id"] = str(res.dynamic_vnic_id) + port_bind_dict["portprofile-name"] = res.portprofile_name + port_bind_dict["vlan-name"] = res.vlan_name + port_bind_dict["vlan-id"] = str(res.vlan_id) + port_bind_dict["qos"] = res.qos + return port_bind_dict + except Exception, e: + raise Exception("Failed to update portprofile binding:%s" % str(e)) + + +class QuantumDB(object): + def get_all_networks(self, tenant_id): + nets = [] + try: + for x in db.network_list(tenant_id): + LOG.debug("Getting network: %s" % x.uuid) + net_dict = {} + net_dict["tenant-id"] = x.tenant_id + net_dict["net-id"] = str(x.uuid) + net_dict["net-name"] = x.name + nets.append(net_dict) + except Exception, e: + LOG.error("Failed to get all networks: %s" % str(e)) + return nets + + def get_network(self, network_id): + net = [] + try: + for x in db.network_get(network_id): + LOG.debug("Getting network: %s" % x.uuid) + net_dict = {} + net_dict["tenant-id"] = x.tenant_id + net_dict["net-id"] = str(x.uuid) + net_dict["net-name"] = x.name + nets.append(net_dict) + except Exception, e: + LOG.error("Failed to get network: %s" % str(e)) + return net + + def create_network(self, tenant_id, net_name): + net_dict = {} + try: + res = db.network_create(tenant_id, net_name) + LOG.debug("Created network: %s" % res.uuid) + net_dict["tenant-id"] = res.tenant_id + net_dict["net-id"] = str(res.uuid) + net_dict["net-name"] = res.name + return net_dict + except Exception, e: + LOG.error("Failed to create network: %s" % str(e)) + + def delete_network(self, net_id): + try: + net = db.network_destroy(net_id) + LOG.debug("Deleted network: %s" % net.uuid) + net_dict = {} + net_dict["net-id"] = str(net.uuid) + return net_dict + except Exception, e: + raise Exception("Failed to delete port: %s" % str(e)) + + def rename_network(self, tenant_id, net_id, new_name): + try: + net = db.network_rename(net_id, tenant_id, new_name) + LOG.debug("Renamed network: %s" % net.uuid) + net_dict = {} + net_dict["net-id"] = str(net.uuid) + net_dict["net-name"] = net.name + return net_dict + except Exception, e: + raise Exception("Failed to rename network: %s" % str(e)) + + def get_all_ports(self, net_id): + ports = [] + try: + for x in db.port_list(net_id): + LOG.debug("Getting port: %s" % x.uuid) + port_dict = {} + port_dict["port-id"] = str(x.uuid) + port_dict["net-id"] = str(x.network_id) + port_dict["int-id"] = x.interface_id + port_dict["state"] = x.state + ports.append(port_dict) + return ports + except Exception, e: + LOG.error("Failed to get all ports: %s" % str(e)) + + def get_port(self, port_id): + port = [] + try: + for x in db.port_get(port_id): + LOG.debug("Getting port: %s" % x.uuid) + port_dict = {} + port_dict["port-id"] = str(x.uuid) + port_dict["net-id"] = str(x.network_id) + port_dict["int-id"] = x.interface_id + port_dict["state"] = x.state + port.append(port_dict) + return port + except Exception, e: + LOG.error("Failed to get port: %s" % str(e)) + + def create_port(self, net_id): + port_dict = {} + try: + port = db.port_create(net_id) + LOG.debug("Creating port %s" % port.uuid) + port_dict["port-id"] = str(port.uuid) + port_dict["net-id"] = str(port.network_id) + port_dict["int-id"] = port.interface_id + port_dict["state"] = port.state + return port_dict + except Exception, e: + LOG.error("Failed to create port: %s" % str(e)) + + def delete_port(self, port_id): + try: + port = db.port_destroy(port_id) + LOG.debug("Deleted port %s" % port.uuid) + port_dict = {} + port_dict["port-id"] = str(port.uuid) + return port_dict + except Exception, e: + raise Exception("Failed to delete port: %s" % str(e)) + + def update_port(self, port_id, port_state): + try: + port = db.port_set_state(port_id, port_state) + LOG.debug("Updated port %s" % port.uuid) + port_dict = {} + port_dict["port-id"] = str(port.uuid) + port_dict["net-id"] = str(port.network_id) + port_dict["int-id"] = port.interface_id + port_dict["state"] = port.state + return port_dict + except Exception, e: + raise Exception("Failed to update port state: %s" % str(e)) + + +class L2networkDB(object): + def get_all_vlan_bindings(self): + vlans = [] + try: + for x in l2network_db.get_all_vlan_bindings(): + LOG.debug("Getting vlan bindings for vlan: %s" % x.vlan_id) + vlan_dict = {} + vlan_dict["vlan-id"] = str(x.vlan_id) + vlan_dict["vlan-name"] = x.vlan_name + vlan_dict["net-id"] = str(x.network_id) + vlans.append(vlan_dict) + except Exception, e: + LOG.error("Failed to get all vlan bindings: %s" % str(e)) + return vlans + + def get_vlan_binding(self, network_id): + vlan = [] + try: + for x in l2network_db.get_vlan_binding(network_id): + LOG.debug("Getting vlan binding for vlan: %s" % x.vlan_id) + vlan_dict = {} + vlan_dict["vlan-id"] = str(x.vlan_id) + vlan_dict["vlan-name"] = x.vlan_name + vlan_dict["net-id"] = str(x.network_id) + vlan.append(vlan_dict) + except Exception, e: + LOG.error("Failed to get vlan binding: %s" % str(e)) + return vlan + + def create_vlan_binding(self, vlan_id, vlan_name, network_id): + vlan_dict = {} + try: + res = l2network_db.add_vlan_binding(vlan_id, vlan_name, network_id) + LOG.debug("Created vlan binding for vlan: %s" % res.vlan_id) + vlan_dict["vlan-id"] = str(res.vlan_id) + vlan_dict["vlan-name"] = res.vlan_name + vlan_dict["net-id"] = str(res.network_id) + return vlan_dict + except Exception, e: + LOG.error("Failed to create vlan binding: %s" % str(e)) + + def delete_vlan_binding(self, network_id): + try: + res = l2network_db.remove_vlan_binding(network_id) + LOG.debug("Deleted vlan binding for vlan: %s" % res.vlan_id) + vlan_dict = {} + vlan_dict["vlan-id"] = str(res.vlan_id) + return vlan_dict + except Exception, e: + raise Exception("Failed to delete vlan binding: %s" % str(e)) + + def update_vlan_binding(self, network_id, vlan_id, vlan_name): + try: + res = l2network_db.update_vlan_binding(network_id, vlan_id, \ + vlan_name) + LOG.debug("Updating vlan binding for vlan: %s" % res.vlan_id) + vlan_dict = {} + vlan_dict["vlan-id"] = str(res.vlan_id) + vlan_dict["vlan-name"] = res.vlan_name + vlan_dict["net-id"] = str(res.network_id) + return vlan_dict + except Exception, e: + raise Exception("Failed to update vlan binding: %s" % str(e)) + + def get_all_portprofiles(self): + pps = [] + try: + for x in l2network_db.get_all_portprofiles(): + LOG.debug("Getting port profile : %s" % x.uuid) + pp_dict = {} + pp_dict["portprofile-id"] = str(x.uuid) + pp_dict["portprofile-name"] = x.name + pp_dict["vlan-id"] = str(x.vlan_id) + pp_dict["qos"] = x.qos + pps.append(pp_dict) + except Exception, e: + LOG.error("Failed to get all port profiles: %s" % str(e)) + return pps + + def get_portprofile(self, port_id): + pp = [] + try: + for x in l2network_db.get_portprofile(port_id): + LOG.debug("Getting port profile : %s" % x.uuid) + pp_dict = {} + pp_dict["portprofile-id"] = str(x.uuid) + pp_dict["portprofile-name"] = x.name + pp_dict["vlan-id"] = str(x.vlan_id) + pp_dict["qos"] = x.qos + pp.append(pp_dict) + except Exception, e: + LOG.error("Failed to get port profile: %s" % str(e)) + return pp + + def create_portprofile(self, name, vlan_id, qos): + pp_dict = {} + try: + res = l2network_db.add_portprofile(name, vlan_id, qos) + LOG.debug("Created port profile: %s" % res.uuid) + pp_dict["portprofile-id"] = str(res.uuid) + pp_dict["portprofile-name"] = res.name + pp_dict["vlan-id"] = str(res.vlan_id) + pp_dict["qos"] = res.qos + return pp_dict + except Exception, e: + LOG.error("Failed to create port profile: %s" % str(e)) + + def delete_portprofile(self, pp_id): + try: + res = l2network_db.remove_portprofile(pp_id) + LOG.debug("Deleted port profile : %s" % res.uuid) + pp_dict = {} + pp_dict["pp-id"] = str(res.uuid) + return pp_dict + except Exception, e: + raise Exception("Failed to delete port profile: %s" % str(e)) + + def update_portprofile(self, pp_id, name, vlan_id, qos): + try: + res = l2network_db.update_portprofile(pp_id, name, vlan_id, qos) + LOG.debug("Updating port profile : %s" % res.uuid) + pp_dict = {} + pp_dict["portprofile-id"] = str(res.uuid) + pp_dict["portprofile-name"] = res.name + pp_dict["vlan-id"] = str(res.vlan_id) + pp_dict["qos"] = res.qos + return pp_dict + except Exception, e: + raise Exception("Failed to update port profile: %s" % str(e)) + + def get_all_pp_bindings(self): + pp_bindings = [] + try: + for x in l2network_db.get_all_pp_bindings(): + LOG.debug("Getting port profile binding: %s" % \ + x.portprofile_id) + ppbinding_dict = {} + ppbinding_dict["portprofile-id"] = str(x.portprofile_id) + ppbinding_dict["net-id"] = str(x.network_id) + ppbinding_dict["tenant-id"] = x.tenant_id + ppbinding_dict["default"] = x.default + pp_bindings.append(ppbinding_dict) + except Exception, e: + LOG.error("Failed to get all port profiles: %s" % str(e)) + return pp_bindings + + def get_pp_binding(self, pp_id): + pp_binding = [] + try: + for x in l2network_db.get_pp_binding(pp_id): + LOG.debug("Getting port profile binding: %s" % \ + x.portprofile_id) + ppbinding_dict = {} + ppbinding_dict["portprofile-id"] = str(x.portprofile_id) + ppbinding_dict["net-id"] = str(x.network_id) + ppbinding_dict["tenant-id"] = x.tenant_id + ppbinding_dict["default"] = x.default + pp_bindings.append(ppbinding_dict) + except Exception, e: + LOG.error("Failed to get port profile binding: %s" % str(e)) + return pp_binding + + def create_pp_binding(self, tenant_id, net_id, pp_id, default): + ppbinding_dict = {} + try: + res = l2network_db.add_pp_binding(tenant_id, net_id, pp_id, \ + default) + LOG.debug("Created port profile binding: %s" % res.portprofile_id) + ppbinding_dict["portprofile-id"] = str(res.portprofile_id) + ppbinding_dict["net-id"] = str(res.network_id) + ppbinding_dict["tenant-id"] = res.tenant_id + ppbinding_dict["default"] = res.default + return ppbinding_dict + except Exception, e: + LOG.error("Failed to create port profile binding: %s" % str(e)) + + def delete_pp_binding(self, pp_id): + try: + res = l2network_db.remove_pp_binding(pp_id) + LOG.debug("Deleted port profile binding : %s" % res.portprofile_id) + ppbinding_dict = {} + ppbinding_dict["portprofile-id"] = str(res.portprofile_id) + return ppbinding_dict + except Exception, e: + raise Exception("Failed to delete port profile: %s" % str(e)) + + def update_pp_binding(self, pp_id, tenant_id, net_id, default): + try: + res = l2network_db.update_pp_binding(pp_id, tenant_id, net_id,\ + default) + LOG.debug("Updating port profile binding: %s" % res.portprofile_id) + ppbinding_dict = {} + ppbinding_dict["portprofile-id"] = str(res.portprofile_id) + ppbinding_dict["net-id"] = str(res.network_id) + ppbinding_dict["tenant-id"] = res.tenant_id + ppbinding_dict["default"] = res.default + return ppbinding_dict + except Exception, e: + raise Exception("Failed to update portprofile binding:%s" % str(e)) + + +class UcsDBTest(unittest.TestCase): + def setUp(self): + self.dbtest = UcsDB() + LOG.debug("Setup") + + def testACreateUcsmBinding(self): + binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") + self.assertTrue(binding1["ucsm-ip"] == "1.2.3.4") + self.tearDownUcsmBinding() + + def testBGetAllUcsmBindings(self): + binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") + binding2 = self.dbtest.create_ucsmbinding("2.3.4.5", "net1") + bindings = self.dbtest.get_all_ucsmbindings() + count = 0 + for x in bindings: + if "net" in x["network-id"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownUcsmBinding() + + def testCDeleteUcsmBinding(self): + binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") + self.dbtest.delete_ucsmbinding(binding1["ucsm-ip"]) + bindings = self.dbtest.get_all_ucsmbindings() + count = 0 + for x in bindings: + if "net " in x["network-id"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownUcsmBinding() + + def testDUpdateUcsmBinding(self): + binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") + binding1 = self.dbtest.update_ucsmbinding(binding1["ucsm-ip"], \ + "newnet1") + bindings = self.dbtest.get_all_ucsmbindings() + count = 0 + for x in bindings: + if "new" in x["network-id"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownUcsmBinding() + + def testECreateDynamicVnic(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) + self.assertTrue(vnic1["device-name"] == "eth1") + self.tearDownDyanmicVnic() + + def testFGetAllDyanmicVnics(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) + vnic2 = self.dbtest.create_dynamicvnic("eth2", blade1["blade-id"]) + vnics = self.dbtest.get_all_dynamicvnics() + count = 0 + for x in vnics: + if "eth" in x["device-name"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownDyanmicVnic() + + def testGDeleteDyanmicVnic(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) + self.dbtest.delete_dynamicvnic(vnic1["vnic-id"]) + vnics = self.dbtest.get_all_dynamicvnics() + count = 0 + for x in vnics: + if "eth " in x["device-name"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownDyanmicVnic() + + def testHUpdateDynamicVnic(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) + vnic1 = self.dbtest.update_dynamicvnic(vnic1["vnic-id"], "neweth1", \ + "newblade2") + vnics = self.dbtest.get_all_dynamicvnics() + count = 0 + for x in vnics: + if "new" in x["device-name"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownDyanmicVnic() + + def testICreateUcsBlade(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + self.assertTrue(blade1["mgmt-ip"] == "1.2.3.4") + self.tearDownUcsBlade() + + def testJGetAllUcsBlade(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + blade2 = self.dbtest.create_blade("2.3.4.5", "efgh", "chassis1", \ + "9.8.7.6") + blades = self.dbtest.get_all_blades() + count = 0 + for x in blades: + if "chassis" in x["chassis-id"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownUcsBlade() + + def testKDeleteUcsBlade(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + self.dbtest.delete_blade(blade1["blade-id"]) + blades = self.dbtest.get_all_blades() + count = 0 + for x in blades: + if "chassis " in x["chassis-id"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownUcsBlade() + + def testLUpdateUcsBlade(self): + blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ + "9.8.7.6") + blade2 = self.dbtest.update_blade(blade1["blade-id"], "2.3.4.5", \ + "newabcd", "chassis1", "9.8.7.6") + blades = self.dbtest.get_all_blades() + count = 0 + for x in blades: + if "new" in x["mac-addr"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownUcsBlade() + + def testMCreatePortBinding(self): + port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ + "vlan1", 10, "qos1") + self.assertTrue(port_bind1["port-id"] == "port1") + self.tearDownPortBinding() + + def testNGetAllPortBinding(self): + port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ + "vlan1", 10, "qos1") + port_bind2 = self.dbtest.create_port_binding("port2", "dv2", "pp2", \ + "vlan2", 20, "qos2") + port_bindings = self.dbtest.get_all_port_bindings() + count = 0 + for x in port_bindings: + if "port" in x["port-id"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownPortBinding() + + def testODeletePortBinding(self): + port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ + "vlan1", 10, "qos1") + self.dbtest.delete_port_binding("port1") + port_bindings = self.dbtest.get_all_port_bindings() + count = 0 + for x in port_bindings: + if "port " in x["port-id"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownPortBinding() + + def testPUpdatePortBinding(self): + port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ + "vlan1", 10, "qos1") + port_bind1 = self.dbtest.update_port_binding("port1", "newdv1", \ + "newpp1", "newvlan1", 11, "newqos1") + port_bindings = self.dbtest.get_all_port_bindings() + count = 0 + for x in port_bindings: + if "new" in x["dynamic-vnic-id"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownPortBinding() + + def tearDownUcsmBinding(self): + print "Tearing Down Ucsm Bindings" + binds = self.dbtest.get_all_ucsmbindings() + for bind in binds: + ip = bind["ucsm-ip"] + self.dbtest.delete_ucsmbinding(ip) + + def tearDownDyanmicVnic(self): + print "Tearing Down Dynamic Vnics" + vnics = self.dbtest.get_all_dynamicvnics() + for vnic in vnics: + id = vnic["vnic-id"] + self.dbtest.delete_dynamicvnic(id) + self.tearDownUcsBlade() + + def tearDownUcsBlade(self): + print "Tearing Down Blades" + blades = self.dbtest.get_all_blades() + for blade in blades: + id = blade["blade-id"] + self.dbtest.delete_blade(id) + + def tearDownPortBinding(self): + print "Tearing Down Port Binding" + port_bindings = self.dbtest.get_all_port_bindings() + for port_binding in port_bindings: + id = port_binding["port-id"] + self.dbtest.delete_port_binding(id) + + +class L2networkDBTest(unittest.TestCase): + def setUp(self): + self.dbtest = L2networkDB() + LOG.debug("Setup") + + def testACreateVlanBinding(self): + vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") + self.assertTrue(vlan1["vlan-id"] == "10") + self.tearDownVlanBinding() + + def testBGetAllVlanBindings(self): + vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") + vlan2 = self.dbtest.create_vlan_binding(20, "vlan2", "netid2") + vlans = self.dbtest.get_all_vlan_bindings() + count = 0 + for x in vlans: + if "netid" in x["net-id"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownVlanBinding() + + def testCDeleteVlanBinding(self): + vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") + self.dbtest.delete_vlan_binding("netid1") + vlans = self.dbtest.get_all_vlan_bindings() + count = 0 + for x in vlans: + if "netid " in x["net-id"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownVlanBinding() + + def testDUpdateVlanBinding(self): + vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") + vlan1 = self.dbtest.update_vlan_binding("netid1", 11, "newvlan1") + vlans = self.dbtest.get_all_vlan_bindings() + count = 0 + for x in vlans: + if "new" in x["vlan-name"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownVlanBinding() + + def testICreatePortProfile(self): + pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") + self.assertTrue(pp1["portprofile-name"] == "portprofile1") + self.tearDownPortProfile() + + def testJGetAllPortProfile(self): + pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") + pp2 = self.dbtest.create_portprofile("portprofile2", 20, "qos2") + pps = self.dbtest.get_all_portprofiles() + count = 0 + for x in pps: + if "portprofile" in x["portprofile-name"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownPortProfile() + + def testKDeletePortProfile(self): + pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") + self.dbtest.delete_portprofile(pp1["portprofile-id"]) + pps = self.dbtest.get_all_portprofiles() + count = 0 + for x in pps: + if "portprofile " in x["portprofile-name"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownPortProfile() + + def testLUpdatePortProfile(self): + pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") + pp1 = self.dbtest.update_portprofile(pp1["portprofile-id"], \ + "newportprofile1", 20, "qos2") + pps = self.dbtest.get_all_portprofiles() + count = 0 + for x in pps: + if "new" in x["portprofile-name"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownPortProfile() + + def testMCreatePortProfileBinding(self): + pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ + "portprofile1", "0") + self.assertTrue(pp_binding1["portprofile-id"] == "portprofile1") + self.tearDownPortProfileBinding() + + def testNGetAllPortProfileBinding(self): + pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ + "portprofile1", "0") + pp_binding2 = self.dbtest.create_pp_binding("t2", "net2", \ + "portprofile2", "0") + pp_bindings = self.dbtest.get_all_pp_bindings() + count = 0 + for x in pp_bindings: + if "portprofile" in x["portprofile-id"]: + count += 1 + self.assertTrue(count == 2) + self.tearDownPortProfileBinding() + + def testODeletePortProfileBinding(self): + pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ + "portprofile1", "0") + self.dbtest.delete_pp_binding(pp_binding1["portprofile-id"]) + pp_bindings = self.dbtest.get_all_pp_bindings() + count = 0 + for x in pp_bindings: + if "portprofile " in x["portprofile-id"]: + count += 1 + self.assertTrue(count == 0) + self.tearDownPortProfileBinding() + + def testPUpdatePortProfileBinding(self): + pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ + "portprofile1", "0") + pp_binding1 = self.dbtest.update_pp_binding("portprofile1", \ + "newt1", "newnet1", "1") + pp_bindings = self.dbtest.get_all_pp_bindings() + count = 0 + for x in pp_bindings: + if "new" in x["net-id"]: + count += 1 + self.assertTrue(count == 1) + self.tearDownPortProfileBinding() + + def tearDownVlanBinding(self): + print "Tearing Down Vlan Binding" + vlans = self.dbtest.get_all_vlan_bindings() + for vlan in vlans: + id = vlan["net-id"] + self.dbtest.delete_vlan_binding(id) + + def tearDownPortProfile(self): + print "Tearing Down Port Profile" + pps = self.dbtest.get_all_portprofiles() + for pp in pps: + id = pp["portprofile-id"] + self.dbtest.delete_portprofile(id) + + def tearDownPortProfileBinding(self): + print "Tearing Down Port Profile Binding" + pp_bindings = self.dbtest.get_all_pp_bindings() + for pp_binding in pp_bindings: + id = pp_binding["portprofile-id"] + self.dbtest.delete_pp_binding(id) + +if __name__ == "__main__": + usagestr = "Usage: %prog [OPTIONS] [args]" + parser = OptionParser(usage=usagestr) + parser.add_option("-v", "--verbose", dest="verbose", + action="store_true", default=False, help="turn on verbose logging") + + options, args = parser.parse_args() + + if options.verbose: + LOG.basicConfig(level=LOG.DEBUG) + else: + LOG.basicConfig(level=LOG.WARN) + + #load the models and db based on the 2nd level plugin argument + if args[0] == "ucs": + ucs_db = __import__("quantum.plugins.cisco.ucs_db", \ + fromlist=["ucs_db"]) + ucs_model = __import__("quantum.plugins.cisco.ucs_models", \ + fromlist=["ucs_models"]) + + db_conf() + + # Run the tests + suite = unittest.TestLoader().loadTestsFromTestCase(L2networkDBTest) + unittest.TextTestRunner(verbosity=2).run(suite) + suite = unittest.TestLoader().loadTestsFromTestCase(UcsDBTest) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/quantum/plugins/cisco/l2network_db.py b/quantum/plugins/cisco/l2network_db.py new file mode 100644 index 00000000000..ca773b6567c --- /dev/null +++ b/quantum/plugins/cisco/l2network_db.py @@ -0,0 +1,239 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. + +from sqlalchemy.orm import exc + +import quantum.db.api as db +import quantum.db.models as models +import l2network_models + + +def get_all_vlan_bindings(): + """Lists all the vlan to network associations""" + session = db.get_session() + try: + bindings = session.query(l2network_models.VlanBinding).\ + all() + return bindings + except exc.NoResultFound: + return [] + + +def get_vlan_binding(netid): + """Lists the vlan given a network_id""" + session = db.get_session() + try: + binding = session.query(l2network_models.VlanBinding).\ + filter_by(network_id=netid).\ + one() + return binding + except exc.NoResultFound: + raise Exception("No network found with net-id = %s" % network_id) + + +def add_vlan_binding(vlanid, vlanname, netid): + """Adds a vlan to network association""" + session = db.get_session() + try: + binding = session.query(l2network_models.VlanBinding).\ + filter_by(vlan_id=vlanid).\ + one() + raise Exception("Vlan with id \"%s\" already exists" % vlanid) + except exc.NoResultFound: + binding = l2network_models.VlanBinding(vlanid, vlanname, netid) + session.add(binding) + session.flush() + return binding + + +def remove_vlan_binding(netid): + """Removes a vlan to network association""" + session = db.get_session() + try: + binding = session.query(l2network_models.VlanBinding).\ + filter_by(network_id=netid).\ + one() + session.delete(binding) + session.flush() + return binding + except exc.NoResultFound: + pass + + +def update_vlan_binding(netid, newvlanid=None, newvlanname=None): + """Updates a vlan to network association""" + session = db.get_session() + try: + binding = session.query(l2network_models.VlanBinding).\ + filter_by(network_id=netid).\ + one() + if newvlanid: + binding.vlan_id = newvlanid + if newvlanname: + binding.vlan_name = newvlanname + session.merge(binding) + session.flush() + return binding + except exc.NoResultFound: + raise Exception("No vlan binding found with network_id = %s" % netid) + + +def get_all_portprofiles(): + """Lists all the port profiles""" + session = db.get_session() + try: + pps = session.query(l2network_models.PortProfile).\ + all() + return pps + except exc.NoResultFound: + return [] + + +def get_portprofile(ppid): + """Lists a port profile""" + session = db.get_session() + try: + pp = session.query(l2network_models.PortProfile).\ + filter_by(uuid=ppid).\ + one() + return pp + except exc.NoResultFound: + raise Exception("No portprofile found with id = %s" % ppid) + + +def add_portprofile(ppname, vlanid, qos): + """Adds a port profile""" + session = db.get_session() + try: + pp = session.query(l2network_models.PortProfile).\ + filter_by(name=ppname).\ + one() + raise Exception("Port profile with name %s already exists" % ppname) + except exc.NoResultFound: + pp = l2network_models.PortProfile(ppname, vlanid, qos) + session.add(pp) + session.flush() + return pp + + +def remove_portprofile(ppid): + """Removes a port profile""" + session = db.get_session() + try: + pp = session.query(l2network_models.PortProfile).\ + filter_by(uuid=ppid).\ + one() + session.delete(pp) + session.flush() + return pp + except exc.NoResultFound: + pass + + +def update_portprofile(ppid, newppname=None, newvlanid=None, newqos=None): + """Updates port profile""" + session = db.get_session() + try: + pp = session.query(l2network_models.PortProfile).\ + filter_by(uuid=ppid).\ + one() + if newppname: + pp.name = newppname + if newvlanid: + pp.vlan_id = newvlanid + if newqos: + pp.qos = newqos + session.merge(pp) + session.flush() + return pp + except exc.NoResultFound: + raise Exception("No port profile with id = %s" % ppid) + + +def get_all_pp_bindings(): + """Lists all the port profiles""" + session = db.get_session() + try: + bindings = session.query(l2network_models.PortProfileBinding).\ + all() + return bindings + except exc.NoResultFound: + return [] + + +def get_pp_binding(ppid): + """Lists a port profile binding""" + session = db.get_session() + try: + binding = session.query(l2network_models.PortProfileBinding).\ + filter_by(portprofile_id=ppid).\ + one() + return binding + except exc.NoResultFound: + raise Exception("No portprofile binding found with id = %s" % ppid) + + +def add_pp_binding(tenantid, networkid, ppid, default): + """Adds a port profile binding""" + session = db.get_session() + try: + binding = session.query(l2network_models.PortProfileBinding).\ + filter_by(portprofile_id=ppid).\ + one() + raise Exception("Port profile binding with id \"%s\" already \ + exists" % ppid) + except exc.NoResultFound: + binding = l2network_models.PortProfileBinding(tenantid, networkid, \ + ppid, default) + session.add(binding) + session.flush() + return binding + + +def remove_pp_binding(ppid): + """Removes a port profile binding""" + session = db.get_session() + try: + binding = session.query(l2network_models.PortProfileBinding).\ + filter_by(portprofile_id=ppid).\ + one() + session.delete(binding) + session.flush() + return binding + except exc.NoResultFound: + pass + + +def update_pp_binding(ppid, newtenantid=None, newnetworkid=None, \ + newdefault=None): + """Updates port profile binding""" + session = db.get_session() + try: + binding = session.query(l2network_models.PortProfileBinding).\ + filter_by(portprofile_id=ppid).\ + one() + if newtenantid: + binding.tenant_id = newtenantid + if newnetworkid: + binding.network_id = newnetworkid + if newdefault: + binding.default = newdefault + session.merge(binding) + session.flush() + return binding + except exc.NoResultFound: + raise Exception("No port profile binding with id = %s" % ppid) diff --git a/quantum/plugins/cisco/l2network_models.py b/quantum/plugins/cisco/l2network_models.py new file mode 100644 index 00000000000..12d75bb303e --- /dev/null +++ b/quantum/plugins/cisco/l2network_models.py @@ -0,0 +1,86 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. + +import uuid + +from sqlalchemy import Column, Integer, String, ForeignKey, Boolean +from sqlalchemy.orm import relation + +from quantum.db.models import BASE + + +class VlanBinding(BASE): + """Represents a binding of vlan_id to network_id""" + __tablename__ = 'vlan_bindings' + + vlan_id = Column(Integer, primary_key=True) + vlan_name = Column(String(255)) + network_id = Column(String(255), nullable=False) + #foreign key to networks.uuid + + def __init__(self, vlan_id, vlan_name, network_id): + self.vlan_id = vlan_id + self.vlan_name = vlan_name + self.network_id = network_id + + def __repr__(self): + return "" % \ + (self.vlan_id, self.vlan_name, self.network_id) + + +class PortProfile(BASE): + """Represents Cisco plugin level PortProfile for a network""" + __tablename__ = 'portprofiles' + + uuid = Column(String(255), primary_key=True) + name = Column(String(255)) + vlan_id = Column(Integer) + qos = Column(String(255)) + + def __init__(self, name, vlan_id, qos=None): + self.uuid = uuid.uuid4() + self.name = name + self.vlan_id = vlan_id + self.qos = qos + + def __repr__(self): + return "" % \ + (self.uuid, self.name, self.vlan_id, self.qos) + + +class PortProfileBinding(BASE): + """Represents PortProfile binding to tenant and network""" + __tablename__ = 'portprofile_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + tenant_id = Column(String(255)) + + network_id = Column(String(255), nullable=False) + #foreign key to networks.uuid + portprofile_id = Column(String(255), nullable=False) + #foreign key to portprofiles.uuid + default = Column(Boolean) + + def __init__(self, tenant_id, network_id, portprofile_id, default): + self.tenant_id = tenant_id + self.network_id = network_id + self.portprofile_id = portprofile_id + self.default = default + + def __repr__(self): + return "" % \ + (self.tenant_id, self.network_id, self.portprofile_id, self.default) diff --git a/quantum/plugins/cisco/ucs_db.py b/quantum/plugins/cisco/ucs_db.py new file mode 100644 index 00000000000..92198e89bb1 --- /dev/null +++ b/quantum/plugins/cisco/ucs_db.py @@ -0,0 +1,314 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. + +from sqlalchemy.orm import exc + +import quantum.db.api as db +import ucs_models + + +def get_all_ucsmbinding(): + """Lists all the ucsm bindings""" + session = db.get_session() + try: + bindings = session.query(ucs_models.UcsmBinding).\ + all() + return bindings + except exc.NoResultFound: + return [] + + +def get_ucsmbinding(ucsm_ip): + """Lists a ucsm binding""" + session = db.get_session() + try: + binding = session.query(ucs_models.UcsmBinding).\ + filter_by(ucsm_ip=ucsm_ip).\ + one() + return binding + except exc.NoResultFound: + raise Exception("No binding found with ip = %s" % ucsm_ip) + + +def add_ucsmbinding(ucsm_ip, network_id): + """Adds a ucsm binding""" + session = db.get_session() + try: + ip = session.query(ucs_models.UcsmBinding).\ + filter_by(ucsm_ip=ucsm_ip).\ + one() + raise Exception("Binding with ucsm ip \"%s\" already exists" % ucsm_ip) + except exc.NoResultFound: + binding = ucs_models.UcsmBinding(ucsm_ip, network_id) + session.add(binding) + session.flush() + return binding + + +def remove_ucsmbinding(ucsm_ip): + """Removes a ucsm binding""" + session = db.get_session() + try: + binding = session.query(ucs_models.UcsmBinding).\ + filter_by(ucsm_ip=ucsm_ip).\ + one() + session.delete(binding) + session.flush() + return binding + except exc.NoResultFound: + pass + + +def update_ucsmbinding(ucsm_ip, new_network_id): + """Updates ucsm binding""" + session = db.get_session() + try: + binding = session.query(ucs_models.UcsmBinding).\ + filter_by(ucsm_ip=ucsm_ip).\ + one() + if new_network_id: + binding.network_id = new_network_id + session.merge(binding) + session.flush() + return binding + except exc.NoResultFound: + raise Exception("No binding with ip = %s" % ucsm_ip) + + +def get_all_dynamicvnics(): + """Lists all the dynamic vnics""" + session = db.get_session() + try: + vnics = session.query(ucs_models.DynamicVnic).\ + all() + return vnics + except exc.NoResultFound: + return [] + + +def get_dynamicvnic(vnic_id): + """Lists a dynamic vnic""" + session = db.get_session() + try: + vnic = session.query(ucs_models.DynamicVnic).\ + filter_by(uuid=vnic_id).\ + one() + return vnic + except exc.NoResultFound: + raise Exception("No dynamic vnic found with id = %s" % vnic_id) + + +def add_dynamicvnic(device_name, blade_id): + """Adds a dynamic vnic""" + session = db.get_session() + try: + name = session.query(ucs_models.DynamicVnic).\ + filter_by(device_name=device_name).\ + one() + raise Exception("Dynamic vnic with device name %s already exists" % \ + device_name) + except exc.NoResultFound: + vnic = ucs_models.DynamicVnic(device_name, blade_id) + session.add(vnic) + session.flush() + return vnic + + +def remove_dynamicvnic(vnic_id): + """Removes a dynamic vnic""" + session = db.get_session() + try: + vnic = session.query(ucs_models.DynamicVnic).\ + filter_by(uuid=vnic_id).\ + one() + session.delete(vnic) + session.flush() + return vnic + except exc.NoResultFound: + pass + + +def update_dynamicvnic(vnic_id, new_device_name=None, new_blade_id=None): + """Updates dynamic vnic""" + session = db.get_session() + try: + vnic = session.query(ucs_models.DynamicVnic).\ + filter_by(uuid=vnic_id).\ + one() + if new_device_name: + vnic.device_name = new_device_name + if new_blade_id: + vnic.blade_id = new_blade_id + session.merge(vnic) + session.flush() + return vnic + except exc.NoResultFound: + raise Exception("No dynamic vnic with id = %s" % vnic_id) + + +def get_all_blades(): + """Lists all the blades details""" + session = db.get_session() + try: + blades = session.query(ucs_models.UcsBlade).\ + all() + return blades + except exc.NoResultFound: + return [] + + +def get_blade(blade_id): + """Lists a blade details""" + session = db.get_session() + try: + blade = session.query(ucs_models.UcsBlade).\ + filter_by(uuid=blade_id).\ + one() + return blade + except exc.NoResultFound: + raise Exception("No blade found with id = %s" % blade_id) + + +def add_blade(mgmt_ip, mac_addr, chassis_id, ucsm_ip): + """Adds a blade""" + session = db.get_session() + try: + ip = session.query(ucs_models.UcsBlade).\ + filter_by(mgmt_ip=mgmt_ip).\ + one() + raise Exception("Blade with ip \"%s\" already exists" % mgmt_ip) + except exc.NoResultFound: + blade = ucs_models.UcsBlade(mgmt_ip, mac_addr, chassis_id, ucsm_ip) + session.add(blade) + session.flush() + return blade + + +def remove_blade(blade_id): + """Removes a blade""" + session = db.get_session() + try: + blade = session.query(ucs_models.UcsBlade).\ + filter_by(uuid=blade_id).\ + one() + session.delete(blade) + session.flush() + return blade + except exc.NoResultFound: + pass + + +def update_blade(blade_id, new_mgmt_ip=None, new_mac_addr=None, \ + new_chassis_id=None, new_ucsm_ip=None): + """Updates details of a blade""" + session = db.get_session() + try: + blade = session.query(ucs_models.UcsBlade).\ + filter_by(uuid=blade_id).\ + one() + if new_mgmt_ip: + blade.mgmt_ip = new_mgmt_ip + if new_mac_addr: + blade.mac_addr = new_mac_addr + if new_chassis_id: + blade.chassis_id = new_chassis_id + if new_ucsm_ip: + blade.ucsm_ip = new_ucsm_ip + session.merge(blade) + session.flush() + return blade + except exc.NoResultFound: + raise Exception("No blade with id = %s" % blade_id) + + +def get_all_portbindings(): + """Lists all the port bindings""" + session = db.get_session() + try: + port_bindings = session.query(ucs_models.PortBinding).\ + all() + return port_bindings + except exc.NoResultFound: + return [] + + +def get_portbinding(port_id): + """Lists a port binding""" + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + return port_binding + except exc.NoResultFound: + raise Exception("No port binding found with port id = %s" % port_id) + + +def add_portbinding(port_id, dynamic_vnic_id, portprofile_name, \ + vlan_name, vlan_id, qos): + """Adds a port binding""" + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + raise Exception("Port Binding with portid %s already exists" % port_id) + except exc.NoResultFound: + port_binding = ucs_models.PortBinding(port_id, dynamic_vnic_id, \ + portprofile_name, vlan_name, vlan_id, qos) + session.add(port_binding) + session.flush() + return port_binding + + +def remove_portbinding(port_id): + """Removes a port binding""" + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + session.delete(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + pass + + +def update_portbinding(port_id, dynamic_vnic_id=None, portprofile_name=None, \ + vlan_name=None, vlan_id=None, qos=None): + """Updates port binding""" + session = db.get_session() + try: + port_binding = session.query(ucs_models.PortBinding).\ + filter_by(port_id=port_id).\ + one() + if dynamic_vnic_id: + port_binding.dynamic_vnic_id = dynamic_vnic_id + if portprofile_name: + port_binding.portprofile_name = portprofile_name + if vlan_name: + port_binding.vlan_name = vlan_name + if vlan_name: + port_binding.vlan_id = vlan_id + if qos: + port_binding.qos = qos + session.merge(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + raise Exception("No port binding with port id = %s" % port_id) diff --git a/quantum/plugins/cisco/ucs_models.py b/quantum/plugins/cisco/ucs_models.py new file mode 100644 index 00000000000..68cd3dddeac --- /dev/null +++ b/quantum/plugins/cisco/ucs_models.py @@ -0,0 +1,113 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. + +import uuid + +from sqlalchemy import Column, Integer, String, ForeignKey, Boolean +from sqlalchemy.orm import relation + +from quantum.db.models import BASE + + +class UcsmBinding(BASE): + """Represents a binding of ucsm to network_id""" + __tablename__ = 'ucsm_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + ucsm_ip = Column(String(255)) + network_id = Column(String(255), nullable=False) + #foreign key to networks.uuid + + def __init__(self, ucsm_ip, network_id): + self.ucsm_ip = ucsm_ip + self.network_id = network_id + + def __repr__(self): + return "" % \ + (self.ucsm_ip, self.network_id) + + +class DynamicVnic(BASE): + """Represents Cisco UCS Dynamic Vnics""" + __tablename__ = 'dynamic_vnics' + + uuid = Column(String(255), primary_key=True) + device_name = Column(String(255)) + blade_id = Column(String(255), ForeignKey("ucs_blades.uuid"), \ + nullable=False) + + def __init__(self, device_name, blade_id): + self.uuid = uuid.uuid4() + self.device_name = device_name + self.blade_id = blade_id + + def __repr__(self): + return "" % \ + (self.uuid, self.device_name, self.blade_id) + + +class UcsBlade(BASE): + """Represents details of ucs blades""" + __tablename__ = 'ucs_blades' + + uuid = Column(String(255), primary_key=True) + mgmt_ip = Column(String(255)) + mac_addr = Column(String(255)) + chassis_id = Column(String(255)) + ucsm_ip = Column(String(255)) + dynamic_vnics = relation(DynamicVnic, order_by=DynamicVnic.uuid, \ + backref="blade") + + def __init__(self, mgmt_ip, mac_addr, chassis_id, ucsm_ip): + self.uuid = uuid.uuid4() + self.mgmt_ip = mgmt_ip + self.mac_addr = mac_addr + self.chassis_id = chassis_id + self.ucsm_ip = ucsm_ip + + def __repr__(self): + return "" % \ + (self.uuid, self.mgmt_ip, self.mac_addr, self.chassis_id, self.ucsm_ip) + + +class PortBinding(BASE): + """Represents Port binding to device interface""" + __tablename__ = 'port_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + port_id = Column(String(255), nullable=False) + #foreign key to ports.uuid + dynamic_vnic_id = Column(String(255), nullable=False) + #foreign key to dynamic_vnics.uuid + portprofile_name = Column(String(255)) + vlan_name = Column(String(255)) + vlan_id = Column(Integer) + qos = Column(String(255)) + + def __init__(self, port_id, dynamic_vnic_id, portprofile_name, vlan_name, \ + vlan_id, qos): + self.port_id = port_id + self.dynamic_vnic_id = dynamic_vnic_id + self.portprofile_name = portprofile_name + self.vlan_name = vlan_name + self.vlan_id = vlan_id + self.qos = qos + + def __repr__(self): + return "" % \ + (self.port_id, self.dynamic_vnic_id, self.portprofile_name, \ + self.vlan_name, self.vlan_id, self.qos) From 6fa2f538e7a825e3e582f44615c2599cb73fce41 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sun, 31 Jul 2011 11:38:26 -0700 Subject: [PATCH 30/76] Changed the directory structure to a more organized one. Fixed the imports to reflect the new structure. --- quantum/plugins/cisco/README | 18 +++++++++--------- .../cisco/{ => common}/cisco_configuration.py | 0 .../cisco/{ => common}/cisco_constants.py | 0 .../cisco/{ => common}/cisco_credentials.py | 2 +- .../cisco/{ => common}/cisco_exceptions.py | 0 .../plugins/cisco/{ => common}/cisco_utils.py | 6 +++--- quantum/plugins/cisco/l2network_plugin.py | 14 +++++++------- .../cisco/{ => nexus}/cisco_nexus_plugin.py | 10 +++++----- quantum/plugins/cisco/{ => nexus}/nxosapi.py | 0 .../{ => ucs}/cisco_ucs_network_driver.py | 6 +++--- .../cisco/{ => ucs}/cisco_ucs_plugin.py | 12 ++++++------ quantum/plugins/cisco/{ => ucs}/get-vif.py | 0 quantum/plugins/cisco/{ => ucs}/get-vif.sh | 0 13 files changed, 34 insertions(+), 34 deletions(-) rename quantum/plugins/cisco/{ => common}/cisco_configuration.py (100%) rename quantum/plugins/cisco/{ => common}/cisco_constants.py (100%) rename quantum/plugins/cisco/{ => common}/cisco_credentials.py (96%) rename quantum/plugins/cisco/{ => common}/cisco_exceptions.py (100%) rename quantum/plugins/cisco/{ => common}/cisco_utils.py (89%) rename quantum/plugins/cisco/{ => nexus}/cisco_nexus_plugin.py (93%) rename quantum/plugins/cisco/{ => nexus}/nxosapi.py (100%) rename quantum/plugins/cisco/{ => ucs}/cisco_ucs_network_driver.py (98%) rename quantum/plugins/cisco/{ => ucs}/cisco_ucs_plugin.py (96%) rename quantum/plugins/cisco/{ => ucs}/get-vif.py (100%) rename quantum/plugins/cisco/{ => ucs}/get-vif.sh (100%) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 6c2bc0cdcdd..cb6057c7243 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -27,16 +27,16 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK provider = quantum.plugins.cisco.l2network_plugin.L2Network * You should have the following files in quantum/quantum/plugins/cisco directory (if you have pulled the Cisco Quantum branch, you will already have them): l2network_plugin.py -cisco_configuration.py -cisco_constants.py -cisco_credentials.py -cisco_exceptions.py -cisco_nexus_plugin.py -cisco_ucs_network_driver.py -cisco_ucs_plugin.py -cisco_utils.py +common/cisco_configuration.py +common/cisco_constants.py +common/cisco_credentials.py +common/cisco_exceptions.py +nexus/cisco_nexus_plugin.py +ucs/cisco_ucs_network_driver.py +ucs/cisco_ucs_plugin.py +common/cisco_utils.py __init__.py -get-vif.sh +ucs/get-vif.sh * Configure the L2 Network Pllugin: + In cisco_configuration.py, - change the UCSM IP in the following statement to your UCSM IP diff --git a/quantum/plugins/cisco/cisco_configuration.py b/quantum/plugins/cisco/common/cisco_configuration.py similarity index 100% rename from quantum/plugins/cisco/cisco_configuration.py rename to quantum/plugins/cisco/common/cisco_configuration.py diff --git a/quantum/plugins/cisco/cisco_constants.py b/quantum/plugins/cisco/common/cisco_constants.py similarity index 100% rename from quantum/plugins/cisco/cisco_constants.py rename to quantum/plugins/cisco/common/cisco_constants.py diff --git a/quantum/plugins/cisco/cisco_credentials.py b/quantum/plugins/cisco/common/cisco_credentials.py similarity index 96% rename from quantum/plugins/cisco/cisco_credentials.py rename to quantum/plugins/cisco/common/cisco_credentials.py index c0cd282530c..cdbb6c379a8 100644 --- a/quantum/plugins/cisco/cisco_credentials.py +++ b/quantum/plugins/cisco/common/cisco_credentials.py @@ -19,7 +19,7 @@ import logging as LOG -from quantum.plugins.cisco import cisco_constants as const +from quantum.plugins.cisco.common import cisco_constants as const LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/cisco_exceptions.py b/quantum/plugins/cisco/common/cisco_exceptions.py similarity index 100% rename from quantum/plugins/cisco/cisco_exceptions.py rename to quantum/plugins/cisco/common/cisco_exceptions.py diff --git a/quantum/plugins/cisco/cisco_utils.py b/quantum/plugins/cisco/common/cisco_utils.py similarity index 89% rename from quantum/plugins/cisco/cisco_utils.py rename to quantum/plugins/cisco/common/cisco_utils.py index bd0b257d549..8d0d803b3f6 100644 --- a/quantum/plugins/cisco/cisco_utils.py +++ b/quantum/plugins/cisco/common/cisco_utils.py @@ -23,9 +23,9 @@ import sys import traceback from quantum.common import exceptions as exc -from quantum.plugins.cisco import cisco_configuration as conf -from quantum.plugins.cisco import cisco_constants as const -from quantum.plugins.cisco import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index 47f63874eed..8be63bb0675 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -20,13 +20,13 @@ import logging as LOG from quantum.common import exceptions as exc -from quantum.plugins.cisco import cisco_configuration as conf -from quantum.plugins.cisco import cisco_constants as const -from quantum.plugins.cisco import cisco_credentials as cred -from quantum.plugins.cisco import cisco_exceptions as cexc -from quantum.plugins.cisco import cisco_nexus_plugin -from quantum.plugins.cisco import cisco_ucs_plugin -from quantum.plugins.cisco import cisco_utils as cutil +from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.nexus import cisco_nexus_plugin +from quantum.plugins.cisco.ucs import cisco_ucs_plugin +from quantum.plugins.cisco.common import cisco_utils as cutil LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py similarity index 93% rename from quantum/plugins/cisco/cisco_nexus_plugin.py rename to quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 545499fe9b4..9d55115ab66 100644 --- a/quantum/plugins/cisco/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -19,11 +19,11 @@ import logging as LOG from quantum.common import exceptions as exc -from quantum.plugins.cisco import cisco_configuration as conf -from quantum.plugins.cisco import cisco_constants as const -from quantum.plugins.cisco import cisco_credentials as cred -from quantum.plugins.cisco import cisco_exceptions as cexc -from quantum.plugins.cisco import cisco_utils as cutil +from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.common import cisco_utils as cutil LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/nxosapi.py b/quantum/plugins/cisco/nexus/nxosapi.py similarity index 100% rename from quantum/plugins/cisco/nxosapi.py rename to quantum/plugins/cisco/nexus/nxosapi.py diff --git a/quantum/plugins/cisco/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py similarity index 98% rename from quantum/plugins/cisco/cisco_ucs_network_driver.py rename to quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index ca912edf92b..593706e3db4 100644 --- a/quantum/plugins/cisco/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -27,9 +27,9 @@ import subprocess from xml.etree import ElementTree as et import urllib -from quantum.plugins.cisco import cisco_configuration as conf -from quantum.plugins.cisco import cisco_constants as const -from quantum.plugins.cisco import cisco_exceptions as cexc +from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as cexc LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/cisco_ucs_plugin.py b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py similarity index 96% rename from quantum/plugins/cisco/cisco_ucs_plugin.py rename to quantum/plugins/cisco/ucs/cisco_ucs_plugin.py index 52195a55521..264d14f7834 100644 --- a/quantum/plugins/cisco/cisco_ucs_plugin.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py @@ -20,12 +20,12 @@ import logging as LOG from quantum.common import exceptions as exc -from quantum.plugins.cisco import cisco_configuration as conf -from quantum.plugins.cisco import cisco_constants as const -from quantum.plugins.cisco import cisco_credentials as cred -from quantum.plugins.cisco import cisco_exceptions as cexc -from quantum.plugins.cisco import cisco_ucs_network_driver -from quantum.plugins.cisco import cisco_utils as cutil +from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.ucs import cisco_ucs_network_driver +from quantum.plugins.cisco.common import cisco_utils as cutil LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) diff --git a/quantum/plugins/cisco/get-vif.py b/quantum/plugins/cisco/ucs/get-vif.py similarity index 100% rename from quantum/plugins/cisco/get-vif.py rename to quantum/plugins/cisco/ucs/get-vif.py diff --git a/quantum/plugins/cisco/get-vif.sh b/quantum/plugins/cisco/ucs/get-vif.sh similarity index 100% rename from quantum/plugins/cisco/get-vif.sh rename to quantum/plugins/cisco/ucs/get-vif.sh From 1ab10658b3439a4c80f597c2e5bcbee1f4c08b5c Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sun, 31 Jul 2011 11:54:29 -0700 Subject: [PATCH 31/76] For the modules to get added, missed in the earlier checkin. --- quantum/plugins/cisco/common/__init__.py | 0 quantum/plugins/cisco/db/__init__.py | 0 quantum/plugins/cisco/nexus/__init__.py | 0 quantum/plugins/cisco/ucs/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 quantum/plugins/cisco/common/__init__.py create mode 100644 quantum/plugins/cisco/db/__init__.py create mode 100644 quantum/plugins/cisco/nexus/__init__.py create mode 100644 quantum/plugins/cisco/ucs/__init__.py diff --git a/quantum/plugins/cisco/common/__init__.py b/quantum/plugins/cisco/common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/quantum/plugins/cisco/db/__init__.py b/quantum/plugins/cisco/db/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/quantum/plugins/cisco/nexus/__init__.py b/quantum/plugins/cisco/nexus/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/quantum/plugins/cisco/ucs/__init__.py b/quantum/plugins/cisco/ucs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From a084284c20bca5dc05df732b9e5323d3950955a0 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sun, 31 Jul 2011 12:04:01 -0700 Subject: [PATCH 32/76] Including copyright info. --- quantum/plugins/cisco/__init__.py | 18 ++++++++++++++++++ quantum/plugins/cisco/common/__init__.py | 18 ++++++++++++++++++ quantum/plugins/cisco/db/__init__.py | 18 ++++++++++++++++++ quantum/plugins/cisco/nexus/__init__.py | 18 ++++++++++++++++++ quantum/plugins/cisco/ucs/__init__.py | 18 ++++++++++++++++++ 5 files changed, 90 insertions(+) diff --git a/quantum/plugins/cisco/__init__.py b/quantum/plugins/cisco/__init__.py index e69de29bb2d..db695fb0afb 100644 --- a/quantum/plugins/cisco/__init__.py +++ b/quantum/plugins/cisco/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# diff --git a/quantum/plugins/cisco/common/__init__.py b/quantum/plugins/cisco/common/__init__.py index e69de29bb2d..db695fb0afb 100644 --- a/quantum/plugins/cisco/common/__init__.py +++ b/quantum/plugins/cisco/common/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# diff --git a/quantum/plugins/cisco/db/__init__.py b/quantum/plugins/cisco/db/__init__.py index e69de29bb2d..db695fb0afb 100644 --- a/quantum/plugins/cisco/db/__init__.py +++ b/quantum/plugins/cisco/db/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# diff --git a/quantum/plugins/cisco/nexus/__init__.py b/quantum/plugins/cisco/nexus/__init__.py index e69de29bb2d..db695fb0afb 100644 --- a/quantum/plugins/cisco/nexus/__init__.py +++ b/quantum/plugins/cisco/nexus/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# diff --git a/quantum/plugins/cisco/ucs/__init__.py b/quantum/plugins/cisco/ucs/__init__.py index e69de29bb2d..db695fb0afb 100644 --- a/quantum/plugins/cisco/ucs/__init__.py +++ b/quantum/plugins/cisco/ucs/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# From 3eb5bcc009961a2d6b11f7428e7c603ede489888 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sun, 31 Jul 2011 18:00:56 -0700 Subject: [PATCH 33/76] Adding a tests directory, this can be used for plugin-specific test cases. --- quantum/plugins/cisco/tests/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 quantum/plugins/cisco/tests/__init__.py diff --git a/quantum/plugins/cisco/tests/__init__.py b/quantum/plugins/cisco/tests/__init__.py new file mode 100644 index 00000000000..db695fb0afb --- /dev/null +++ b/quantum/plugins/cisco/tests/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# From 88c074c0a2b49ad163da3ca0b0c2c01716d4eb7e Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Mon, 1 Aug 2011 12:07:50 +0530 Subject: [PATCH 34/76] Vinkesh/Santhosh | Moved the stub classes in test_extensions to a separate file extension_stubs --- tests/unit/test_extensions.py | 65 ++++------------------------------- 1 file changed, 6 insertions(+), 59 deletions(-) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 9772f89e9ff..22079df6779 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -18,78 +18,25 @@ import unittest import routes import os.path from tests.unit import BaseTest -from abc import abstractmethod from webtest import TestApp -from quantum.common import extensions from quantum.common import wsgi from quantum.common import config +from quantum.common import extensions +from quantum.plugins.SamplePlugin import QuantumEchoPlugin +from tests.unit.extension_stubs import (StubExtension, StubPlugin, + StubPluginInterface, + StubBaseAppController, + ExtensionExpectingPluginInterface) from quantum.common.extensions import (ExtensionManager, PluginAwareExtensionManager, ExtensionMiddleware) -from quantum.plugins.SamplePlugin import QuantumEchoPlugin test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'etc', 'quantum.conf.test') extensions_path = os.path.join(os.path.dirname(__file__), "extensions") -class StubExtension(object): - - def __init__(self, alias="stub_extension"): - self.alias = alias - - def get_name(self): - return "Stub Extension" - - def get_alias(self): - return self.alias - - def get_description(self): - return "" - - def get_namespace(self): - return "" - - def get_updated(self): - return "" - - -class StubPlugin(object): - - def __init__(self, supported_extensions=[]): - self.supported_extension_aliases = supported_extensions - - -class ExtensionExpectingPluginInterface(StubExtension): - """ - This extension expects plugin to implement all the methods defined - in StubPluginInterface - """ - - def get_plugin_interface(self): - return StubPluginInterface - - -class StubPluginInterface(extensions.PluginInterface): - - @abstractmethod - def get_foo(self, bar=None): - pass - - -class StubBaseAppController(wsgi.Controller): - - def index(self, request): - return "base app index" - - def show(self, request, id): - return {'fort': 'knox'} - - def update(self, request, id): - return {'uneditable': 'original_value'} - - class ExtensionsTestApp(wsgi.Router): def __init__(self, options={}): From 07281aa581dccfde3a98b5c7aa338dad28397b5a Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Mon, 1 Aug 2011 12:40:07 -0700 Subject: [PATCH 35/76] Adding the Nexus OS driver based on the new PlugIn structure --- quantum/plugins/cisco/README | 9 +- .../cisco/common/cisco_configuration.py | 7 + .../cisco/nexus/cisco_nexus_network_driver.py | 236 ++++++++++++++++++ .../plugins/cisco/nexus/cisco_nexus_plugin.py | 28 ++- 4 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 3bf20a75125..2672675cd89 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -7,6 +7,7 @@ * UCS B200 series blades with M81KR VIC installed. * UCSM 2.0 (Capitola) Build 230 * RHEL 6.1 +* ncclcient v0.3.1 - Python library for NETCONF clients (http://schmizz.net/ncclient/) * UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at: http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM * To run Quantum on RHEL, you will need to have the correct version of python-routes (version 1.12.3 or later). The RHEL 6.1 package contains an older version. Do the following and check your python-routes version: @@ -41,6 +42,10 @@ ucs/get-vif.sh + In cisco_configuration.py, - change the UCSM IP in the following statement to your UCSM IP flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of UCSM') + - change the NEXUS 7K IP in the following statement to your N7K Switch IP + flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of N7K') + - change the NEXUS Interface in the following statement to the interface number in your N7K which is connected to your UCSM UpLink port + flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface connected from the Nexus 7K Switch to UCSM 6120') - change the Nova MySQL DB IP if you are running Quantum on a different host than the OpenStack Cloud Controller (in other words you do not need to change the IP if Quantum is running on the same host on which the Nova DB is running). DB IP is changed in the following statement: flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server') - change the hostname of the OpenStack Cloud Controller below @@ -58,9 +63,11 @@ ucs/get-vif.sh - Change the path to reflect the location of the get-vif.sh script, if you have followed the instructions in this README, this location should be the same as that of your other plugin modules flags.DEFINE_string('get_next_vif', "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", 'This is the location of the script to get the next available dynamic nic') + In cisco_credentials.py, - - Change the following stucture to reflect the correct UCS and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack + - Change the following structure to reflect the correct UCS, N7K and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack + N7K_IP_ADDRESS has to match with your Nexus 7k switch IP Address, N7K_USERNAME is the administrator user-name and N7K_PASSWORD is the password. _creds_dictionary = { 'UCSM_IP_ADDRESS':["UCSM_USERNAME", "UCSM_PASSWORD"], + 'N7K_IP_ADDRESS':["N7K_USERNAME", "N7K_PASSWORD"], 'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"] } * Start the Quantum service diff --git a/quantum/plugins/cisco/common/cisco_configuration.py b/quantum/plugins/cisco/common/cisco_configuration.py index 5ab2aaf336c..12ca0d42802 100644 --- a/quantum/plugins/cisco/common/cisco_configuration.py +++ b/quantum/plugins/cisco/common/cisco_configuration.py @@ -15,6 +15,7 @@ # under the License. # # @author: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Edgar Magana, Cisco Systems, Inc. # from quantum.common import flags @@ -26,6 +27,10 @@ FLAGS = flags.FLAGS # flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of \ UCSM') +flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of \ + Nexus Switch') +flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface \ + connected from the Nexus Switch to UCSM 6120') flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB \ server') flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud \ @@ -68,6 +73,8 @@ flags.DEFINE_string('get_next_vif', # Inventory items UCSM_IP_ADDRESS = FLAGS.ucsm_ip_address +NEXUS_IP_ADDRESS = FLAGS.nexus_ip_address +NEXUS_PORT = FLAGS.nexus_port DB_SERVER_IP = FLAGS.db_server_ip NOVA_HOST_NAME = FLAGS.nova_host_name diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py new file mode 100644 index 00000000000..2e95409d35d --- /dev/null +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -0,0 +1,236 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Debojyoti Dutta, Cisco Systems, Inc. +# @author: Edgar Magana, Cisco Systems Inc. +# +""" +Implements a Nexus-OS NETCONF over SSHv2 API Client +""" + +import logging as LOG +import string +import subprocess + +from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as cexc + +from ncclient import manager + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +# The following are standard strings, messages used to communicate with Nexus, +#only place holder values change for each message +exec_conf_prefix = """ + + + <__XML__MODE__exec_configure> +""" + + +exec_conf_postfix = """ + + + +""" + + +cmd_vlan_conf_snippet = """ + + + <__XML__PARAM_value>%s + <__XML__MODE_vlan> + + %s + + + active + + + + + + + +""" + +cmd_no_vlan_conf_snippet = """ + + + + <__XML__PARAM_value>%s + + + +""" + +cmd_vlan_int_snippet = """ + + + %s + <__XML__MODE_if-ethernet-switch> + + + + + + <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> + %s + + + + + + + + +""" + +cmd_port_trunk = """ + + + %s + <__XML__MODE_if-ethernet-switch> + + + + + + + + + + +""" + +cmd_no_switchport = """ + + + %s + <__XML__MODE_if-ethernet-switch> + + + + + + + +""" + + +cmd_no_vlan_int_snippet = """ + + + %s + <__XML__MODE_if-ethernet-switch> + + + + + + + <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> + %s + + + + + + + + + +""" + + +filter_show_vlan_brief_snippet = """ + + + + + """ + + +class CiscoNEXUSDriver(): + + def __init__(self): + pass + + def nxos_connect(self, nexus_host, port, nexus_user, nexus_password): + m = manager.connect(host=nexus_host, port=22, username=nexus_user, + password=nexus_password) + return m + + def enable_vlan(self, mgr, vlanid, vlanname): + confstr = cmd_vlan_conf_snippet % (vlanid, vlanname) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + mgr.edit_config(target='running', config=confstr) + + def disable_vlan(self, mgr, vlanid): + confstr = cmd_no_vlan_conf_snippet % vlanid + confstr = exec_conf_prefix + confstr + exec_conf_postfix + mgr.edit_config(target='running', config=confstr) + + def enable_port_trunk(self, mgr, interface): + confstr = cmd_port_trunk % (interface) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + print confstr + mgr.edit_config(target='running', config=confstr) + + def enable_vlan_on_trunk_int(self, mgr, interface, vlanid): + confstr = cmd_vlan_int_snippet % (interface, vlanid) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + print confstr + mgr.edit_config(target='running', config=confstr) + + def disable_vlan_on_trunk_int(self, mgr, interface, vlanid): + confstr = cmd_no_vlan_int_snippet % (interface, vlanid) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + print confstr + mgr.edit_config(target='running', config=confstr) + + def test_nxos_api(self, host, user, password): + with self.nxos_connect(host, port=22, user=user, + password=password) as m: + #enable_vlan(m, '100', 'ccn1') + #enable_vlan_on_trunk_int(m, '2/1', '100') + #disable_vlan_on_trunk_int(m, '2/1', '100') + #disable_vlan(m, '100') + result = m.get(("subtree", filter_show_vlan_brief_snippet)) + print result + + def create_vlan(self, vlan_name, vlan_id, nexus_host, nexus_user, + nexus_password, nexus_interface): + #TODO (Edgar) Move the SSH port to the configuration file + with self.nxos_connect(nexus_host, 22, nexus_user, + nexus_password) as m: + self.enable_vlan(m, vlan_id, vlan_name) + self.enable_port_trunk(m, nexus_interface) + + def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password): + with self.nxos_connect(nexus_host, 22, nexus_user, + nexus_password) as m: + self.disable_vlan(m, vlan_id) + + +def main(): + client = CiscoNEXUSDriver() + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 9d55115ab66..4d8cfa654f4 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -15,6 +15,7 @@ # under the License. # # @author: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Edgar Magana, Cisco Systems, Inc. # import logging as LOG @@ -25,6 +26,8 @@ from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_utils as cutil +from quantum.plugins.cisco.nexus import cisco_nexus_network_driver + LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) @@ -33,10 +36,12 @@ class NexusPlugin(object): _networks = {} def __init__(self): - """ - Initialize the Nexus driver here - """ - pass + self._client = cisco_nexus_network_driver.CiscoNEXUSDriver() + #TODO (Edgar) Using just one Nexus 7K Switch and Port + self._nexus_ip = conf.NEXUS_IP_ADDRESS + self._nexus_username = cred.Store.getUsername(conf.NEXUS_IP_ADDRESS) + self._nexus_password = cred.Store.getPassword(conf.NEXUS_IP_ADDRESS) + self._nexus_port = conf.NEXUS_PORT def get_all_networks(self, tenant_id): """ @@ -53,8 +58,9 @@ class NexusPlugin(object): for this VLAN """ LOG.debug("NexusPlugin:create_network() called\n") - # TODO (Sumit): Call the nexus driver here to create the VLAN, and - # configure the appropriate interfaces + self._client.create_vlan(vlan_name, str(vlan_id), self._nexus_ip, + self._nexus_username, self._nexus_password, self._nexus_port) + new_net_dict = {const.NET_ID: net_id, const.NET_NAME: net_name, const.NET_PORTS: {}, @@ -70,9 +76,10 @@ class NexusPlugin(object): """ LOG.debug("NexusPlugin:delete_network() called\n") net = self._networks.get(net_id) + vlan_id = self._get_vlan_id_for_network(tenant_id, net_id) if net: - # TODO (Sumit): Call the nexus driver here to create the VLAN, - # and configure the appropriate interfaces + self._client.delete_vlan(str(vlan_id), self._nexus_ip, + self._nexus_username, self._nexus_password) self._networks.pop(net_id) return net # Network not found @@ -145,6 +152,11 @@ class NexusPlugin(object): """ LOG.debug("NexusPlugin:unplug_interface() called\n") + def _get_vlan_id_for_network(self, tenant_id, network_id): + net = self._get_network(tenant_id, network_id) + vlan_id = net[const.NET_VLAN_ID] + return vlan_id + def _get_network(self, tenant_id, network_id): network = self._networks.get(network_id) if not network: From 5c0dbe9aee15c403910bf7c0deb7eb92f18d0967 Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Mon, 1 Aug 2011 18:32:25 -0700 Subject: [PATCH 36/76] Including a flag to activate the NX-OS driver Updating the README documentation --- quantum/plugins/cisco/README | 25 +++++++++++++++---- .../cisco/common/cisco_configuration.py | 3 +++ quantum/plugins/cisco/l2network_plugin.py | 22 +++++++++++++--- .../plugins/cisco/nexus/cisco_nexus_plugin.py | 1 + 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 2672675cd89..b77ce22ca89 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -38,14 +38,10 @@ ucs/cisco_ucs_plugin.py common/cisco_utils.py __init__.py ucs/get-vif.sh -* Configure the L2 Network Pllugin: +* Configure the L2 Network Plugin: + In cisco_configuration.py, - change the UCSM IP in the following statement to your UCSM IP flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of UCSM') - - change the NEXUS 7K IP in the following statement to your N7K Switch IP - flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of N7K') - - change the NEXUS Interface in the following statement to the interface number in your N7K which is connected to your UCSM UpLink port - flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface connected from the Nexus 7K Switch to UCSM 6120') - change the Nova MySQL DB IP if you are running Quantum on a different host than the OpenStack Cloud Controller (in other words you do not need to change the IP if Quantum is running on the same host on which the Nova DB is running). DB IP is changed in the following statement: flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server') - change the hostname of the OpenStack Cloud Controller below @@ -62,6 +58,24 @@ ucs/get-vif.sh flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name given to the port profile') - Change the path to reflect the location of the get-vif.sh script, if you have followed the instructions in this README, this location should be the same as that of your other plugin modules flags.DEFINE_string('get_next_vif', "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", 'This is the location of the script to get the next available dynamic nic') + + + In cisco_credentials.py, + - Change the following structure to reflect the correct UCS, N7K and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack + N7K_IP_ADDRESS has to match with your Nexus 7k switch IP Address, N7K_USERNAME is the administrator user-name and N7K_PASSWORD is the password. + _creds_dictionary = { + 'UCSM_IP_ADDRESS':["UCSM_USERNAME", "UCSM_PASSWORD"], + 'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"] + } + +* Configure the L2 Network Plugin with Nexus OS Driver for testing VLANs CRUD on Nexus 7k Switch. Making these changes requires one Nexus 7K Switch connected to the UCSM and the ncclient patch not just the regular library, otherwise the system will fail. + + In cisco_configuration.py, + - change the NEXUS 7K IP in the following statement to your N7K Switch IP + flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of N7K') + - change the NEXUS Interface in the following statement to the interface number in your N7K which is connected to your UCSM UpLink port + flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface connected from the Nexus 7K Switch to UCSM 6120') + - change NEXUS Driver Flag to "on" in the following statement + flags.DEFINE_string('nexus_driver_active', "off", 'Flag to activate Nexus OS Driver') + + In cisco_credentials.py, - Change the following structure to reflect the correct UCS, N7K and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack N7K_IP_ADDRESS has to match with your Nexus 7k switch IP Address, N7K_USERNAME is the administrator user-name and N7K_PASSWORD is the password. @@ -70,6 +84,7 @@ ucs/get-vif.sh 'N7K_IP_ADDRESS':["N7K_USERNAME", "N7K_PASSWORD"], 'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"] } + * Start the Quantum service ** Additional installation required on Nova Compute: diff --git a/quantum/plugins/cisco/common/cisco_configuration.py b/quantum/plugins/cisco/common/cisco_configuration.py index 12ca0d42802..01762e5fa88 100644 --- a/quantum/plugins/cisco/common/cisco_configuration.py +++ b/quantum/plugins/cisco/common/cisco_configuration.py @@ -31,6 +31,8 @@ flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of \ Nexus Switch') flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface \ connected from the Nexus Switch to UCSM 6120') +flags.DEFINE_string('nexus_driver_active', "off", 'Flag to activate Nexus OS\ + Driver') flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB \ server') flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud \ @@ -74,6 +76,7 @@ flags.DEFINE_string('get_next_vif', # Inventory items UCSM_IP_ADDRESS = FLAGS.ucsm_ip_address NEXUS_IP_ADDRESS = FLAGS.nexus_ip_address +NEXUS_DRIVER_ACTIVE = FLAGS.nexus_driver_active NEXUS_PORT = FLAGS.nexus_port DB_SERVER_IP = FLAGS.db_server_ip NOVA_HOST_NAME = FLAGS.nova_host_name diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index 8be63bb0675..9a8c4a4f0e6 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -15,6 +15,7 @@ # under the License. # # @author: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Edgar Magana, Cisco Systems, Inc. # import logging as LOG @@ -65,8 +66,13 @@ class L2Network(object): new_net_id = self._get_unique_net_id(tenant_id) vlan_id = self._get_vlan_for_tenant(tenant_id, net_name) vlan_name = self._get_vlan_name(new_net_id, str(vlan_id)) - self._nexus_plugin.create_network(tenant_id, net_name, new_net_id, + nexus_driver_flag = conf.NEXUS_DRIVER_ACTIVE + if nexus_driver_flag == 'on': + LOG.debug("Nexus OS Driver called\n") + self._nexus_plugin.create_network(tenant_id, net_name, new_net_id, vlan_name, vlan_id) + else: + LOG.debug("No Nexus OS Driver available\n") self._ucs_plugin.create_network(tenant_id, net_name, new_net_id, vlan_name, vlan_id) new_net_dict = {const.NET_ID: new_net_id, @@ -88,12 +94,17 @@ class L2Network(object): """ LOG.debug("delete_network() called\n") net = self._networks.get(net_id) + nexus_driver_flag = conf.NEXUS_DRIVER_ACTIVE # TODO (Sumit) : Verify that no attachments are plugged into the # network if net: # TODO (Sumit) : Before deleting the network, make sure all the # ports associated with this network are also deleted - self._nexus_plugin.delete_network(tenant_id, net_id) + if nexus_driver_flag == 'on': + LOG.debug("Nexus OS Driver called\n") + self._nexus_plugin.delete_network(tenant_id, net_id) + else: + LOG.debug("No Nexus OS Driver available\n") self._ucs_plugin.delete_network(tenant_id, net_id) self._networks.pop(net_id) tenant = self._get_tenant(tenant_id) @@ -118,7 +129,12 @@ class L2Network(object): Virtual Network. """ LOG.debug("rename_network() called\n") - self._nexus_plugin.rename_network(tenant_id, net_id) + nexus_driver_flag = conf.NEXUS_DRIVER_ACTIVE + if nexus_driver_flag == 'on': + LOG.debug("Nexus OS Driver called\n") + self._nexus_plugin.rename_network(tenant_id, net_id) + else: + LOG.debug("No Nexus OS Driver available\n") self._ucs_plugin.rename_network(tenant_id, net_id) network = self._get_network(tenant_id, net_id) network[const.NET_NAME] = new_name diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 4d8cfa654f4..349a69ca550 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -98,6 +98,7 @@ class NexusPlugin(object): Updates the symbolic name belonging to a particular Virtual Network. """ + #TODO (Edgar) We need to add an update method in the Nexus Driver LOG.debug("NexusPlugin:rename_network() called\n") network = self._get_network(tenant_id, net_id) network[const.NET_NAME] = new_name From d17344aede6ef3ba955af0ae1d3380de387004d2 Mon Sep 17 00:00:00 2001 From: vinkesh banka Date: Tue, 2 Aug 2011 10:19:32 +0530 Subject: [PATCH 37/76] Vinkesh | Changed import orders according to pep8 recommendations --- quantum/common/extensions.py | 7 +++---- quantum/common/flags.py | 4 +--- quantum/common/utils.py | 8 ++++---- quantum/common/wsgi.py | 8 ++++---- quantum/db/api.py | 2 ++ quantum/db/models.py | 2 ++ tests/unit/test_api.py | 2 +- tests/unit/test_extensions.py | 8 +++++--- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 430c0c94de7..10795f80536 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -16,18 +16,17 @@ # 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 imp +import logging import os import routes -import logging import webob.dec import webob.exc + from gettext import gettext as _ from abc import ABCMeta - -from quantum.manager import QuantumManager from quantum.common import exceptions +from quantum.manager import QuantumManager from quantum.common import wsgi LOG = logging.getLogger('quantum.common.extensions') diff --git a/quantum/common/flags.py b/quantum/common/flags.py index 5adf4c3854a..16badd33293 100644 --- a/quantum/common/flags.py +++ b/quantum/common/flags.py @@ -21,14 +21,12 @@ Wraps gflags. Global flags should be defined here, the rest are defined where they're used. """ - import getopt +import gflags import os import string import sys -import gflags - class FlagValues(gflags.FlagValues): """Extension of gflags.FlagValues that allows undefined and runtime flags. diff --git a/quantum/common/utils.py b/quantum/common/utils.py index ea6241e21b5..5662e7465a3 100644 --- a/quantum/common/utils.py +++ b/quantum/common/utils.py @@ -18,8 +18,10 @@ """ System-level utilities and helper functions. """ - +import ConfigParser import datetime +import exceptions as exception +import flags import inspect import logging import os @@ -27,10 +29,8 @@ import random import subprocess import socket import sys -import ConfigParser -import exceptions as exception -import flags + from exceptions import ProcessExecutionError diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index ca9734e878e..94df7913c63 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -22,17 +22,17 @@ Utility methods for working with WSGI servers import logging import sys - -from xml.dom import minidom - import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True) import routes.middleware import webob.dec import webob.exc +from xml.dom import minidom + -from quantum import utils from quantum.common import exceptions as exception +from quantum import utils + LOG = logging.getLogger('quantum.common.wsgi') diff --git a/quantum/db/api.py b/quantum/db/api.py index 6813d2096ce..2b7167ba8d4 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -20,8 +20,10 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, exc + from quantum.db import models + _ENGINE = None _MAKER = None BASE = models.BASE diff --git a/quantum/db/models.py b/quantum/db/models.py index 1a18e7e5188..240b28349ef 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -19,10 +19,12 @@ import uuid + from sqlalchemy import Column, String, ForeignKey from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relation, object_mapper + BASE = declarative_base() diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index d17e6ed87fa..4eca189cfcf 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -20,8 +20,8 @@ import logging import unittest -import tests.unit.testlib_api as testlib +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 diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 22079df6779..b22a8a3c55f 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -14,12 +14,13 @@ # License for the specific language governing permissions and limitations # under the License. import json -import unittest -import routes import os.path +import routes +import unittest from tests.unit import BaseTest - from webtest import TestApp + + from quantum.common import wsgi from quantum.common import config from quantum.common import extensions @@ -32,6 +33,7 @@ from quantum.common.extensions import (ExtensionManager, PluginAwareExtensionManager, ExtensionMiddleware) + test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'etc', 'quantum.conf.test') extensions_path = os.path.join(os.path.dirname(__file__), "extensions") From 9f8b25e53c9957f09bab49a14f198356e00bce6c Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Tue, 2 Aug 2011 17:08:58 -0700 Subject: [PATCH 38/76] Truncated the port profile client name length to 16 characters (ucsm excepts max 17 chars). --- quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index 593706e3db4..9383f9b9650 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -198,7 +198,7 @@ class CiscoUCSMDriver(): ucsm_password): data = self._create_profile_post_data(profile_name, vlan_name) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) - data = self._create_profile_client_post_data(profile_name) + data = self._create_profile_client_post_data(profile_name[-16:]) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) def change_vlan_in_profile(self, profile_name, old_vlan_name, From 33ee4cabd061ba696c227289bea932e57a865554 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Tue, 2 Aug 2011 18:39:51 -0700 Subject: [PATCH 39/76] Earlier fix resulted in a different issue (profile client name, was also being used as profile name, hence breaking). --- quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index 9383f9b9650..be1c89e2236 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -158,9 +158,9 @@ class CiscoUCSMDriver(): data = data.replace(VLAN_NAME, vlan_name) return data - def _create_profile_client_post_data(self, profile_name): + def _create_profile_client_post_data(self, profile_name, profile_client_name): data = ASSOCIATE_PROFILE.replace(PROFILE_NAME, profile_name) - data = data.replace(PROFILE_CLIENT, profile_name) + data = data.replace(PROFILE_CLIENT, profile_client_name) return data def _change_vlan_in_profile_post_data(self, profile_name, old_vlan_name, @@ -198,7 +198,7 @@ class CiscoUCSMDriver(): ucsm_password): data = self._create_profile_post_data(profile_name, vlan_name) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) - data = self._create_profile_client_post_data(profile_name[-16:]) + data = self._create_profile_client_post_data(profile_name, profile_name[-16:]) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) def change_vlan_in_profile(self, profile_name, old_vlan_name, From b113a0a2550781e3e76cc9d6f716ed57f8f06a11 Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Wed, 3 Aug 2011 13:29:43 -0700 Subject: [PATCH 40/76] Fixed an issue selecting the right port interface and also properly switching off the Nexus Interface --- .../cisco/nexus/cisco_nexus_network_driver.py | 12 ++++++++++-- quantum/plugins/cisco/nexus/cisco_nexus_plugin.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py index 2e95409d35d..27e63ce4cb5 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -193,6 +193,12 @@ class CiscoNEXUSDriver(): print confstr mgr.edit_config(target='running', config=confstr) + def disable_switch_port(self, mgr, interface): + confstr = cmd_no_switchport % (interface) + confstr = exec_conf_prefix + confstr + exec_conf_postfix + print confstr + mgr.edit_config(target='running', config=confstr) + def enable_vlan_on_trunk_int(self, mgr, interface, vlanid): confstr = cmd_vlan_int_snippet % (interface, vlanid) confstr = exec_conf_prefix + confstr + exec_conf_postfix @@ -221,12 +227,14 @@ class CiscoNEXUSDriver(): with self.nxos_connect(nexus_host, 22, nexus_user, nexus_password) as m: self.enable_vlan(m, vlan_id, vlan_name) - self.enable_port_trunk(m, nexus_interface) + self.enable_vlan_on_trunk_int(m, nexus_interface, vlan_id) - def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password): + def delete_vlan(self, vlan_id, nexus_host, nexus_user, + nexus_password, nexus_interface): with self.nxos_connect(nexus_host, 22, nexus_user, nexus_password) as m: self.disable_vlan(m, vlan_id) + self.disable_switch_port(m, nexus_interface) def main(): diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 349a69ca550..31baac47297 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -79,7 +79,7 @@ class NexusPlugin(object): vlan_id = self._get_vlan_id_for_network(tenant_id, net_id) if net: self._client.delete_vlan(str(vlan_id), self._nexus_ip, - self._nexus_username, self._nexus_password) + self._nexus_username, self._nexus_password, self._nexus_port) self._networks.pop(net_id) return net # Network not found From e4e1891cc13c8318a1351b3df44db1cb30f2eeef Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Wed, 3 Aug 2011 15:41:06 -0700 Subject: [PATCH 41/76] Fixed pep8 error. --- quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index be1c89e2236..2d994ddec11 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -158,7 +158,8 @@ class CiscoUCSMDriver(): data = data.replace(VLAN_NAME, vlan_name) return data - def _create_profile_client_post_data(self, profile_name, profile_client_name): + def _create_profile_client_post_data(self, profile_name, + profile_client_name): data = ASSOCIATE_PROFILE.replace(PROFILE_NAME, profile_name) data = data.replace(PROFILE_CLIENT, profile_client_name) return data @@ -198,7 +199,8 @@ class CiscoUCSMDriver(): ucsm_password): data = self._create_profile_post_data(profile_name, vlan_name) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) - data = self._create_profile_client_post_data(profile_name, profile_name[-16:]) + data = self._create_profile_client_post_data(profile_name, + profile_name[-16:]) self._post_data(ucsm_ip, ucsm_username, ucsm_password, data) def change_vlan_in_profile(self, profile_name, old_vlan_name, From b653ea1c5ccd5626043d7ec75d4aec47f19eedb0 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Wed, 3 Aug 2011 16:17:06 -0700 Subject: [PATCH 43/76] Removed quantum/plugins/cisco/db/ and quantum/cisco_extensions since these will be merged separately. --- cisco_extensions/__init__.py | 71 -- cisco_extensions/exceptions.py | 148 --- cisco_extensions/extensions.py | 42 - cisco_extensions/faults.py | 111 -- cisco_extensions/portprofiles.py | 180 --- cisco_extensions/pprofiles.py | 45 - quantum/plugins/cisco/db/db_conn.ini | 5 - quantum/plugins/cisco/db/db_test_plugin.py | 1049 ------------------ quantum/plugins/cisco/db/l2network_db.py | 239 ---- quantum/plugins/cisco/db/l2network_models.py | 86 -- quantum/plugins/cisco/db/ucs_db.py | 314 ------ quantum/plugins/cisco/db/ucs_models.py | 113 -- 12 files changed, 2403 deletions(-) delete mode 100644 cisco_extensions/__init__.py delete mode 100644 cisco_extensions/exceptions.py delete mode 100644 cisco_extensions/extensions.py delete mode 100644 cisco_extensions/faults.py delete mode 100644 cisco_extensions/portprofiles.py delete mode 100644 cisco_extensions/pprofiles.py delete mode 100644 quantum/plugins/cisco/db/db_conn.ini delete mode 100644 quantum/plugins/cisco/db/db_test_plugin.py delete mode 100644 quantum/plugins/cisco/db/l2network_db.py delete mode 100644 quantum/plugins/cisco/db/l2network_models.py delete mode 100644 quantum/plugins/cisco/db/ucs_db.py delete mode 100644 quantum/plugins/cisco/db/ucs_models.py diff --git a/cisco_extensions/__init__.py b/cisco_extensions/__init__.py deleted file mode 100644 index 5fc5d889193..00000000000 --- a/cisco_extensions/__init__.py +++ /dev/null @@ -1,71 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# @author: Ying Liu, Cisco Systems, Inc. -# - -import logging -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 -from quantum.common import flags -from quantum.common import wsgi -from cisco_extensions import portprofiles -from cisco_extensions import extensions - - -LOG = logging.getLogger('quantum_extension.api') -FLAGS = flags.FLAGS - - -class ExtRouterV01(wsgi.Router): - """ - Routes requests on the Quantum API to the appropriate controller - """ - - def __init__(self, ext_mgr=None): - uri_prefix = '/tenants/{tenant_id}/' - - mapper = routes.Mapper() - plugin = manager.QuantumManager().get_plugin() - controller = portprofiles.Controller(plugin) - ext_controller = extensions.Controller(plugin) - mapper.connect("home", "/", controller=ext_controller, - action="list_extension", - conditions=dict(method=['GET'])) - #mapper.redirect("/", "www.google.com") - mapper.resource("portprofiles", "portprofiles", - controller=controller, - path_prefix=uri_prefix) - mapper.connect("associate_portprofile", - uri_prefix - + 'portprofiles/{portprofile_id}/assignment{.format}', - controller=controller, - action="associate_portprofile", - conditions=dict(method=['PUT'])) - mapper.connect("disassociate_portprofile", - uri_prefix - + 'portprofiles/{portprofile_id}/assignment{.format}', - controller=controller, - action="disassociate_portprofile", - conditions=dict(method=['DELETE'])) - - super(ExtRouterV01, self).__init__(mapper) diff --git a/cisco_extensions/exceptions.py b/cisco_extensions/exceptions.py deleted file mode 100644 index 415731e3851..00000000000 --- a/cisco_extensions/exceptions.py +++ /dev/null @@ -1,148 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# @author: Ying Liu, Cisco Systems, Inc. -# -import logging - - -class ExtensionException(Exception): - """Quantum Cisco api 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 - with the keyword arguments provided to the constructor. - - """ - message = _("An unknown exception occurred.") - - def __init__(self, **kwargs): - try: - self._error_string = self.message % kwargs - - except Exception: - # at least get the core message out if something happened - self._error_string = self.message - - def __str__(self): - return self._error_string - - -class ProcessExecutionError(IOError): - def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, - description=None): - if description is None: - description = "Unexpected error while running command." - if exit_code is None: - exit_code = '-' - message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( - description, cmd, exit_code, stdout, stderr) - IOError.__init__(self, message) - - -class Error(Exception): - def __init__(self, message=None): - super(Error, self).__init__(message) - - -class ApiError(Error): - def __init__(self, message='Unknown', code='Unknown'): - self.message = message - self.code = code - super(ApiError, self).__init__('%s: %s' % (code, message)) - - -class NotFound(ExtensionException): - pass - - -class ClassNotFound(NotFound): - message = _("Class %(class_name)s could not be found") - - -class PortprofileNotFound(NotFound): - message = _("Portprofile %(_id)s could not be found") - - -class PortNotFound(NotFound): - message = _("Port %(port_id)s could not be found " \ - "on Network %(net_id)s") - - -""" - - -class PortprofileInUse(ExtensionException): - message = _("Unable to complete operation on Portprofile %(net_id)s. " \ - "There is one or more attachments plugged into its ports.") - - -class PortInUse(ExtensionException): - message = _("Unable to complete operation on port %(port_id)s " \ - "for Portprofile %(net_id)s. The attachment '%(att_id)s" \ - "is plugged into the logical port.") - -class AlreadyAttached(ExtensionException): - message = _("Unable to plug the attachment %(att_id)s into port " \ - "%(port_id)s for Portprofile %(net_id)s. The attachment is " \ - "already plugged into port %(att_port_id)s") - -""" - - -class Duplicate(Error): - pass - - -class NotAuthorized(Error): - pass - - -class NotEmpty(Error): - pass - - -class Invalid(Error): - pass - - -class InvalidContentType(Invalid): - message = _("Invalid content type %(content_type)s.") - - -class BadInputError(Exception): - """Error resulting from a client sending bad input to a server""" - pass - - -class MissingArgumentError(Error): - pass - - -def wrap_exception(f): - def _wrap(*args, **kw): - try: - return f(*args, **kw) - except Exception, e: - if not isinstance(e, Error): - #exc_type, exc_value, exc_traceback = sys.exc_info() - logging.exception('Uncaught exception') - #logging.error(traceback.extract_stack(exc_traceback)) - raise Error(str(e)) - raise - _wrap.func_name = f.func_name - return _wrap diff --git a/cisco_extensions/extensions.py b/cisco_extensions/extensions.py deleted file mode 100644 index 34bc37e814a..00000000000 --- a/cisco_extensions/extensions.py +++ /dev/null @@ -1,42 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# @author: Ying Liu, Cisco Systems, Inc. -# -import logging -import webob.dec - -from quantum.common import wsgi -from quantum.api import api_common as common - - -LOG = logging.getLogger('quantum.api.cisco_extension.extensions') - - -class Controller(common.QuantumController): - - def __init__(self, plugin): - #self._plugin = plugin - #super(QuantumController, self).__init__() - self._resource_name = 'extensions' - super(Controller, self).__init__(plugin) - - def list_extension(self, req): - """Respond to a request for listing all extension api.""" - response = "extensions api list" - return response - - \ No newline at end of file diff --git a/cisco_extensions/faults.py b/cisco_extensions/faults.py deleted file mode 100644 index c965f731dc6..00000000000 --- a/cisco_extensions/faults.py +++ /dev/null @@ -1,111 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# @author: Ying Liu, Cisco Systems, Inc. -# -import webob.dec -import webob.exc - -from quantum.api import api_common as common -from quantum.common import wsgi - - -class Fault(webob.exc.HTTPException): - """Error codes for API faults""" - - _fault_names = { - 400: "malformedRequest", - 401: "unauthorized", - 420: "networkNotFound", - 421: "PortprofileInUse", - 430: "portNotFound", - 431: "requestedStateInvalid", - 432: "portInUse", - 440: "alreadyAttached", - 450: "PortprofileNotFound", - 470: "serviceUnavailable", - 471: "pluginFault"} - - def __init__(self, exception): - """Create a Fault for the given webob.exc.exception.""" - self.wrapped_exc = exception - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - """Generate a WSGI response based on the exception passed to ctor.""" - #print ("*********TEST2") - # Replace the body with fault details. - code = self.wrapped_exc.status_int - fault_name = self._fault_names.get(code, "quantumServiceFault") - fault_data = { - fault_name: { - 'code': code, - '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'}}} - default_xmlns = common.XML_NS_V10 - serializer = wsgi.Serializer(metadata, default_xmlns) - content_type = req.best_match_content_type() - self.wrapped_exc.body = serializer.serialize(fault_data, content_type) - self.wrapped_exc.content_type = content_type - return self.wrapped_exc - - -class PortprofileNotFound(webob.exc.HTTPClientError): - """ - subclass of :class:`~HTTPClientError` - - This indicates that the server did not find the Portprofile specified - in the HTTP request - - code: 450, title: Portprofile not Found - """ - #print ("*********TEST1") - code = 450 - title = 'Portprofile Not Found' - explanation = ('Unable to find a Portprofile with' - + ' the specified identifier.') - - -class PortNotFound(webob.exc.HTTPClientError): - """ - subclass of :class:`~HTTPClientError` - - This indicates that the server did not find the port specified - in the HTTP request for a given network - - code: 430, title: Port not Found - """ - code = 430 - title = 'Port not Found' - explanation = ('Unable to find a port with the specified identifier.') - - -class RequestedStateInvalid(webob.exc.HTTPClientError): - """ - subclass of :class:`~HTTPClientError` - - This indicates that the server could not update the port state to - to the request value - - code: 431, title: Requested State Invalid - """ - code = 431 - title = 'Requested State Invalid' - explanation = ('Unable to update port state with specified value.') - - diff --git a/cisco_extensions/portprofiles.py b/cisco_extensions/portprofiles.py deleted file mode 100644 index 5d195528f87..00000000000 --- a/cisco_extensions/portprofiles.py +++ /dev/null @@ -1,180 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# @author: Ying Liu, Cisco Systems, Inc. -# - -import logging -import webob.dec -from quantum.common import wsgi -from webob import exc - -from quantum.api import api_common as common - -from cisco_extensions import pprofiles as pprofiles_view -from cisco_extensions import exceptions as exception -from cisco_extensions import faults as faults - -LOG = logging.getLogger('quantum.api.portprofiles') - - -class Controller(common.QuantumController): - """ portprofile API controller - based on QuantumController """ - - _portprofile_ops_param_list = [{ - 'param-name': 'portprofile-name', - 'required': True}, { - 'param-name': 'vlan-id', - 'required': True}, { - 'param-name': 'assignment', - 'required': False}] - - _assignprofile_ops_param_list = [{ - 'param-name': 'network-id', - 'required': True}, { - 'param-name': 'port-id', - 'required': True}] - - _serialization_metadata = { - "application/xml": { - "attributes": { - "portprofile": ["id", "name"], - }, - }, - } - - def __init__(self, plugin): - self._resource_name = 'portprofile' - super(Controller, self).__init__(plugin) - - def index(self, request, tenant_id): - """ Returns a list of portprofile ids """ - #TODO: this should be for a given tenant!!! - return self._items(request, tenant_id, is_detail=False) - - def _items(self, request, tenant_id, is_detail): - """ Returns a list of portprofiles. """ - portprofiles = self._plugin.get_all_portprofiles(tenant_id) - builder = pprofiles_view.get_view_builder(request) - result = [builder.build(portprofile, is_detail)['portprofile'] - for portprofile in portprofiles] - return dict(portprofiles=result) - - def show(self, request, tenant_id, id): - """ Returns portprofile details for the given portprofile id """ - try: - portprofile = self._plugin.get_portprofile_details( - tenant_id, id) - builder = pprofiles_view.get_view_builder(request) - #build response with details - result = builder.build(portprofile, True) - return dict(portprofiles=result) - except exception.PortprofileNotFound as e: - return faults.Fault(faults.PortprofileNotFound(e)) - #return faults.Fault(e) - - def create(self, request, tenant_id): - """ Creates a new portprofile for a given tenant """ - #look for portprofile name in request - try: - req_params = \ - self._parse_request_params(request, - self._portprofile_ops_param_list) - except exc.HTTPError as e: - return faults.Fault(e) - portprofile = self._plugin.\ - create_portprofile(tenant_id, - req_params['portprofile-name'], - req_params['vlan-id']) - builder = pprofiles_view.get_view_builder(request) - result = builder.build(portprofile) - return dict(portprofiles=result) - - def update(self, request, tenant_id, id): - """ Updates the name for the portprofile with the given id """ - try: - req_params = \ - self._parse_request_params(request, - self._portprofile_ops_param_list) - except exc.HTTPError as e: - return faults.Fault(e) - try: - portprofile = self._plugin.\ - rename_portprofile(tenant_id, - id, req_params['portprofile-name']) - - builder = pprofiles_view.get_view_builder(request) - result = builder.build(portprofile, True) - return dict(portprofiles=result) - except exception.PortprofileNotFound as e: - return faults.Fault(faults.PortprofileNotFound(e)) - - def delete(self, request, tenant_id, id): - """ Destroys the portprofile with the given id """ - try: - self._plugin.delete_portprofile(tenant_id, id) - return exc.HTTPAccepted() - except exception.PortprofileNotFound as e: - return faults.Fault(faults.PortprofileNotFound(e)) - - #added for cisco's extension - def associate_portprofile(self, request, tenant_id, portprofile_id): - content_type = request.best_match_content_type() - print "Content type:%s" % content_type - - try: - req_params = \ - self._parse_request_params(request, - self._assignprofile_ops_param_list) - except exc.HTTPError as e: - return faults.Fault(e) - net_id = req_params['network-id'].strip() - #print "*****net id "+net_id - port_id = req_params['port-id'].strip() - try: - self._plugin.associate_portprofile(tenant_id, - net_id, port_id, - portprofile_id) - return exc.HTTPAccepted() - except exception.PortprofileNotFound as e: - return faults.Fault(faults.PortprofileNotFound(e)) - except exception.PortNotFound as e: - return faults.Fault(faults.PortNotFound(e)) - - #added for Cisco extension - def disassociate_portprofile(self, request, tenant_id, portprofile_id): - content_type = request.best_match_content_type() - print "Content type:%s" % content_type - - try: - req_params = \ - self._parse_request_params(request, - self._assignprofile_ops_param_list) - except exc.HTTPError as e: - return faults.Fault(e) - net_id = req_params['network-id'].strip() - #print "*****net id "+net_id - port_id = req_params['port-id'].strip() - try: - self._plugin. \ - disassociate_portprofile(tenant_id, - net_id, port_id, portprofile_id) - return exc.HTTPAccepted() - except exception.PortprofileNotFound as e: - return faults.Fault(faults.PortprofileNotFound(e)) - except exception.PortNotFound as e: - return faults.Fault(faults.PortNotFound(e)) diff --git a/cisco_extensions/pprofiles.py b/cisco_extensions/pprofiles.py deleted file mode 100644 index ba7f8a328bb..00000000000 --- a/cisco_extensions/pprofiles.py +++ /dev/null @@ -1,45 +0,0 @@ - - -import os - - -def get_view_builder(req): - base_url = req.application_url - return ViewBuilder(base_url) - - -class ViewBuilder(object): - """ - ViewBuilder for Portprofile, - derived from quantum.views.networks - """ - def __init__(self, base_url): - """ - :param base_url: url of the root wsgi application - """ - self.base_url = base_url - - def build(self, portprofile_data, is_detail=False): - """Generic method used to generate a portprofile entity.""" - print "portprofile-DATA:%s" %portprofile_data - if is_detail: - portprofile = self._build_detail(portprofile_data) - else: - portprofile = self._build_simple(portprofile_data) - return portprofile - - def _build_simple(self, portprofile_data): - """Return a simple model of a server.""" - return dict(portprofile=dict(id=portprofile_data['profile-id'])) - - def _build_detail(self, portprofile_data): - """Return a simple model of a server.""" - if (portprofile_data['assignment']==None): - return dict(portprofile=dict(id=portprofile_data['profile-id'], - name=portprofile_data['profile-name'], - vlan_id=portprofile_data['vlan-id'])) - else: - return dict(portprofile=dict(id=portprofile_data['profile-id'], - name=portprofile_data['profile-name'], - vlan_id=portprofile_data['vlan-id'], - assignment=portprofile_data['assignment'])) diff --git a/quantum/plugins/cisco/db/db_conn.ini b/quantum/plugins/cisco/db/db_conn.ini deleted file mode 100644 index 55066aaeb14..00000000000 --- a/quantum/plugins/cisco/db/db_conn.ini +++ /dev/null @@ -1,5 +0,0 @@ -[DATABASE] -name = quantum_l2network -user = root -pass = nova -host = 127.0.0.1 diff --git a/quantum/plugins/cisco/db/db_test_plugin.py b/quantum/plugins/cisco/db/db_test_plugin.py deleted file mode 100644 index e0ec6fa9f45..00000000000 --- a/quantum/plugins/cisco/db/db_test_plugin.py +++ /dev/null @@ -1,1049 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. - -import ConfigParser -import os -import logging as LOG -import unittest - -from optparse import OptionParser -from quantum.plugins.cisco.common import cisco_constants as const - -import quantum.db.api as db -import quantum.db.models -import quantum.plugins.cisco.db.l2network_db as l2network_db -import quantum.plugins.cisco.db.l2network_models - -CONF_FILE = "db_conn.ini" -LOG.getLogger(const.LOGGER_COMPONENT_NAME) - - -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 - - -def db_conf(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__))) - if configfile == None: - raise Exception("Configuration file \"%s\" doesn't exist" % - (configfile)) - LOG.debug("Using configuration file: %s" % configfile) - config.read(configfile) - - DB_NAME = config.get("DATABASE", "name") - DB_USER = config.get("DATABASE", "user") - DB_PASS = config.get("DATABASE", "pass") - DB_HOST = config.get("DATABASE", "host") - options = {"sql_connection": "mysql://%s:%s@%s/%s" % (DB_USER, - DB_PASS, DB_HOST, DB_NAME)} - db.configure_db(options) - - -class UcsDB(object): - def get_all_ucsmbindings(self): - bindings = [] - try: - for x in ucs_db.get_all_ucsmbinding(): - LOG.debug("Getting ucsm binding : %s" % x.ucsm_ip) - bind_dict = {} - bind_dict["ucsm-ip"] = str(x.ucsm_ip) - bind_dict["network-id"] = str(x.network_id) - bindings.append(bind_dict) - except Exception, e: - LOG.error("Failed to get all bindings: %s" % str(e)) - return bindings - - def get_ucsmbinding(self, ucsm_ip): - binding = [] - try: - for x in ucs_db.get_ucsmbinding(ucsm_ip): - LOG.debug("Getting ucsm binding : %s" % x.ucsm_ip) - bind_dict = {} - bind_dict["ucsm-ip"] = str(res.ucsm_ip) - bind_dict["network-id"] = str(res.network_id) - binding.append(bind_dict) - except Exception, e: - LOG.error("Failed to get binding: %s" % str(e)) - return binding - - def create_ucsmbinding(self, ucsm_ip, networ_id): - bind_dict = {} - try: - res = ucs_db.add_ucsmbinding(ucsm_ip, networ_id) - LOG.debug("Created ucsm binding: %s" % res.ucsm_ip) - bind_dict["ucsm-ip"] = str(res.ucsm_ip) - bind_dict["network-id"] = str(res.network_id) - return bind_dict - except Exception, e: - LOG.error("Failed to create ucsm binding: %s" % str(e)) - - def delete_ucsmbinding(self, ucsm_ip): - try: - res = ucs_db.remove_ucsmbinding(ucsm_ip) - LOG.debug("Deleted ucsm binding : %s" % res.ucsm_ip) - bind_dict = {} - bind_dict["ucsm-ip"] = str(res.ucsm_ip) - return bind_dict - except Exception, e: - raise Exception("Failed to delete dynamic vnic: %s" % str(e)) - - def update_ucsmbinding(self, ucsm_ip, network_id): - try: - res = ucs_db.update_ucsmbinding(ucsm_ip, network_id) - LOG.debug("Updating ucsm binding : %s" % res.ucsm_ip) - bind_dict = {} - bind_dict["ucsm-ip"] = str(res.ucsm_ip) - bind_dict["network-id"] = str(res.network_id) - return bind_dict - except Exception, e: - raise Exception("Failed to update dynamic vnic: %s" % str(e)) - - def get_all_dynamicvnics(self): - vnics = [] - try: - for x in ucs_db.get_all_dynamicvnics(): - LOG.debug("Getting dynamic vnic : %s" % x.uuid) - vnic_dict = {} - vnic_dict["vnic-id"] = str(x.uuid) - vnic_dict["device-name"] = x.device_name - vnic_dict["blade-id"] = str(x.blade_id) - vnics.append(vnic_dict) - except Exception, e: - LOG.error("Failed to get all dynamic vnics: %s" % str(e)) - return vnics - - def get_dynamicvnic(self, vnic_id): - vnic = [] - try: - for x in ucs_db.get_dynamicvnic(vnic_id): - LOG.debug("Getting dynamic vnic : %s" % x.uuid) - vnic_dict = {} - vnic_dict["vnic-id"] = str(x.uuid) - vnic_dict["device-name"] = x.device_name - vnic_dict["blade-id"] = str(x.blade_id) - vnic.append(vnic_dict) - except Exception, e: - LOG.error("Failed to get dynamic vnic: %s" % str(e)) - return vnic - - def create_dynamicvnic(self, device_name, blade_id): - vnic_dict = {} - try: - res = ucs_db.add_dynamicvnic(device_name, blade_id) - LOG.debug("Created dynamic vnic: %s" % res.uuid) - vnic_dict["vnic-id"] = str(res.uuid) - vnic_dict["device-name"] = res.device_name - vnic_dict["blade-id"] = str(res.blade_id) - return vnic_dict - except Exception, e: - LOG.error("Failed to create dynamic vnic: %s" % str(e)) - - def delete_dynamicvnic(self, vnic_id): - try: - res = ucs_db.remove_dynamicvnic(vnic_id) - LOG.debug("Deleted dynamic vnic : %s" % res.uuid) - vnic_dict = {} - vnic_dict["vnic-id"] = str(res.uuid) - return vnic_dict - except Exception, e: - raise Exception("Failed to delete dynamic vnic: %s" % str(e)) - - def update_dynamicvnic(self, vnic_id, device_name=None, blade_id=None): - try: - res = ucs_db.update_dynamicvnic(vnic_id, device_name, blade_id) - LOG.debug("Updating dynamic vnic : %s" % res.uuid) - vnic_dict = {} - vnic_dict["vnic-id"] = str(res.uuid) - vnic_dict["device-name"] = res.device_name - vnic_dict["blade-id"] = str(res.blade_id) - return vnic_dict - except Exception, e: - raise Exception("Failed to update dynamic vnic: %s" % str(e)) - - def get_all_blades(self): - blades = [] - try: - for x in ucs_db.get_all_blades(): - LOG.debug("Getting blade : %s" % x.uuid) - blade_dict = {} - blade_dict["blade-id"] = str(x.uuid) - blade_dict["mgmt-ip"] = str(x.mgmt_ip) - blade_dict["mac-addr"] = str(x.mac_addr) - blade_dict["chassis-id"] = str(x.chassis_id) - blade_dict["ucsm-ip"] = str(x.ucsm_ip) - blades.append(blade_dict) - except Exception, e: - LOG.error("Failed to get all blades: %s" % str(e)) - return blades - - def get_blade(self, blade_id): - blade = [] - try: - for x in ucs_db.get_blade(blade_id): - LOG.debug("Getting blade : %s" % x.uuid) - blade_dict = {} - blade_dict["blade-id"] = str(x.uuid) - blade_dict["mgmt-ip"] = str(x.mgmt_ip) - blade_dict["mac-addr"] = str(x.mac_addr) - blade_dict["chassis-id"] = str(x.chassis_id) - blade_dict["ucsm-ip"] = str(x.ucsm_ip) - blade.append(blade_dict) - except Exception, e: - LOG.error("Failed to get all blades: %s" % str(e)) - return blade - - def create_blade(self, mgmt_ip, mac_addr, chassis_id, ucsm_ip): - blade_dict = {} - try: - res = ucs_db.add_blade(mgmt_ip, mac_addr, chassis_id, ucsm_ip) - LOG.debug("Created blade: %s" % res.uuid) - blade_dict["blade-id"] = str(res.uuid) - blade_dict["mgmt-ip"] = str(res.mgmt_ip) - blade_dict["mac-addr"] = str(res.mac_addr) - blade_dict["chassis-id"] = str(res.chassis_id) - blade_dict["ucsm-ip"] = str(res.ucsm_ip) - return blade_dict - except Exception, e: - LOG.error("Failed to create blade: %s" % str(e)) - - def delete_blade(self, blade_id): - try: - res = ucs_db.remove_blade(blade_id) - LOG.debug("Deleted blade : %s" % res.uuid) - blade_dict = {} - blade_dict["blade-id"] = str(res.uuid) - return blade_dict - except Exception, e: - raise Exception("Failed to delete blade: %s" % str(e)) - - def update_blade(self, blade_id, mgmt_ip=None, mac_addr=None,\ - chassis_id=None, ucsm_ip=None): - try: - res = ucs_db.update_blade(blade_id, mgmt_ip, mac_addr, \ - chassis_id, ucsm_ip) - LOG.debug("Updating blade : %s" % res.uuid) - blade_dict = {} - blade_dict["blade-id"] = str(res.uuid) - blade_dict["mgmt-ip"] = str(res.mgmt_ip) - blade_dict["mac-addr"] = str(res.mac_addr) - blade_dict["chassis-id"] = str(res.chassis_id) - blade_dict["ucsm-ip"] = str(res.ucsm_ip) - return blade_dict - except Exception, e: - raise Exception("Failed to update blade: %s" % str(e)) - - def get_all_port_bindings(self): - port_bindings = [] - try: - for x in ucs_db.get_all_portbindings(): - LOG.debug("Getting port binding for port: %s" % x.port_id) - port_bind_dict = {} - port_bind_dict["port-id"] = x.port_id - port_bind_dict["dynamic-vnic-id"] = str(x.dynamic_vnic_id) - port_bind_dict["portprofile-name"] = x.portprofile_name - port_bind_dict["vlan-name"] = x.vlan_name - port_bind_dict["vlan-id"] = str(x.vlan_id) - port_bind_dict["qos"] = x.qos - port_bindings.append(port_bind_dict) - except Exception, e: - LOG.error("Failed to get all port bindings: %s" % str(e)) - return port_bindings - - def get_port_binding(self): - port_binding = [] - try: - for x in ucs_db.get_portbinding(port_id): - LOG.debug("Getting port binding for port: %s" % x.port_id) - port_bind_dict = {} - port_bind_dict["port-id"] = x.port_id - port_bind_dict["dynamic-vnic-id"] = str(x.dynamic_vnic_id) - port_bind_dict["portprofile-name"] = x.portprofile_name - port_bind_dict["vlan-name"] = x.vlan_name - port_bind_dict["vlan-id"] = str(x.vlan_id) - port_bind_dict["qos"] = x.qos - port_bindings.append(port_bind_dict) - except Exception, e: - LOG.error("Failed to get port binding: %s" % str(e)) - return port_binding - - def create_port_binding(self, port_id, dynamic_vnic_id, portprofile_name, \ - vlan_name, vlan_id, qos): - port_bind_dict = {} - try: - res = ucs_db.add_portbinding(port_id, dynamic_vnic_id, \ - portprofile_name, vlan_name, vlan_id, qos) - LOG.debug("Created port binding: %s" % res.port_id) - port_bind_dict["port-id"] = res.port_id - port_bind_dict["dynamic-vnic-id"] = str(res.dynamic_vnic_id) - port_bind_dict["portprofile-name"] = res.portprofile_name - port_bind_dict["vlan-name"] = res.vlan_name - port_bind_dict["vlan-id"] = str(res.vlan_id) - port_bind_dict["qos"] = res.qos - return port_bind_dict - except Exception, e: - LOG.error("Failed to create port binding: %s" % str(e)) - - def delete_port_binding(self, port_id): - try: - res = ucs_db.remove_portbinding(port_id) - LOG.debug("Deleted port binding : %s" % res.port_id) - port_bind_dict = {} - port_bind_dict["port-id"] = res.port_id - return port_bind_dict - except Exception, e: - raise Exception("Failed to delete port profile: %s" % str(e)) - - def update_port_binding(self, port_id, dynamic_vnic_id, \ - portprofile_name, vlan_name, vlan_id, qos): - try: - res = ucs_db.update_portbinding(port_id, dynamic_vnic_id, \ - portprofile_name, vlan_name, vlan_id, qos) - LOG.debug("Updating port binding: %s" % res.port_id) - port_bind_dict = {} - port_bind_dict["port-id"] = res.port_id - port_bind_dict["dynamic-vnic-id"] = str(res.dynamic_vnic_id) - port_bind_dict["portprofile-name"] = res.portprofile_name - port_bind_dict["vlan-name"] = res.vlan_name - port_bind_dict["vlan-id"] = str(res.vlan_id) - port_bind_dict["qos"] = res.qos - return port_bind_dict - except Exception, e: - raise Exception("Failed to update portprofile binding:%s" % str(e)) - - -class QuantumDB(object): - def get_all_networks(self, tenant_id): - nets = [] - try: - for x in db.network_list(tenant_id): - LOG.debug("Getting network: %s" % x.uuid) - net_dict = {} - net_dict["tenant-id"] = x.tenant_id - net_dict["net-id"] = str(x.uuid) - net_dict["net-name"] = x.name - nets.append(net_dict) - except Exception, e: - LOG.error("Failed to get all networks: %s" % str(e)) - return nets - - def get_network(self, network_id): - net = [] - try: - for x in db.network_get(network_id): - LOG.debug("Getting network: %s" % x.uuid) - net_dict = {} - net_dict["tenant-id"] = x.tenant_id - net_dict["net-id"] = str(x.uuid) - net_dict["net-name"] = x.name - nets.append(net_dict) - except Exception, e: - LOG.error("Failed to get network: %s" % str(e)) - return net - - def create_network(self, tenant_id, net_name): - net_dict = {} - try: - res = db.network_create(tenant_id, net_name) - LOG.debug("Created network: %s" % res.uuid) - net_dict["tenant-id"] = res.tenant_id - net_dict["net-id"] = str(res.uuid) - net_dict["net-name"] = res.name - return net_dict - except Exception, e: - LOG.error("Failed to create network: %s" % str(e)) - - def delete_network(self, net_id): - try: - net = db.network_destroy(net_id) - LOG.debug("Deleted network: %s" % net.uuid) - net_dict = {} - net_dict["net-id"] = str(net.uuid) - return net_dict - except Exception, e: - raise Exception("Failed to delete port: %s" % str(e)) - - def rename_network(self, tenant_id, net_id, new_name): - try: - net = db.network_rename(net_id, tenant_id, new_name) - LOG.debug("Renamed network: %s" % net.uuid) - net_dict = {} - net_dict["net-id"] = str(net.uuid) - net_dict["net-name"] = net.name - return net_dict - except Exception, e: - raise Exception("Failed to rename network: %s" % str(e)) - - def get_all_ports(self, net_id): - ports = [] - try: - for x in db.port_list(net_id): - LOG.debug("Getting port: %s" % x.uuid) - port_dict = {} - port_dict["port-id"] = str(x.uuid) - port_dict["net-id"] = str(x.network_id) - port_dict["int-id"] = x.interface_id - port_dict["state"] = x.state - ports.append(port_dict) - return ports - except Exception, e: - LOG.error("Failed to get all ports: %s" % str(e)) - - def get_port(self, port_id): - port = [] - try: - for x in db.port_get(port_id): - LOG.debug("Getting port: %s" % x.uuid) - port_dict = {} - port_dict["port-id"] = str(x.uuid) - port_dict["net-id"] = str(x.network_id) - port_dict["int-id"] = x.interface_id - port_dict["state"] = x.state - port.append(port_dict) - return port - except Exception, e: - LOG.error("Failed to get port: %s" % str(e)) - - def create_port(self, net_id): - port_dict = {} - try: - port = db.port_create(net_id) - LOG.debug("Creating port %s" % port.uuid) - port_dict["port-id"] = str(port.uuid) - port_dict["net-id"] = str(port.network_id) - port_dict["int-id"] = port.interface_id - port_dict["state"] = port.state - return port_dict - except Exception, e: - LOG.error("Failed to create port: %s" % str(e)) - - def delete_port(self, port_id): - try: - port = db.port_destroy(port_id) - LOG.debug("Deleted port %s" % port.uuid) - port_dict = {} - port_dict["port-id"] = str(port.uuid) - return port_dict - except Exception, e: - raise Exception("Failed to delete port: %s" % str(e)) - - def update_port(self, port_id, port_state): - try: - port = db.port_set_state(port_id, port_state) - LOG.debug("Updated port %s" % port.uuid) - port_dict = {} - port_dict["port-id"] = str(port.uuid) - port_dict["net-id"] = str(port.network_id) - port_dict["int-id"] = port.interface_id - port_dict["state"] = port.state - return port_dict - except Exception, e: - raise Exception("Failed to update port state: %s" % str(e)) - - -class L2networkDB(object): - def get_all_vlan_bindings(self): - vlans = [] - try: - for x in l2network_db.get_all_vlan_bindings(): - LOG.debug("Getting vlan bindings for vlan: %s" % x.vlan_id) - vlan_dict = {} - vlan_dict["vlan-id"] = str(x.vlan_id) - vlan_dict["vlan-name"] = x.vlan_name - vlan_dict["net-id"] = str(x.network_id) - vlans.append(vlan_dict) - except Exception, e: - LOG.error("Failed to get all vlan bindings: %s" % str(e)) - return vlans - - def get_vlan_binding(self, network_id): - vlan = [] - try: - for x in l2network_db.get_vlan_binding(network_id): - LOG.debug("Getting vlan binding for vlan: %s" % x.vlan_id) - vlan_dict = {} - vlan_dict["vlan-id"] = str(x.vlan_id) - vlan_dict["vlan-name"] = x.vlan_name - vlan_dict["net-id"] = str(x.network_id) - vlan.append(vlan_dict) - except Exception, e: - LOG.error("Failed to get vlan binding: %s" % str(e)) - return vlan - - def create_vlan_binding(self, vlan_id, vlan_name, network_id): - vlan_dict = {} - try: - res = l2network_db.add_vlan_binding(vlan_id, vlan_name, network_id) - LOG.debug("Created vlan binding for vlan: %s" % res.vlan_id) - vlan_dict["vlan-id"] = str(res.vlan_id) - vlan_dict["vlan-name"] = res.vlan_name - vlan_dict["net-id"] = str(res.network_id) - return vlan_dict - except Exception, e: - LOG.error("Failed to create vlan binding: %s" % str(e)) - - def delete_vlan_binding(self, network_id): - try: - res = l2network_db.remove_vlan_binding(network_id) - LOG.debug("Deleted vlan binding for vlan: %s" % res.vlan_id) - vlan_dict = {} - vlan_dict["vlan-id"] = str(res.vlan_id) - return vlan_dict - except Exception, e: - raise Exception("Failed to delete vlan binding: %s" % str(e)) - - def update_vlan_binding(self, network_id, vlan_id, vlan_name): - try: - res = l2network_db.update_vlan_binding(network_id, vlan_id, \ - vlan_name) - LOG.debug("Updating vlan binding for vlan: %s" % res.vlan_id) - vlan_dict = {} - vlan_dict["vlan-id"] = str(res.vlan_id) - vlan_dict["vlan-name"] = res.vlan_name - vlan_dict["net-id"] = str(res.network_id) - return vlan_dict - except Exception, e: - raise Exception("Failed to update vlan binding: %s" % str(e)) - - def get_all_portprofiles(self): - pps = [] - try: - for x in l2network_db.get_all_portprofiles(): - LOG.debug("Getting port profile : %s" % x.uuid) - pp_dict = {} - pp_dict["portprofile-id"] = str(x.uuid) - pp_dict["portprofile-name"] = x.name - pp_dict["vlan-id"] = str(x.vlan_id) - pp_dict["qos"] = x.qos - pps.append(pp_dict) - except Exception, e: - LOG.error("Failed to get all port profiles: %s" % str(e)) - return pps - - def get_portprofile(self, port_id): - pp = [] - try: - for x in l2network_db.get_portprofile(port_id): - LOG.debug("Getting port profile : %s" % x.uuid) - pp_dict = {} - pp_dict["portprofile-id"] = str(x.uuid) - pp_dict["portprofile-name"] = x.name - pp_dict["vlan-id"] = str(x.vlan_id) - pp_dict["qos"] = x.qos - pp.append(pp_dict) - except Exception, e: - LOG.error("Failed to get port profile: %s" % str(e)) - return pp - - def create_portprofile(self, name, vlan_id, qos): - pp_dict = {} - try: - res = l2network_db.add_portprofile(name, vlan_id, qos) - LOG.debug("Created port profile: %s" % res.uuid) - pp_dict["portprofile-id"] = str(res.uuid) - pp_dict["portprofile-name"] = res.name - pp_dict["vlan-id"] = str(res.vlan_id) - pp_dict["qos"] = res.qos - return pp_dict - except Exception, e: - LOG.error("Failed to create port profile: %s" % str(e)) - - def delete_portprofile(self, pp_id): - try: - res = l2network_db.remove_portprofile(pp_id) - LOG.debug("Deleted port profile : %s" % res.uuid) - pp_dict = {} - pp_dict["pp-id"] = str(res.uuid) - return pp_dict - except Exception, e: - raise Exception("Failed to delete port profile: %s" % str(e)) - - def update_portprofile(self, pp_id, name, vlan_id, qos): - try: - res = l2network_db.update_portprofile(pp_id, name, vlan_id, qos) - LOG.debug("Updating port profile : %s" % res.uuid) - pp_dict = {} - pp_dict["portprofile-id"] = str(res.uuid) - pp_dict["portprofile-name"] = res.name - pp_dict["vlan-id"] = str(res.vlan_id) - pp_dict["qos"] = res.qos - return pp_dict - except Exception, e: - raise Exception("Failed to update port profile: %s" % str(e)) - - def get_all_pp_bindings(self): - pp_bindings = [] - try: - for x in l2network_db.get_all_pp_bindings(): - LOG.debug("Getting port profile binding: %s" % \ - x.portprofile_id) - ppbinding_dict = {} - ppbinding_dict["portprofile-id"] = str(x.portprofile_id) - ppbinding_dict["net-id"] = str(x.network_id) - ppbinding_dict["tenant-id"] = x.tenant_id - ppbinding_dict["default"] = x.default - pp_bindings.append(ppbinding_dict) - except Exception, e: - LOG.error("Failed to get all port profiles: %s" % str(e)) - return pp_bindings - - def get_pp_binding(self, pp_id): - pp_binding = [] - try: - for x in l2network_db.get_pp_binding(pp_id): - LOG.debug("Getting port profile binding: %s" % \ - x.portprofile_id) - ppbinding_dict = {} - ppbinding_dict["portprofile-id"] = str(x.portprofile_id) - ppbinding_dict["net-id"] = str(x.network_id) - ppbinding_dict["tenant-id"] = x.tenant_id - ppbinding_dict["default"] = x.default - pp_bindings.append(ppbinding_dict) - except Exception, e: - LOG.error("Failed to get port profile binding: %s" % str(e)) - return pp_binding - - def create_pp_binding(self, tenant_id, net_id, pp_id, default): - ppbinding_dict = {} - try: - res = l2network_db.add_pp_binding(tenant_id, net_id, pp_id, \ - default) - LOG.debug("Created port profile binding: %s" % res.portprofile_id) - ppbinding_dict["portprofile-id"] = str(res.portprofile_id) - ppbinding_dict["net-id"] = str(res.network_id) - ppbinding_dict["tenant-id"] = res.tenant_id - ppbinding_dict["default"] = res.default - return ppbinding_dict - except Exception, e: - LOG.error("Failed to create port profile binding: %s" % str(e)) - - def delete_pp_binding(self, pp_id): - try: - res = l2network_db.remove_pp_binding(pp_id) - LOG.debug("Deleted port profile binding : %s" % res.portprofile_id) - ppbinding_dict = {} - ppbinding_dict["portprofile-id"] = str(res.portprofile_id) - return ppbinding_dict - except Exception, e: - raise Exception("Failed to delete port profile: %s" % str(e)) - - def update_pp_binding(self, pp_id, tenant_id, net_id, default): - try: - res = l2network_db.update_pp_binding(pp_id, tenant_id, net_id,\ - default) - LOG.debug("Updating port profile binding: %s" % res.portprofile_id) - ppbinding_dict = {} - ppbinding_dict["portprofile-id"] = str(res.portprofile_id) - ppbinding_dict["net-id"] = str(res.network_id) - ppbinding_dict["tenant-id"] = res.tenant_id - ppbinding_dict["default"] = res.default - return ppbinding_dict - except Exception, e: - raise Exception("Failed to update portprofile binding:%s" % str(e)) - - -class UcsDBTest(unittest.TestCase): - def setUp(self): - self.dbtest = UcsDB() - LOG.debug("Setup") - - def testACreateUcsmBinding(self): - binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") - self.assertTrue(binding1["ucsm-ip"] == "1.2.3.4") - self.tearDownUcsmBinding() - - def testBGetAllUcsmBindings(self): - binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") - binding2 = self.dbtest.create_ucsmbinding("2.3.4.5", "net1") - bindings = self.dbtest.get_all_ucsmbindings() - count = 0 - for x in bindings: - if "net" in x["network-id"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownUcsmBinding() - - def testCDeleteUcsmBinding(self): - binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") - self.dbtest.delete_ucsmbinding(binding1["ucsm-ip"]) - bindings = self.dbtest.get_all_ucsmbindings() - count = 0 - for x in bindings: - if "net " in x["network-id"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownUcsmBinding() - - def testDUpdateUcsmBinding(self): - binding1 = self.dbtest.create_ucsmbinding("1.2.3.4", "net1") - binding1 = self.dbtest.update_ucsmbinding(binding1["ucsm-ip"], \ - "newnet1") - bindings = self.dbtest.get_all_ucsmbindings() - count = 0 - for x in bindings: - if "new" in x["network-id"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownUcsmBinding() - - def testECreateDynamicVnic(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) - self.assertTrue(vnic1["device-name"] == "eth1") - self.tearDownDyanmicVnic() - - def testFGetAllDyanmicVnics(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) - vnic2 = self.dbtest.create_dynamicvnic("eth2", blade1["blade-id"]) - vnics = self.dbtest.get_all_dynamicvnics() - count = 0 - for x in vnics: - if "eth" in x["device-name"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownDyanmicVnic() - - def testGDeleteDyanmicVnic(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) - self.dbtest.delete_dynamicvnic(vnic1["vnic-id"]) - vnics = self.dbtest.get_all_dynamicvnics() - count = 0 - for x in vnics: - if "eth " in x["device-name"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownDyanmicVnic() - - def testHUpdateDynamicVnic(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - vnic1 = self.dbtest.create_dynamicvnic("eth1", blade1["blade-id"]) - vnic1 = self.dbtest.update_dynamicvnic(vnic1["vnic-id"], "neweth1", \ - "newblade2") - vnics = self.dbtest.get_all_dynamicvnics() - count = 0 - for x in vnics: - if "new" in x["device-name"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownDyanmicVnic() - - def testICreateUcsBlade(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - self.assertTrue(blade1["mgmt-ip"] == "1.2.3.4") - self.tearDownUcsBlade() - - def testJGetAllUcsBlade(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - blade2 = self.dbtest.create_blade("2.3.4.5", "efgh", "chassis1", \ - "9.8.7.6") - blades = self.dbtest.get_all_blades() - count = 0 - for x in blades: - if "chassis" in x["chassis-id"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownUcsBlade() - - def testKDeleteUcsBlade(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - self.dbtest.delete_blade(blade1["blade-id"]) - blades = self.dbtest.get_all_blades() - count = 0 - for x in blades: - if "chassis " in x["chassis-id"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownUcsBlade() - - def testLUpdateUcsBlade(self): - blade1 = self.dbtest.create_blade("1.2.3.4", "abcd", "chassis1", \ - "9.8.7.6") - blade2 = self.dbtest.update_blade(blade1["blade-id"], "2.3.4.5", \ - "newabcd", "chassis1", "9.8.7.6") - blades = self.dbtest.get_all_blades() - count = 0 - for x in blades: - if "new" in x["mac-addr"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownUcsBlade() - - def testMCreatePortBinding(self): - port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ - "vlan1", 10, "qos1") - self.assertTrue(port_bind1["port-id"] == "port1") - self.tearDownPortBinding() - - def testNGetAllPortBinding(self): - port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ - "vlan1", 10, "qos1") - port_bind2 = self.dbtest.create_port_binding("port2", "dv2", "pp2", \ - "vlan2", 20, "qos2") - port_bindings = self.dbtest.get_all_port_bindings() - count = 0 - for x in port_bindings: - if "port" in x["port-id"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownPortBinding() - - def testODeletePortBinding(self): - port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ - "vlan1", 10, "qos1") - self.dbtest.delete_port_binding("port1") - port_bindings = self.dbtest.get_all_port_bindings() - count = 0 - for x in port_bindings: - if "port " in x["port-id"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownPortBinding() - - def testPUpdatePortBinding(self): - port_bind1 = self.dbtest.create_port_binding("port1", "dv1", "pp1", \ - "vlan1", 10, "qos1") - port_bind1 = self.dbtest.update_port_binding("port1", "newdv1", \ - "newpp1", "newvlan1", 11, "newqos1") - port_bindings = self.dbtest.get_all_port_bindings() - count = 0 - for x in port_bindings: - if "new" in x["dynamic-vnic-id"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownPortBinding() - - def tearDownUcsmBinding(self): - print "Tearing Down Ucsm Bindings" - binds = self.dbtest.get_all_ucsmbindings() - for bind in binds: - ip = bind["ucsm-ip"] - self.dbtest.delete_ucsmbinding(ip) - - def tearDownDyanmicVnic(self): - print "Tearing Down Dynamic Vnics" - vnics = self.dbtest.get_all_dynamicvnics() - for vnic in vnics: - id = vnic["vnic-id"] - self.dbtest.delete_dynamicvnic(id) - self.tearDownUcsBlade() - - def tearDownUcsBlade(self): - print "Tearing Down Blades" - blades = self.dbtest.get_all_blades() - for blade in blades: - id = blade["blade-id"] - self.dbtest.delete_blade(id) - - def tearDownPortBinding(self): - print "Tearing Down Port Binding" - port_bindings = self.dbtest.get_all_port_bindings() - for port_binding in port_bindings: - id = port_binding["port-id"] - self.dbtest.delete_port_binding(id) - - -class L2networkDBTest(unittest.TestCase): - def setUp(self): - self.dbtest = L2networkDB() - LOG.debug("Setup") - - def testACreateVlanBinding(self): - vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") - self.assertTrue(vlan1["vlan-id"] == "10") - self.tearDownVlanBinding() - - def testBGetAllVlanBindings(self): - vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") - vlan2 = self.dbtest.create_vlan_binding(20, "vlan2", "netid2") - vlans = self.dbtest.get_all_vlan_bindings() - count = 0 - for x in vlans: - if "netid" in x["net-id"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownVlanBinding() - - def testCDeleteVlanBinding(self): - vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") - self.dbtest.delete_vlan_binding("netid1") - vlans = self.dbtest.get_all_vlan_bindings() - count = 0 - for x in vlans: - if "netid " in x["net-id"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownVlanBinding() - - def testDUpdateVlanBinding(self): - vlan1 = self.dbtest.create_vlan_binding(10, "vlan1", "netid1") - vlan1 = self.dbtest.update_vlan_binding("netid1", 11, "newvlan1") - vlans = self.dbtest.get_all_vlan_bindings() - count = 0 - for x in vlans: - if "new" in x["vlan-name"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownVlanBinding() - - def testICreatePortProfile(self): - pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") - self.assertTrue(pp1["portprofile-name"] == "portprofile1") - self.tearDownPortProfile() - - def testJGetAllPortProfile(self): - pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") - pp2 = self.dbtest.create_portprofile("portprofile2", 20, "qos2") - pps = self.dbtest.get_all_portprofiles() - count = 0 - for x in pps: - if "portprofile" in x["portprofile-name"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownPortProfile() - - def testKDeletePortProfile(self): - pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") - self.dbtest.delete_portprofile(pp1["portprofile-id"]) - pps = self.dbtest.get_all_portprofiles() - count = 0 - for x in pps: - if "portprofile " in x["portprofile-name"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownPortProfile() - - def testLUpdatePortProfile(self): - pp1 = self.dbtest.create_portprofile("portprofile1", 10, "qos1") - pp1 = self.dbtest.update_portprofile(pp1["portprofile-id"], \ - "newportprofile1", 20, "qos2") - pps = self.dbtest.get_all_portprofiles() - count = 0 - for x in pps: - if "new" in x["portprofile-name"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownPortProfile() - - def testMCreatePortProfileBinding(self): - pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ - "portprofile1", "0") - self.assertTrue(pp_binding1["portprofile-id"] == "portprofile1") - self.tearDownPortProfileBinding() - - def testNGetAllPortProfileBinding(self): - pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ - "portprofile1", "0") - pp_binding2 = self.dbtest.create_pp_binding("t2", "net2", \ - "portprofile2", "0") - pp_bindings = self.dbtest.get_all_pp_bindings() - count = 0 - for x in pp_bindings: - if "portprofile" in x["portprofile-id"]: - count += 1 - self.assertTrue(count == 2) - self.tearDownPortProfileBinding() - - def testODeletePortProfileBinding(self): - pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ - "portprofile1", "0") - self.dbtest.delete_pp_binding(pp_binding1["portprofile-id"]) - pp_bindings = self.dbtest.get_all_pp_bindings() - count = 0 - for x in pp_bindings: - if "portprofile " in x["portprofile-id"]: - count += 1 - self.assertTrue(count == 0) - self.tearDownPortProfileBinding() - - def testPUpdatePortProfileBinding(self): - pp_binding1 = self.dbtest.create_pp_binding("t1", "net1", \ - "portprofile1", "0") - pp_binding1 = self.dbtest.update_pp_binding("portprofile1", \ - "newt1", "newnet1", "1") - pp_bindings = self.dbtest.get_all_pp_bindings() - count = 0 - for x in pp_bindings: - if "new" in x["net-id"]: - count += 1 - self.assertTrue(count == 1) - self.tearDownPortProfileBinding() - - def tearDownVlanBinding(self): - print "Tearing Down Vlan Binding" - vlans = self.dbtest.get_all_vlan_bindings() - for vlan in vlans: - id = vlan["net-id"] - self.dbtest.delete_vlan_binding(id) - - def tearDownPortProfile(self): - print "Tearing Down Port Profile" - pps = self.dbtest.get_all_portprofiles() - for pp in pps: - id = pp["portprofile-id"] - self.dbtest.delete_portprofile(id) - - def tearDownPortProfileBinding(self): - print "Tearing Down Port Profile Binding" - pp_bindings = self.dbtest.get_all_pp_bindings() - for pp_binding in pp_bindings: - id = pp_binding["portprofile-id"] - self.dbtest.delete_pp_binding(id) - -if __name__ == "__main__": - usagestr = "Usage: %prog [OPTIONS] [args]" - parser = OptionParser(usage=usagestr) - parser.add_option("-v", "--verbose", dest="verbose", - action="store_true", default=False, help="turn on verbose logging") - - options, args = parser.parse_args() - - if options.verbose: - LOG.basicConfig(level=LOG.DEBUG) - else: - LOG.basicConfig(level=LOG.WARN) - - #load the models and db based on the 2nd level plugin argument - if args[0] == "ucs": - ucs_db = __import__("quantum.plugins.cisco.db.ucs_db", \ - fromlist=["ucs_db"]) - ucs_model = __import__("quantum.plugins.cisco.db.ucs_models", \ - fromlist=["ucs_models"]) - - db_conf() - - # Run the tests - suite = unittest.TestLoader().loadTestsFromTestCase(L2networkDBTest) - unittest.TextTestRunner(verbosity=2).run(suite) - suite = unittest.TestLoader().loadTestsFromTestCase(UcsDBTest) - unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/quantum/plugins/cisco/db/l2network_db.py b/quantum/plugins/cisco/db/l2network_db.py deleted file mode 100644 index 15861eee301..00000000000 --- a/quantum/plugins/cisco/db/l2network_db.py +++ /dev/null @@ -1,239 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. - -from sqlalchemy.orm import exc - -import l2network_models -import quantum.db.api as db -import quantum.db.models as models - - -def get_all_vlan_bindings(): - """Lists all the vlan to network associations""" - session = db.get_session() - try: - bindings = session.query(l2network_models.VlanBinding).\ - all() - return bindings - except exc.NoResultFound: - return [] - - -def get_vlan_binding(netid): - """Lists the vlan given a network_id""" - session = db.get_session() - try: - binding = session.query(l2network_models.VlanBinding).\ - filter_by(network_id=netid).\ - one() - return binding - except exc.NoResultFound: - raise Exception("No network found with net-id = %s" % network_id) - - -def add_vlan_binding(vlanid, vlanname, netid): - """Adds a vlan to network association""" - session = db.get_session() - try: - binding = session.query(l2network_models.VlanBinding).\ - filter_by(vlan_id=vlanid).\ - one() - raise Exception("Vlan with id \"%s\" already exists" % vlanid) - except exc.NoResultFound: - binding = l2network_models.VlanBinding(vlanid, vlanname, netid) - session.add(binding) - session.flush() - return binding - - -def remove_vlan_binding(netid): - """Removes a vlan to network association""" - session = db.get_session() - try: - binding = session.query(l2network_models.VlanBinding).\ - filter_by(network_id=netid).\ - one() - session.delete(binding) - session.flush() - return binding - except exc.NoResultFound: - pass - - -def update_vlan_binding(netid, newvlanid=None, newvlanname=None): - """Updates a vlan to network association""" - session = db.get_session() - try: - binding = session.query(l2network_models.VlanBinding).\ - filter_by(network_id=netid).\ - one() - if newvlanid: - binding.vlan_id = newvlanid - if newvlanname: - binding.vlan_name = newvlanname - session.merge(binding) - session.flush() - return binding - except exc.NoResultFound: - raise Exception("No vlan binding found with network_id = %s" % netid) - - -def get_all_portprofiles(): - """Lists all the port profiles""" - session = db.get_session() - try: - pps = session.query(l2network_models.PortProfile).\ - all() - return pps - except exc.NoResultFound: - return [] - - -def get_portprofile(ppid): - """Lists a port profile""" - session = db.get_session() - try: - pp = session.query(l2network_models.PortProfile).\ - filter_by(uuid=ppid).\ - one() - return pp - except exc.NoResultFound: - raise Exception("No portprofile found with id = %s" % ppid) - - -def add_portprofile(ppname, vlanid, qos): - """Adds a port profile""" - session = db.get_session() - try: - pp = session.query(l2network_models.PortProfile).\ - filter_by(name=ppname).\ - one() - raise Exception("Port profile with name %s already exists" % ppname) - except exc.NoResultFound: - pp = l2network_models.PortProfile(ppname, vlanid, qos) - session.add(pp) - session.flush() - return pp - - -def remove_portprofile(ppid): - """Removes a port profile""" - session = db.get_session() - try: - pp = session.query(l2network_models.PortProfile).\ - filter_by(uuid=ppid).\ - one() - session.delete(pp) - session.flush() - return pp - except exc.NoResultFound: - pass - - -def update_portprofile(ppid, newppname=None, newvlanid=None, newqos=None): - """Updates port profile""" - session = db.get_session() - try: - pp = session.query(l2network_models.PortProfile).\ - filter_by(uuid=ppid).\ - one() - if newppname: - pp.name = newppname - if newvlanid: - pp.vlan_id = newvlanid - if newqos: - pp.qos = newqos - session.merge(pp) - session.flush() - return pp - except exc.NoResultFound: - raise Exception("No port profile with id = %s" % ppid) - - -def get_all_pp_bindings(): - """Lists all the port profiles""" - session = db.get_session() - try: - bindings = session.query(l2network_models.PortProfileBinding).\ - all() - return bindings - except exc.NoResultFound: - return [] - - -def get_pp_binding(ppid): - """Lists a port profile binding""" - session = db.get_session() - try: - binding = session.query(l2network_models.PortProfileBinding).\ - filter_by(portprofile_id=ppid).\ - one() - return binding - except exc.NoResultFound: - raise Exception("No portprofile binding found with id = %s" % ppid) - - -def add_pp_binding(tenantid, networkid, ppid, default): - """Adds a port profile binding""" - session = db.get_session() - try: - binding = session.query(l2network_models.PortProfileBinding).\ - filter_by(portprofile_id=ppid).\ - one() - raise Exception("Port profile binding with id \"%s\" already \ - exists" % ppid) - except exc.NoResultFound: - binding = l2network_models.PortProfileBinding(tenantid, networkid, \ - ppid, default) - session.add(binding) - session.flush() - return binding - - -def remove_pp_binding(ppid): - """Removes a port profile binding""" - session = db.get_session() - try: - binding = session.query(l2network_models.PortProfileBinding).\ - filter_by(portprofile_id=ppid).\ - one() - session.delete(binding) - session.flush() - return binding - except exc.NoResultFound: - pass - - -def update_pp_binding(ppid, newtenantid=None, newnetworkid=None, \ - newdefault=None): - """Updates port profile binding""" - session = db.get_session() - try: - binding = session.query(l2network_models.PortProfileBinding).\ - filter_by(portprofile_id=ppid).\ - one() - if newtenantid: - binding.tenant_id = newtenantid - if newnetworkid: - binding.network_id = newnetworkid - if newdefault: - binding.default = newdefault - session.merge(binding) - session.flush() - return binding - except exc.NoResultFound: - raise Exception("No port profile binding with id = %s" % ppid) diff --git a/quantum/plugins/cisco/db/l2network_models.py b/quantum/plugins/cisco/db/l2network_models.py deleted file mode 100644 index 12d75bb303e..00000000000 --- a/quantum/plugins/cisco/db/l2network_models.py +++ /dev/null @@ -1,86 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. - -import uuid - -from sqlalchemy import Column, Integer, String, ForeignKey, Boolean -from sqlalchemy.orm import relation - -from quantum.db.models import BASE - - -class VlanBinding(BASE): - """Represents a binding of vlan_id to network_id""" - __tablename__ = 'vlan_bindings' - - vlan_id = Column(Integer, primary_key=True) - vlan_name = Column(String(255)) - network_id = Column(String(255), nullable=False) - #foreign key to networks.uuid - - def __init__(self, vlan_id, vlan_name, network_id): - self.vlan_id = vlan_id - self.vlan_name = vlan_name - self.network_id = network_id - - def __repr__(self): - return "" % \ - (self.vlan_id, self.vlan_name, self.network_id) - - -class PortProfile(BASE): - """Represents Cisco plugin level PortProfile for a network""" - __tablename__ = 'portprofiles' - - uuid = Column(String(255), primary_key=True) - name = Column(String(255)) - vlan_id = Column(Integer) - qos = Column(String(255)) - - def __init__(self, name, vlan_id, qos=None): - self.uuid = uuid.uuid4() - self.name = name - self.vlan_id = vlan_id - self.qos = qos - - def __repr__(self): - return "" % \ - (self.uuid, self.name, self.vlan_id, self.qos) - - -class PortProfileBinding(BASE): - """Represents PortProfile binding to tenant and network""" - __tablename__ = 'portprofile_bindings' - - id = Column(Integer, primary_key=True, autoincrement=True) - tenant_id = Column(String(255)) - - network_id = Column(String(255), nullable=False) - #foreign key to networks.uuid - portprofile_id = Column(String(255), nullable=False) - #foreign key to portprofiles.uuid - default = Column(Boolean) - - def __init__(self, tenant_id, network_id, portprofile_id, default): - self.tenant_id = tenant_id - self.network_id = network_id - self.portprofile_id = portprofile_id - self.default = default - - def __repr__(self): - return "" % \ - (self.tenant_id, self.network_id, self.portprofile_id, self.default) diff --git a/quantum/plugins/cisco/db/ucs_db.py b/quantum/plugins/cisco/db/ucs_db.py deleted file mode 100644 index 92198e89bb1..00000000000 --- a/quantum/plugins/cisco/db/ucs_db.py +++ /dev/null @@ -1,314 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. - -from sqlalchemy.orm import exc - -import quantum.db.api as db -import ucs_models - - -def get_all_ucsmbinding(): - """Lists all the ucsm bindings""" - session = db.get_session() - try: - bindings = session.query(ucs_models.UcsmBinding).\ - all() - return bindings - except exc.NoResultFound: - return [] - - -def get_ucsmbinding(ucsm_ip): - """Lists a ucsm binding""" - session = db.get_session() - try: - binding = session.query(ucs_models.UcsmBinding).\ - filter_by(ucsm_ip=ucsm_ip).\ - one() - return binding - except exc.NoResultFound: - raise Exception("No binding found with ip = %s" % ucsm_ip) - - -def add_ucsmbinding(ucsm_ip, network_id): - """Adds a ucsm binding""" - session = db.get_session() - try: - ip = session.query(ucs_models.UcsmBinding).\ - filter_by(ucsm_ip=ucsm_ip).\ - one() - raise Exception("Binding with ucsm ip \"%s\" already exists" % ucsm_ip) - except exc.NoResultFound: - binding = ucs_models.UcsmBinding(ucsm_ip, network_id) - session.add(binding) - session.flush() - return binding - - -def remove_ucsmbinding(ucsm_ip): - """Removes a ucsm binding""" - session = db.get_session() - try: - binding = session.query(ucs_models.UcsmBinding).\ - filter_by(ucsm_ip=ucsm_ip).\ - one() - session.delete(binding) - session.flush() - return binding - except exc.NoResultFound: - pass - - -def update_ucsmbinding(ucsm_ip, new_network_id): - """Updates ucsm binding""" - session = db.get_session() - try: - binding = session.query(ucs_models.UcsmBinding).\ - filter_by(ucsm_ip=ucsm_ip).\ - one() - if new_network_id: - binding.network_id = new_network_id - session.merge(binding) - session.flush() - return binding - except exc.NoResultFound: - raise Exception("No binding with ip = %s" % ucsm_ip) - - -def get_all_dynamicvnics(): - """Lists all the dynamic vnics""" - session = db.get_session() - try: - vnics = session.query(ucs_models.DynamicVnic).\ - all() - return vnics - except exc.NoResultFound: - return [] - - -def get_dynamicvnic(vnic_id): - """Lists a dynamic vnic""" - session = db.get_session() - try: - vnic = session.query(ucs_models.DynamicVnic).\ - filter_by(uuid=vnic_id).\ - one() - return vnic - except exc.NoResultFound: - raise Exception("No dynamic vnic found with id = %s" % vnic_id) - - -def add_dynamicvnic(device_name, blade_id): - """Adds a dynamic vnic""" - session = db.get_session() - try: - name = session.query(ucs_models.DynamicVnic).\ - filter_by(device_name=device_name).\ - one() - raise Exception("Dynamic vnic with device name %s already exists" % \ - device_name) - except exc.NoResultFound: - vnic = ucs_models.DynamicVnic(device_name, blade_id) - session.add(vnic) - session.flush() - return vnic - - -def remove_dynamicvnic(vnic_id): - """Removes a dynamic vnic""" - session = db.get_session() - try: - vnic = session.query(ucs_models.DynamicVnic).\ - filter_by(uuid=vnic_id).\ - one() - session.delete(vnic) - session.flush() - return vnic - except exc.NoResultFound: - pass - - -def update_dynamicvnic(vnic_id, new_device_name=None, new_blade_id=None): - """Updates dynamic vnic""" - session = db.get_session() - try: - vnic = session.query(ucs_models.DynamicVnic).\ - filter_by(uuid=vnic_id).\ - one() - if new_device_name: - vnic.device_name = new_device_name - if new_blade_id: - vnic.blade_id = new_blade_id - session.merge(vnic) - session.flush() - return vnic - except exc.NoResultFound: - raise Exception("No dynamic vnic with id = %s" % vnic_id) - - -def get_all_blades(): - """Lists all the blades details""" - session = db.get_session() - try: - blades = session.query(ucs_models.UcsBlade).\ - all() - return blades - except exc.NoResultFound: - return [] - - -def get_blade(blade_id): - """Lists a blade details""" - session = db.get_session() - try: - blade = session.query(ucs_models.UcsBlade).\ - filter_by(uuid=blade_id).\ - one() - return blade - except exc.NoResultFound: - raise Exception("No blade found with id = %s" % blade_id) - - -def add_blade(mgmt_ip, mac_addr, chassis_id, ucsm_ip): - """Adds a blade""" - session = db.get_session() - try: - ip = session.query(ucs_models.UcsBlade).\ - filter_by(mgmt_ip=mgmt_ip).\ - one() - raise Exception("Blade with ip \"%s\" already exists" % mgmt_ip) - except exc.NoResultFound: - blade = ucs_models.UcsBlade(mgmt_ip, mac_addr, chassis_id, ucsm_ip) - session.add(blade) - session.flush() - return blade - - -def remove_blade(blade_id): - """Removes a blade""" - session = db.get_session() - try: - blade = session.query(ucs_models.UcsBlade).\ - filter_by(uuid=blade_id).\ - one() - session.delete(blade) - session.flush() - return blade - except exc.NoResultFound: - pass - - -def update_blade(blade_id, new_mgmt_ip=None, new_mac_addr=None, \ - new_chassis_id=None, new_ucsm_ip=None): - """Updates details of a blade""" - session = db.get_session() - try: - blade = session.query(ucs_models.UcsBlade).\ - filter_by(uuid=blade_id).\ - one() - if new_mgmt_ip: - blade.mgmt_ip = new_mgmt_ip - if new_mac_addr: - blade.mac_addr = new_mac_addr - if new_chassis_id: - blade.chassis_id = new_chassis_id - if new_ucsm_ip: - blade.ucsm_ip = new_ucsm_ip - session.merge(blade) - session.flush() - return blade - except exc.NoResultFound: - raise Exception("No blade with id = %s" % blade_id) - - -def get_all_portbindings(): - """Lists all the port bindings""" - session = db.get_session() - try: - port_bindings = session.query(ucs_models.PortBinding).\ - all() - return port_bindings - except exc.NoResultFound: - return [] - - -def get_portbinding(port_id): - """Lists a port binding""" - session = db.get_session() - try: - port_binding = session.query(ucs_models.PortBinding).\ - filter_by(port_id=port_id).\ - one() - return port_binding - except exc.NoResultFound: - raise Exception("No port binding found with port id = %s" % port_id) - - -def add_portbinding(port_id, dynamic_vnic_id, portprofile_name, \ - vlan_name, vlan_id, qos): - """Adds a port binding""" - session = db.get_session() - try: - port_binding = session.query(ucs_models.PortBinding).\ - filter_by(port_id=port_id).\ - one() - raise Exception("Port Binding with portid %s already exists" % port_id) - except exc.NoResultFound: - port_binding = ucs_models.PortBinding(port_id, dynamic_vnic_id, \ - portprofile_name, vlan_name, vlan_id, qos) - session.add(port_binding) - session.flush() - return port_binding - - -def remove_portbinding(port_id): - """Removes a port binding""" - session = db.get_session() - try: - port_binding = session.query(ucs_models.PortBinding).\ - filter_by(port_id=port_id).\ - one() - session.delete(port_binding) - session.flush() - return port_binding - except exc.NoResultFound: - pass - - -def update_portbinding(port_id, dynamic_vnic_id=None, portprofile_name=None, \ - vlan_name=None, vlan_id=None, qos=None): - """Updates port binding""" - session = db.get_session() - try: - port_binding = session.query(ucs_models.PortBinding).\ - filter_by(port_id=port_id).\ - one() - if dynamic_vnic_id: - port_binding.dynamic_vnic_id = dynamic_vnic_id - if portprofile_name: - port_binding.portprofile_name = portprofile_name - if vlan_name: - port_binding.vlan_name = vlan_name - if vlan_name: - port_binding.vlan_id = vlan_id - if qos: - port_binding.qos = qos - session.merge(port_binding) - session.flush() - return port_binding - except exc.NoResultFound: - raise Exception("No port binding with port id = %s" % port_id) diff --git a/quantum/plugins/cisco/db/ucs_models.py b/quantum/plugins/cisco/db/ucs_models.py deleted file mode 100644 index 68cd3dddeac..00000000000 --- a/quantum/plugins/cisco/db/ucs_models.py +++ /dev/null @@ -1,113 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011, Cisco Systems, 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: Rohit Agarwalla, Cisco Systems, Inc. - -import uuid - -from sqlalchemy import Column, Integer, String, ForeignKey, Boolean -from sqlalchemy.orm import relation - -from quantum.db.models import BASE - - -class UcsmBinding(BASE): - """Represents a binding of ucsm to network_id""" - __tablename__ = 'ucsm_bindings' - - id = Column(Integer, primary_key=True, autoincrement=True) - ucsm_ip = Column(String(255)) - network_id = Column(String(255), nullable=False) - #foreign key to networks.uuid - - def __init__(self, ucsm_ip, network_id): - self.ucsm_ip = ucsm_ip - self.network_id = network_id - - def __repr__(self): - return "" % \ - (self.ucsm_ip, self.network_id) - - -class DynamicVnic(BASE): - """Represents Cisco UCS Dynamic Vnics""" - __tablename__ = 'dynamic_vnics' - - uuid = Column(String(255), primary_key=True) - device_name = Column(String(255)) - blade_id = Column(String(255), ForeignKey("ucs_blades.uuid"), \ - nullable=False) - - def __init__(self, device_name, blade_id): - self.uuid = uuid.uuid4() - self.device_name = device_name - self.blade_id = blade_id - - def __repr__(self): - return "" % \ - (self.uuid, self.device_name, self.blade_id) - - -class UcsBlade(BASE): - """Represents details of ucs blades""" - __tablename__ = 'ucs_blades' - - uuid = Column(String(255), primary_key=True) - mgmt_ip = Column(String(255)) - mac_addr = Column(String(255)) - chassis_id = Column(String(255)) - ucsm_ip = Column(String(255)) - dynamic_vnics = relation(DynamicVnic, order_by=DynamicVnic.uuid, \ - backref="blade") - - def __init__(self, mgmt_ip, mac_addr, chassis_id, ucsm_ip): - self.uuid = uuid.uuid4() - self.mgmt_ip = mgmt_ip - self.mac_addr = mac_addr - self.chassis_id = chassis_id - self.ucsm_ip = ucsm_ip - - def __repr__(self): - return "" % \ - (self.uuid, self.mgmt_ip, self.mac_addr, self.chassis_id, self.ucsm_ip) - - -class PortBinding(BASE): - """Represents Port binding to device interface""" - __tablename__ = 'port_bindings' - - id = Column(Integer, primary_key=True, autoincrement=True) - port_id = Column(String(255), nullable=False) - #foreign key to ports.uuid - dynamic_vnic_id = Column(String(255), nullable=False) - #foreign key to dynamic_vnics.uuid - portprofile_name = Column(String(255)) - vlan_name = Column(String(255)) - vlan_id = Column(Integer) - qos = Column(String(255)) - - def __init__(self, port_id, dynamic_vnic_id, portprofile_name, vlan_name, \ - vlan_id, qos): - self.port_id = port_id - self.dynamic_vnic_id = dynamic_vnic_id - self.portprofile_name = portprofile_name - self.vlan_name = vlan_name - self.vlan_id = vlan_id - self.qos = qos - - def __repr__(self): - return "" % \ - (self.port_id, self.dynamic_vnic_id, self.portprofile_name, \ - self.vlan_name, self.vlan_id, self.qos) From fdf92daaa4d96ec12c4bc87b19dbf57f626b501f Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Thu, 4 Aug 2011 12:02:43 -0700 Subject: [PATCH 44/76] Removing extra file in Nexus Driver --- quantum/plugins/cisco/nexus/nxosapi.py | 172 ------------------------- 1 file changed, 172 deletions(-) delete mode 100644 quantum/plugins/cisco/nexus/nxosapi.py diff --git a/quantum/plugins/cisco/nexus/nxosapi.py b/quantum/plugins/cisco/nexus/nxosapi.py deleted file mode 100644 index 0f0ebe04f01..00000000000 --- a/quantum/plugins/cisco/nexus/nxosapi.py +++ /dev/null @@ -1,172 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 Cisco Systems Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Debojyoti Dutta, Cisco Systems, Inc. - -import sys -import os -import warnings -warnings.simplefilter("ignore", DeprecationWarning) -from ncclient import manager -from ncclient import NCClientError -from ncclient.transport.errors import * - -exec_conf_prefix = """ - - - <__XML__MODE__exec_configure> -""" - - -exec_conf_postfix = """ - - - -""" - - -cmd_vlan_conf_snippet = """ - - - <__XML__PARAM_value>%s - <__XML__MODE_vlan> - - %s - - - active - - - - - - - -""" - -cmd_no_vlan_conf_snippet = """ - - - - <__XML__PARAM_value>%s - - - -""" - -cmd_vlan_int_snippet = """ - - - %s - <__XML__MODE_if-ethernet-switch> - - - - - - <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> - %s - - - - - - - - -""" - - -cmd_no_vlan_int_snippet = """ - - - %s - <__XML__MODE_if-ethernet-switch> - - - - - - - <__XML__BLK_Cmd_switchport_trunk_allowed_allow-vlans> - %s - - - - - - - - - -""" - - -filter_show_vlan_brief_snippet = """ - - - - - """ - - -def nxos_connect(host, port, user, password): - try: - m = manager.connect(host=host, port=port, username=user, - password=password) - return m - except SSHUnknownHostError: - sys.stderr.write('SSH unknown host error\n') - exit() - - -def enable_vlan(mgr, vlanid, vlanname): - confstr = cmd_vlan_conf_snippet % (vlanid, vlanname) - confstr = exec_conf_prefix + confstr + exec_conf_postfix - mgr.edit_config(target='running', config=confstr) - - -def disable_vlan(mgr, vlanid): - confstr = cmd_no_vlan_conf_snippet % vlanid - confstr = exec_conf_prefix + confstr + exec_conf_postfix - mgr.edit_config(target='running', config=confstr) - - -def enable_vlan_on_trunk_int(mgr, interface, vlanid): - confstr = cmd_vlan_int_snippet % (interface, vlanid) - confstr = exec_conf_prefix + confstr + exec_conf_postfix - print confstr - mgr.edit_config(target='running', config=confstr) - - -def disable_vlan_on_trunk_int(mgr, interface, vlanid): - confstr = cmd_no_vlan_int_snippet % (interface, vlanid) - confstr = exec_conf_prefix + confstr + exec_conf_postfix - print confstr - mgr.edit_config(target='running', config=confstr) - - -def test_nxos_api(host, user, password): - with nxos_connect(host, port=22, user=user, password=password) as m: - enable_vlan(m, '100', 'ccn1') - enable_vlan_on_trunk_int(m, '2/1', '100') - disable_vlan_on_trunk_int(m, '2/1', '100') - disable_vlan(m, '100') - result = m.get(("subtree", filter_show_vlan_brief_snippet)) - #print result - - -if __name__ == '__main__': - test_nxos_api(sys.argv[1], sys.argv[2], sys.argv[3]) From fc50da6fc64b6821c43a09b1637cf071722566e6 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Fri, 5 Aug 2011 11:58:45 +0530 Subject: [PATCH 45/76] Santhosh/Vinkesh | Added extension_stubs file --- tests/unit/extension_stubs.py | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/unit/extension_stubs.py diff --git a/tests/unit/extension_stubs.py b/tests/unit/extension_stubs.py new file mode 100644 index 00000000000..c8a7385f607 --- /dev/null +++ b/tests/unit/extension_stubs.py @@ -0,0 +1,75 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from abc import abstractmethod + +from quantum.common import extensions +from quantum.common import wsgi + + +class StubExtension(object): + + def __init__(self, alias="stub_extension"): + self.alias = alias + + def get_name(self): + return "Stub Extension" + + def get_alias(self): + return self.alias + + def get_description(self): + return "" + + def get_namespace(self): + return "" + + def get_updated(self): + return "" + + +class StubPlugin(object): + + def __init__(self, supported_extensions=[]): + self.supported_extension_aliases = supported_extensions + + +class ExtensionExpectingPluginInterface(StubExtension): + """ + This extension expects plugin to implement all the methods defined + in StubPluginInterface + """ + + def get_plugin_interface(self): + return StubPluginInterface + + +class StubPluginInterface(extensions.PluginInterface): + + @abstractmethod + def get_foo(self, bar=None): + pass + + +class StubBaseAppController(wsgi.Controller): + + def index(self, request): + return "base app index" + + def show(self, request, id): + return {'fort': 'knox'} + + def update(self, request, id): + return {'uneditable': 'original_value'} From bb537a081dbc75cb99b9d0b6970475db262e7068 Mon Sep 17 00:00:00 2001 From: vinkesh banka Date: Fri, 5 Aug 2011 12:35:04 +0530 Subject: [PATCH 46/76] Deepak/Vinkesh | Fixed show action in extension controller to return 404, added example to include namespace in a request extension --- quantum/common/extensions.py | 5 ++++- tests/unit/test_extensions.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/quantum/common/extensions.py b/quantum/common/extensions.py index 10795f80536..f13a0c3370e 100644 --- a/quantum/common/extensions.py +++ b/quantum/common/extensions.py @@ -199,7 +199,10 @@ class ExtensionController(wsgi.Controller): def show(self, request, id): # NOTE(dprince): the extensions alias is used as the 'id' for show - ext = self.extension_manager.extensions[id] + ext = self.extension_manager.extensions.get(id, None) + if not ext: + raise webob.exc.HTTPNotFound( + _("Extension with alias %s does not exist") % id) return self._translate(ext) def delete(self, request, id): diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index b22a8a3c55f..48027cb950f 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -168,7 +168,7 @@ class RequestExtensionTest(BaseTest): def test_extend_get_resource_response(self): def extend_response_data(req, res): data = json.loads(res.body) - data['extended_key'] = req.GET.get('extended_key') + data['FOXNSOX:extended_key'] = req.GET.get('extended_key') res.body = json.dumps(data) return res @@ -177,7 +177,8 @@ class RequestExtensionTest(BaseTest): self.assertEqual(200, response.status_int) response_data = json.loads(response.body) - self.assertEqual('extended_data', response_data['extended_key']) + self.assertEqual('extended_data', + response_data['FOXNSOX:extended_key']) self.assertEqual('knox', response_data['fort']) def test_get_resources(self): @@ -344,6 +345,11 @@ class ExtensionControllerTest(unittest.TestCase): self.assertEqual(foxnsox_extension["namespace"], "http://www.fox.in.socks/api/ext/pie/v1.0") + def test_show_returns_not_found_for_non_existant_extension(self): + response = self.test_app.get("/extensions/non_existant", status="*") + + self.assertEqual(response.status_int, 404) + def app_factory(global_conf, **local_conf): conf = global_conf.copy() From 3c461229f83fc03064c074939b91f38b012fc528 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 5 Aug 2011 02:59:54 -0700 Subject: [PATCH 47/76] Loading of device-specific plugins and drivers is done dynamically by setting configuration. All configuration is driven through configuration files place in the conf directory. Each .ini conf file contains info on the configuration. README file updated to reflect all the changes. Fixed issue with delete_network deleting the network even when attachments were present. Fixed issue with port id generation. --- etc/quantum.conf | 5 - quantum/plugins/cisco/README | 78 +++------------ .../cisco/common/cisco_configparser.py | 49 ++++++++++ .../cisco/common/cisco_configuration.py | 97 ------------------- .../plugins/cisco/common/cisco_constants.py | 51 ++++++++++ .../plugins/cisco/common/cisco_credentials.py | 55 +++++------ .../cisco/common/cisco_nova_configuration.py | 42 ++++++++ quantum/plugins/cisco/common/cisco_utils.py | 6 +- quantum/plugins/cisco/conf/credentials.ini | 15 +++ .../plugins/cisco/conf/l2network_plugin.ini | 11 +++ quantum/plugins/cisco/conf/nexus.ini | 8 ++ quantum/plugins/cisco/conf/nova.ini | 8 ++ quantum/plugins/cisco/conf/plugins.ini | 4 + quantum/plugins/cisco/conf/ucs.ini | 10 ++ quantum/plugins/cisco/l2network_plugin.py | 82 ++++++++-------- .../cisco/l2network_plugin_configuration.py | 51 ++++++++++ .../cisco/nexus/cisco_nexus_configuration.py | 42 ++++++++ .../cisco/nexus/cisco_nexus_network_driver.py | 1 - .../plugins/cisco/nexus/cisco_nexus_plugin.py | 8 +- .../cisco/ucs/{get-vif.py => cisco_getvif.py} | 23 ++++- .../cisco/ucs/cisco_ucs_configuration.py | 44 +++++++++ .../cisco/ucs/cisco_ucs_network_driver.py | 19 ++-- quantum/plugins/cisco/ucs/cisco_ucs_plugin.py | 7 +- quantum/plugins/cisco/ucs/get-vif.sh | 15 --- 24 files changed, 452 insertions(+), 279 deletions(-) create mode 100644 quantum/plugins/cisco/common/cisco_configparser.py delete mode 100644 quantum/plugins/cisco/common/cisco_configuration.py create mode 100644 quantum/plugins/cisco/common/cisco_nova_configuration.py create mode 100644 quantum/plugins/cisco/conf/credentials.ini create mode 100644 quantum/plugins/cisco/conf/l2network_plugin.ini create mode 100644 quantum/plugins/cisco/conf/nexus.ini create mode 100644 quantum/plugins/cisco/conf/nova.ini create mode 100644 quantum/plugins/cisco/conf/plugins.ini create mode 100644 quantum/plugins/cisco/conf/ucs.ini create mode 100644 quantum/plugins/cisco/l2network_plugin_configuration.py create mode 100644 quantum/plugins/cisco/nexus/cisco_nexus_configuration.py rename quantum/plugins/cisco/ucs/{get-vif.py => cisco_getvif.py} (60%) create mode 100644 quantum/plugins/cisco/ucs/cisco_ucs_configuration.py delete mode 100755 quantum/plugins/cisco/ucs/get-vif.sh diff --git a/etc/quantum.conf b/etc/quantum.conf index 6bd962790c2..2580913c321 100644 --- a/etc/quantum.conf +++ b/etc/quantum.conf @@ -15,14 +15,9 @@ bind_port = 9696 use = egg:Paste#urlmap /: quantumversions /v0.1: quantumapi -/v0.1/extensions:cisco_extensions [app:quantumversions] paste.app_factory = quantum.api.versions:Versions.factory [app:quantumapi] paste.app_factory = quantum.api:APIRouterV01.factory - -[app:cisco_extensions] -paste.app_factory = cisco_extensions:ExtRouterV01.factory - diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index b77ce22ca89..f8711193ab9 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -7,9 +7,13 @@ * UCS B200 series blades with M81KR VIC installed. * UCSM 2.0 (Capitola) Build 230 * RHEL 6.1 -* ncclcient v0.3.1 - Python library for NETCONF clients (http://schmizz.net/ncclient/) * UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at: http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM + +* If you have a Nexus switch in your topology and decide to turn on Nexus support, you will need: + - ncclcient v0.3.1 - Python library for NETCONF clients (http://schmizz.net/ncclient/). + - paramiko library (do: yum install python-paramiko.noarch) + * To run Quantum on RHEL, you will need to have the correct version of python-routes (version 1.12.3 or later). The RHEL 6.1 package contains an older version. Do the following and check your python-routes version: rpm -qav | grep "python-routes" @@ -26,64 +30,12 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK ** Plugin Installation Instructions: * Make a backup copy of quantum/quantum/plugins.ini, and edit the file to remove all exisiting entries and add the following entry: provider = quantum.plugins.cisco.l2network_plugin.L2Network -* You should have the following files in quantum/quantum/plugins/cisco directory (if you have pulled the Cisco Quantum branch, you will already have them): -l2network_plugin.py -common/cisco_configuration.py -common/cisco_constants.py -common/cisco_credentials.py -common/cisco_exceptions.py -nexus/cisco_nexus_plugin.py -ucs/cisco_ucs_network_driver.py -ucs/cisco_ucs_plugin.py -common/cisco_utils.py -__init__.py -ucs/get-vif.sh -* Configure the L2 Network Plugin: - + In cisco_configuration.py, - - change the UCSM IP in the following statement to your UCSM IP - flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of UCSM') - - change the Nova MySQL DB IP if you are running Quantum on a different host than the OpenStack Cloud Controller (in other words you do not need to change the IP if Quantum is running on the same host on which the Nova DB is running). DB IP is changed in the following statement: - flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server') - - change the hostname of the OpenStack Cloud Controller below - flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud controller hostname') - - change the name of the OpenStack project - flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova') - - change the start range of the VLAN (if you are not sure about this number, leave this unchanged) - flags.DEFINE_string('vlan_start', "100", 'This is the start value of the allowable VLANs') - - change the end range of the VLAN (if you are not sure about this number, leave this unchanged) - flags.DEFINE_string('vlan_end', "3000", 'This is the end value of the allowable VLANs') - - unless you have VLANs created in UCSM which start with the name "q-", you do not need to change the following property. If you do need to change it, change "q-" to some other string. Do not use more than 6 characters. - flags.DEFINE_string('vlan_name_prefix', "q-", 'Prefix of the name given to the VLAN') - - unless you have Port Profiles created in UCSM which start with the name "q-", you do not need to change the following property. If you do need to change it, change "q-" to some other string. Do not use more than 6 characters. - flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name given to the port profile') - - Change the path to reflect the location of the get-vif.sh script, if you have followed the instructions in this README, this location should be the same as that of your other plugin modules - flags.DEFINE_string('get_next_vif', "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", 'This is the location of the script to get the next available dynamic nic') - - + In cisco_credentials.py, - - Change the following structure to reflect the correct UCS, N7K and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack - N7K_IP_ADDRESS has to match with your Nexus 7k switch IP Address, N7K_USERNAME is the administrator user-name and N7K_PASSWORD is the password. - _creds_dictionary = { - 'UCSM_IP_ADDRESS':["UCSM_USERNAME", "UCSM_PASSWORD"], - 'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"] - } - -* Configure the L2 Network Plugin with Nexus OS Driver for testing VLANs CRUD on Nexus 7k Switch. Making these changes requires one Nexus 7K Switch connected to the UCSM and the ncclient patch not just the regular library, otherwise the system will fail. - + In cisco_configuration.py, - - change the NEXUS 7K IP in the following statement to your N7K Switch IP - flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of N7K') - - change the NEXUS Interface in the following statement to the interface number in your N7K which is connected to your UCSM UpLink port - flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface connected from the Nexus 7K Switch to UCSM 6120') - - change NEXUS Driver Flag to "on" in the following statement - flags.DEFINE_string('nexus_driver_active', "off", 'Flag to activate Nexus OS Driver') - - + In cisco_credentials.py, - - Change the following structure to reflect the correct UCS, N7K and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack - N7K_IP_ADDRESS has to match with your Nexus 7k switch IP Address, N7K_USERNAME is the administrator user-name and N7K_PASSWORD is the password. - _creds_dictionary = { - 'UCSM_IP_ADDRESS':["UCSM_USERNAME", "UCSM_PASSWORD"], - 'N7K_IP_ADDRESS':["N7K_USERNAME", "N7K_PASSWORD"], - 'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"] - } + +* All configuration files are located under quantum/plugins/cisco/conf. Where you find kind of placeholder, please replace it entirely with the relevant values (don't forget to remove the angle brackets as well) + +* If you are not running Quantum on the same host as the OpenStack Cloud Controller, you will need to change the db_ip_address configuration in nova.ini + +* If you want to turn on Nexus support, please uncomment the nexus_plugin property in the nexus.ini file. You will also require a patch to the ncclient in addition to the library mentioned in the prerequisities. * Start the Quantum service @@ -98,11 +50,3 @@ mysql -uroot -p nova -e 'create table ports (port_id VARCHA /usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py * Restart nova-compute service - -**L2network database usage requirements - -* mysql database "quantum_l2network" is required for persistence of the l2network plugin data. - + The database can be created as follows - - - # mysql -uroot -pnova; (username/password here is root/nova) - - mysql> create database quantum_l2network; - - mysql> use quantum_l2network; - + The database and login details must be specified in quantum/plugins/cisco/db/db_conn.ini. diff --git a/quantum/plugins/cisco/common/cisco_configparser.py b/quantum/plugins/cisco/common/cisco_configparser.py new file mode 100644 index 00000000000..312c230acba --- /dev/null +++ b/quantum/plugins/cisco/common/cisco_configparser.py @@ -0,0 +1,49 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import logging as LOG +import os + +from configobj import ConfigObj +from validate import Validator + +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as cexc + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class CiscoConfigParser(ConfigObj): + + def __init__(self, filename): + super(CiscoConfigParser, self).__init__(filename, raise_errors=True, + file_error=True) + + def dummy(self, section, key): + return section[key] + + +def main(): + cp = CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + "test.ini") + print ("%s\n") % cp['PLUGIN']['provider'] + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/common/cisco_configuration.py b/quantum/plugins/cisco/common/cisco_configuration.py deleted file mode 100644 index 01762e5fa88..00000000000 --- a/quantum/plugins/cisco/common/cisco_configuration.py +++ /dev/null @@ -1,97 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# Copyright 2011 Cisco Systems, Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# @author: Sumit Naiksatam, Cisco Systems, Inc. -# @author: Edgar Magana, Cisco Systems, Inc. -# -from quantum.common import flags - -# Note: All configuration values defined here are strings -FLAGS = flags.FLAGS -# -# TODO (Sumit): The following are defaults, but we also need to add to config -# file -# -flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of \ - UCSM') -flags.DEFINE_string('nexus_ip_address', "172.20.231.61", 'IP address of \ - Nexus Switch') -flags.DEFINE_string('nexus_port', "3/23", 'Port number of the Interface \ - connected from the Nexus Switch to UCSM 6120') -flags.DEFINE_string('nexus_driver_active', "off", 'Flag to activate Nexus OS\ - Driver') -flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB \ - server') -flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud \ - controller hostname') - -flags.DEFINE_string('db_name', "nova", 'DB name') -flags.DEFINE_string('vlan_name_prefix', "q-", 'Prefix of the name given \ - to the VLAN') -flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name \ - given to the port profile') -flags.DEFINE_string('vlan_start', "100", 'This is the start value of the \ - allowable VLANs') -flags.DEFINE_string('vlan_end', "3000", 'This is the end value of the \ - allowable VLANs') -flags.DEFINE_string('default_vlan_name', "default", 'This is the name of \ - the VLAN which will be associated with the port profile \ - when it is created, by default the VMs will be on this \ - VLAN, until attach is called') -flags.DEFINE_string('default_vlan_id', "1", 'This is the name of the VLAN \ - which will be associated with the port profile when it \ - is created, by default the VMs will be on this VLAN, \ - until attach is called') -flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova') -# -# TODO (Sumit): SAVBU to provide the accurate number below -# -flags.DEFINE_string('max_ucsm_port_profiles', "1024", 'This is the maximum \ - number port profiles that can be handled by one UCSM.') -flags.DEFINE_string('max_port_profiles', "65568", 'This is the maximum \ - number port profiles that can be handled by Cisco \ - plugin. Currently this is just an arbitrary number.') -flags.DEFINE_string('max_networks', "65568", 'This is the maximum number \ - of networks that can be handled by Cisco plugin. \ - Currently this is just an arbitrary number.') - -flags.DEFINE_string('get_next_vif', - "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", - 'This is the location of the script to get the next \ - next available dynamic nic') - -# Inventory items -UCSM_IP_ADDRESS = FLAGS.ucsm_ip_address -NEXUS_IP_ADDRESS = FLAGS.nexus_ip_address -NEXUS_DRIVER_ACTIVE = FLAGS.nexus_driver_active -NEXUS_PORT = FLAGS.nexus_port -DB_SERVER_IP = FLAGS.db_server_ip -NOVA_HOST_NAME = FLAGS.nova_host_name - -# General configuration items -DB_NAME = FLAGS.db_name -VLAN_NAME_PREFIX = FLAGS.vlan_name_prefix -PROFILE_NAME_PREFIX = FLAGS.profile_name_prefix -VLAN_START = FLAGS.vlan_start -VLAN_END = FLAGS.vlan_end -DEFAULT_VLAN_NAME = FLAGS.default_vlan_name -DEFAULT_VLAN_ID = FLAGS.default_vlan_id -NOVA_PROJ_NAME = FLAGS.nova_proj_name -MAX_UCSM_PORT_PROFILES = FLAGS.max_ucsm_port_profiles -MAX_PORT_PROFILES = FLAGS.max_port_profiles -MAX_NETWORKS = FLAGS.max_networks - -GET_NEXT_VIF_SCRIPT = FLAGS.get_next_vif diff --git a/quantum/plugins/cisco/common/cisco_constants.py b/quantum/plugins/cisco/common/cisco_constants.py index 7f2367d7947..b14ce093471 100644 --- a/quantum/plugins/cisco/common/cisco_constants.py +++ b/quantum/plugins/cisco/common/cisco_constants.py @@ -17,6 +17,8 @@ # @author: Sumit Naiksatam, Cisco Systems, Inc. # +PLUGINS = 'PLUGINS' + PORT_STATE = 'port-state' PORT_UP = "UP" PORT_DOWN = "DOWN" @@ -35,6 +37,8 @@ TENANT_ID = 'tenant-id' TENANT_NETWORKS = 'tenant-networks' TENANT_NAME = 'tenant-name' TENANT_PORTPROFILES = 'tenant-portprofiles' +TENANT_QOS_LEVELS = 'tenant-qos-levels' +TENANT_CREDENTIALS = 'tenant-credentials' PORT_PROFILE = 'port-profile' PROFILE_ID = 'profile-id' @@ -44,4 +48,51 @@ PROFILE_VLAN_ID = 'vlan-id' PROFILE_QOS = 'profile-qos' PROFILE_ASSOCIATIONS = 'assignment' +QOS_LEVEL_ID = 'qos-id' +QOS_LEVEL_NAME = 'qos-name' +QOS_LEVEL_ASSOCIATIONS = 'qos-level-associations' +QOS_LEVEL_DESCRIPTION = 'qos-desc' + +CREDENTIAL_ID = 'credential-id' +CREDENTIAL_NAME = 'credential-name' +CREDENTIAL_USERNAME = 'credential-username' +CREDENTIAL_PASSWORD = 'credential-password' +MASKED_PASSWORD = '********' + +USERNAME = 'username' +PASSWORD = 'password' + LOGGER_COMPONENT_NAME = "cisco_plugin" + +BLADE_INTF_DN = "blade-intf-distinguished-name" +BLADE_INTF_ORDER = "blade-intf-order" +BLADE_INTF_LINK_STATE = "blade-intf-link-state" +BLADE_INTF_OPER_STATE = "blade-intf-operational-state" +BLADE_INTF_INST_TYPE = "blade-intf-inst-type" +BLADE_INTF_RHEL_DEVICE_NAME = "blade-intf-rhel-device-name" +BLADE_INTF_DYNAMIC = "dynamic" +BLADE_INTF_STATE_UNKNOWN = "unknown" +BLADE_INTF_STATE_UNALLOCATED = "unallocated" +BLADE_INTF_RESERVED = "blade-intf-reserved" +BLADE_INTF_UNRESERVED = "blade-intf-unreserved" +BLADE_INTF_RESERVATION = "blade-intf-reservation-status" +BLADE_UNRESERVED_INTF_COUNT = "blade-unreserved-interfaces-count" +BLADE_INTF_DATA = "blade-intf-data" + +LEAST_RSVD_BLADE_UCSM = "least-reserved-blade-ucsm" +LEAST_RSVD_BLADE_CHASSIS = "least-reserved-blade-chassis" +LEAST_RSVD_BLADE_ID = "least-reserved-blade-id" +LEAST_RSVD_BLADE_DATA = "least-reserved-blade-data" + +RESERVED_NIC_HOSTNAME = "reserved-dynamic-nic-hostname" +RESERVED_NIC_NAME = "reserved-dynamic-nic-device-name" + +RESERVED_INTERFACE_UCSM = "reserved-interface-ucsm-ip" +RESERVED_INTERFACE_CHASSIS = "reserved-interface-chassis" +RESERVED_INTERFACE_BLADE = "reserved-interface-blade" +RESERVED_INTERFACE_DN = "reserved-interface-dn" + +RHEL_DEVICE_NAME_REPFIX = "eth" + +UCS_PLUGIN = 'ucs_plugin' +NEXUS_PLUGIN = 'nexus_plugin' diff --git a/quantum/plugins/cisco/common/cisco_credentials.py b/quantum/plugins/cisco/common/cisco_credentials.py index cdbb6c379a8..edc2288cdea 100644 --- a/quantum/plugins/cisco/common/cisco_credentials.py +++ b/quantum/plugins/cisco/common/cisco_credentials.py @@ -18,54 +18,51 @@ # import logging as LOG +import os from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_configparser as confp LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) -_creds_dictionary = {'10.10.10.10': ["username", "password"], - '127.0.0.1': ["root", "nova"]} +CREDENTIALS_FILE = "../conf/credentials.ini" + +cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CREDENTIALS_FILE) +_creds_dictionary = cp.walk(cp.dummy) class Store(object): - # The format for this store is {"ip-address" :{"username", "password"}} - def __init__(self): - pass - @staticmethod - def putId(id): - _creds_dictionary[id] = [] - - @staticmethod - def putUsername(id, username): - creds = _creds_dictionary.get(id) - creds.insert(0, username) - - @staticmethod - def putPassword(id, password): - creds = _creds_dictionary.get(id) - creds.insert(1, password) + def putCredential(id, username, password): + _creds_dictionary[id] = {const.USERNAME: username, + const.PASSWORD: password} @staticmethod def getUsername(id): - creds = _creds_dictionary.get(id) - return creds[0] + return _creds_dictionary[id][const.USERNAME] @staticmethod def getPassword(id): - creds = _creds_dictionary.get(id) - return creds[1] + return _creds_dictionary[id][const.PASSWORD] + + @staticmethod + def getCredential(id): + return _creds_dictionary[id] + + @staticmethod + def getCredentials(): + return _creds_dictionary + + @staticmethod + def deleteCredential(id): + return _creds_dictionary.pop(id) def main(): - LOG.debug("username %s\n" % Store.getUsername("172.20.231.27")) - LOG.debug("password %s\n" % Store.getPassword("172.20.231.27")) - Store.putId("192.168.1.1") - Store.putUsername("192.168.1.1", "guest-username") - Store.putPassword("192.168.1.1", "guest-password") - LOG.debug("username %s\n" % Store.getUsername("192.168.1.1")) - LOG.debug("password %s\n" % Store.getPassword("192.168.1.1")) + Store.putCredential("10.10.10.10", "foo", "bar") + print ("%s\n") % Store.getCredentials() if __name__ == '__main__': main() diff --git a/quantum/plugins/cisco/common/cisco_nova_configuration.py b/quantum/plugins/cisco/common/cisco_nova_configuration.py new file mode 100644 index 00000000000..f1cbf2a1ec2 --- /dev/null +++ b/quantum/plugins/cisco/common/cisco_nova_configuration.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import os + +from quantum.plugins.cisco.common import cisco_configparser as confp + +CONF_FILE = "../conf/nova.ini" + +cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) + +section = cp['NOVA'] +DB_SERVER_IP = section['db_server_ip'] +DB_NAME = section['db_name'] +DB_USERNAME = section['db_username'] +DB_PASSWORD = section['db_password'] +NOVA_HOST_NAME = section['nova_host_name'] +NOVA_PROJ_NAME = section['nova_proj_name'] + + +def main(): + print NOVA_PROJ_NAME + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/common/cisco_utils.py b/quantum/plugins/cisco/common/cisco_utils.py index 8d0d803b3f6..695136db8da 100644 --- a/quantum/plugins/cisco/common/cisco_utils.py +++ b/quantum/plugins/cisco/common/cisco_utils.py @@ -23,9 +23,9 @@ import sys import traceback from quantum.common import exceptions as exc -from quantum.plugins.cisco.common import cisco_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_nova_configuration as conf LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) @@ -38,8 +38,8 @@ class DBUtils(object): def _get_db_connection(self): db_ip = conf.DB_SERVER_IP - db_username = cred.Store.getUsername(db_ip) - db_password = cred.Store.getPassword(db_ip) + db_username = conf.DB_USERNAME + db_password = conf.DB_PASSWORD self.db = MySQLdb.connect(db_ip, db_username, db_password, conf.DB_NAME) return self.db diff --git a/quantum/plugins/cisco/conf/credentials.ini b/quantum/plugins/cisco/conf/credentials.ini new file mode 100644 index 00000000000..96e912cc700 --- /dev/null +++ b/quantum/plugins/cisco/conf/credentials.ini @@ -0,0 +1,15 @@ +#Provide the UCSM credentials +[] +username= +password= + +#Provide the Nova DB credentials, the IP address should be the same as in nova.ini +[] +username= +password= + +#Provide the Nexus credentials, if you are using Nexus +[] +username= +password= + diff --git a/quantum/plugins/cisco/conf/l2network_plugin.ini b/quantum/plugins/cisco/conf/l2network_plugin.ini new file mode 100644 index 00000000000..f6ac8613bfe --- /dev/null +++ b/quantum/plugins/cisco/conf/l2network_plugin.ini @@ -0,0 +1,11 @@ +[VLANS] +#Change the following to reflect the VLAN range in your system +vlan_start=100 +vlan_end=3000 +vlan_name_prefix=q- + +[PORTS] +max_ports=100 + +[NETWORKS] +max_networks=65568 diff --git a/quantum/plugins/cisco/conf/nexus.ini b/quantum/plugins/cisco/conf/nexus.ini new file mode 100644 index 00000000000..fede46eb835 --- /dev/null +++ b/quantum/plugins/cisco/conf/nexus.ini @@ -0,0 +1,8 @@ +[SWITCH] +# Change the following to reflect the Nexus switch details +nexus_ip_address= +#Port number of the Interface connected from the Nexus 7K Switch to UCSM 6120, e.g.: 3/23 +nexus_port= + +[DRIVER] +name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver diff --git a/quantum/plugins/cisco/conf/nova.ini b/quantum/plugins/cisco/conf/nova.ini new file mode 100644 index 00000000000..357a5864308 --- /dev/null +++ b/quantum/plugins/cisco/conf/nova.ini @@ -0,0 +1,8 @@ +[NOVA] +#Change the following details to reflect your OpenStack Nova configuration. If you are running this service on the same machine as the Nova DB, you do not have to change the IP address. +db_server_ip=127.0.0.1 +db_name=nova +db_username=root +db_password=nova +nova_host_name=openstack0203 +nova_proj_name=demo diff --git a/quantum/plugins/cisco/conf/plugins.ini b/quantum/plugins/cisco/conf/plugins.ini new file mode 100644 index 00000000000..15f6e5412f8 --- /dev/null +++ b/quantum/plugins/cisco/conf/plugins.ini @@ -0,0 +1,4 @@ +[PLUGINS] +ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin.UCSVICPlugin +#Uncomment the following line if you want to enable Nexus support +#nexus_plugin=quantum.plugins.cisco.ucs.cisco_nexus_plugin.NexusPlugin diff --git a/quantum/plugins/cisco/conf/ucs.ini b/quantum/plugins/cisco/conf/ucs.ini new file mode 100644 index 00000000000..43102531310 --- /dev/null +++ b/quantum/plugins/cisco/conf/ucs.ini @@ -0,0 +1,10 @@ +[UCSM] +#change the following to the appropriate UCSM IP address +ip_address= +default_vlan_name=default +default_vlan_id=1 +max_ucsm_port_profiles=1024 +profile_name_prefix=q- + +[DRIVER] +name=quantum.plugins.cisco.ucs.cisco_ucs_network_driver.CiscoUCSMDriver diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index 9a8c4a4f0e6..e34a1c491e0 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -15,18 +15,16 @@ # under the License. # # @author: Sumit Naiksatam, Cisco Systems, Inc. -# @author: Edgar Magana, Cisco Systems, Inc. # import logging as LOG from quantum.common import exceptions as exc -from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.common import utils +from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc -from quantum.plugins.cisco.nexus import cisco_nexus_plugin -from quantum.plugins.cisco.ucs import cisco_ucs_plugin from quantum.plugins.cisco.common import cisco_utils as cutil LOG.basicConfig(level=LOG.WARN) @@ -37,13 +35,18 @@ class L2Network(object): _networks = {} _tenants = {} _portprofiles = {} + _plugins = {} def __init__(self): self._net_counter = 0 self._portprofile_counter = 0 + self._port_counter = 0 self._vlan_counter = int(conf.VLAN_START) - 1 - self._ucs_plugin = cisco_ucs_plugin.UCSVICPlugin() - self._nexus_plugin = cisco_nexus_plugin.NexusPlugin() + for key in conf.plugins[const.PLUGINS].keys(): + self._plugins[key] = utils.import_object( + conf.plugins[const.PLUGINS][key]) + LOG.debug("Loaded device plugin %s\n" % \ + conf.plugins[const.PLUGINS][key]) """ Core API implementation @@ -66,15 +69,9 @@ class L2Network(object): new_net_id = self._get_unique_net_id(tenant_id) vlan_id = self._get_vlan_for_tenant(tenant_id, net_name) vlan_name = self._get_vlan_name(new_net_id, str(vlan_id)) - nexus_driver_flag = conf.NEXUS_DRIVER_ACTIVE - if nexus_driver_flag == 'on': - LOG.debug("Nexus OS Driver called\n") - self._nexus_plugin.create_network(tenant_id, net_name, new_net_id, - vlan_name, vlan_id) - else: - LOG.debug("No Nexus OS Driver available\n") - self._ucs_plugin.create_network(tenant_id, net_name, new_net_id, - vlan_name, vlan_id) + for pluginClass in self._plugins.values(): + pluginClass.create_network(tenant_id, net_name, + new_net_id, vlan_name, vlan_id) new_net_dict = {const.NET_ID: new_net_id, const.NET_NAME: net_name, const.NET_PORTS: {}, @@ -94,18 +91,20 @@ class L2Network(object): """ LOG.debug("delete_network() called\n") net = self._networks.get(net_id) - nexus_driver_flag = conf.NEXUS_DRIVER_ACTIVE # TODO (Sumit) : Verify that no attachments are plugged into the # network if net: + if len(net[const.NET_PORTS].values()) > 0: + ports_on_net = net[const.NET_PORTS].values() + for port in ports_on_net: + if port[const.ATTACHMENT]: + raise exc.NetworkInUse(net_id=net_id) + for port in ports_on_net: + self.delete_port(tenant_id, net_id, port[const.PORT_ID]) # TODO (Sumit) : Before deleting the network, make sure all the # ports associated with this network are also deleted - if nexus_driver_flag == 'on': - LOG.debug("Nexus OS Driver called\n") - self._nexus_plugin.delete_network(tenant_id, net_id) - else: - LOG.debug("No Nexus OS Driver available\n") - self._ucs_plugin.delete_network(tenant_id, net_id) + for pluginClass in self._plugins.values(): + pluginClass.delete_network(tenant_id, net_id) self._networks.pop(net_id) tenant = self._get_tenant(tenant_id) tenant_networks = tenant[const.TENANT_NETWORKS] @@ -129,13 +128,8 @@ class L2Network(object): Virtual Network. """ LOG.debug("rename_network() called\n") - nexus_driver_flag = conf.NEXUS_DRIVER_ACTIVE - if nexus_driver_flag == 'on': - LOG.debug("Nexus OS Driver called\n") - self._nexus_plugin.rename_network(tenant_id, net_id) - else: - LOG.debug("No Nexus OS Driver available\n") - self._ucs_plugin.rename_network(tenant_id, net_id) + for pluginClass in self._plugins.values(): + pluginClas.rename_network(tenant_id, net_id) network = self._get_network(tenant_id, net_id) network[const.NET_NAME] = new_name return network @@ -158,8 +152,9 @@ class L2Network(object): net = self._get_network(tenant_id, net_id) ports = net[const.NET_PORTS] unique_port_id_string = self._get_unique_port_id(tenant_id, net_id) - self._ucs_plugin.create_port(tenant_id, net_id, port_state, - unique_port_id_string) + self._plugins[const.UCS_PLUGIN].create_port(tenant_id, net_id, + port_state, + unique_port_id_string) new_port_dict = {const.PORT_ID: unique_port_id_string, const.PORT_STATE: const.PORT_UP, const.ATTACHMENT: None} @@ -181,7 +176,8 @@ class L2Network(object): try: #TODO (Sumit): Before deleting port profile make sure that there # is no VM using this port profile - self._ucs_plugin.delete_port(tenant_id, net_id, port_id) + self._plugins[const.UCS_PLUGIN].delete_port(tenant_id, net_id, + port_id) net = self._get_network(tenant_id, net_id) net[const.NET_PORTS].pop(port_id) except KeyError: @@ -218,8 +214,9 @@ class L2Network(object): if port[const.ATTACHMENT]: raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port[const.ATTACHMENT]) - self._ucs_plugin.plug_interface(tenant_id, net_id, port_id, - remote_interface_id) + self._plugins[const.UCS_PLUGIN].plug_interface(tenant_id, + net_id, port_id, + remote_interface_id) port[const.ATTACHMENT] = remote_interface_id def unplug_interface(self, tenant_id, net_id, port_id): @@ -229,8 +226,8 @@ class L2Network(object): """ LOG.debug("unplug_interface() called\n") port = self._get_port(tenant_id, net_id, port_id) - self._ucs_plugin.unplug_interface(tenant_id, net_id, - port_id) + self._plugins[const.UCS_PLUGIN].unplug_interface(tenant_id, net_id, + port_id) port[const.ATTACHMENT] = None """ @@ -361,17 +358,15 @@ class L2Network(object): "-n-" + ("0" * (6 - len(str(self._net_counter)))) + \ str(self._net_counter) # TODO (Sumit): Need to check if the ID has already been allocated + # ID will be generated by DB return id def _get_unique_port_id(self, tenant_id, net_id): - net = self._get_network(tenant_id, net_id) - ports = net[const.NET_PORTS] - if len(ports) == 0: - new_port_id = 1 - else: - new_port_id = max(ports.keys()) + 1 - id = net_id + "-p-" + str(new_port_id) + self._port_counter += 1 + self._port_counter %= int(conf.MAX_PORTS) + id = net_id + "-p-" + str(self._port_counter) # TODO (Sumit): Need to check if the ID has already been allocated + # ID will be generated by DB return id def _get_unique_profile_id(self, tenant_id): @@ -380,6 +375,7 @@ class L2Network(object): id = tenant_id[:3] + "-pp-" + \ ("0" * (6 - len(str(self._net_counter)))) + str(self._net_counter) # TODO (Sumit): Need to check if the ID has already been allocated + # ID will be generated by DB return id # TODO (Sumit): diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py new file mode 100644 index 00000000000..e0fe786a515 --- /dev/null +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -0,0 +1,51 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import os + +from quantum.plugins.cisco.common import cisco_configparser as confp + +CONF_FILE = "conf/l2network_plugin.ini" + +cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) + +section = cp['VLANS'] +VLAN_NAME_PREFIX = section['vlan_name_prefix'] +VLAN_START = section['vlan_start'] +VLAN_END = section['vlan_end'] + +section = cp['PORTS'] +MAX_PORTS = section['max_ports'] + +section = cp['NETWORKS'] +MAX_NETWORKS = section['max_networks'] + +CONF_FILE = "conf/plugins.ini" + +cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) +plugins = cp.walk(cp.dummy) + + +def main(): + print plugins['PLUGINS'] + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py new file mode 100644 index 00000000000..f1ad1490281 --- /dev/null +++ b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py @@ -0,0 +1,42 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Edgar Magana, Cisco Systems, Inc. +# + +import os + +from quantum.plugins.cisco.common import cisco_configparser as confp + +CONF_FILE = "../conf/nexus.ini" + +cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) + +section = cp['SWITCH'] +NEXUS_IP_ADDRESS = section['nexus_ip_address'] +NEXUS_PORT = section['nexus_port'] + +section = cp['DRIVER'] +NEXUS_DRIVER = section['name'] + + +def main(): + print NEXUS_PORT + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py index 27e63ce4cb5..78090d5f9cf 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -25,7 +25,6 @@ import logging as LOG import string import subprocess -from quantum.plugins.cisco.common import cisco_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_exceptions as cexc diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 31baac47297..8fa6424f930 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -20,13 +20,12 @@ import logging as LOG from quantum.common import exceptions as exc -from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.common import utils from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_utils as cutil - -from quantum.plugins.cisco.nexus import cisco_nexus_network_driver +from quantum.plugins.cisco.nexus import cisco_nexus_configuration as conf LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) @@ -36,7 +35,8 @@ class NexusPlugin(object): _networks = {} def __init__(self): - self._client = cisco_nexus_network_driver.CiscoNEXUSDriver() + self._client = utils.import_object(conf.NEXUS_DRIVER) + LOG.debug("Loaded driver %s\n" % conf.NEXUS_DRIVER) #TODO (Edgar) Using just one Nexus 7K Switch and Port self._nexus_ip = conf.NEXUS_IP_ADDRESS self._nexus_username = cred.Store.getUsername(conf.NEXUS_IP_ADDRESS) diff --git a/quantum/plugins/cisco/ucs/get-vif.py b/quantum/plugins/cisco/ucs/cisco_getvif.py similarity index 60% rename from quantum/plugins/cisco/ucs/get-vif.py rename to quantum/plugins/cisco/ucs/cisco_getvif.py index 0512ecb0dd5..f00116ee1fc 100644 --- a/quantum/plugins/cisco/ucs/get-vif.py +++ b/quantum/plugins/cisco/ucs/cisco_getvif.py @@ -1,3 +1,21 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Rohit Agarwalla, Cisco Systems Inc. +# import sys import subprocess @@ -23,7 +41,7 @@ def get_next_dynic(argv=[]): for lines in f_cmd_output.splitlines()] #print deviceid if deviceid[0] == "0044": - cmd = ["/usr/sbin/ip", "link", "show", eth] + cmd = ["/sbin/ip", "link", "show", eth] f_cmd_output = subprocess.Popen(cmd, stdout=subprocess.PIPE).\ communicate()[0] used = [lines for lines in f_cmd_output.splitlines() \ @@ -33,5 +51,6 @@ def get_next_dynic(argv=[]): return eth if __name__ == '__main__': - nic = get_next_dynic(sys.argv) + #nic = get_next_dynic(sys.argv) + nic = get_next_dynic() print nic diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py new file mode 100644 index 00000000000..30537417124 --- /dev/null +++ b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import os + +from quantum.plugins.cisco.common import cisco_configparser as confp + +CONF_FILE = "../conf/ucs.ini" + +cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + + "/" + CONF_FILE) + +section = cp['UCSM'] +UCSM_IP_ADDRESS = section['ip_address'] +DEFAULT_VLAN_NAME = section['default_vlan_name'] +DEFAULT_VLAN_ID = section['default_vlan_id'] +MAX_UCSM_PORT_PROFILES = section['max_ucsm_port_profiles'] +PROFILE_NAME_PREFIX = section['profile_name_prefix'] + +section = cp['DRIVER'] +UCSM_DRIVER = section['name'] + + +def main(): + print MAX_UCSM_PORT_PROFILES + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index 2d994ddec11..acf0df100de 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -27,9 +27,10 @@ import subprocess from xml.etree import ElementTree as et import urllib -from quantum.plugins.cisco.common import cisco_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.ucs import cisco_getvif as gvif + LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) @@ -180,11 +181,7 @@ class CiscoUCSMDriver(): return data def _get_next_dynamic_nic(self): - # TODO (Sumit): following should be a call to a python module - # (which will in turn eliminate the reference to the path and script) - dynamic_nic_id = string.strip(subprocess.Popen( - conf.GET_NEXT_VIF_SCRIPT, - stdout=subprocess.PIPE).communicate()[0]) + dynamic_nic_id = gvif.get_next_dynic() if len(dynamic_nic_id) > 0: return dynamic_nic_id else: @@ -247,12 +244,14 @@ def main(): #client.get_dynamic_nic("dummy") #client.get_dynamic_nic("dummy") #client.release_dynamic_nic("dummy") - #client.get_dynamic_nic("dummy") - #client.change_vlan_in_profile("br100", "default", "test-2", - # "172.20.231.27","admin", - # "c3l12345") + print client.get_dynamic_nic("dummy") + """ + client.change_vlan_in_profile("br100", "default", "test-2", + "172.20.231.27","admin", + "c3l12345") client.change_vlan_in_profile("br100", "test-2", "default", "172.20.231.27", "admin", "c3l12345") + """ if __name__ == '__main__': main() diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py index 264d14f7834..f7969cab135 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py @@ -20,11 +20,11 @@ import logging as LOG from quantum.common import exceptions as exc -from quantum.plugins.cisco.common import cisco_configuration as conf +from quantum.common import utils from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc -from quantum.plugins.cisco.ucs import cisco_ucs_network_driver +from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf from quantum.plugins.cisco.common import cisco_utils as cutil LOG.basicConfig(level=LOG.WARN) @@ -35,7 +35,8 @@ class UCSVICPlugin(object): _networks = {} def __init__(self): - self._client = cisco_ucs_network_driver.CiscoUCSMDriver() + self._client = utils.import_object(conf.UCSM_DRIVER) + LOG.debug("Loaded driver %s\n" % conf.UCSM_DRIVER) self._utils = cutil.DBUtils() # TODO (Sumit) This is for now, when using only one chassis self._ucsm_ip = conf.UCSM_IP_ADDRESS diff --git a/quantum/plugins/cisco/ucs/get-vif.sh b/quantum/plugins/cisco/ucs/get-vif.sh deleted file mode 100755 index d424b5fea28..00000000000 --- a/quantum/plugins/cisco/ucs/get-vif.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -eths=`ifconfig -a | grep eth | cut -f1 -d " "` -for eth in $eths; do - bdf=`ethtool -i $eth | grep bus-info | cut -f2 -d " "` - deviceid=`lspci -n -s $bdf | cut -f4 -d ":" | cut -f1 -d " "` - if [ $deviceid = "0044" ]; then - used=`/sbin/ip link show $eth | grep "UP"` - avail=$? - if [ $avail -eq 1 ]; then - echo $eth - exit - fi - fi -done - From d523ce7c18420c998f5f0e8bf16ffad64e005c42 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 5 Aug 2011 12:05:14 -0700 Subject: [PATCH 48/76] Fixes the broken call to second level of plugins. Renaming will work now. --- quantum/plugins/cisco/l2network_plugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index e34a1c491e0..b9ada4d5fd8 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -115,12 +115,14 @@ class L2Network(object): def get_network_details(self, tenant_id, net_id): """ - Deletes the Virtual Network belonging to a the - spec + Gets the details of a particular network """ LOG.debug("get_network_details() called\n") network = self._get_network(tenant_id, net_id) - return network + ports_on_net = network[const.NET_PORTS].values() + return {const.NET_ID: network[const.NET_ID], + const.NET_NAME: network[const.NET_NAME], + const.NET_PORTS: ports_on_net} def rename_network(self, tenant_id, net_id, new_name): """ @@ -129,7 +131,7 @@ class L2Network(object): """ LOG.debug("rename_network() called\n") for pluginClass in self._plugins.values(): - pluginClas.rename_network(tenant_id, net_id) + pluginClass.rename_network(tenant_id, net_id, new_name) network = self._get_network(tenant_id, net_id) network[const.NET_NAME] = new_name return network From c5bc02a04b99c093fcd7a76bc19dabd19a356ffb Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 5 Aug 2011 12:45:28 -0700 Subject: [PATCH 49/76] Fixed issue with creating new port profiles (one configuration parameter got left out during the migration to the new configuration scheme). Also fixed a bug in the calculation of the profile id. --- .../plugins/cisco/conf/l2network_plugin.ini | 4 +++- quantum/plugins/cisco/l2network_plugin.py | 19 ++++++++++++++++--- .../cisco/l2network_plugin_configuration.py | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/quantum/plugins/cisco/conf/l2network_plugin.ini b/quantum/plugins/cisco/conf/l2network_plugin.ini index f6ac8613bfe..5a0d7d7a932 100644 --- a/quantum/plugins/cisco/conf/l2network_plugin.ini +++ b/quantum/plugins/cisco/conf/l2network_plugin.ini @@ -1,5 +1,4 @@ [VLANS] -#Change the following to reflect the VLAN range in your system vlan_start=100 vlan_end=3000 vlan_name_prefix=q- @@ -7,5 +6,8 @@ vlan_name_prefix=q- [PORTS] max_ports=100 +[PORTPROFILES] +max_port_profiles=65568 + [NETWORKS] max_networks=65568 diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index b9ada4d5fd8..e3642515ce8 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -375,10 +375,23 @@ class L2Network(object): self._portprofile_counter += 1 self._portprofile_counter %= int(conf.MAX_PORT_PROFILES) id = tenant_id[:3] + "-pp-" + \ - ("0" * (6 - len(str(self._net_counter)))) + str(self._net_counter) + ("0" * (6 - len(str(self._net_counter)))) \ + + str(self._portprofile_counter) # TODO (Sumit): Need to check if the ID has already been allocated # ID will be generated by DB return id -# TODO (Sumit): - # (1) Persistent storage + +def main(): + client = L2Network() + client.create_portprofile("12345", "tpp1", "2") + client.create_portprofile("12345", "tpp2", "3") + print ("%s\n") % client.get_all_portprofiles("12345") + +if __name__ == '__main__': + main() + +""" +TODO (Sumit): +(1) Persistent storage +""" diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index e0fe786a515..fe619ddfcd8 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -34,6 +34,9 @@ VLAN_END = section['vlan_end'] section = cp['PORTS'] MAX_PORTS = section['max_ports'] +section = cp['PORTPROFILES'] +MAX_PORT_PROFILES = section['max_port_profiles'] + section = cp['NETWORKS'] MAX_NETWORKS = section['max_networks'] From 9093c6f81c741ac716967665a3ad46b043f92590 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 5 Aug 2011 19:15:27 -0700 Subject: [PATCH 50/76] Edits to reflect conf changes, made it easier to follow. --- quantum/plugins/cisco/README | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index f8711193ab9..74e9e534806 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -2,6 +2,7 @@ ================== *** Current support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh *** +*** Also supports Nexus 7k *** ** Pre-requisities * UCS B200 series blades with M81KR VIC installed. @@ -28,14 +29,16 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK ** Plugin Installation Instructions: -* Make a backup copy of quantum/quantum/plugins.ini, and edit the file to remove all exisiting entries and add the following entry: +* Make a backup copy of quantum/quantum/plugins.ini, and edit the "provider" entry to point to the L2Network-plugin: provider = quantum.plugins.cisco.l2network_plugin.L2Network -* All configuration files are located under quantum/plugins/cisco/conf. Where you find kind of placeholder, please replace it entirely with the relevant values (don't forget to remove the angle brackets as well) +* All configuration files are located under quantum/plugins/cisco/conf. Wherever you find kind of placeholder, please replace it entirely with the relevant values (don't forget to remove the angle brackets as well) * If you are not running Quantum on the same host as the OpenStack Cloud Controller, you will need to change the db_ip_address configuration in nova.ini -* If you want to turn on Nexus support, please uncomment the nexus_plugin property in the nexus.ini file. You will also require a patch to the ncclient in addition to the library mentioned in the prerequisities. +* If you want to turn on Nexus support, please uncomment the nexus_plugin property in the plugins.ini file, and enter the relevant configuration in the nexus.ini file. You will also require a patch to the ncclient in addition to the library mentioned in the prerequisities. If you are not turning the Nexus support on, your Nexus configuration will be ignored. + +* Check again if you have gone through every conf file and made the required changes (check if all IP addresses are correct, and check if you have entered the credentials correponding to each of those IP addresses in the credentials.ini file). * Start the Quantum service From 742e77389f5e1b5b20933203f88a16a27a12c075 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 5 Aug 2011 22:16:25 -0700 Subject: [PATCH 51/76] Nexus plugin classpath was incorrect, fixed it. --- quantum/plugins/cisco/conf/plugins.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quantum/plugins/cisco/conf/plugins.ini b/quantum/plugins/cisco/conf/plugins.ini index 15f6e5412f8..8b4b476a0ad 100644 --- a/quantum/plugins/cisco/conf/plugins.ini +++ b/quantum/plugins/cisco/conf/plugins.ini @@ -1,4 +1,3 @@ [PLUGINS] ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin.UCSVICPlugin -#Uncomment the following line if you want to enable Nexus support -#nexus_plugin=quantum.plugins.cisco.ucs.cisco_nexus_plugin.NexusPlugin +#nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin From d725cb3a8807e4c6df48455c759ace43005d3147 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Fri, 5 Aug 2011 23:01:55 -0700 Subject: [PATCH 52/76] Added info about ssh conf required for nexus switch. --- quantum/plugins/cisco/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 74e9e534806..36ea4ea2748 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -36,7 +36,7 @@ provider = quantum.plugins.cisco.l2network_plugin.L2Network * If you are not running Quantum on the same host as the OpenStack Cloud Controller, you will need to change the db_ip_address configuration in nova.ini -* If you want to turn on Nexus support, please uncomment the nexus_plugin property in the plugins.ini file, and enter the relevant configuration in the nexus.ini file. You will also require a patch to the ncclient in addition to the library mentioned in the prerequisities. If you are not turning the Nexus support on, your Nexus configuration will be ignored. +* If you want to turn on Nexus support, please uncomment the nexus_plugin property in the plugins.ini file, and enter the relevant configuration in the nexus.ini file. In addition, you will require a patch to the ncclient library. Also make sure that the switch's host key is known to the host on which you are running the Quantum service (since the connection to the switch is over ssh). If you are not turning the Nexus support on, your Nexus configuration will be ignored. * Check again if you have gone through every conf file and made the required changes (check if all IP addresses are correct, and check if you have entered the credentials correponding to each of those IP addresses in the credentials.ini file). From 6f5eced38fa6b930b5a75905d2ac8610af509ead Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sat, 6 Aug 2011 18:41:27 -0700 Subject: [PATCH 53/76] Generalized and put placeholders. --- quantum/plugins/cisco/conf/nova.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quantum/plugins/cisco/conf/nova.ini b/quantum/plugins/cisco/conf/nova.ini index 357a5864308..7ef5d439681 100644 --- a/quantum/plugins/cisco/conf/nova.ini +++ b/quantum/plugins/cisco/conf/nova.ini @@ -2,7 +2,7 @@ #Change the following details to reflect your OpenStack Nova configuration. If you are running this service on the same machine as the Nova DB, you do not have to change the IP address. db_server_ip=127.0.0.1 db_name=nova -db_username=root -db_password=nova -nova_host_name=openstack0203 -nova_proj_name=demo +db_username= +db_password= +nova_host_name= +nova_proj_name= From bc522e178853337da10939444605e248f5462ecd Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sat, 6 Aug 2011 20:43:08 -0700 Subject: [PATCH 54/76] Added QuantunPluginBase as the base class for the l2network_plugin. --- quantum/plugins/cisco/l2network_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index e3642515ce8..7149bc5f104 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -21,6 +21,7 @@ import logging as LOG from quantum.common import exceptions as exc from quantum.common import utils +from quantum.quantum_plugin_base import QuantumPluginBase from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred @@ -31,7 +32,7 @@ LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) -class L2Network(object): +class L2Network(QuantumPluginBase): _networks = {} _tenants = {} _portprofiles = {} From e3b8bafbb9f3b70e7086ef69adf4f78dc2faa4d3 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sun, 7 Aug 2011 04:58:50 -0700 Subject: [PATCH 55/76] Changes to enhance L2 network plugin framework. --- quantum/plugins/cisco/README | 3 + .../plugins/cisco/common/cisco_constants.py | 5 + .../plugins/cisco/conf/l2network_plugin.ini | 7 +- quantum/plugins/cisco/l2device_plugin_base.py | 152 ++++++++++++++++++ quantum/plugins/cisco/l2network_model.py | 105 ++++++++++++ quantum/plugins/cisco/l2network_model_base.py | 150 +++++++++++++++++ quantum/plugins/cisco/l2network_plugin.py | 67 ++++---- .../cisco/l2network_plugin_configuration.py | 3 + .../plugins/cisco/nexus/cisco_nexus_plugin.py | 27 ++-- quantum/plugins/cisco/ucs/cisco_ucs_plugin.py | 31 ++-- 10 files changed, 493 insertions(+), 57 deletions(-) create mode 100644 quantum/plugins/cisco/l2device_plugin_base.py create mode 100644 quantum/plugins/cisco/l2network_model.py create mode 100644 quantum/plugins/cisco/l2network_model_base.py diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 36ea4ea2748..ee03219593d 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -1,6 +1,8 @@ L2 Network Plugin ================== +*** Reference implementation of plugin framework for L2 network *** +*** Multi-switch (devices and types) capability *** *** Current support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh *** *** Also supports Nexus 7k *** @@ -10,6 +12,7 @@ * RHEL 6.1 * UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at: http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM +* Package python-configobj-4.6.0-3.el6.noarch * If you have a Nexus switch in your topology and decide to turn on Nexus support, you will need: - ncclcient v0.3.1 - Python library for NETCONF clients (http://schmizz.net/ncclient/). diff --git a/quantum/plugins/cisco/common/cisco_constants.py b/quantum/plugins/cisco/common/cisco_constants.py index b14ce093471..71ae304d180 100644 --- a/quantum/plugins/cisco/common/cisco_constants.py +++ b/quantum/plugins/cisco/common/cisco_constants.py @@ -96,3 +96,8 @@ RHEL_DEVICE_NAME_REPFIX = "eth" UCS_PLUGIN = 'ucs_plugin' NEXUS_PLUGIN = 'nexus_plugin' + +PLUGIN_OBJ_REF = 'plugin-obj-ref' +PARAM_LIST = 'param-list' + +DEVICE_IP = 'device-ip' diff --git a/quantum/plugins/cisco/conf/l2network_plugin.ini b/quantum/plugins/cisco/conf/l2network_plugin.ini index 5a0d7d7a932..3a740a9713c 100644 --- a/quantum/plugins/cisco/conf/l2network_plugin.ini +++ b/quantum/plugins/cisco/conf/l2network_plugin.ini @@ -1,6 +1,6 @@ [VLANS] -vlan_start=100 -vlan_end=3000 +vlan_start= +vlan_end= vlan_name_prefix=q- [PORTS] @@ -11,3 +11,6 @@ max_port_profiles=65568 [NETWORKS] max_networks=65568 + +[MODEL] +model_class=quantum.plugins.cisco.l2network_model.L2NetworkModel diff --git a/quantum/plugins/cisco/l2device_plugin_base.py b/quantum/plugins/cisco/l2device_plugin_base.py new file mode 100644 index 00000000000..b108d024230 --- /dev/null +++ b/quantum/plugins/cisco/l2device_plugin_base.py @@ -0,0 +1,152 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import inspect +from abc import ABCMeta, abstractmethod + + +class L2DevicePluginBase(object): + + __metaclass__ = ABCMeta + + @abstractmethod + def get_all_networks(self, tenant_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, + **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def delete_network(self, tenant_id, net_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def get_network_details(self, tenant_id, net_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def rename_network(self, tenant_id, net_id, new_name, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def get_all_ports(self, tenant_id, net_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def delete_port(self, tenant_id, net_id, port_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def get_port_details(self, tenant_id, net_id, port_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id, + **kwargs): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def unplug_interface(self, tenant_id, net_id, port_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + @classmethod + def __subclasshook__(cls, klass): + """ + The __subclasshook__ method is a class method + that will be called everytime a class is tested + using issubclass(klass, Plugin). + In that case, it will check that every method + marked with the abstractmethod decorator is + provided by the plugin class. + """ + if cls is QuantumPluginBase: + for method in cls.__abstractmethods__: + 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 + return NotImplemented diff --git a/quantum/plugins/cisco/l2network_model.py b/quantum/plugins/cisco/l2network_model.py new file mode 100644 index 00000000000..3fa986b2a38 --- /dev/null +++ b/quantum/plugins/cisco/l2network_model.py @@ -0,0 +1,105 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import inspect +import logging as LOG + +from quantum.common import utils +from quantum.plugins.cisco.l2network_model_base import L2NetworkModelBase +from quantum.plugins.cisco import l2network_plugin_configuration as conf +from quantum.plugins.cisco.common import cisco_constants as const + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + + +class L2NetworkModel(L2NetworkModelBase): + _plugins = {} + + def __init__(self): + for key in conf.plugins[const.PLUGINS].keys(): + self._plugins[key] = utils.import_object( + conf.plugins[const.PLUGINS][key]) + LOG.debug("Loaded device plugin %s\n" % \ + conf.plugins[const.PLUGINS][key]) + + def _funcName(self, offset=0): + return inspect.stack()[1 + offset][3] + + def _invokeAllDevicePlugins(self, function_name, args, kwargs): + for pluginObjRef in self._plugins.values(): + getattr(pluginObjRef, function_name)(*args, **kwargs) + + def _invokeUCSPlugin(self, function_name, args, kwargs): + getattr(self._plugins[const.UCS_PLUGIN], + function_name)(*args, **kwargs) + + def _invokeNexusPlugin(self, function_name, args, kwargs): + getattr(self._plugins[const.NEXUS_PLUGIN], + function_name)(*args, **kwargs) + + def get_all_networks(self, args): + pass + + def create_network(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeAllDevicePlugins(self._funcName(), args, deviceParams) + + def delete_network(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeAllDevicePlugins(self._funcName(), args, deviceParams) + + def get_network_details(self, args): + pass + + def rename_network(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeAllDevicePlugins(self._funcName(), args, deviceParams) + + def get_all_ports(self, args): + pass + + def create_port(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeUCSPlugin(self._funcName(), args, deviceParams) + + def delete_port(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeUCSPlugin(self._funcName(), args, deviceParams) + + def update_port(self, args): + pass + + def get_port_details(self, args): + pass + + def plug_interface(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeUCSPlugin(self._funcName(), args, deviceParams) + + def unplug_interface(self, args): + deviceParams = {const.DEVICE_IP: ""} + self._invokeUCSPlugin(self._funcName(), args, deviceParams) + + +def main(): + client = L2NetworkModel() + +if __name__ == '__main__': + main() diff --git a/quantum/plugins/cisco/l2network_model_base.py b/quantum/plugins/cisco/l2network_model_base.py new file mode 100644 index 00000000000..f23fce4cfcd --- /dev/null +++ b/quantum/plugins/cisco/l2network_model_base.py @@ -0,0 +1,150 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Sumit Naiksatam, Cisco Systems, Inc. +# + +import inspect +from abc import ABCMeta, abstractmethod + + +class L2NetworkModelBase(object): + + __metaclass__ = ABCMeta + + @abstractmethod + def get_all_networks(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def create_network(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def delete_network(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def get_network_details(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def rename_network(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def get_all_ports(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def create_port(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def delete_port(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def update_port(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def get_port_details(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def plug_interface(self, args): + """ + :returns: + :raises: + """ + pass + + @abstractmethod + def unplug_interface(self, args): + """ + :returns: + :raises: + """ + pass + + @classmethod + def __subclasshook__(cls, klass): + """ + The __subclasshook__ method is a class method + that will be called everytime a class is tested + using issubclass(klass, Plugin). + In that case, it will check that every method + marked with the abstractmethod decorator is + provided by the plugin class. + """ + if cls is QuantumPluginBase: + for method in cls.__abstractmethods__: + 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 + return NotImplemented diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index 7149bc5f104..f627de25414 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -17,6 +17,7 @@ # @author: Sumit Naiksatam, Cisco Systems, Inc. # +import inspect import logging as LOG from quantum.common import exceptions as exc @@ -24,9 +25,7 @@ from quantum.common import utils from quantum.quantum_plugin_base import QuantumPluginBase from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.cisco.common import cisco_constants as const -from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc -from quantum.plugins.cisco.common import cisco_utils as cutil LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) @@ -36,18 +35,13 @@ class L2Network(QuantumPluginBase): _networks = {} _tenants = {} _portprofiles = {} - _plugins = {} def __init__(self): self._net_counter = 0 self._portprofile_counter = 0 self._port_counter = 0 self._vlan_counter = int(conf.VLAN_START) - 1 - for key in conf.plugins[const.PLUGINS].keys(): - self._plugins[key] = utils.import_object( - conf.plugins[const.PLUGINS][key]) - LOG.debug("Loaded device plugin %s\n" % \ - conf.plugins[const.PLUGINS][key]) + self._model = utils.import_object(conf.MODEL_CLASS) """ Core API implementation @@ -59,6 +53,7 @@ class L2Network(QuantumPluginBase): the specified tenant. """ LOG.debug("get_all_networks() called\n") + self._invokeDevicePlugins(self._funcName(), [tenant_id]) return self._networks.values() def create_network(self, tenant_id, net_name): @@ -70,9 +65,9 @@ class L2Network(QuantumPluginBase): new_net_id = self._get_unique_net_id(tenant_id) vlan_id = self._get_vlan_for_tenant(tenant_id, net_name) vlan_name = self._get_vlan_name(new_net_id, str(vlan_id)) - for pluginClass in self._plugins.values(): - pluginClass.create_network(tenant_id, net_name, - new_net_id, vlan_name, vlan_id) + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_name, + new_net_id, vlan_name, + vlan_id]) new_net_dict = {const.NET_ID: new_net_id, const.NET_NAME: net_name, const.NET_PORTS: {}, @@ -92,8 +87,6 @@ class L2Network(QuantumPluginBase): """ LOG.debug("delete_network() called\n") net = self._networks.get(net_id) - # TODO (Sumit) : Verify that no attachments are plugged into the - # network if net: if len(net[const.NET_PORTS].values()) > 0: ports_on_net = net[const.NET_PORTS].values() @@ -102,10 +95,8 @@ class L2Network(QuantumPluginBase): raise exc.NetworkInUse(net_id=net_id) for port in ports_on_net: self.delete_port(tenant_id, net_id, port[const.PORT_ID]) - # TODO (Sumit) : Before deleting the network, make sure all the - # ports associated with this network are also deleted - for pluginClass in self._plugins.values(): - pluginClass.delete_network(tenant_id, net_id) + + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id]) self._networks.pop(net_id) tenant = self._get_tenant(tenant_id) tenant_networks = tenant[const.TENANT_NETWORKS] @@ -119,6 +110,7 @@ class L2Network(QuantumPluginBase): Gets the details of a particular network """ LOG.debug("get_network_details() called\n") + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id]) network = self._get_network(tenant_id, net_id) ports_on_net = network[const.NET_PORTS].values() return {const.NET_ID: network[const.NET_ID], @@ -131,8 +123,8 @@ class L2Network(QuantumPluginBase): Virtual Network. """ LOG.debug("rename_network() called\n") - for pluginClass in self._plugins.values(): - pluginClass.rename_network(tenant_id, net_id, new_name) + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + new_name]) network = self._get_network(tenant_id, net_id) network[const.NET_NAME] = new_name return network @@ -143,6 +135,7 @@ class L2Network(QuantumPluginBase): specified Virtual Network. """ LOG.debug("get_all_ports() called\n") + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id]) network = self._get_network(tenant_id, net_id) ports_on_net = network[const.NET_PORTS].values() return ports_on_net @@ -155,9 +148,9 @@ class L2Network(QuantumPluginBase): net = self._get_network(tenant_id, net_id) ports = net[const.NET_PORTS] unique_port_id_string = self._get_unique_port_id(tenant_id, net_id) - self._plugins[const.UCS_PLUGIN].create_port(tenant_id, net_id, - port_state, - unique_port_id_string) + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + port_state, + unique_port_id_string]) new_port_dict = {const.PORT_ID: unique_port_id_string, const.PORT_STATE: const.PORT_UP, const.ATTACHMENT: None} @@ -179,8 +172,8 @@ class L2Network(QuantumPluginBase): try: #TODO (Sumit): Before deleting port profile make sure that there # is no VM using this port profile - self._plugins[const.UCS_PLUGIN].delete_port(tenant_id, net_id, - port_id) + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + port_id]) net = self._get_network(tenant_id, net_id) net[const.NET_PORTS].pop(port_id) except KeyError: @@ -191,6 +184,8 @@ class L2Network(QuantumPluginBase): Updates the state of a port on the specified Virtual Network. """ LOG.debug("update_port() called\n") + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + port_id, port_state]) port = self._get_port(tenant_id, net_id, port_id) self._validate_port_state(port_state) port[const.PORT_STATE] = port_state @@ -202,6 +197,8 @@ class L2Network(QuantumPluginBase): that is attached to this particular port. """ LOG.debug("get_port_details() called\n") + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + port_id]) return self._get_port(tenant_id, net_id, port_id) def plug_interface(self, tenant_id, net_id, port_id, @@ -217,9 +214,9 @@ class L2Network(QuantumPluginBase): if port[const.ATTACHMENT]: raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port[const.ATTACHMENT]) - self._plugins[const.UCS_PLUGIN].plug_interface(tenant_id, - net_id, port_id, - remote_interface_id) + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + port_id, + remote_interface_id]) port[const.ATTACHMENT] = remote_interface_id def unplug_interface(self, tenant_id, net_id, port_id): @@ -229,8 +226,8 @@ class L2Network(QuantumPluginBase): """ LOG.debug("unplug_interface() called\n") port = self._get_port(tenant_id, net_id, port_id) - self._plugins[const.UCS_PLUGIN].unplug_interface(tenant_id, net_id, - port_id) + self._invokeDevicePlugins(self._funcName(), [tenant_id, net_id, + port_id]) port[const.ATTACHMENT] = None """ @@ -290,6 +287,12 @@ class L2Network(QuantumPluginBase): """ Private functions """ + def _invokeDevicePlugins(self, function_name, args): + """ + All device-specific calls are delegate to the model + """ + getattr(self._model, function_name)(args) + def _get_vlan_for_tenant(self, tenant_id, net_name): # TODO (Sumit): # The VLAN ID for a tenant might need to be obtained from @@ -382,12 +385,18 @@ class L2Network(QuantumPluginBase): # ID will be generated by DB return id + def _funcName(self, offset=0): + return inspect.stack()[1 + offset][3] + def main(): client = L2Network() + """ client.create_portprofile("12345", "tpp1", "2") client.create_portprofile("12345", "tpp2", "3") print ("%s\n") % client.get_all_portprofiles("12345") + """ + if __name__ == '__main__': main() diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index fe619ddfcd8..81f221f0393 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -40,6 +40,9 @@ MAX_PORT_PROFILES = section['max_port_profiles'] section = cp['NETWORKS'] MAX_NETWORKS = section['max_networks'] +section = cp['MODEL'] +MODEL_CLASS = section['model_class'] + CONF_FILE = "conf/plugins.ini" cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py index 8fa6424f930..fdb4ef44660 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin.py @@ -25,13 +25,14 @@ from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc from quantum.plugins.cisco.common import cisco_utils as cutil +from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase from quantum.plugins.cisco.nexus import cisco_nexus_configuration as conf LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) -class NexusPlugin(object): +class NexusPlugin(L2DevicePluginBase): _networks = {} def __init__(self): @@ -52,7 +53,8 @@ class NexusPlugin(object): LOG.debug("NexusPlugin:get_all_networks() called\n") return self._networks.values() - def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id): + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, + **kwargs): """ Create a VLAN in the switch, and configure the appropriate interfaces for this VLAN @@ -69,7 +71,7 @@ class NexusPlugin(object): self._networks[net_id] = new_net_dict return new_net_dict - def delete_network(self, tenant_id, net_id): + def delete_network(self, tenant_id, net_id, **kwargs): """ Deletes a VLAN in the switch, and removes the VLAN configuration from the relevant interfaces @@ -85,7 +87,7 @@ class NexusPlugin(object): # Network not found raise exc.NetworkNotFound(net_id=net_id) - def get_network_details(self, tenant_id, net_id): + def get_network_details(self, tenant_id, net_id, **kwargs): """ Returns the details of a particular network """ @@ -93,7 +95,7 @@ class NexusPlugin(object): network = self._get_network(tenant_id, net_id) return network - def rename_network(self, tenant_id, net_id, new_name): + def rename_network(self, tenant_id, net_id, new_name, **kwargs): """ Updates the symbolic name belonging to a particular Virtual Network. @@ -104,49 +106,50 @@ class NexusPlugin(object): network[const.NET_NAME] = new_name return network - def get_all_ports(self, tenant_id, net_id): + def get_all_ports(self, tenant_id, net_id, **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. """ LOG.debug("NexusPlugin:get_all_ports() called\n") - def create_port(self, tenant_id, net_id, port_state, port_id): + def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. """ LOG.debug("NexusPlugin:create_port() called\n") - def delete_port(self, tenant_id, net_id, port_id): + def delete_port(self, tenant_id, net_id, port_id, **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. """ LOG.debug("NexusPlugin:delete_port() called\n") - def update_port(self, tenant_id, net_id, port_id, port_state): + def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. """ LOG.debug("NexusPlugin:update_port() called\n") - def get_port_details(self, tenant_id, net_id, port_id): + def get_port_details(self, tenant_id, net_id, port_id, **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. """ LOG.debug("NexusPlugin:get_port_details() called\n") - def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id, + **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. """ LOG.debug("NexusPlugin:plug_interface() called\n") - def unplug_interface(self, tenant_id, net_id, port_id): + def unplug_interface(self, tenant_id, net_id, port_id, **kwargs): """ This is probably not applicable to the Nexus plugin. Delete if not required. diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py index f7969cab135..ad70c74b579 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_plugin.py @@ -24,14 +24,15 @@ from quantum.common import utils from quantum.plugins.cisco.common import cisco_constants as const from quantum.plugins.cisco.common import cisco_credentials as cred from quantum.plugins.cisco.common import cisco_exceptions as cexc -from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf from quantum.plugins.cisco.common import cisco_utils as cutil +from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase +from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf LOG.basicConfig(level=LOG.WARN) LOG.getLogger(const.LOGGER_COMPONENT_NAME) -class UCSVICPlugin(object): +class UCSVICPlugin(L2DevicePluginBase): _networks = {} def __init__(self): @@ -45,7 +46,7 @@ class UCSVICPlugin(object): # TODO (Sumit) Make the counter per UCSM self._port_profile_counter = 0 - def get_all_networks(self, tenant_id): + def get_all_networks(self, tenant_id, **kwargs): """ Returns a dictionary containing all for @@ -54,7 +55,8 @@ class UCSVICPlugin(object): LOG.debug("UCSVICPlugin:get_all_networks() called\n") return self._networks.values() - def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id): + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, + **kwargs): """ Creates a new Virtual Network, and assigns it a symbolic name. @@ -70,7 +72,7 @@ class UCSVICPlugin(object): self._networks[net_id] = new_net_dict return new_net_dict - def delete_network(self, tenant_id, net_id): + def delete_network(self, tenant_id, net_id, **kwargs): """ Deletes the network with the specified network identifier belonging to the specified tenant. @@ -88,7 +90,7 @@ class UCSVICPlugin(object): return net raise exc.NetworkNotFound(net_id=net_id) - def get_network_details(self, tenant_id, net_id): + def get_network_details(self, tenant_id, net_id, **kwargs): """ Deletes the Virtual Network belonging to a the spec @@ -97,7 +99,7 @@ class UCSVICPlugin(object): network = self._get_network(tenant_id, net_id) return network - def rename_network(self, tenant_id, net_id, new_name): + def rename_network(self, tenant_id, net_id, new_name, **kwargs): """ Updates the symbolic name belonging to a particular Virtual Network. @@ -107,7 +109,7 @@ class UCSVICPlugin(object): network[const.NET_NAME] = new_name return network - def get_all_ports(self, tenant_id, net_id): + def get_all_ports(self, tenant_id, net_id, **kwargs): """ Retrieves all port identifiers belonging to the specified Virtual Network. @@ -117,7 +119,7 @@ class UCSVICPlugin(object): ports_on_net = network[const.NET_PORTS].values() return ports_on_net - def create_port(self, tenant_id, net_id, port_state, port_id): + def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs): """ Creates a port on the specified Virtual Network. """ @@ -145,7 +147,7 @@ class UCSVICPlugin(object): ports[port_id] = new_port_dict return new_port_dict - def delete_port(self, tenant_id, net_id, port_id): + def delete_port(self, tenant_id, net_id, port_id, **kwargs): """ Deletes a port on a specified Virtual Network, if the port contains a remote interface attachment, @@ -172,7 +174,7 @@ class UCSVICPlugin(object): except KeyError: raise exc.PortNotFound(net_id=net_id, port_id=port_id) - def update_port(self, tenant_id, net_id, port_id, port_state): + def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs): """ Updates the state of a port on the specified Virtual Network. """ @@ -182,7 +184,7 @@ class UCSVICPlugin(object): port[const.PORT_STATE] = port_state return port - def get_port_details(self, tenant_id, net_id, port_id): + def get_port_details(self, tenant_id, net_id, port_id, **kwargs): """ This method allows the user to retrieve a remote interface that is attached to this particular port. @@ -190,7 +192,8 @@ class UCSVICPlugin(object): LOG.debug("UCSVICPlugin:get_port_details() called\n") return self._get_port(tenant_id, net_id, port_id) - def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id, + **kwargs): """ Attaches a remote interface to the specified port on the specified Virtual Network. @@ -215,7 +218,7 @@ class UCSVICPlugin(object): port_profile[const.PROFILE_VLAN_NAME] = new_vlan_name port_profile[const.PROFILE_VLAN_ID] = new_vlan_id - def unplug_interface(self, tenant_id, net_id, port_id): + def unplug_interface(self, tenant_id, net_id, port_id, **kwargs): """ Detaches a remote interface from the specified port on the specified Virtual Network. From 7b39a29dedc7c201906f31cb3de5eec8ec887b15 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sun, 7 Aug 2011 17:13:33 -0700 Subject: [PATCH 56/76] RHEL limitation updated. --- quantum/plugins/cisco/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index ee03219593d..3f02dc5a1b8 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -9,7 +9,7 @@ ** Pre-requisities * UCS B200 series blades with M81KR VIC installed. * UCSM 2.0 (Capitola) Build 230 -* RHEL 6.1 +* RHEL 6.1 (Currently UCS support is only on RHEL, Ubuntu will be supporting in upcoming releases) * UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at: http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM * Package python-configobj-4.6.0-3.el6.noarch From ec9a458ed1237b7b6e2dcd6622729319da748a47 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sun, 7 Aug 2011 21:03:24 -0700 Subject: [PATCH 57/76] minor enhancements to quantum client-lib --- quantum/client.py | 70 ++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/quantum/client.py b/quantum/client.py index ca05236faae..7069786162e 100644 --- a/quantum/client.py +++ b/quantum/client.py @@ -57,7 +57,8 @@ class Client(object): attachment_path = "/networks/%s/ports/%s/attachment" def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, - format="xml", testingStub=None, key_file=None, cert_file=None): + format="xml", testingStub=None, key_file=None, cert_file=None, + logger=None): """ Creates a new client to some service. @@ -79,6 +80,7 @@ class Client(object): self.testingStub = testingStub self.key_file = key_file self.cert_file = cert_file + self.logger = logger def get_connection_type(self): """ @@ -132,14 +134,26 @@ class Client(object): else: c = connection_type(self.host, self.port) + if self.logger: + self.logger.debug("Quantum Client Request:\n" \ + + method + " " + action + "\n") + if body: + self.logger.debug(body) + c.request(method, action, body, headers) res = c.getresponse() status_code = self.get_status_code(res) + data = res.read() + + if self.logger: + self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ + % (str(status_code), data)) + if status_code in (httplib.OK, httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - return self.deserialize(res) + return self.deserialize(data, status_code) else: raise Exception("Server returned error: %s" % res.read()) @@ -158,13 +172,18 @@ class Client(object): return response.status def serialize(self, data): - if type(data) is dict: + if data is None: + return None + elif type(data) is dict: return Serializer().serialize(data, self.content_type()) + else: + raise Exception("unable to deserialize object of type = '%s'" \ + % type(data)) - def deserialize(self, data): - if self.get_status_code(data) == 202: - return data.read() - return Serializer().deserialize(data.read(), self.content_type()) + def deserialize(self, data, status_code): + if status_code == 202: + return data + return Serializer().deserialize(data, self.content_type()) def content_type(self, format=None): if not format: @@ -174,21 +193,21 @@ class Client(object): @api_call def list_networks(self): """ - Queries the server for a list of networks + Fetches a list of all networks for a tenant """ return self.do_request("GET", self.networks_path) @api_call - def list_network_details(self, network): + def show_network_details(self, network): """ - Queries the server for the details of a certain network + Fetches the details of a certain network """ return self.do_request("GET", self.network_path % (network)) @api_call def create_network(self, body=None): """ - Creates a new network on the server + Creates a new network """ body = self.serialize(body) return self.do_request("POST", self.networks_path, body=body) @@ -196,7 +215,7 @@ class Client(object): @api_call def update_network(self, network, body=None): """ - Updates a network on the server + Updates a network """ body = self.serialize(body) return self.do_request("PUT", self.network_path % (network), body=body) @@ -204,58 +223,59 @@ class Client(object): @api_call def delete_network(self, network): """ - Deletes a network on the server + Deletes the specified network """ return self.do_request("DELETE", self.network_path % (network)) @api_call def list_ports(self, network): """ - Queries the server for a list of ports on a given network + Fetches a list of ports on a given network """ return self.do_request("GET", self.ports_path % (network)) @api_call - def list_port_details(self, network, port): + def show_port_details(self, network, port): """ - Queries the server for a list of ports on a given network + Fetches the details of a certain port """ return self.do_request("GET", self.port_path % (network, port)) @api_call - def create_port(self, network): + def create_port(self, network, body=None): """ - Creates a new port on a network on the server + Creates a new port on a given network """ - return self.do_request("POST", self.ports_path % (network)) + body = self.serialize(body) + return self.do_request("POST", self.ports_path % (network), body=body) @api_call def delete_port(self, network, port): """ - Deletes a port from a network on the server + Deletes the specified port from a network """ return self.do_request("DELETE", self.port_path % (network, port)) @api_call def set_port_state(self, network, port, body=None): """ - Sets the state of a port on the server + Sets the state of the specified port """ body = self.serialize(body) return self.do_request("PUT", self.port_path % (network, port), body=body) @api_call - def list_port_attachments(self, network, port): + def show_port_attachment(self, network, port): """ - Deletes a port from a network on the server + Fetches the attachment-id associated with the specified port """ return self.do_request("GET", self.attachment_path % (network, port)) @api_call def attach_resource(self, network, port, body=None): """ - Deletes a port from a network on the server + Sets the attachment-id of the specified port """ body = self.serialize(body) return self.do_request("PUT", @@ -264,7 +284,7 @@ class Client(object): @api_call def detach_resource(self, network, port): """ - Deletes a port from a network on the server + Removes the attachment-id of the specified port """ return self.do_request("DELETE", self.attachment_path % (network, port)) From fb9e81b380b1be0e59e324b43a2fd594683b3b0a Mon Sep 17 00:00:00 2001 From: Shweta P Date: Mon, 8 Aug 2011 00:01:03 -0700 Subject: [PATCH 58/76] Adding Cisco Unit Tests --- quantum/plugins/cisco/README | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index b77ce22ca89..f3152e75eb6 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -106,3 +106,13 @@ mysql -uroot -p nova -e 'create table ports (port_id VARCHA - mysql> create database quantum_l2network; - mysql> use quantum_l2network; + The database and login details must be specified in quantum/plugins/cisco/db/db_conn.ini. + +** Execute the Test cases +* The unit tests are located at quantum/plugins/cisco/tests/unit. They are executed from quantum/plugins/cisco/ using the runtests.py script. + +* Execution of the runtests.py script. + All unit tests + python runtests.py unit + Specific Plugin unit test + python runtests.py unit. + From cc660e0cff0fdaa72f90a8e829dd18cc2643603b Mon Sep 17 00:00:00 2001 From: Shweta P Date: Mon, 8 Aug 2011 00:23:44 -0700 Subject: [PATCH 59/76] Adding Unit Test Cases Now --- quantum/plugins/cisco/run_tests.py | 301 ++++++ quantum/plugins/cisco/tests/unit/__init__.py | 32 + .../cisco/tests/unit/test_l2networkApi.py | 892 ++++++++++++++++++ .../cisco/tests/unit/test_nexus_plugin.py | 282 ++++++ .../cisco/tests/unit/test_ucs_driver.py | 165 ++++ .../cisco/tests/unit/test_ucs_plugin.py | 480 ++++++++++ 6 files changed, 2152 insertions(+) create mode 100644 quantum/plugins/cisco/run_tests.py create mode 100644 quantum/plugins/cisco/tests/unit/__init__.py create mode 100644 quantum/plugins/cisco/tests/unit/test_l2networkApi.py create mode 100644 quantum/plugins/cisco/tests/unit/test_nexus_plugin.py create mode 100644 quantum/plugins/cisco/tests/unit/test_ucs_driver.py create mode 100644 quantum/plugins/cisco/tests/unit/test_ucs_plugin.py diff --git a/quantum/plugins/cisco/run_tests.py b/quantum/plugins/cisco/run_tests.py new file mode 100644 index 00000000000..d63cc34a425 --- /dev/null +++ b/quantum/plugins/cisco/run_tests.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack, LLC +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Colorizer Code is borrowed from Twisted: +# Copyright (c) 2001-2010 Twisted Matrix Laboratories. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Unittest runner for quantum + +To run all test:: + python run_tests.py + +To run all unit tests:: + python run_tests.py unit + +To run all functional tests:: + python run_tests.py functional + +To run a single unit test:: + python run_tests.py unit.test_stores:TestSwiftBackend.test_get + +To run a single functional test:: + python run_tests.py functional.test_service:TestController.test_create + +To run a single unit test module:: + python run_tests.py unit.test_stores + +To run a single functional test module:: + python run_tests.py functional.test_stores +""" + +import gettext +import logging +import os +import unittest +import sys + +from nose import config +from nose import result +from nose import core + + +class _AnsiColorizer(object): + """ + A colorizer is an object that loosely wraps around a stream, allowing + callers to write text to the stream in a particular color. + + Colorizer classes must implement C{supported()} and C{write(text, color)}. + """ + _colors = dict(black=30, red=31, green=32, yellow=33, + blue=34, magenta=35, cyan=36, white=37) + + def __init__(self, stream): + self.stream = stream + + def supported(cls, stream=sys.stdout): + """ + A class method that returns True if the current platform supports + coloring terminal output using this method. Returns False otherwise. + """ + if not stream.isatty(): + return False # auto color only on TTYs + try: + import curses + except ImportError: + return False + else: + try: + try: + return curses.tigetnum("colors") > 2 + except curses.error: + curses.setupterm() + return curses.tigetnum("colors") > 2 + except: + raise + # guess false in case of error + return False + supported = classmethod(supported) + + def write(self, text, color): + """ + Write the given text to the stream in the given color. + + @param text: Text to be written to the stream. + + @param color: A string label for a color. e.g. 'red', 'white'. + """ + color = self._colors[color] + self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) + + +class _Win32Colorizer(object): + """ + See _AnsiColorizer docstring. + """ + def __init__(self, stream): + from win32console import GetStdHandle, STD_OUT_HANDLE, \ + FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \ + FOREGROUND_INTENSITY + red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN, + FOREGROUND_BLUE, FOREGROUND_INTENSITY) + self.stream = stream + self.screenBuffer = GetStdHandle(STD_OUT_HANDLE) + self._colors = { + 'normal': red | green | blue, + 'red': red | bold, + 'green': green | bold, + 'blue': blue | bold, + 'yellow': red | green | bold, + 'magenta': red | blue | bold, + 'cyan': green | blue | bold, + 'white': red | green | blue | bold} + + def supported(cls, stream=sys.stdout): + try: + import win32console + screenBuffer = win32console.GetStdHandle( + win32console.STD_OUT_HANDLE) + except ImportError: + return False + import pywintypes + try: + screenBuffer.SetConsoleTextAttribute( + win32console.FOREGROUND_RED | + win32console.FOREGROUND_GREEN | + win32console.FOREGROUND_BLUE) + except pywintypes.error: + return False + else: + return True + supported = classmethod(supported) + + def write(self, text, color): + color = self._colors[color] + self.screenBuffer.SetConsoleTextAttribute(color) + self.stream.write(text) + self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) + + +class _NullColorizer(object): + """ + See _AnsiColorizer docstring. + """ + def __init__(self, stream): + self.stream = stream + + def supported(cls, stream=sys.stdout): + return True + supported = classmethod(supported) + + def write(self, text, color): + self.stream.write(text) + + +class QuantumTestResult(result.TextTestResult): + def __init__(self, *args, **kw): + result.TextTestResult.__init__(self, *args, **kw) + self._last_case = None + self.colorizer = None + # NOTE(vish, tfukushima): reset stdout for the terminal check + stdout = sys.__stdout__ + for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: + if colorizer.supported(): + self.colorizer = colorizer(self.stream) + break + sys.stdout = stdout + + def getDescription(self, test): + return str(test) + + # NOTE(vish, tfukushima): copied from unittest with edit to add color + def addSuccess(self, test): + unittest.TestResult.addSuccess(self, test) + if self.showAll: + self.colorizer.write("OK", 'green') + self.stream.writeln() + elif self.dots: + self.stream.write('.') + self.stream.flush() + + # NOTE(vish, tfukushima): copied from unittest with edit to add color + def addFailure(self, test, err): + unittest.TestResult.addFailure(self, test, err) + if self.showAll: + self.colorizer.write("FAIL", 'red') + self.stream.writeln() + elif self.dots: + self.stream.write('F') + self.stream.flush() + + # NOTE(vish, tfukushima): copied from unittest with edit to add color + def addError(self, test, err): + """Overrides normal addError to add support for errorClasses. + If the exception is a registered class, the error will be added + to the list for that class, not errors. + """ + stream = getattr(self, 'stream', None) + ec, ev, tb = err + try: + exc_info = self._exc_info_to_string(err, test) + except TypeError: + # This is for compatibility with Python 2.3. + exc_info = self._exc_info_to_string(err) + for cls, (storage, label, isfail) in self.errorClasses.items(): + if result.isclass(ec) and issubclass(ec, cls): + if isfail: + test.passwd = False + storage.append((test, exc_info)) + # Might get patched into a streamless result + if stream is not None: + if self.showAll: + message = [label] + detail = result._exception_details(err[1]) + if detail: + message.append(detail) + stream.writeln(": ".join(message)) + elif self.dots: + stream.write(label[:1]) + return + self.errors.append((test, exc_info)) + test.passed = False + if stream is not None: + if self.showAll: + self.colorizer.write("ERROR", 'red') + self.stream.writeln() + elif self.dots: + stream.write('E') + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + current_case = test.test.__class__.__name__ + + if self.showAll: + if current_case != self._last_case: + self.stream.writeln(current_case) + self._last_case = current_case + + self.stream.write( + ' %s' % str(test.test._testMethodName).ljust(60)) + self.stream.flush() + + +class QuantumTestRunner(core.TextTestRunner): + def _makeResult(self): + return QuantumTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + +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) + sys.exit(not core.run(config=c, testRunner=runner)) diff --git a/quantum/plugins/cisco/tests/unit/__init__.py b/quantum/plugins/cisco/tests/unit/__init__.py new file mode 100644 index 00000000000..5910e354941 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/__init__.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# See http://code.google.com/p/python-nose/issues/detail?id=373 +# The code below enables nosetests to work with i18n _() blocks +import __builtin__ +import unittest +setattr(__builtin__, '_', lambda x: x) + + +class BaseTest(unittest.TestCase): + + def setUp(self): + pass + + +def setUp(): + pass diff --git a/quantum/plugins/cisco/tests/unit/test_l2networkApi.py b/quantum/plugins/cisco/tests/unit/test_l2networkApi.py new file mode 100644 index 00000000000..9cd619ef64a --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/test_l2networkApi.py @@ -0,0 +1,892 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Shweta Padubidri, Cisco Systems, Inc. +# + +import logging +import unittest +from quantum.common import exceptions as exc +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco import l2network_plugin +from quantum.plugins.cisco import l2network_plugin_configuration as conf + +LOG = logging.getLogger('quantum.tests.test_core_api_func') + + +class CoreAPITestFunc(unittest.TestCase): + + def test_create_network(self, net_tenant_id=None, net_name=None): + + """ + Tests creation of new Virtual Network. + """ + + LOG.debug("test_create_network - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if net_name: + network_name = net_name + else: + network_name = self.network_name + new_net_dict = self._l2network_plugin.create_network( + tenant_id, network_name) + self.assertEqual(new_net_dict[const.NET_NAME], network_name) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_create_network - END") + + def test_delete_network(self, net_tenant_id=None): + """ + Tests deletion of a Virtual Network. + """ + LOG.debug("test_delete_network - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + delete_net_dict = self._l2network_plugin.delete_network( + tenant_id, new_net_dict[const.NET_ID]) + self.assertEqual( + new_net_dict[const.NET_ID], delete_net_dict[const.NET_ID]) + LOG.debug("test_delete_network - END") + + def test_delete_networkDNE(self, net_tenant_id=None, net_id='0005'): + """ + Tests deletion of a Virtual Network when Network does not exist. + """ + LOG.debug("test_delete_network_not_found - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + self.assertRaises( + exc.NetworkNotFound, self._l2network_plugin.delete_network, + tenant_id, net_id) + LOG.debug("test_delete_network_not_found - END") + + def test_delete_networkInUse(self, tenant_id='test_network'): + """ + Tests deletion of a Virtual Network when Network is in Use. + """ + LOG.debug("test_delete_networkInUse - START") + + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + self._l2network_plugin.plug_interface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], self.remote_interface) + self.assertRaises(exc.NetworkInUse, + self._l2network_plugin.delete_network, tenant_id, + new_net_dict[const.NET_ID]) + self.tearDownNetworkPortInterface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_delete_networkInUse - END") + + def test_show_network(self, net_tenant_id=None): + """ + Tests display of details of a Virtual Network . + """ + + LOG.debug("test_show_network - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + result_net_dict = self._l2network_plugin.get_network_details( + tenant_id, new_net_dict[const.NET_ID]) + self.assertEqual( + new_net_dict[const.NET_ID], result_net_dict[const.NET_ID]) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_show_network - END") + + def test_show_networkDNE(self, net_tenant_id=None, net_id='0005'): + """ + Tests display of a Virtual Network when Network does not exist. + """ + + LOG.debug("test_show_network_not_found - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.get_network_details, + tenant_id, net_id) + LOG.debug("test_show_network_not_found - END") + + def test_rename_network(self, net_tenant_id=None, + new_name='new_test_network'): + """ + Tests rename of a Virtual Network . + """ + + LOG.debug("test_rename_network - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + rename_net_dict = self._l2network_plugin.rename_network( + tenant_id, new_net_dict[const.NET_ID], new_name) + self.assertEqual(new_name, rename_net_dict[const.NET_NAME]) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_rename_network - END") + + def test_rename_networkDNE(self, net_tenant_id=None, + net_id='0005', new_name='new_test_network'): + """ + Tests rename of a Virtual Network when Network does not exist. + """ + + LOG.debug("test_rename_network_not_found - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.rename_network, + tenant_id, net_id, new_name) + LOG.debug("test_rename_network_not_found - END") + + def test_list_networks(self, tenant_id='test_network'): + """ + Tests listing of all the Virtual Networks . + """ + + LOG.debug("test_list_networks - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + new_net_dict2 = self._l2network_plugin.create_network( + tenant_id, 'test_net2') + net_list = self._l2network_plugin.get_all_networks(tenant_id) + net_temp_list = [new_net_dict, new_net_dict2] + self.assertEqual(len(net_list), 2) + self.assertTrue(net_list[0] in net_temp_list) + self.assertTrue(net_list[1] in net_temp_list) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + self.tearDownNetwork(tenant_id, new_net_dict2[const.NET_ID]) + LOG.debug("test_list_networks - END") + + def test_list_ports(self, tenant_id='test_network'): + """ + Tests listing of all the Ports. + """ + + LOG.debug("test_list_ports - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + port_dict2 = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + port_list = self._l2network_plugin.get_all_ports( + tenant_id, new_net_dict[const.NET_ID]) + port_temp_list = [port_dict, port_dict2] + self.assertEqual(len(port_list), 2) + self.assertTrue(port_list[0] in port_temp_list) + self.assertTrue(port_list[1] in port_temp_list) + + self.tearDownPortOnly(tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID], + port_dict2[const.PORT_ID]) + LOG.debug("test_list_ports - END") + + def test_create_port(self, tenant_id='test_network', + port_state=const.PORT_UP): + """ + Tests creation of Ports. + """ + + LOG.debug("test_create_port - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], port_state) + self.assertEqual(port_dict[const.PORT_STATE], port_state) + self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_create_port - END") + + def test_create_port_network_DNE( + self, net_tenant_id=None, net_id='0005', port_state=const.PORT_UP): + + """ + Tests creation of Ports when network does not exist. + """ + + LOG.debug("test_create_port_network_DNE - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.create_port, + tenant_id, net_id, port_state) + LOG.debug("test_create_port_network_DNE - END:") + + def test_delete_port(self, tenant_id='test_tenant', + port_state=const.PORT_UP): + """ + Tests deletion of Ports + """ + + LOG.debug("test_delete_port - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], port_state) + delete_port_dict = self._l2network_plugin.delete_port( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + self.assertEqual(delete_port_dict, None) + LOG.debug("test_delete_port - END") + + def test_delete_port_networkDNE(self, tenant_id='test_tenant', + net_id='0005', port_id='p0005'): + """ + Tests deletion of Ports when network does not exist. + """ + + LOG.debug("test_delete_port_networkDNE - START") + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.delete_port, tenant_id, + net_id, port_id) + LOG.debug("test_delete_port_networkDNE - END") + + def test_delete_portDNE(self, tenant_id='test_tenant', port_id='p0005'): + """ + Tests deletion of Ports when port does not exist. + """ + + LOG.debug("test_delete_portDNE - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + self.assertRaises(exc.PortNotFound, self._l2network_plugin.delete_port, + tenant_id, new_net_dict[const.NET_ID], port_id) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_delete_portDNE - END") + + def test_delete_portInUse(self, tenant_id='test_tenant'): + """ + Tests deletion of Ports when port is in Use. + """ + + LOG.debug("test_delete_portInUse - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], + self.port_state) + self._l2network_plugin.plug_interface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], self.remote_interface) + self.assertRaises(exc.PortInUse, + self._l2network_plugin.delete_port, tenant_id, + new_net_dict[const.NET_ID], port_dict[const.PORT_ID]) + self.tearDownNetworkPortInterface( + tenant_id, new_net_dict[const.NET_ID], port_dict[const.PORT_ID]) + LOG.debug("test_delete_portInUse - END") + + def test_update_port(self, tenant_id='test_tenant', + port_state=const.PORT_DOWN): + """ + Tests updation of Ports. + """ + + LOG.debug("test_update_port - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + update_port_dict = self._l2network_plugin.update_port( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], port_state) + self.assertEqual(update_port_dict[const.PORT_STATE], port_state) + self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_update_port - END") + + def test_update_port_networkDNE(self, tenant_id='test_tenant', + net_id='0005', port_id='p0005'): + """ + Tests updation of Ports when network does not exist. + """ + + LOG.debug("test_update_port_networkDNE - START") + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.update_port, tenant_id, + net_id, port_id, self.port_state) + LOG.debug("test_update_port_networkDNE - END") + + def test_update_portDNE(self, tenant_id='test_tenant', port_id='p0005'): + """ + Tests updation of Ports when port does not exist. + """ + + LOG.debug("test_update_portDNE - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + self.assertRaises( + exc.PortNotFound, self._l2network_plugin.update_port, tenant_id, + new_net_dict[const.NET_ID], port_id, self.port_state) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_update_portDNE - END") + + def test_show_port(self, tenant_id='test_tenant'): + """ + Tests display of Ports + """ + + LOG.debug("test_show_port - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + get_port_dict = self._l2network_plugin.get_port_details( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + self.assertEqual(get_port_dict[const.PORT_STATE], self.port_state) + self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_show_port - END") + + def test_show_port_networkDNE(self, tenant_id='test_tenant', + net_id='0005', port_id='p0005'): + """ + Tests display of Ports when network does not exist + """ + + LOG.debug("test_show_port_networkDNE - START") + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.get_port_details, + tenant_id, net_id, port_id) + LOG.debug("test_show_port_networkDNE - END") + + def test_show_portDNE(self, tenant_id='test_tenant', port_id='p0005'): + """ + Tests display of Ports when port does not exist + """ + + LOG.debug("test_show_portDNE - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + self.assertRaises(exc.PortNotFound, + self._l2network_plugin.get_port_details, tenant_id, + new_net_dict[const.NET_ID], port_id) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_show_portDNE - END") + + def test_plug_interface(self, tenant_id='test_tenant', + remote_interface='new_interface'): + """ + Tests attachment of interface to the port + """ + + LOG.debug("test_plug_interface - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + self._l2network_plugin.plug_interface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], remote_interface) + self.assertEqual( + self._l2network_plugin._networks[new_net_dict[const.NET_ID]] + [const.NET_PORTS][port_dict[const.PORT_ID]] + [const.ATTACHMENT], remote_interface) + self.tearDownNetworkPortInterface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_plug_interface - END") + + def test_plug_interface_networkDNE( + self, tenant_id='test_tenant', net_id='0005', + port_id='p0005', remote_interface='new_interface'): + """ + Tests attachment of interface network does not exist + """ + + LOG.debug("test_plug_interface_networkDNE - START") + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.plug_interface, tenant_id, + net_id, port_id, remote_interface) + LOG.debug("test_plug_interface_networkDNE - END") + + def test_plug_interface_portDNE(self, tenant_id='test_tenant', + port_id='p0005', + remote_interface='new_interface'): + """ + Tests attachment of interface port does not exist + """ + + LOG.debug("test_plug_interface_portDNE - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + self.assertRaises( + exc.PortNotFound, self._l2network_plugin.plug_interface, tenant_id, + new_net_dict[const.NET_ID], port_id, remote_interface) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_plug_interface_portDNE - END") + + def test_plug_interface_portInUse(self, tenant_id='test_tenant', + remote_interface='new_interface'): + + """ + Tests attachment of new interface to the port when there is an + existing attachment + """ + + LOG.debug("test_plug_interface_portInUse - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], self.port_state) + self._l2network_plugin.plug_interface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], remote_interface) + self.assertRaises(exc.AlreadyAttached, + self._l2network_plugin.plug_interface, tenant_id, + new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], remote_interface) + self.tearDownNetworkPortInterface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_plug_interface_portInUse - END") + + def test_unplug_interface(self, tenant_id='test_tenant'): + """ + Tests detaachment of an interface to a port + """ + + LOG.debug("test_unplug_interface - START") + new_net_dict = self._l2network_plugin.create_network( + tenant_id, self.network_name) + port_dict = self._l2network_plugin.create_port( + tenant_id, new_net_dict[const.NET_ID], + self.port_state) + self._l2network_plugin.plug_interface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID], self.remote_interface) + self._l2network_plugin.unplug_interface( + tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + self.assertEqual(self._l2network_plugin._networks + [new_net_dict[const.NET_ID]][const.NET_PORTS] + [port_dict[const.PORT_ID]][const.ATTACHMENT], None) + self.tearDownNetworkPort(tenant_id, new_net_dict[const.NET_ID], + port_dict[const.PORT_ID]) + LOG.debug("test_unplug_interface - END") + + def test_unplug_interface_networkDNE(self, tenant_id='test_tenant', + net_id='0005', port_id='p0005'): + """ + Tests detaachment of an interface to a port, when the network does + not exist + """ + + LOG.debug("test_unplug_interface_networkDNE - START") + self.assertRaises(exc.NetworkNotFound, + self._l2network_plugin.unplug_interface, + tenant_id, net_id, port_id) + LOG.debug("test_unplug_interface_networkDNE - END") + + def test_unplug_interface_portDNE(self, tenant_id='test_tenant', + port_id='p0005'): + """ + Tests detaachment of an interface to a port, when the port does + not exist + """ + + LOG.debug("test_unplug_interface_portDNE - START") + new_net_dict = self._l2network_plugin.create_network(tenant_id, + self.network_name) + self.assertRaises(exc.PortNotFound, + self._l2network_plugin.unplug_interface, tenant_id, + new_net_dict[const.NET_ID], port_id) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_unplug_interface_portDNE - END") + + def test_create_portprofile(self, net_tenant_id=None, + net_profile_name=None, net_vlan_id=None): + """ + Tests creation of a port-profile + """ + + LOG.debug("test_create_portprofile - tenant id: %s - START", + net_tenant_id) + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if net_profile_name: + profile_name = net_profile_name + else: + profile_name = self.profile_name + if net_vlan_id: + vlan_id = net_vlan_id + else: + vlan_id = self.vlan_id + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, profile_name, vlan_id) + port_profile_id = port_profile_dict['profile-id'] + self.assertEqual( + self._l2network_plugin._portprofiles[port_profile_id]['vlan-id'], + vlan_id) + self.assertEqual( + self._l2network_plugin._portprofiles[port_profile_id] + ['profile-name'], profile_name) + self.tearDownPortProfile(tenant_id, port_profile_id) + LOG.debug("test_create_portprofile - tenant id: %s - END", + net_tenant_id) + + def test_delete_portprofile(self, net_tenant_id=None): + """ + Tests deletion of a port-profile + """ + + LOG.debug("test_delete_portprofile - tenant id: %s - START", + net_tenant_id) + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_id = port_profile_dict['profile-id'] + self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id) + self.assertEqual(self._l2network_plugin._portprofiles, {}) + LOG.debug("test_delete_portprofile - tenant id: %s - END", + net_tenant_id) + + def test_delete_portprofileDNE(self, tenant_id='test_tenant', + profile_id='pr0005'): + """ + Tests deletion of a port-profile when netowrk does not exist + """ + + LOG.debug("test_delete_portprofileDNE - START") + self.assertRaises(cexc.PortProfileNotFound, + self._l2network_plugin.delete_portprofile, + tenant_id, profile_id) + LOG.debug("test_delete_portprofileDNE - END") + + def test_delete_portprofileAssociated(self, tenant_id='test_tenant'): + + """ + Tests deletion of an associatedport-profile + """ + + LOG.debug("test_delete_portprofileAssociated - START") + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_id = port_profile_dict['profile-id'] + self._l2network_plugin.associate_portprofile( + tenant_id, self.net_id, self.port_id, port_profile_id) + self.assertRaises(cexc.PortProfileInvalidDelete, + self._l2network_plugin.delete_portprofile, + tenant_id, port_profile_id) + self.tearDownAssociatePortProfile(tenant_id, self.net_id, + self.port_id, port_profile_id) + LOG.debug("test_delete_portprofileAssociated - END") + + def test_list_portprofile(self, tenant_id='test_tenant'): + """ + Tests listing of port-profiles + """ + + LOG.debug("test_list_portprofile - tenant id: %s - START", tenant_id) + profile_name2 = tenant_id + '_port_profile2' + vlan_id2 = tenant_id + '201' + port_profile_dict1 = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_dict2 = self._l2network_plugin.create_portprofile( + tenant_id, profile_name2, vlan_id2) + port_profile_id1 = port_profile_dict1['profile-id'] + port_profile_id2 = port_profile_dict2['profile-id'] + list_all_portprofiles = self._l2network_plugin.get_all_portprofiles( + tenant_id) + self.assertEqual(self._l2network_plugin._portprofiles + [port_profile_id1]['vlan-id'], self.vlan_id) + self.assertEqual(self._l2network_plugin._portprofiles + [port_profile_id1]['profile-name'], self.profile_name) + self.assertEqual(self._l2network_plugin._portprofiles + [port_profile_id2]['vlan-id'], vlan_id2) + self.assertEqual(self._l2network_plugin._portprofiles + [port_profile_id2]['profile-name'], profile_name2) + LOG.debug("test_create_portprofile - tenant id: %s - END", tenant_id) + + def test_show_portprofile(self, net_tenant_id=None): + """ + Tests display of a port-profile + """ + + LOG.debug("test_show_portprofile - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_id = port_profile_dict['profile-id'] + result_port_profile = self._l2network_plugin.get_portprofile_details( + tenant_id, port_profile_id) + self.assertEqual(result_port_profile[const.PROFILE_VLAN_ID], + self.vlan_id) + self.assertEqual(result_port_profile[const.PROFILE_NAME], + self.profile_name) + self.tearDownPortProfile(tenant_id, port_profile_id) + LOG.debug("test_show_portprofile - tenant id: %s - END", net_tenant_id) + + def test_show_portprofileDNE(self, tenant_id='test_tenant', + profile_id='pr0005'): + """ + Tests display of a port-profile when network does not exist + """ + + LOG.debug("test_show_portprofileDNE - START") + self.assertRaises(cexc.PortProfileNotFound, + self._l2network_plugin.get_portprofile_details, + tenant_id, profile_id) + LOG.debug("test_show_portprofileDNE - END") + + def test_rename_portprofile(self, tenant_id='test_tenant', + new_profile_name='new_profile_name'): + """ + Tests rename of a port-profile + """ + + LOG.debug("test_rename_portprofile - START") + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_id = port_profile_dict['profile-id'] + result_port_profile_dict = self._l2network_plugin.rename_portprofile( + tenant_id, port_profile_id, new_profile_name) + self.assertEqual(result_port_profile_dict[const.PROFILE_NAME], + new_profile_name) + self.tearDownPortProfile(tenant_id, port_profile_id) + LOG.debug("test_show_portprofile - tenant id: %s - END") + + def test_rename_portprofileDNE(self, tenant_id='test_tenant', + profile_id='pr0005', + new_profile_name='new_profile_name'): + """ + Tests rename of a port-profile when network does not exist + """ + + LOG.debug("test_rename_portprofileDNE - START") + self.assertRaises(cexc.PortProfileNotFound, + self._l2network_plugin.rename_portprofile, + tenant_id, profile_id, new_profile_name) + LOG.debug("test_rename_portprofileDNE - END") + + def test_associate_portprofile(self, tenant_id='test_tenant', + net_id='0005', port_id='p00005'): + """ + Tests association of a port-profile + """ + + LOG.debug("test_associate_portprofile - START") + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_id = port_profile_dict['profile-id'] + self._l2network_plugin.associate_portprofile( + tenant_id, net_id, port_id, port_profile_id) + self.assertEqual( + self._l2network_plugin._portprofiles[port_profile_id] + [const.PROFILE_ASSOCIATIONS][0], port_id) + self.tearDownAssociatePortProfile(tenant_id, net_id, + port_id, port_profile_id) + LOG.debug("test_associate_portprofile - END") + + def test_associate_portprofileDNE(self, tenant_id='test_tenant', + net_id='0005', port_id='p00005', + profile_id='pr0005'): + """ + Tests association of a port-profile when a network does not exist + """ + + LOG.debug("test_associate_portprofileDNE - START") + self.assertRaises(cexc.PortProfileNotFound, + self._l2network_plugin.associate_portprofile, + tenant_id, net_id, port_id, profile_id) + LOG.debug("test_associate_portprofileDNE - END") + + def test_disassociate_portprofile(self, tenant_id='test_tenant', + net_id='0005', port_id='p00005'): + """ + Tests disassociation of a port-profile + """ + + LOG.debug("test_disassociate_portprofile - START") + port_profile_dict = self._l2network_plugin.create_portprofile( + tenant_id, self.profile_name, self.vlan_id) + port_profile_id = port_profile_dict['profile-id'] + self._l2network_plugin.associate_portprofile(tenant_id, net_id, + port_id, port_profile_id) + self._l2network_plugin.disassociate_portprofile( + tenant_id, net_id, port_id, port_profile_id) + self.assertEqual(self._l2network_plugin._portprofiles + [port_profile_id][const.PROFILE_ASSOCIATIONS], []) + self.tearDownPortProfile(tenant_id, port_profile_id) + LOG.debug("test_disassociate_portprofile - END") + + def test_disassociate_portprofileDNE(self, tenant_id='test_tenant', + net_id='0005', port_id='p00005', profile_id='pr0005'): + """ + Tests disassociation of a port-profile when network does not exist + """ + + LOG.debug("test_disassociate_portprofileDNE - START") + self.assertRaises(cexc.PortProfileNotFound, + self._l2network_plugin.disassociate_portprofile, + tenant_id, net_id, port_id, profile_id) + LOG.debug("test_disassociate_portprofileDNE - END") + +# def test_disassociate_portprofile_Unassociated + + def test_get_tenant(self, net_tenant_id=None): + """ + Tests get tenant + """ + + LOG.debug("test_get_tenant - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + tenant_dict = self._l2network_plugin._get_tenant(tenant_id) + self.assertEqual(tenant_dict[const.TENANT_ID], tenant_id) + self.assertEqual(tenant_dict[const.TENANT_NAME], tenant_id) + LOG.debug("test_get_tenant - END") + + def test_get_vlan_name(self, net_tenant_id=None, vlan_name="NewVlan", + vlan_prefix=conf.VLAN_NAME_PREFIX): + """ + Tests get vlan name + """ + + LOG.debug("test_get_vlan_name - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + result_vlan_name = self._l2network_plugin._get_vlan_name(tenant_id, + vlan_name) + expected_output = vlan_prefix + tenant_id + "-" + vlan_name + self.assertEqual(result_vlan_name, expected_output) + LOG.debug("test_get_vlan_name - END") + + def test_validate_port_state(self, port_state=const.PORT_UP): + """ + Tests validate port state + """ + + LOG.debug("test_validate_port_state - START") + result = self._l2network_plugin._validate_port_state(port_state) + self.assertEqual(result, True) + LOG.debug("test_validate_port_state - END") + + def test_invalid_port_state(self, port_state="BADSTATE"): + """ + Tests invalidate port state + """ + + LOG.debug("test_validate_port_state - START") + self.assertRaises(exc.StateInvalid, + self._l2network_plugin._validate_port_state, + port_state) + LOG.debug("test_validate_port_state - END") + + def test_validate_attachment(self, net_tenant_id=None, + remote_interface_id="new_interface"): + """ + Tests validate attachment + """ + + LOG.debug("test_validate_attachment - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + net_name = self.network_name + new_network_dict = self._l2network_plugin.create_network(tenant_id, + net_name) + network_id = new_network_dict[const.NET_ID] + new_port_dict = self._l2network_plugin.create_port(tenant_id, + network_id) + port_id = new_port_dict[const.PORT_ID] + self._l2network_plugin.plug_interface( + tenant_id, new_network_dict[const.NET_ID], port_id, + remote_interface_id) + self.assertRaises(exc.AlreadyAttached, + self._l2network_plugin._validate_attachment, + tenant_id, network_id, port_id, remote_interface_id) + self.tearDownNetworkPortInterface( + tenant_id, new_network_dict[const.NET_ID], port_id) + LOG.debug("test_validate_attachment - END") + + def setUp(self): + self.tenant_id = "test_tenant" + self.network_name = "test_network" + self.profile_name = "test_tenant_port_profile" + self.vlan_id = "test_tenant_vlanid300" + self.port_state = const.PORT_UP + self.net_id = '00005' + self.port_id = 'p0005' + self.remote_interface = 'new_interface' + self._l2network_plugin = l2network_plugin.L2Network() + + """ + Clean up functions after the tests + """ + + def tearDownNetwork(self, tenant_id, network_dict_id): + self._l2network_plugin.delete_network(tenant_id, network_dict_id) + + def tearDownPortOnly(self, tenant_id, network_dict_id, port_id): + self._l2network_plugin.delete_port(tenant_id, network_dict_id, port_id) + + def tearDownNetworkPort(self, tenant_id, network_dict_id, port_id): + self._l2network_plugin.delete_port(tenant_id, network_dict_id, port_id) + self.tearDownNetwork(tenant_id, network_dict_id) + + def tearDownNetworkPortInterface(self, tenant_id, network_dict_id, + port_id): + self._l2network_plugin.unplug_interface(tenant_id, + network_dict_id, port_id) + self.tearDownNetworkPort(tenant_id, network_dict_id, port_id) + + def tearDownPortProfile(self, tenant_id, port_profile_id): + self._l2network_plugin.delete_portprofile(tenant_id, port_profile_id) + + def tearDownAssociatePortProfile(self, tenant_id, net_id, port_id, + port_profile_id): + self._l2network_plugin.disassociate_portprofile( + tenant_id, net_id, port_id, port_profile_id) + self.tearDownPortProfile(tenant_id, port_profile_id) diff --git a/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py b/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py new file mode 100644 index 00000000000..013d7989408 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/test_nexus_plugin.py @@ -0,0 +1,282 @@ +# copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Shweta Padubidri, Peter Strunk, Cisco Systems, Inc. +# +import unittest +import logging +from quantum.common import exceptions as exc +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.nexus import cisco_nexus_plugin + +LOG = logging.getLogger('quantum.tests.test_nexus') + + +class TestNexusPlugin(unittest.TestCase): + + def setUp(self): + + self.tenant_id = "test_tenant_cisco1" + self.net_name = "test_network_cisco1" + self.net_id = 000007 + self.vlan_name = "q-" + str(self.net_id) + "vlan" + self.vlan_id = 267 + self.port_id = "9" + self._cisco_nexus_plugin = cisco_nexus_plugin.NexusPlugin() + + def test_create_network(self, net_tenant_id=None, network_name=None, + network_id=None, net_vlan_name=None, + net_vlan_id=None): + """ + Tests creation of new Virtual Network. + """ + + LOG.debug("test_create_network - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if network_name: + net_name = network_name + else: + net_name = self.net_name + if network_id: + net_id = network_id + else: + net_id = self.net_id + if net_vlan_name: + vlan_name = net_vlan_name + else: + vlan_name = self.vlan_name + if net_vlan_id: + vlan_id = net_vlan_id + else: + vlan_id = self.vlan_id + + new_net_dict = self._cisco_nexus_plugin.create_network( + tenant_id, net_name, net_id, vlan_name, vlan_id) + + self.assertEqual(new_net_dict[const.NET_ID], self.net_id) + self.assertEqual(new_net_dict[const.NET_NAME], self.net_name) + self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name) + self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_create_network - END") + + def test_delete_network(self, net_tenant_id=None, network_id=None): + """ + Tests deletion of a Virtual Network. + """ + + LOG.debug("test_delete_network - START") + + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if network_id: + net_id = network_id + else: + net_id = self.net_id + + new_net_dict = self._cisco_nexus_plugin.create_network( + tenant_id, self.net_name, net_id, self.vlan_name, self.vlan_id) + deleted_net_dict = self._cisco_nexus_plugin.delete_network( + tenant_id, new_net_dict[const.NET_ID]) + self.assertEqual(deleted_net_dict[const.NET_ID], net_id) + LOG.debug("test_delete_network - END") + + def test_delete_network_DNE(self, net_tenant_id=None, net_id='0005'): + """ + Tests deletion of a Virtual Network when Network does not exist. + """ + + LOG.debug("test_delete_network_DNE - START") + + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + + self.assertRaises(exc.NetworkNotFound, + self._cisco_nexus_plugin.delete_network, + tenant_id, net_id) + + LOG.debug("test_delete_network_DNE - END") + + def test_get_network_details(self, net_tenant_id=None, network_id=None): + """ + Tests displays details of a Virtual Network . + """ + + LOG.debug("test_get_network_details - START") + + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if network_id: + net_id = network_id + else: + net_id = self.net_id + + new_net_dict = self._cisco_nexus_plugin.create_network( + tenant_id, self.net_name, net_id, self.vlan_name, self.vlan_id) + check_net_dict = self._cisco_nexus_plugin.get_network_details( + tenant_id, net_id) + + self.assertEqual(check_net_dict[const.NET_ID], net_id) + self.assertEqual(check_net_dict[const.NET_NAME], self.net_name) + self.assertEqual(check_net_dict[const.NET_VLAN_NAME], self.vlan_name) + self.assertEqual(check_net_dict[const.NET_VLAN_ID], self.vlan_id) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_get_network_details - END") + + def test_get_networkDNE(self, net_tenant_id=None, net_id='0005'): + """ + Tests display of a Virtual Network when Network does not exist. + """ + + LOG.debug("test_get_network_details_network_does_not_exist - START") + + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + + self.assertRaises(exc.NetworkNotFound, + self._cisco_nexus_plugin.get_network_details, + tenant_id, net_id) + + LOG.debug("test_get_network_details_network_does_not_exist - END") + + def test_rename_network(self, new_name="new_network_name", + net_tenant_id=None, network_id=None): + """ + Tests rename of a Virtual Network . + """ + + LOG.debug("test_rename_network - START") + + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if network_id: + net_id = network_id + else: + net_id = self.net_id + + new_net_dict = self._cisco_nexus_plugin.create_network( + tenant_id, self.net_name, net_id, self.vlan_name, + self.vlan_id) + rename_net_dict = self._cisco_nexus_plugin.rename_network( + tenant_id, new_net_dict[const.NET_ID], new_name) + self.assertEqual(rename_net_dict[const.NET_NAME], new_name) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_rename_network - END") + + def test_rename_network_DNE(self, new_name="new_network_name", + net_tenant_id=None, network_id='0005'): + """ + Tests rename of a Virtual Network when Network does not exist. + """ + + LOG.debug("test_rename_network_DNE - START") + + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if network_id: + net_id = network_id + else: + net_id = self.net_id + + self.assertRaises(exc.NetworkNotFound, + self._cisco_nexus_plugin.rename_network, + new_name, tenant_id, net_id) + + LOG.debug("test_rename_network_DNE - END") + + def test_list_all_networks(self, net_tenant_id=None): + """ + Tests listing of all the Virtual Networks . + """ + + LOG.debug("test_list_all_networks - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + new_net_dict1 = self._cisco_nexus_plugin.create_network( + tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + new_net_dict2 = self._cisco_nexus_plugin.create_network( + tenant_id, "New_Network2", "0011", + "second_vlan", "2003") + list_net_dict = self._cisco_nexus_plugin.get_all_networks(tenant_id) + net_temp_list = [new_net_dict1, new_net_dict2] + self.assertEqual(len(list_net_dict), 2) + self.assertTrue(list_net_dict[0] in net_temp_list) + self.assertTrue(list_net_dict[1] in net_temp_list) + self.tearDownNetwork(tenant_id, new_net_dict1[const.NET_ID]) + self.tearDownNetwork(tenant_id, new_net_dict2[const.NET_ID]) + LOG.debug("test_list_all_networks - END") + + def test_get_vlan_id_for_network(self, net_tenant_id=None, + network_id=None): + """ + Tests retrieval of vlan id for a Virtual Networks . + """ + + LOG.debug("test_get_vlan_id_for_network - START") + if net_tenant_id: + tenant_id = net_tenant_id + else: + tenant_id = self.tenant_id + if network_id: + net_id = network_id + else: + net_id = self.net_id + new_net_dict = self._cisco_nexus_plugin.create_network( + tenant_id, self.net_name, net_id, self.vlan_name, + self.vlan_id) + result_vlan_id = self._cisco_nexus_plugin._get_vlan_id_for_network( + tenant_id, net_id) + self.assertEqual(result_vlan_id, self.vlan_id) + self.tearDownNetwork(tenant_id, new_net_dict[const.NET_ID]) + LOG.debug("test_get_vlan_id_for_network - END") + + """ + Clean up functions after the tests + """ + + def tearDownNetwork(self, tenant_id, network_dict_id): + self._cisco_nexus_plugin.delete_network(tenant_id, network_dict_id) + +# def test_create_network(self): +# _test_create_network(self._cisco_nexus_plugin) + +# def test_delete_network(self): +# _test_delete_network(self._cisco_nexus_plugin) + +# def test_rename_network(self): +# _test_rename_network(self._cisco_nexus_plugin) + +# def test_show_network(self): +# _test_get_network_details(self._cisco_nexus_plugin) + +# def test_list_networks(self): +# _test_list_all_networks(self._cisco_nexus_plugin) diff --git a/quantum/plugins/cisco/tests/unit/test_ucs_driver.py b/quantum/plugins/cisco/tests/unit/test_ucs_driver.py new file mode 100644 index 00000000000..ab93d778f9d --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/test_ucs_driver.py @@ -0,0 +1,165 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Shweta Padubidri, Cisco Systems, Inc. +# + +import logging +import unittest + +from quantum.plugins.cisco.ucs import cisco_ucs_network_driver + +LOG = logging.getLogger('quantum.tests.test_ucs_driver') + +create_vlan_output = " "\ +" "\ +"" + +create_profile_output = " "\ +" " + +change_vlan_output = " "\ +" "\ +" " + +delete_vlan_output = " "\ +" "\ +" " + +delete_profile_output = " "\ +" " + +associate_profile_output = " "\ +" " \ +" " + + +class TestUCSDriver(unittest.TestCase): + + def setUp(self): + self._ucsmDriver = cisco_ucs_network_driver.CiscoUCSMDriver() + self.vlan_name = 'New Vlan' + self.vlan_id = '200' + self.profile_name = 'New Profile' + self.old_vlan_name = 'Old Vlan' + self.profile_client_name = 'New Profile Client' + + def test_create_vlan_post_data(self, expected_output=create_vlan_output): + """ + Tests creation of vlan post Data + """ + + LOG.debug("test_create_vlan") + vlan_details = self._ucsmDriver._create_vlan_post_data( + self.vlan_name, self.vlan_id) + self.assertEqual(vlan_details, expected_output) + LOG.debug("test_create_vlan - END") + + def test_create_profile_post_data( + self, expected_output=create_profile_output): + """ + Tests creation of profile post Data + """ + + LOG.debug("test_create_profile_post_data - START") + profile_details = self._ucsmDriver._create_profile_post_data( + self.profile_name, self.vlan_name) + self.assertEqual(profile_details, expected_output) + LOG.debug("test_create_profile_post - END") + + def test_change_vlan_in_profile_post_data( + self, expected_output=change_vlan_output): + """ + Tests creation of change vlan in profile post Data + """ + + LOG.debug("test_create_profile_post_data - START") + profile_details = self._ucsmDriver._change_vlan_in_profile_post_data( + self.profile_name, self.old_vlan_name, self.vlan_name) + self.assertEqual(profile_details, expected_output) + LOG.debug("test_create_profile_post - END") + + def test_delete_vlan_post_data(self, expected_output=delete_vlan_output): + LOG.debug("test_create_profile_post_data - START") + """ + Tests deletion of vlan post Data + """ + + vlan_details = self._ucsmDriver._create_vlan_post_data( + self.vlan_name, self.vlan_id) + vlan_delete_details = self._ucsmDriver._delete_vlan_post_data( + self.vlan_name) + self.assertEqual(vlan_delete_details, expected_output) + LOG.debug("test_create_profile_post - END") + + def test_delete_profile_post_data( + self, expected_output=delete_profile_output): + """ + Tests deletion of profile post Data + """ + + LOG.debug("test_create_profile_post_data - START") + profile_details = self._ucsmDriver._create_profile_post_data( + self.profile_name, self.vlan_name) + profile_delete_details = self._ucsmDriver._delete_profile_post_data( + self.profile_name) + self.assertEqual(profile_delete_details, expected_output) + LOG.debug("test_create_profile_post - END") + + def test_create_profile_client_post_data( + self, expected_output=associate_profile_output): + """ + Tests creation of profile client post Data + """ + + LOG.debug("test_create_profile_client_post_data - START") + profile_details = self._ucsmDriver._create_profile_client_post_data( + self.profile_name, self.profile_client_name) + self.assertEqual(profile_details, expected_output) + LOG.debug("test_create_profile_post - END") + + def test_get_next_dynamic_nic(self): + """ + Tests get next dynamic nic + """ + + LOG.debug("test_get_next_dynamic_nic - START") + dynamic_nic_id = self._ucsmDriver._get_next_dynamic_nic() + self.assertTrue(len(dynamic_nic_id) > 0) + LOG.debug("test_get_next_dynamic_nic - END") diff --git a/quantum/plugins/cisco/tests/unit/test_ucs_plugin.py b/quantum/plugins/cisco/tests/unit/test_ucs_plugin.py new file mode 100644 index 00000000000..978c026482d --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/test_ucs_plugin.py @@ -0,0 +1,480 @@ +#vim: tabstop=4 shiftwidth=4 softtabstop=4 +#copyright 2011 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0(the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Shubhangi Satras, Cisco Systems, Inc. +# +import unittest +import logging as LOG +from quantum.common import exceptions as exc +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.ucs import cisco_ucs_plugin +from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger("cisco_plugin") + + +class UCSVICTestPlugin(unittest.TestCase): + + def setUp(self): + + self.tenant_id = "test_tenant_cisco12" + self.net_name = "test_network_cisco12" + self.net_id = 000007 + self.vlan_name = "q-" + str(self.net_id) + "vlan" + self.vlan_id = 266 + self.port_id = "4" + self._cisco_ucs_plugin = cisco_ucs_plugin.UCSVICPlugin() + + def test_create_network(self): + """ + Tests creation of new Virtual Network. + """ + LOG.debug("UCSVICTestPlugin:_test_create_network() called\n") + new_net_dict = self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + self.assertEqual(new_net_dict[const.NET_ID], self.net_id) + self.assertEqual(new_net_dict[const.NET_NAME], self.net_name) + self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name) + self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_delete_network(self): + """ + Tests deletion of the network with the specified network identifier + belonging to the specified tenant. + """ + LOG.debug("UCSVICTestPlugin:test_delete_network() called\n") + self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + new_net_dict = self._cisco_ucs_plugin.delete_network( + self.tenant_id, self.net_id) + self.assertEqual(new_net_dict[const.NET_ID], self.net_id) + + def test_get_network_details(self): + """ + Tests the deletion the Virtual Network belonging to a the + spec + """ + LOG.debug("UCSVICTestPlugin:test_get_network_details() called\n") + self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + new_net_dict = self._cisco_ucs_plugin.get_network_details( + self.tenant_id, self.net_id) + self.assertEqual(new_net_dict[const.NET_ID], self.net_id) + self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name) + self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_get_all_networks(self): + """ + Tests whether dictionary is returned containing all + for + the specified tenant. + """ + LOG.debug("UCSVICTestPlugin:test_get_all_networks() called\n") + new_net_dict1 = self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + new_net_dict2 = self._cisco_ucs_plugin.create_network( + self.tenant_id, "test_network2", + 000006, "q-000006vlan", "6") + net_list = self._cisco_ucs_plugin.get_all_networks(self.tenant_id) + net_id_list = [new_net_dict1, new_net_dict2] + self.assertTrue(net_list[0] in net_id_list) + self.assertTrue(net_list[1] in net_id_list) + self.tearDownNetwork(self.tenant_id, new_net_dict1[const.NET_ID]) + self.tearDownNetwork(self.tenant_id, new_net_dict2[const.NET_ID]) + + def test_get_all_ports(self): + """ + Retrieves all port identifiers belonging to the + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:get_all_ports() called\n") + new_net_dict = self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + port_dict1 = self._cisco_ucs_plugin.create_port( + self.tenant_id, self.net_id, const.PORT_UP, + self.port_id) + port_dict2 = self._cisco_ucs_plugin.create_port( + self.tenant_id, self.net_id, + const.PORT_UP, "10") + ports_on_net = self._cisco_ucs_plugin.get_all_ports( + self.tenant_id, self.net_id) + port_list = [port_dict1, port_dict2] + self.assertTrue(port_list[0] in ports_on_net) + self.assertTrue(port_list[1] in ports_on_net) + self._cisco_ucs_plugin.delete_port(self.tenant_id, self.net_id, + self.port_id) + self.tearDownNetworkPort(self.tenant_id, new_net_dict[const.NET_ID], + port_dict2[const.PORT_ID]) + + def _test_rename_network(self, new_name): + """ + Tests whether symbolic name is updated for the particular + Virtual Network. + """ + LOG.debug("UCSVICTestPlugin:_test_rename_network() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + new_net_dict = self._cisco_ucs_plugin.rename_network( + self.tenant_id, self.net_id, new_name) + self.assertEqual(new_net_dict[const.NET_NAME], new_name) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_rename_network(self): + self._test_rename_network("new_test_network1") + + def _test_create_port(self, port_state): + """ + Tests creation of a port on the specified Virtual Network. + """ + LOG.debug("UCSVICTestPlugin:_test_create_port() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + new_port_dict = self._cisco_ucs_plugin.create_port( + self.tenant_id, self.net_id, port_state, self.port_id) + self.assertEqual(new_port_dict[const.PORT_ID], self.port_id) + self.assertEqual(new_port_dict[const.PORT_STATE], port_state) + self.assertEqual(new_port_dict[const.ATTACHMENT], None) + profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id) + new_port_profile = new_port_dict[const.PORT_PROFILE] + self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME], + conf.DEFAULT_VLAN_NAME) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID], + conf.DEFAULT_VLAN_ID) + self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id) + + def test_create_port(self): + self._test_create_port(const.PORT_UP) + + def _test_delete_port(self, port_state): + """ + Tests Deletion of a port on a specified Virtual Network, + if the port contains a remote interface attachment, + the remote interface should first be un-plugged and + then the port can be deleted. + """ + LOG.debug("UCSVICTestPlugin:_test_delete_port() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + port_state, self.port_id) + self._cisco_ucs_plugin.delete_port(self.tenant_id, self.net_id, + self.port_id) + net = self._cisco_ucs_plugin._get_network(self.tenant_id, self.net_id) + self.assertEqual(net[const.NET_PORTS], {}) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_delete_port(self): + self._test_delete_port(const.PORT_UP) + + def _test_update_port(self, port_state): + """ + Tests Updation of the state of a port on the specified Virtual Network. + """ + LOG.debug("UCSVICTestPlugin:_test_update_port() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + port_state, self.port_id) + port = self._cisco_ucs_plugin.update_port( + self.tenant_id, self.net_id, + self.port_id, port_state) + self.assertEqual(port[const.PORT_STATE], port_state) + self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id) + + def test_update_port_state_up(self): + self._test_update_port(const.PORT_UP) + + def test_update_port_state_down(self): + self._test_update_port(const.PORT_DOWN) + + def _test_get_port_details_state_up(self, port_state): + """ + Tests whether user is able to retrieve a remote interface + that is attached to this particular port when port state is Up. + """ + LOG.debug("UCSVICTestPlugin:_test_get_port_details_state_up()" + + "called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + port_state, self.port_id) + port = self._cisco_ucs_plugin.get_port_details( + self.tenant_id, self.net_id, self.port_id) + self.assertEqual(port[const.PORT_ID], self.port_id) + self.assertEqual(port[const.PORT_STATE], port_state) + self.assertEqual(port[const.ATTACHMENT], None) + new_port_profile = port[const.PORT_PROFILE] + profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME], + conf.DEFAULT_VLAN_NAME) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID], + conf.DEFAULT_VLAN_ID) + self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name) + self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id) + + def _test_get_port_details_state_down(self, port_state): + """ + Tests whether user is able to retrieve a remote interface + that is attached to this particular port when port state is down. + """ + LOG.debug("UCSVICTestPlugin:_test_get_port_details_state_down()" + + "called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + port_state, self.port_id) + port = self._cisco_ucs_plugin.get_port_details(self.tenant_id, + self.net_id, + self.port_id) + self.assertEqual(port[const.PORT_ID], self.port_id) + self.assertNotEqual(port[const.PORT_STATE], port_state) + self.assertEqual(port[const.ATTACHMENT], None) + new_port_profile = port[const.PORT_PROFILE] + profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME], + conf.DEFAULT_VLAN_NAME) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID], + conf.DEFAULT_VLAN_ID) + self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name) + self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id) + + def test_get_port_details_state_up(self): + self._test_get_port_details_state_up(const.PORT_UP) + + def test_get_port_details_state_down(self): + self._test_get_port_details_state_down(const.PORT_DOWN) + + def test_create_port_profile(self): + LOG.debug("UCSVICTestPlugin:test_create_port_profile() called\n") + new_port_profile = self._cisco_ucs_plugin._create_port_profile( + self.tenant_id, self.net_id, self.port_id, + self.vlan_name, self.vlan_id) + profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id) + self.assertEqual(new_port_profile[const.PROFILE_NAME], profile_name) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_NAME], + self.vlan_name) + self.assertEqual(new_port_profile[const.PROFILE_VLAN_ID], self.vlan_id) + self._cisco_ucs_plugin._delete_port_profile(self.port_id, profile_name) + + def test_delete_port_profile(self): + LOG.debug("UCSVICTestPlugin:test_delete_port_profile() called\n") + self._cisco_ucs_plugin._create_port_profile( + self.tenant_id, self.net_id, self.port_id, self.vlan_name, + self.vlan_id) + profile_name = self._cisco_ucs_plugin._get_profile_name(self.port_id) + counter1 = self._cisco_ucs_plugin._port_profile_counter + self._cisco_ucs_plugin._delete_port_profile(self.port_id, + profile_name) + counter2 = self._cisco_ucs_plugin._port_profile_counter + self.assertNotEqual(counter1, counter2) + + def _test_plug_interface(self, remote_interface_id): + """ + Attaches a remote interface to the specified port on the + specified Virtual Network. + """ + LOG.debug("UCSVICTestPlugin:_test_plug_interface() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + const.PORT_UP, self.port_id) + self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id, + self.port_id, + remote_interface_id) + port = self._cisco_ucs_plugin._get_port( + self.tenant_id, self.net_id, self.port_id) + self.assertEqual(port[const.ATTACHMENT], remote_interface_id) + port_profile = port[const.PORT_PROFILE] + profile_name = port_profile[const.PROFILE_NAME] + new_vlan_name = self._cisco_ucs_plugin._get_vlan_name_for_network( + self.tenant_id, self.net_id) + new_vlan_id = self._cisco_ucs_plugin._get_vlan_id_for_network( + self.tenant_id, self.net_id) + self.assertEqual(port_profile[const.PROFILE_VLAN_NAME], new_vlan_name) + self.assertEqual(port_profile[const.PROFILE_VLAN_ID], new_vlan_id) + self.tearDownNetworkPortInterface(self.tenant_id, self.net_id, + self.port_id) + + def test_plug_interface(self): + self._test_plug_interface("4") + + def _test_unplug_interface(self, remote_interface_id): + """ + Tests whether remote interface detaches from the specified port on the + specified Virtual Network. + """ + LOG.debug("UCSVICTestPlugin:_test_unplug_interface() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + const.PORT_UP, self.port_id) + self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id, + self.port_id, + remote_interface_id) + self._cisco_ucs_plugin.unplug_interface(self.tenant_id, self.net_id, + self.port_id) + port = self._cisco_ucs_plugin._get_port( + self.tenant_id, self.net_id, self.port_id) + self.assertEqual(port[const.ATTACHMENT], None) + port_profile = port[const.PORT_PROFILE] + profile_name = port_profile[const.PROFILE_NAME] + self.assertEqual(port_profile[const.PROFILE_VLAN_NAME], + conf.DEFAULT_VLAN_NAME) + self.assertEqual(port_profile[const.PROFILE_VLAN_ID], + conf.DEFAULT_VLAN_ID) + self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id) + + def test_unplug_interface(self): + self._test_unplug_interface("4") + + def test_get_vlan_name_for_network(self): + LOG.debug("UCSVICTestPlugin:test_get_vlan_name_for_network() called\n") + net = self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, + self.vlan_name, self.vlan_id) + self.assertEqual(net[const.NET_VLAN_NAME], self.vlan_name) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_get_vlan_id_for_network(self): + LOG.debug("UCSVICTestPlugin:test_get_vlan_id_for_network() called\n") + net = self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, self.vlan_name, + self.vlan_id) + self.assertEqual(net[const.NET_VLAN_ID], self.vlan_id) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_get_network(self): + LOG.debug("UCSVICTestPlugin:test_get_network() called\n") + net = self._cisco_ucs_plugin.create_network( + self.tenant_id, self.net_name, self.net_id, self.vlan_name, + self.vlan_id) + self.assertEqual(net[const.NET_ID], self.net_id) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_get_port(self): + LOG.debug("UCSVICTestPlugin:test_get_port() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + new_port_dict = self._cisco_ucs_plugin.create_port( + self.tenant_id, self.net_id, + const.PORT_UP, self.port_id) + self.assertEqual(new_port_dict[const.PORT_ID], self.port_id) + self.tearDownNetworkPort(self.tenant_id, self.net_id, self.port_id) + + def test_get_network_NetworkNotFound(self): + self.assertRaises(exc.NetworkNotFound, + self._cisco_ucs_plugin._get_network, + *(self.tenant_id, self.net_id)) + + def test_delete_network_NetworkNotFound(self): + self.assertRaises(exc.NetworkNotFound, + self._cisco_ucs_plugin.delete_network, + *(self.tenant_id, self.net_id)) + + def test_delete_port_PortInUse(self): + self._test_delete_port_PortInUse("4") + + def _test_delete_port_PortInUse(self, remote_interface_id): + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + const.PORT_UP, self.port_id) + self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id, + self.port_id, + remote_interface_id) + self.assertRaises(exc.PortInUse, self._cisco_ucs_plugin.delete_port, + *(self.tenant_id, self.net_id, self.port_id)) + self.tearDownNetworkPortInterface(self.tenant_id, self.net_id, + self.port_id) + + def test_delete_port_PortNotFound(self): + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self.assertRaises(exc.PortNotFound, self._cisco_ucs_plugin.delete_port, + *(self.tenant_id, self.net_id, self.port_id)) + self.tearDownNetwork(self.tenant_id, self.net_id) + + def test_plug_interface_PortInUse(self): + self._test_plug_interface_PortInUse("6", "5") + + def _test_plug_interface_PortInUse(self, remote_interface_id1, + remote_interface_id2): + LOG.debug("UCSVICTestPlugin:_test_plug_interface_PortInUse() called\n") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + const.PORT_UP, self.port_id) + self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id, + self.port_id, + remote_interface_id1) + self.assertRaises(exc.PortInUse, self._cisco_ucs_plugin.plug_interface, + *(self.tenant_id, self.net_id, self.port_id, + remote_interface_id2)) + self.tearDownNetworkPortInterface(self.tenant_id, self.net_id, + self.port_id) + + def test_validate_attachment_AlreadyAttached(self): + LOG.debug("UCSVICTestPlugin:testValidateAttachmentAlreadyAttached") + self._test_validate_attachment_AlreadyAttached("4") + + def _test_validate_attachment_AlreadyAttached(self, remote_interface_id): + LOG.debug("UCSVICTestPlugin:_test_validate_attachmentAlreadyAttached") + self._cisco_ucs_plugin.create_network(self.tenant_id, self.net_name, + self.net_id, self.vlan_name, + self.vlan_id) + self._cisco_ucs_plugin.create_port(self.tenant_id, self.net_id, + const.PORT_UP, self.port_id) + self._cisco_ucs_plugin.plug_interface(self.tenant_id, self.net_id, + self.port_id, + remote_interface_id) + self.assertRaises( + exc.AlreadyAttached, self._cisco_ucs_plugin._validate_attachment, + *(self.tenant_id, self.net_id, self.port_id, remote_interface_id)) + self.tearDownNetworkPortInterface(self.tenant_id, self.net_id, + self.port_id) + + def tearDownNetwork(self, tenant_id, net_id): + self._cisco_ucs_plugin.delete_network(tenant_id, net_id) + + def tearDownNetworkPort(self, tenant_id, net_id, port_id): + self._cisco_ucs_plugin.delete_port(tenant_id, net_id, + port_id) + self.tearDownNetwork(tenant_id, net_id) + + def tearDownNetworkPortInterface(self, tenant_id, net_id, port_id): + self._cisco_ucs_plugin.unplug_interface(tenant_id, net_id, + port_id) + self.tearDownNetworkPort(tenant_id, net_id, port_id) From 71dc75bb7bb33a71529b17a28ac149e03770c950 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 8 Aug 2011 11:54:19 -0700 Subject: [PATCH 60/76] Making a check for the presence of UCS/Nexus plugin (earlier it was not in certain cases). With this change, if the UCS/Nexus plugins are not enabled, the core API tests can be run even on Ubuntu (and RHEL without the requirement of any specific network hardware). --- quantum/plugins/cisco/README | 8 ++++---- quantum/plugins/cisco/l2network_model.py | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 5467fb03a9d..a5d3146a911 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -60,9 +60,9 @@ mysql -uroot -p nova -e 'create table ports (port_id VARCHA ** Execute the Test cases * The unit tests are located at quantum/plugins/cisco/tests/unit. They are executed from quantum/plugins/cisco/ using the runtests.py script. (Note that to execute the test cases one currently requires the environment setup as outlined in the pre-requisites.) -* Execution of the runtests.py script. +* Execution of the run_tests.py script. All unit tests - python runtests.py unit + python run_tests.py unit Specific Plugin unit test - python runtests.py unit. - e.g. python run_tests.py unit.test_coreApi + python run_tests.py unit. + e.g. python run_tests.py unit.test_l2networkApi diff --git a/quantum/plugins/cisco/l2network_model.py b/quantum/plugins/cisco/l2network_model.py index 3fa986b2a38..e24e0a372ff 100644 --- a/quantum/plugins/cisco/l2network_model.py +++ b/quantum/plugins/cisco/l2network_model.py @@ -47,12 +47,14 @@ class L2NetworkModel(L2NetworkModelBase): getattr(pluginObjRef, function_name)(*args, **kwargs) def _invokeUCSPlugin(self, function_name, args, kwargs): - getattr(self._plugins[const.UCS_PLUGIN], - function_name)(*args, **kwargs) + if const.UCS_PLUGIN in self._plugins.keys(): + getattr(self._plugins[const.UCS_PLUGIN], + function_name)(*args, **kwargs) def _invokeNexusPlugin(self, function_name, args, kwargs): - getattr(self._plugins[const.NEXUS_PLUGIN], - function_name)(*args, **kwargs) + if const.NEXUS_PLUGIN in self._plugins.keys(): + getattr(self._plugins[const.NEXUS_PLUGIN], + function_name)(*args, **kwargs) def get_all_networks(self, args): pass From 66d6a1b400b0041ac64df5148429079024f0ed9e Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 8 Aug 2011 12:54:42 -0700 Subject: [PATCH 61/76] Incorporated changes in response to review comments from Ram. --- quantum/plugins/cisco/README | 41 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index a5d3146a911..7455c92e698 100644 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -1,23 +1,21 @@ - L2 Network Plugin -================== + L2 Network Plugin Framework +============================ *** Reference implementation of plugin framework for L2 network *** *** Multi-switch (devices and types) capability *** -*** Current support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh *** -*** Also supports Nexus 7k *** +*** Support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh *** +*** Support for Nexus 7000 *** ** Pre-requisities * UCS B200 series blades with M81KR VIC installed. * UCSM 2.0 (Capitola) Build 230 * RHEL 6.1 (Currently UCS support is only on RHEL, Ubuntu will be supporting in upcoming releases) -* UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at: -http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM +* UCS & VIC installation (support for KVM) +* OpenStack Cactus release installation (patch is required, details follow in this document) * Package python-configobj-4.6.0-3.el6.noarch - * If you have a Nexus switch in your topology and decide to turn on Nexus support, you will need: - ncclcient v0.3.1 - Python library for NETCONF clients (http://schmizz.net/ncclient/). - paramiko library (do: yum install python-paramiko.noarch) - * To run Quantum on RHEL, you will need to have the correct version of python-routes (version 1.12.3 or later). The RHEL 6.1 package contains an older version. Do the following and check your python-routes version: rpm -qav | grep "python-routes" @@ -49,20 +47,27 @@ provider = quantum.plugins.cisco.l2network_plugin.L2Network * Create DB Table in Nova DB (On the Cloud Controller) mysql -uroot -p nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));' -* Replace the following files with the files from the Cisco Nova branch: +* Replace the following file with the files from the Cisco OpenStack Cactus patch: /usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py -* Add the following files from the Cisco Nova branch: +* Add the following file from the Cisco OpenStack Cactus patch to your installation: /usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py * Restart nova-compute service -** Execute the Test cases -* The unit tests are located at quantum/plugins/cisco/tests/unit. They are executed from quantum/plugins/cisco/ using the runtests.py script. (Note that to execute the test cases one currently requires the environment setup as outlined in the pre-requisites.) +* Note that the requirement for the above patch is temporary and will go away with the integration with OpenStack Diablo. A 802.1Qbh-specific VIF driver will be made available as per the specification here: +http://wiki.openstack.org/network-refactoring#VIF_driver -* Execution of the run_tests.py script. - All unit tests - python run_tests.py unit - Specific Plugin unit test - python run_tests.py unit. - e.g. python run_tests.py unit.test_l2networkApi +** Testing +* The unit tests are located at quantum/plugins/cisco/tests/unit. They are executed from quantum/plugins/cisco/ using the run_tests.py script. + +* Execution of the run_tests.py script: + + Testing the core API (without UCS/Nexus/RHEL hardware, can be run on Ubuntu) + - Disable all device-specific plugins by commenting the entries in the quantum/plugins/cisco/conf/plugins.ini configuration file. + $> python run_tests.py unit.test_l2networkApi + + Specific Plugin unit test (needs environment setup as indicated in the pre-requisites) + - python run_tests.py unit. + e.g. + $> python run_tests.py unit.test_ucs_plugin.py + + All unit tests (needs environment setup as indicated in the pre-requisites) + $> python run_tests.py unit From b511b696e0dd4826861d38cea319447a6a2f50dd Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 8 Aug 2011 16:51:58 -0700 Subject: [PATCH 62/76] README file updates (pointer to Nova Cactus branch), and numerous other edits based on Mark's template. --- quantum/plugins/cisco/README | 299 ++++++++++++++++++++++++++--------- 1 file changed, 226 insertions(+), 73 deletions(-) mode change 100644 => 100755 quantum/plugins/cisco/README diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README old mode 100644 new mode 100755 index 7455c92e698..c429bcd601d --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -1,73 +1,226 @@ - L2 Network Plugin Framework -============================ - -*** Reference implementation of plugin framework for L2 network *** -*** Multi-switch (devices and types) capability *** -*** Support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh *** -*** Support for Nexus 7000 *** - -** Pre-requisities -* UCS B200 series blades with M81KR VIC installed. -* UCSM 2.0 (Capitola) Build 230 -* RHEL 6.1 (Currently UCS support is only on RHEL, Ubuntu will be supporting in upcoming releases) -* UCS & VIC installation (support for KVM) -* OpenStack Cactus release installation (patch is required, details follow in this document) -* Package python-configobj-4.6.0-3.el6.noarch -* If you have a Nexus switch in your topology and decide to turn on Nexus support, you will need: - - ncclcient v0.3.1 - Python library for NETCONF clients (http://schmizz.net/ncclient/). - - paramiko library (do: yum install python-paramiko.noarch) -* To run Quantum on RHEL, you will need to have the correct version of python-routes (version 1.12.3 or later). The RHEL 6.1 package contains an older version. Do the following and check your python-routes version: -rpm -qav | grep "python-routes" - -If it's an older version, you will need to upgrade to 1.12.3 or later. One quick way to do it as by adding the following to your /etc/yum.repos.d/openstack.repo (assuming that you had installed OpenStack on this host, and hence had this repo; else you could add to any other operational repo config), and then update the python-routes package. That should get you the python-routes-1.12.3-2.el6.noarch package. - -[openstack-deps] -name=OpenStack Nova Compute Dependencies -baseurl=http://yum.griddynamics.net/yum/cactus/deps -enabled=1 -gpgcheck=1 -gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK - - -** Plugin Installation Instructions: -* Make a backup copy of quantum/quantum/plugins.ini, and edit the "provider" entry to point to the L2Network-plugin: -provider = quantum.plugins.cisco.l2network_plugin.L2Network - -* All configuration files are located under quantum/plugins/cisco/conf. Wherever you find kind of placeholder, please replace it entirely with the relevant values (don't forget to remove the angle brackets as well) - -* If you are not running Quantum on the same host as the OpenStack Cloud Controller, you will need to change the db_ip_address configuration in nova.ini - -* If you want to turn on Nexus support, please uncomment the nexus_plugin property in the plugins.ini file, and enter the relevant configuration in the nexus.ini file. In addition, you will require a patch to the ncclient library. Also make sure that the switch's host key is known to the host on which you are running the Quantum service (since the connection to the switch is over ssh). If you are not turning the Nexus support on, your Nexus configuration will be ignored. - -* Check again if you have gone through every conf file and made the required changes (check if all IP addresses are correct, and check if you have entered the credentials correponding to each of those IP addresses in the credentials.ini file). - -* Start the Quantum service - -** Additional installation required on Nova Compute: -* Create DB Table in Nova DB (On the Cloud Controller) -mysql -uroot -p nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));' - -* Replace the following file with the files from the Cisco OpenStack Cactus patch: -/usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py - -* Add the following file from the Cisco OpenStack Cactus patch to your installation: -/usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py - -* Restart nova-compute service - -* Note that the requirement for the above patch is temporary and will go away with the integration with OpenStack Diablo. A 802.1Qbh-specific VIF driver will be made available as per the specification here: -http://wiki.openstack.org/network-refactoring#VIF_driver - -** Testing -* The unit tests are located at quantum/plugins/cisco/tests/unit. They are executed from quantum/plugins/cisco/ using the run_tests.py script. - -* Execution of the run_tests.py script: - + Testing the core API (without UCS/Nexus/RHEL hardware, can be run on Ubuntu) - - Disable all device-specific plugins by commenting the entries in the quantum/plugins/cisco/conf/plugins.ini configuration file. - $> python run_tests.py unit.test_l2networkApi - + Specific Plugin unit test (needs environment setup as indicated in the pre-requisites) - - python run_tests.py unit. - e.g. - $> python run_tests.py unit.test_ucs_plugin.py - + All unit tests (needs environment setup as indicated in the pre-requisites) - $> python run_tests.py unit +============================================ +README: Quantum L2 Network Plugin Framework +============================================ + +:Author: Sumit Naiksatam, Ram Durairaj, Mark Voelker, Edgar Magana, Shweta Padubidri, Rohit Agarwalla, Ying Liu +:Contact: netstack@lists.launchpad.net +:Web site: https://launchpad.net/~cisco-openstack +:Copyright: 2011 Cisco Systems, Inc. + +.. contents:: + +Introduction +------------ + +This plugin implementation provides the following capabilities +to help you take your Layer 2 network for a Quantum leap: + +* A reference implementation of plugin framework for L2 network +* Supports multiple switches in the network +* Supports multiple models of switches concurrently +* Supports Cisco UCS blade servers with M81KR Virtual Interface Cards + (aka "Palo adapters") via 802.1Qbh. +* Supports the Cisco Nexus family of switches. + +It does not provide: + +* A hologram of Al that only you can see. +* A map to help you find your way through time. +* A cure for amnesia or your swiss-cheesed brain. + +Let's leap in! + +Pre-requisites +-------------- +* One or more UCS B200 series blade servers with M81KR VIC (aka + Palo adapters) installed. +* UCSM 2.0 (Capitola) Build 230 or above. +* OpenStack Cactus release installation (additional patch is required, + details follow in this document) +* RHEL 6.1 (as of this writing, UCS only officially supports RHEL, but + it should be noted that Ubuntu support is planned in coming releases as well) + ** Package: python-configobj-4.6.0-3.el6.noarch (or newer) + ** Package: python-routes-1.12.3-2.el6.noarch (or newer) + +If you are using a Nexus switch in your topology, you'll need the following +packages to enable Nexus support: +* paramiko library - SSHv2 protocol library for python + ** To install on RHEL 6.1, run: yum install python-paramiko +* ncclient v0.3.1 - Python library for NETCONF clients + ** RedHat does not provide a package for ncclient in RHEL 6.1. + ** See http://schmizz.net/ncclient/ for documentation and downloads. + +To verify the version of any package you have installed on your system, +run "rpm -qav | grep ", where is the +package you want to query (for example: python-routes). + +Note that you can get access to recent versions of the packages above +and other OpenStack software packages by adding a new repository to +your yum configuration. To do so, edit or create +/etc/yum.repos.d/openstack.repo and add the following: + +[openstack-deps] +name=OpenStack Nova Compute Dependencies +baseurl=http://yum.griddynamics.net/yum/cactus/deps +enabled=1 +gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK + +Then run "yum install python-routes". + + +Module Structure: +----------------- +* quantum/plugins/cisco/ - Contains the L2-Network Plugin Framework + /common - Modules common to the entire plugin + /conf - All configuration files + /db - Persistence framework + /nexus - Nexus-specific modules + /ucs - UCS-specific modules + + +Plugin Installation Instructions +---------------------------------- +1. Make a backup copy of quantum/quantum/plugins.ini. + +2. Edit quantum/quantum/plugins.ini and edit the "provider" entry to point + to the L2Network-plugin: + +provider = quantum.plugins.cisco.l2network_plugin.L2Network + +3. If you are not running Quantum on the same host as the OpenStack Cloud + Controller, you will need to change the db_ip_address configuration + in nova.ini. + +4. If you want to turn on support for Cisco Nexus switches: + 4a. Uncomment the nexus_plugin property in + quantum/plugins/cisco/conf/plugins.ini to read: + +nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin + + 4b. Enter the relevant configuration in the + quantum/plugins/cisco/conf/nexus.ini file. Example: + +[SWITCH] +# Change the following to reflect the IP address of the Nexus switch. +# This will be the address at which Quantum sends and receives configuration +# information via SSHv2. +nexus_ip_address=10.0.0.1 +# Port number on the Nexus switch to which the UCSM 6120 is connected +# Use shortened interface syntax, e.g. "3/23" not "Ethernet3/23". +nexus_port=3/23 + +[DRIVER] +name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver + + 4c. Make sure that SSH host key of the Nexus switch is known to the + host on which you are running the Quantum service. You can do + this simply by logging in to your Quantum host as the user that + Quantum runs as and SSHing to the switch at least once. If the + host key changes (e.g. due to replacement of the supervisor or + clearing of the SSH config on the switch), you may need to repeat + this step and remove the old hostkey from ~/.ssh/known_hosts. + +5. Verify that you have the correct credentials for each IP address listed + in quantum/plugins/cisco/conf/credentials.ini. Example: + +# Provide the UCSM credentials +# UCSM IP address, username and password. +[10.0.0.2] +username=admin +password=mySecretPasswordForUCSM + +# Provide the Nova DB credentials. +# The IP address should be the same as in nova.ini. +[10.0.0.3] +username=nova +password=mySecretPasswordForNova + +# Provide the Nexus credentials, if you are using Nexus switches. +# If not this will be ignored. +[10.0.0.1] +username=admin +password=mySecretPasswordForNexus + +6. Start the Quantum service. If something doesn't work, verify that + your configuration of each of the above files hasn't gone a little kaka. + Once you've put right what once went wrong, leap on. + + +How to test the installation +---------------------------- +The unit tests are located at quantum/plugins/cisco/tests/unit. They can be +executed from quantum/plugins/cisco/ using the run_tests.py script. + +1. Testing the core API (without UCS/Nexus/RHEL hardware, and can be run on + Ubuntu): + First disable all device-specific plugins by commenting out the entries in: + quantum/plugins/cisco/conf/plugins.ini + Then run the test script: + +python run_tests.py unit.test_l2networkApi + +2. Specific Plugin unit test (needs environment setup as indicated in the + pre-requisites): + python run_tests.py unit. + E.g.: + +python run_tests.py unit.test_ucs_plugin.py + +3. All unit tests (needs environment setup as indicated in the pre-requisites): + +python run_tests.py unit + + +Additional installation required on Nova Compute +------------------------------------------------ +1. Create a table in the "nova" database for ports. This can be + accomplished with the following SQL statement: + +CREATE TABLE ports ( + port_id VARCHAR(255) PRIMARY KEY, + profile_name VARCHAR(255), + dynamic_vnic VARCHAR(255), + host VARCHAR(255), + instance_name VARCHAR(255), + instance_nic_name VARCHAR(255), + used TINYINT(1) +); + + Assuming you're using MySQL, you can run the following command from a + shell prompt on the Cloud Controller node to create the table: + +mysql -uroot -p nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));' + +You'll be prompted for a password. + +2. A patch is available for the Cactus release in this branch: + https://code.launchpad.net/~snaiksat/quantum/cactus-ucs-support + replace the following file in your installation: + /usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py + with the file from the branch: + nova/virt/libvirt_conn.py + +3. Add the following file from the Cisco Nova branch: + nova/virt/cisco_ucs.py + to: + /usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py + +4. Add the 802.1Qbh specific libvirt template file, from: + nova/virt/libvirt-qbh.xml.template + tO: + /usr/share/nova/libvirt-qbh.xml.template + +5. Edit /etc/nova.conf to set the libvirt XML template to the above template: + --libvirt_xml_template=/usr/share/nova/libvirt-qbh.xml.template + +6. Restart the nova-compute service. + + (Note that the requirement for the above patch is temporary and will go away + with the integration with OpenStack Diablo. A 802.1Qbh-specific VIF driver + will be made available as per the specification here: + http://wiki.openstack.org/network-refactoring#VIF_driver) + +Bingo bango bongo! That's it! Thanks for taking the leap into Quantum. + +...Oh, boy! From 8ec0f04f667ab341d8751023fc6d3303125b13b4 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 8 Aug 2011 16:57:18 -0700 Subject: [PATCH 63/76] Fixed typo in README --- quantum/plugins/cisco/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index c429bcd601d..0e494b188df 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -208,7 +208,7 @@ You'll be prompted for a password. 4. Add the 802.1Qbh specific libvirt template file, from: nova/virt/libvirt-qbh.xml.template - tO: + to: /usr/share/nova/libvirt-qbh.xml.template 5. Edit /etc/nova.conf to set the libvirt XML template to the above template: From 56e702465521c0c5c7cbdb29afb400a7f84ce90c Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Mon, 8 Aug 2011 17:53:21 -0700 Subject: [PATCH 64/76] Adding the required build for Nexus support --- quantum/plugins/cisco/README | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 0e494b188df..44e37a9a4cd 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -43,7 +43,8 @@ Pre-requisites ** Package: python-routes-1.12.3-2.el6.noarch (or newer) If you are using a Nexus switch in your topology, you'll need the following -packages to enable Nexus support: +NX-OS version and packages to enable Nexus support: +* NX-OS 5.2.1 (Delhi) Build 69 or above. * paramiko library - SSHv2 protocol library for python ** To install on RHEL 6.1, run: yum install python-paramiko * ncclient v0.3.1 - Python library for NETCONF clients From b95329e94aa4c633c1ba8be7bca06609c41e4d00 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 8 Aug 2011 17:55:00 -0700 Subject: [PATCH 65/76] Added "tests" directory to the list modules in the README file. --- quantum/plugins/cisco/README | 1 + 1 file changed, 1 insertion(+) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 0e494b188df..a9ff3f7ab07 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -76,6 +76,7 @@ Module Structure: /conf - All configuration files /db - Persistence framework /nexus - Nexus-specific modules + /tests - Tests specific to this plugin /ucs - UCS-specific modules From c15470f1e15cabeb7b1d21f91651a7819ef20836 Mon Sep 17 00:00:00 2001 From: vinkesh banka Date: Tue, 9 Aug 2011 11:19:36 +0530 Subject: [PATCH 66/76] Rajaram/Vinkesh | Added examples of scoping extension alias in request and action extension --- tests/unit/extensions/foxinsocks.py | 15 +++++++++------ tests/unit/test_extensions.py | 8 ++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/unit/extensions/foxinsocks.py b/tests/unit/extensions/foxinsocks.py index 5bdefde95fd..4a1aa2377fb 100644 --- a/tests/unit/extensions/foxinsocks.py +++ b/tests/unit/extensions/foxinsocks.py @@ -66,10 +66,12 @@ class Foxinsocks(object): return resources def get_actions(self): - return [extensions.ActionExtension('dummy_resources', 'add_tweedle', + return [extensions.ActionExtension('dummy_resources', + 'FOXNSOX:add_tweedle', self._add_tweedle_handler), extensions.ActionExtension('dummy_resources', - 'delete_tweedle', self._delete_tweedle_handler)] + 'FOXNSOX:delete_tweedle', + self._delete_tweedle_handler)] def get_request_extensions(self): request_exts = [] @@ -78,7 +80,7 @@ class Foxinsocks(object): #NOTE: This only handles JSON responses. # You can use content type header to test for XML. data = json.loads(res.body) - data['googoose'] = req.GET.get('chewing') + data['FOXNSOX:googoose'] = req.GET.get('chewing') res.body = json.dumps(data) return res @@ -90,7 +92,7 @@ class Foxinsocks(object): #NOTE: This only handles JSON responses. # You can use content type header to test for XML. data = json.loads(res.body) - data['big_bands'] = 'Pig Bands!' + data['FOXNSOX:big_bands'] = 'Pig Bands!' res.body = json.dumps(data) return res @@ -100,8 +102,9 @@ class Foxinsocks(object): return request_exts def _add_tweedle_handler(self, input_dict, req, id): - return "Tweedle {0} Added.".format(input_dict['add_tweedle']['name']) + return "Tweedle {0} Added.".format( + input_dict['FOXNSOX:add_tweedle']['name']) def _delete_tweedle_handler(self, input_dict, req, id): return "Tweedle {0} Deleted.".format( - input_dict['delete_tweedle']['name']) + input_dict['FOXNSOX:delete_tweedle']['name']) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py index 48027cb950f..5ad2a193e66 100644 --- a/tests/unit/test_extensions.py +++ b/tests/unit/test_extensions.py @@ -114,7 +114,7 @@ class ActionExtensionTest(unittest.TestCase): self.extension_app = setup_extensions_test_app() def test_extended_action_for_adding_extra_data(self): - action_name = 'add_tweedle' + action_name = 'FOXNSOX:add_tweedle' action_params = dict(name='Beetle') req_body = json.dumps({action_name: action_params}) response = self.extension_app.post('/dummy_resources/1/action', @@ -122,7 +122,7 @@ class ActionExtensionTest(unittest.TestCase): self.assertEqual("Tweedle Beetle Added.", response.body) def test_extended_action_for_deleting_extra_data(self): - action_name = 'delete_tweedle' + action_name = 'FOXNSOX:delete_tweedle' action_params = dict(name='Bailey') req_body = json.dumps({action_name: action_params}) response = self.extension_app.post("/dummy_resources/1/action", @@ -187,8 +187,8 @@ class RequestExtensionTest(BaseTest): response = app.get("/dummy_resources/1?chewing=newblue") response_data = json.loads(response.body) - self.assertEqual('newblue', response_data['googoose']) - self.assertEqual("Pig Bands!", response_data['big_bands']) + self.assertEqual('newblue', response_data['FOXNSOX:googoose']) + self.assertEqual("Pig Bands!", response_data['FOXNSOX:big_bands']) def test_edit_previously_uneditable_field(self): From 23dc0ed8cf8322831363dd4e5aa9c145ec17be22 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 8 Aug 2011 23:44:36 -0700 Subject: [PATCH 67/76] Changed to default plugin class name. --- quantum/plugins.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quantum/plugins.ini b/quantum/plugins.ini index 5192ade26ba..307d2b48d2c 100644 --- a/quantum/plugins.ini +++ b/quantum/plugins.ini @@ -1,4 +1,3 @@ [PLUGIN] # Quantum plugin provider module -provider = quantum.plugins.cisco.l2network_plugin.L2Network -#provider = quantum.plugins.SamplePlugin.FakePlugin +provider = quantum.plugins.SamplePlugin.FakePlugin From d44714c5d296a5ebdf9bc56a0d58d1daf3ff007e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 9 Aug 2011 09:11:53 -0700 Subject: [PATCH 68/76] rename client_lib unit tests so it is run by ./run_tests.sh, update tests to handle name changes --- tests/unit/api.py | 625 ---------------------------------------------- 1 file changed, 625 deletions(-) delete mode 100644 tests/unit/api.py diff --git a/tests/unit/api.py b/tests/unit/api.py deleted file mode 100644 index aed6c4bacf5..00000000000 --- a/tests/unit/api.py +++ /dev/null @@ -1,625 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Cisco Systems -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Tyler Smith, Cisco Systems - -import logging -import unittest -import re - -from quantum.common.wsgi import Serializer -from quantum.client import Client - -LOG = logging.getLogger('quantum.tests.test_api') - -# Set a couple tenants to use for testing -TENANT_1 = 'totore' -TENANT_2 = 'totore2' - - -class ServerStub(): - """This class stubs a basic server for the API client to talk to""" - - class Response(object): - """This class stubs a basic response to send the API client""" - def __init__(self, content=None, status=None): - self.content = content - self.status = status - - def read(self): - return self.content - - def status(self): - return status - - # To test error codes, set the host to 10.0.0.1, and the port to the code - def __init__(self, host, port=9696, key_file="", cert_file=""): - self.host = host - self.port = port - self.key_file = key_file - self.cert_file = cert_file - - def request(self, method, action, body, headers): - self.method = method - self.action = action - self.body = body - - def status(self, status=None): - return status or 200 - - def getresponse(self): - res = self.Response(status=self.status()) - - # If the host is 10.0.0.1, return the port as an error code - if self.host == "10.0.0.1": - res.status = self.port - return res - - # Extract important information from the action string to assure sanity - match = re.search('tenants/(.+?)/(.+)\.(json|xml)$', self.action) - - tenant = match.group(1) - path = match.group(2) - format = match.group(3) - - data = {'data': {'method': self.method, 'action': self.action, - 'body': self.body, 'tenant': tenant, 'path': path, - 'format': format, 'key_file': self.key_file, - 'cert_file': self.cert_file}} - - # Serialize it to the proper format so the API client can handle it - if data['data']['format'] == 'json': - res.content = Serializer().serialize(data, "application/json") - else: - res.content = Serializer().serialize(data, "application/xml") - return res - - -class APITest(unittest.TestCase): - - def setUp(self): - """ Setups a test environment for the API client """ - HOST = '127.0.0.1' - PORT = 9696 - USE_SSL = False - - self.client = Client(HOST, PORT, USE_SSL, TENANT_1, 'json', ServerStub) - - def _assert_sanity(self, call, status, method, path, data=[], params={}): - """ Perform common assertions to test the sanity of client requests """ - - # Handle an error case first - if status != 200: - (self.client.host, self.client.port) = ("10.0.0.1", status) - self.assertRaises(Exception, call, *data, **params) - return - - # Make the call, then get the data from the root node and assert it - data = call(*data, **params)['data'] - - self.assertEqual(data['method'], method) - self.assertEqual(data['format'], params['format']) - self.assertEqual(data['tenant'], params['tenant']) - self.assertEqual(data['path'], path) - - return data - - def _test_list_networks(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_networks - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_networks, - status, - "GET", - "networks", - data=[], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_networks - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_list_network_details(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_network_details - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_network_details, - status, - "GET", - "networks/001", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_network_details - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_create_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_create_network - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.create_network, - status, - "POST", - "networks", - data=[{'network': {'net-name': 'testNetwork'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_create_network - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_update_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_update_network - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.update_network, - status, - "PUT", - "networks/001", - data=["001", - {'network': {'net-name': 'newName'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_update_network - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_delete_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_delete_network - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.delete_network, - status, - "DELETE", - "networks/001", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_delete_network - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_list_ports(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_ports - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_ports, - status, - "GET", - "networks/001/ports", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_ports - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_list_port_details(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_port_details - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_port_details, - status, - "GET", - "networks/001/ports/001", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_port_details - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_create_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_create_port - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.create_port, - status, - "POST", - "networks/001/ports", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_create_port - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_delete_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_delete_port - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.delete_port, - status, - "DELETE", - "networks/001/ports/001", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_delete_port - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_set_port_state(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_set_port_state - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.set_port_state, - status, - "PUT", - "networks/001/ports/001", - data=["001", "001", - {'port': {'state': 'ACTIVE'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_set_port_state - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_list_port_attachments(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_port_attachments - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_port_attachments, - status, - "GET", - "networks/001/ports/001/attachment", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_port_attachments - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_attach_resource(self, tenant=TENANT_1, - format='json', status=200): - LOG.debug("_test_attach_resource - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.attach_resource, - status, - "PUT", - "networks/001/ports/001/attachment", - data=["001", "001", - {'resource': {'id': '1234'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_attach_resource - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_detach_resource(self, tenant=TENANT_1, - format='json', status=200): - LOG.debug("_test_detach_resource - tenant:%s "\ - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.detach_resource, - status, - "DELETE", - "networks/001/ports/001/attachment", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_detach_resource - tenant:%s "\ - "- format:%s - END", format, tenant) - - def _test_ssl_certificates(self, tenant=TENANT_1, - format='json', status=200): - LOG.debug("_test_ssl_certificates - tenant:%s "\ - "- format:%s - START", format, tenant) - - # Set SSL, and our cert file - self.client.use_ssl = True - cert_file = "/fake.cert" - self.client.key_file = self.client.cert_file = cert_file - - data = self._assert_sanity(self.client.list_networks, - status, - "GET", - "networks", - data=[], - params={'tenant': tenant, 'format': format}) - - self.assertEquals(data["key_file"], cert_file) - self.assertEquals(data["cert_file"], cert_file) - - LOG.debug("_test_ssl_certificates - tenant:%s "\ - "- format:%s - END", format, tenant) - - def test_list_networks_json(self): - self._test_list_networks(format='json') - - def test_list_networks_xml(self): - self._test_list_networks(format='xml') - - def test_list_networks_alt_tenant(self): - self._test_list_networks(tenant=TENANT_2) - - def test_list_networks_error_470(self): - self._test_list_networks(status=470) - - def test_list_networks_error_401(self): - self._test_list_networks(status=401) - - def test_list_network_details_json(self): - self._test_list_network_details(format='json') - - def test_list_network_details_xml(self): - self._test_list_network_details(format='xml') - - def test_list_network_details_alt_tenant(self): - self._test_list_network_details(tenant=TENANT_2) - - def test_list_network_details_error_470(self): - self._test_list_network_details(status=470) - - def test_list_network_details_error_401(self): - self._test_list_network_details(status=401) - - def test_list_network_details_error_420(self): - self._test_list_network_details(status=420) - - def test_create_network_json(self): - self._test_create_network(format='json') - - def test_create_network_xml(self): - self._test_create_network(format='xml') - - def test_create_network_alt_tenant(self): - self._test_create_network(tenant=TENANT_2) - - def test_create_network_error_470(self): - self._test_create_network(status=470) - - def test_create_network_error_401(self): - self._test_create_network(status=401) - - def test_create_network_error_400(self): - self._test_create_network(status=400) - - def test_create_network_error_422(self): - self._test_create_network(status=422) - - def test_update_network_json(self): - self._test_update_network(format='json') - - def test_update_network_xml(self): - self._test_update_network(format='xml') - - def test_update_network_alt_tenant(self): - self._test_update_network(tenant=TENANT_2) - - def test_update_network_error_470(self): - self._test_update_network(status=470) - - def test_update_network_error_401(self): - self._test_update_network(status=401) - - def test_update_network_error_400(self): - self._test_update_network(status=400) - - def test_update_network_error_420(self): - self._test_update_network(status=420) - - def test_update_network_error_422(self): - self._test_update_network(status=422) - - def test_delete_network_json(self): - self._test_delete_network(format='json') - - def test_delete_network_xml(self): - self._test_delete_network(format='xml') - - def test_delete_network_alt_tenant(self): - self._test_delete_network(tenant=TENANT_2) - - def test_delete_network_error_470(self): - self._test_delete_network(status=470) - - def test_delete_network_error_401(self): - self._test_delete_network(status=401) - - def test_delete_network_error_420(self): - self._test_delete_network(status=420) - - def test_delete_network_error_421(self): - self._test_delete_network(status=421) - - def test_list_ports_json(self): - self._test_list_ports(format='json') - - def test_list_ports_xml(self): - self._test_list_ports(format='xml') - - def test_list_ports_alt_tenant(self): - self._test_list_ports(tenant=TENANT_2) - - def test_list_ports_error_470(self): - self._test_list_ports(status=470) - - def test_list_ports_error_401(self): - self._test_list_ports(status=401) - - def test_list_ports_error_420(self): - self._test_list_ports(status=420) - - def test_list_port_details_json(self): - self._test_list_ports(format='json') - - def test_list_port_details_xml(self): - self._test_list_ports(format='xml') - - def test_list_port_details_alt_tenant(self): - self._test_list_ports(tenant=TENANT_2) - - def test_list_port_details_error_470(self): - self._test_list_port_details(status=470) - - def test_list_port_details_error_401(self): - self._test_list_ports(status=401) - - def test_list_port_details_error_420(self): - self._test_list_ports(status=420) - - def test_list_port_details_error_430(self): - self._test_list_ports(status=430) - - def test_create_port_json(self): - self._test_create_port(format='json') - - def test_create_port_xml(self): - self._test_create_port(format='xml') - - def test_create_port_alt_tenant(self): - self._test_create_port(tenant=TENANT_2) - - def test_create_port_error_470(self): - self._test_create_port(status=470) - - def test_create_port_error_401(self): - self._test_create_port(status=401) - - def test_create_port_error_400(self): - self._test_create_port(status=400) - - def test_create_port_error_420(self): - self._test_create_port(status=420) - - def test_create_port_error_430(self): - self._test_create_port(status=430) - - def test_create_port_error_431(self): - self._test_create_port(status=431) - - def test_delete_port_json(self): - self._test_delete_port(format='json') - - def test_delete_port_xml(self): - self._test_delete_port(format='xml') - - def test_delete_port_alt_tenant(self): - self._test_delete_port(tenant=TENANT_2) - - def test_delete_port_error_470(self): - self._test_delete_port(status=470) - - def test_delete_port_error_401(self): - self._test_delete_port(status=401) - - def test_delete_port_error_420(self): - self._test_delete_port(status=420) - - def test_delete_port_error_430(self): - self._test_delete_port(status=430) - - def test_delete_port_error_432(self): - self._test_delete_port(status=432) - - def test_set_port_state_json(self): - self._test_set_port_state(format='json') - - def test_set_port_state_xml(self): - self._test_set_port_state(format='xml') - - def test_set_port_state_alt_tenant(self): - self._test_set_port_state(tenant=TENANT_2) - - def test_set_port_state_error_470(self): - self._test_set_port_state(status=470) - - def test_set_port_state_error_401(self): - self._test_set_port_state(status=401) - - def test_set_port_state_error_400(self): - self._test_set_port_state(status=400) - - def test_set_port_state_error_420(self): - self._test_set_port_state(status=420) - - def test_set_port_state_error_430(self): - self._test_set_port_state(status=430) - - def test_set_port_state_error_431(self): - self._test_set_port_state(status=431) - - def test_list_port_attachments_json(self): - self._test_list_port_attachments(format='json') - - def test_list_port_attachments_xml(self): - self._test_list_port_attachments(format='xml') - - def test_list_port_attachments_alt_tenant(self): - self._test_list_port_attachments(tenant=TENANT_2) - - def test_list_port_attachments_error_470(self): - self._test_list_port_attachments(status=470) - - def test_list_port_attachments_error_401(self): - self._test_list_port_attachments(status=401) - - def test_list_port_attachments_error_400(self): - self._test_list_port_attachments(status=400) - - def test_list_port_attachments_error_420(self): - self._test_list_port_attachments(status=420) - - def test_list_port_attachments_error_430(self): - self._test_list_port_attachments(status=430) - - def test_attach_resource_json(self): - self._test_attach_resource(format='json') - - def test_attach_resource_xml(self): - self._test_attach_resource(format='xml') - - def test_attach_resource_alt_tenant(self): - self._test_attach_resource(tenant=TENANT_2) - - def test_attach_resource_error_470(self): - self._test_attach_resource(status=470) - - def test_attach_resource_error_401(self): - self._test_attach_resource(status=401) - - def test_attach_resource_error_400(self): - self._test_attach_resource(status=400) - - def test_attach_resource_error_420(self): - self._test_attach_resource(status=420) - - def test_attach_resource_error_430(self): - self._test_attach_resource(status=430) - - def test_attach_resource_error_432(self): - self._test_attach_resource(status=432) - - def test_attach_resource_error_440(self): - self._test_attach_resource(status=440) - - def test_detach_resource_json(self): - self._test_detach_resource(format='json') - - def test_detach_resource_xml(self): - self._test_detach_resource(format='xml') - - def test_detach_resource_alt_tenant(self): - self._test_detach_resource(tenant=TENANT_2) - - def test_detach_resource_error_470(self): - self._test_detach_resource(status=470) - - def test_detach_resource_error_401(self): - self._test_detach_resource(status=401) - - def test_detach_resource_error_420(self): - self._test_detach_resource(status=420) - - def test_detach_resource_error_430(self): - self._test_detach_resource(status=430) - - def test_ssl_certificates(self): - self._test_ssl_certificates() From 9ad5564566940f7557edd445178941cfad44bac9 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 9 Aug 2011 09:15:04 -0700 Subject: [PATCH 69/76] update CLI to use show instead of list for calls that do not return a list --- quantum/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quantum/cli.py b/quantum/cli.py index a015565d872..6b19f98fcff 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -104,7 +104,7 @@ def detail_net(manager, *args): def api_detail_net(client, *args): tid, nid = args try: - res = client.list_network_details(nid)["networks"]["network"] + res = client.show_network_details(nid)["networks"]["network"] except Exception, e: LOG.error("Failed to get network details: %s" % e) return @@ -119,7 +119,7 @@ def api_detail_net(client, *args): print "Remote Interfaces on Virtual Network:%s\n" % nid for port in ports["ports"]: pid = port["id"] - res = client.list_port_attachments(nid, pid) + res = client.show_port_attachment(nid, pid) LOG.debug(res) remote_iface = res["attachment"] print "\tRemote interface:%s" % remote_iface @@ -214,7 +214,7 @@ def detail_port(manager, *args): def api_detail_port(client, *args): tid, nid, pid = args try: - port = client.list_port_details(nid, pid)["ports"]["port"] + port = client.show_port_details(nid, pid)["ports"]["port"] except Exception, e: LOG.error("Failed to get port details: %s" % e) return From 48349e32d267d0a48f95c963e9094de1ece1d35a Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Tue, 9 Aug 2011 17:14:48 -0700 Subject: [PATCH 70/76] Tiny change to the README file, instructions on how to get ncclient. --- quantum/plugins/cisco/README | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 3275b223117..68d1b5cf424 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -48,8 +48,14 @@ NX-OS version and packages to enable Nexus support: * paramiko library - SSHv2 protocol library for python ** To install on RHEL 6.1, run: yum install python-paramiko * ncclient v0.3.1 - Python library for NETCONF clients - ** RedHat does not provide a package for ncclient in RHEL 6.1. - ** See http://schmizz.net/ncclient/ for documentation and downloads. + ** RedHat does not provide a package for ncclient in RHEL 6.1. Here is how + to get it, from your shell prompt do: + + git clone git@github.com:ddutta/ncclient.git + sudo python ./setup.py install + + ** For more information of ncclient, see: + http://schmizz.net/ncclient/ To verify the version of any package you have installed on your system, run "rpm -qav | grep ", where is the From f4d7acfe54a4cf03d7b33723c85af88fa6641fee Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 9 Aug 2011 19:15:14 -0700 Subject: [PATCH 71/76] adding renamed client-lib tests --- tests/unit/test_clientlib.py | 625 +++++++++++++++++++++++++++++++++++ 1 file changed, 625 insertions(+) create mode 100644 tests/unit/test_clientlib.py diff --git a/tests/unit/test_clientlib.py b/tests/unit/test_clientlib.py new file mode 100644 index 00000000000..128d0692344 --- /dev/null +++ b/tests/unit/test_clientlib.py @@ -0,0 +1,625 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Cisco Systems +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# @author: Tyler Smith, Cisco Systems + +import logging +import unittest +import re + +from quantum.common.wsgi import Serializer +from quantum.client import Client + +LOG = logging.getLogger('quantum.tests.test_api') + +# Set a couple tenants to use for testing +TENANT_1 = 'totore' +TENANT_2 = 'totore2' + + +class ServerStub(): + """This class stubs a basic server for the API client to talk to""" + + class Response(object): + """This class stubs a basic response to send the API client""" + def __init__(self, content=None, status=None): + self.content = content + self.status = status + + def read(self): + return self.content + + def status(self): + return status + + # To test error codes, set the host to 10.0.0.1, and the port to the code + def __init__(self, host, port=9696, key_file="", cert_file=""): + self.host = host + self.port = port + self.key_file = key_file + self.cert_file = cert_file + + def request(self, method, action, body, headers): + self.method = method + self.action = action + self.body = body + + def status(self, status=None): + return status or 200 + + def getresponse(self): + res = self.Response(status=self.status()) + + # If the host is 10.0.0.1, return the port as an error code + if self.host == "10.0.0.1": + res.status = self.port + return res + + # Extract important information from the action string to assure sanity + match = re.search('tenants/(.+?)/(.+)\.(json|xml)$', self.action) + + tenant = match.group(1) + path = match.group(2) + format = match.group(3) + + data = {'data': {'method': self.method, 'action': self.action, + 'body': self.body, 'tenant': tenant, 'path': path, + 'format': format, 'key_file': self.key_file, + 'cert_file': self.cert_file}} + + # Serialize it to the proper format so the API client can handle it + if data['data']['format'] == 'json': + res.content = Serializer().serialize(data, "application/json") + else: + res.content = Serializer().serialize(data, "application/xml") + return res + + +class APITest(unittest.TestCase): + + def setUp(self): + """ Setups a test environment for the API client """ + HOST = '127.0.0.1' + PORT = 9696 + USE_SSL = False + + self.client = Client(HOST, PORT, USE_SSL, TENANT_1, 'json', ServerStub) + + def _assert_sanity(self, call, status, method, path, data=[], params={}): + """ Perform common assertions to test the sanity of client requests """ + + # Handle an error case first + if status != 200: + (self.client.host, self.client.port) = ("10.0.0.1", status) + self.assertRaises(Exception, call, *data, **params) + return + + # Make the call, then get the data from the root node and assert it + data = call(*data, **params)['data'] + + self.assertEqual(data['method'], method) + self.assertEqual(data['format'], params['format']) + self.assertEqual(data['tenant'], params['tenant']) + self.assertEqual(data['path'], path) + + return data + + def _test_list_networks(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_list_networks - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.list_networks, + status, + "GET", + "networks", + data=[], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_list_networks - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_show_network_details(self, + tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_show_network_details - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.show_network_details, + status, + "GET", + "networks/001", + data=["001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_show_network_details - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_create_network(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_create_network - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.create_network, + status, + "POST", + "networks", + data=[{'network': {'net-name': 'testNetwork'}}], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_create_network - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_update_network(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_update_network - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.update_network, + status, + "PUT", + "networks/001", + data=["001", + {'network': {'net-name': 'newName'}}], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_update_network - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_delete_network(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_delete_network - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.delete_network, + status, + "DELETE", + "networks/001", + data=["001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_delete_network - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_list_ports(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_list_ports - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.list_ports, + status, + "GET", + "networks/001/ports", + data=["001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_list_ports - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_show_port_details(self, + tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_show_port_details - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.show_port_details, + status, + "GET", + "networks/001/ports/001", + data=["001", "001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_show_port_details - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_create_port(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_create_port - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.create_port, + status, + "POST", + "networks/001/ports", + data=["001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_create_port - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_delete_port(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_delete_port - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.delete_port, + status, + "DELETE", + "networks/001/ports/001", + data=["001", "001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_delete_port - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_set_port_state(self, tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_set_port_state - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.set_port_state, + status, + "PUT", + "networks/001/ports/001", + data=["001", "001", + {'port': {'state': 'ACTIVE'}}], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_set_port_state - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_show_port_attachment(self, + tenant=TENANT_1, format='json', status=200): + LOG.debug("_test_show_port_attachment - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.show_port_attachment, + status, + "GET", + "networks/001/ports/001/attachment", + data=["001", "001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_show_port_attachment - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_attach_resource(self, tenant=TENANT_1, + format='json', status=200): + LOG.debug("_test_attach_resource - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.attach_resource, + status, + "PUT", + "networks/001/ports/001/attachment", + data=["001", "001", + {'resource': {'id': '1234'}}], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_attach_resource - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_detach_resource(self, tenant=TENANT_1, + format='json', status=200): + LOG.debug("_test_detach_resource - tenant:%s "\ + "- format:%s - START", format, tenant) + + self._assert_sanity(self.client.detach_resource, + status, + "DELETE", + "networks/001/ports/001/attachment", + data=["001", "001"], + params={'tenant': tenant, 'format': format}) + + LOG.debug("_test_detach_resource - tenant:%s "\ + "- format:%s - END", format, tenant) + + def _test_ssl_certificates(self, tenant=TENANT_1, + format='json', status=200): + LOG.debug("_test_ssl_certificates - tenant:%s "\ + "- format:%s - START", format, tenant) + + # Set SSL, and our cert file + self.client.use_ssl = True + cert_file = "/fake.cert" + self.client.key_file = self.client.cert_file = cert_file + + data = self._assert_sanity(self.client.list_networks, + status, + "GET", + "networks", + data=[], + params={'tenant': tenant, 'format': format}) + + self.assertEquals(data["key_file"], cert_file) + self.assertEquals(data["cert_file"], cert_file) + + LOG.debug("_test_ssl_certificates - tenant:%s "\ + "- format:%s - END", format, tenant) + + def test_list_networks_json(self): + self._test_list_networks(format='json') + + def test_list_networks_xml(self): + self._test_list_networks(format='xml') + + def test_list_networks_alt_tenant(self): + self._test_list_networks(tenant=TENANT_2) + + def test_list_networks_error_470(self): + self._test_list_networks(status=470) + + def test_list_networks_error_401(self): + self._test_list_networks(status=401) + + def test_show_network_details_json(self): + self._test_show_network_details(format='json') + + def test_show_network_details_xml(self): + self._test_show_network_details(format='xml') + + def test_show_network_details_alt_tenant(self): + self._test_show_network_details(tenant=TENANT_2) + + def test_show_network_details_error_470(self): + self._test_show_network_details(status=470) + + def test_show_network_details_error_401(self): + self._test_show_network_details(status=401) + + def test_show_network_details_error_420(self): + self._test_show_network_details(status=420) + + def test_create_network_json(self): + self._test_create_network(format='json') + + def test_create_network_xml(self): + self._test_create_network(format='xml') + + def test_create_network_alt_tenant(self): + self._test_create_network(tenant=TENANT_2) + + def test_create_network_error_470(self): + self._test_create_network(status=470) + + def test_create_network_error_401(self): + self._test_create_network(status=401) + + def test_create_network_error_400(self): + self._test_create_network(status=400) + + def test_create_network_error_422(self): + self._test_create_network(status=422) + + def test_update_network_json(self): + self._test_update_network(format='json') + + def test_update_network_xml(self): + self._test_update_network(format='xml') + + def test_update_network_alt_tenant(self): + self._test_update_network(tenant=TENANT_2) + + def test_update_network_error_470(self): + self._test_update_network(status=470) + + def test_update_network_error_401(self): + self._test_update_network(status=401) + + def test_update_network_error_400(self): + self._test_update_network(status=400) + + def test_update_network_error_420(self): + self._test_update_network(status=420) + + def test_update_network_error_422(self): + self._test_update_network(status=422) + + def test_delete_network_json(self): + self._test_delete_network(format='json') + + def test_delete_network_xml(self): + self._test_delete_network(format='xml') + + def test_delete_network_alt_tenant(self): + self._test_delete_network(tenant=TENANT_2) + + def test_delete_network_error_470(self): + self._test_delete_network(status=470) + + def test_delete_network_error_401(self): + self._test_delete_network(status=401) + + def test_delete_network_error_420(self): + self._test_delete_network(status=420) + + def test_delete_network_error_421(self): + self._test_delete_network(status=421) + + def test_list_ports_json(self): + self._test_list_ports(format='json') + + def test_list_ports_xml(self): + self._test_list_ports(format='xml') + + def test_list_ports_alt_tenant(self): + self._test_list_ports(tenant=TENANT_2) + + def test_list_ports_error_470(self): + self._test_list_ports(status=470) + + def test_list_ports_error_401(self): + self._test_list_ports(status=401) + + def test_list_ports_error_420(self): + self._test_list_ports(status=420) + + def test_show_port_details_json(self): + self._test_list_ports(format='json') + + def test_show_port_details_xml(self): + self._test_list_ports(format='xml') + + def test_show_port_details_alt_tenant(self): + self._test_list_ports(tenant=TENANT_2) + + def test_show_port_details_error_470(self): + self._test_show_port_details(status=470) + + def test_show_port_details_error_401(self): + self._test_show_port_details(status=401) + + def test_show_port_details_error_420(self): + self._test_show_port_details(status=420) + + def test_show_port_details_error_430(self): + self._test_show_port_details(status=430) + + def test_create_port_json(self): + self._test_create_port(format='json') + + def test_create_port_xml(self): + self._test_create_port(format='xml') + + def test_create_port_alt_tenant(self): + self._test_create_port(tenant=TENANT_2) + + def test_create_port_error_470(self): + self._test_create_port(status=470) + + def test_create_port_error_401(self): + self._test_create_port(status=401) + + def test_create_port_error_400(self): + self._test_create_port(status=400) + + def test_create_port_error_420(self): + self._test_create_port(status=420) + + def test_create_port_error_430(self): + self._test_create_port(status=430) + + def test_create_port_error_431(self): + self._test_create_port(status=431) + + def test_delete_port_json(self): + self._test_delete_port(format='json') + + def test_delete_port_xml(self): + self._test_delete_port(format='xml') + + def test_delete_port_alt_tenant(self): + self._test_delete_port(tenant=TENANT_2) + + def test_delete_port_error_470(self): + self._test_delete_port(status=470) + + def test_delete_port_error_401(self): + self._test_delete_port(status=401) + + def test_delete_port_error_420(self): + self._test_delete_port(status=420) + + def test_delete_port_error_430(self): + self._test_delete_port(status=430) + + def test_delete_port_error_432(self): + self._test_delete_port(status=432) + + def test_set_port_state_json(self): + self._test_set_port_state(format='json') + + def test_set_port_state_xml(self): + self._test_set_port_state(format='xml') + + def test_set_port_state_alt_tenant(self): + self._test_set_port_state(tenant=TENANT_2) + + def test_set_port_state_error_470(self): + self._test_set_port_state(status=470) + + def test_set_port_state_error_401(self): + self._test_set_port_state(status=401) + + def test_set_port_state_error_400(self): + self._test_set_port_state(status=400) + + def test_set_port_state_error_420(self): + self._test_set_port_state(status=420) + + def test_set_port_state_error_430(self): + self._test_set_port_state(status=430) + + def test_set_port_state_error_431(self): + self._test_set_port_state(status=431) + + def test_show_port_attachment_json(self): + self._test_show_port_attachment(format='json') + + def test_show_port_attachment_xml(self): + self._test_show_port_attachment(format='xml') + + def test_show_port_attachment_alt_tenant(self): + self._test_show_port_attachment(tenant=TENANT_2) + + def test_show_port_attachment_error_470(self): + self._test_show_port_attachment(status=470) + + def test_show_port_attachment_error_401(self): + self._test_show_port_attachment(status=401) + + def test_show_port_attachment_error_400(self): + self._test_show_port_attachment(status=400) + + def test_show_port_attachment_error_420(self): + self._test_show_port_attachment(status=420) + + def test_show_port_attachment_error_430(self): + self._test_show_port_attachment(status=430) + + def test_attach_resource_json(self): + self._test_attach_resource(format='json') + + def test_attach_resource_xml(self): + self._test_attach_resource(format='xml') + + def test_attach_resource_alt_tenant(self): + self._test_attach_resource(tenant=TENANT_2) + + def test_attach_resource_error_470(self): + self._test_attach_resource(status=470) + + def test_attach_resource_error_401(self): + self._test_attach_resource(status=401) + + def test_attach_resource_error_400(self): + self._test_attach_resource(status=400) + + def test_attach_resource_error_420(self): + self._test_attach_resource(status=420) + + def test_attach_resource_error_430(self): + self._test_attach_resource(status=430) + + def test_attach_resource_error_432(self): + self._test_attach_resource(status=432) + + def test_attach_resource_error_440(self): + self._test_attach_resource(status=440) + + def test_detach_resource_json(self): + self._test_detach_resource(format='json') + + def test_detach_resource_xml(self): + self._test_detach_resource(format='xml') + + def test_detach_resource_alt_tenant(self): + self._test_detach_resource(tenant=TENANT_2) + + def test_detach_resource_error_470(self): + self._test_detach_resource(status=470) + + def test_detach_resource_error_401(self): + self._test_detach_resource(status=401) + + def test_detach_resource_error_420(self): + self._test_detach_resource(status=420) + + def test_detach_resource_error_430(self): + self._test_detach_resource(status=430) + + def test_ssl_certificates(self): + self._test_ssl_certificates() From 6ab0309d0bb6bf7bf30da9c6b531f43dea3c4db1 Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Thu, 11 Aug 2011 12:15:12 -0700 Subject: [PATCH 72/76] Adding automattic serialization to all requests by moving it to do_request --- quantum/client.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/quantum/client.py b/quantum/client.py index ca05236faae..9297d3e879f 100644 --- a/quantum/client.py +++ b/quantum/client.py @@ -118,6 +118,9 @@ class Client(object): if type(params) is dict: action += '?' + urllib.urlencode(params) + if body != None: + body = self.serialize(body) + try: connection_type = self.get_connection_type() headers = headers or {"Content-Type": @@ -190,7 +193,6 @@ class Client(object): """ Creates a new network on the server """ - body = self.serialize(body) return self.do_request("POST", self.networks_path, body=body) @api_call @@ -198,7 +200,6 @@ class Client(object): """ Updates a network on the server """ - body = self.serialize(body) return self.do_request("PUT", self.network_path % (network), body=body) @api_call @@ -241,7 +242,6 @@ class Client(object): """ Sets the state of a port on the server """ - body = self.serialize(body) return self.do_request("PUT", self.port_path % (network, port), body=body) @@ -257,7 +257,6 @@ class Client(object): """ Deletes a port from a network on the server """ - body = self.serialize(body) return self.do_request("PUT", self.attachment_path % (network, port), body=body) From c737329187be4e4d20e8b81158bf1bfeaddef924 Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Thu, 11 Aug 2011 15:44:50 -0700 Subject: [PATCH 73/76] Simplifying condition --- quantum/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantum/client.py b/quantum/client.py index 9297d3e879f..85d4057147e 100644 --- a/quantum/client.py +++ b/quantum/client.py @@ -118,7 +118,7 @@ class Client(object): if type(params) is dict: action += '?' + urllib.urlencode(params) - if body != None: + if body: body = self.serialize(body) try: From d8563be5237dbb4720830d2f42fc2e4fb25a5d45 Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Sat, 13 Aug 2011 18:28:02 -0700 Subject: [PATCH 74/76] Removed main from modules as per review comments. --- quantum/plugins/cisco/README | 2 ++ .../cisco/common/cisco_configparser.py | 9 -------- .../plugins/cisco/common/cisco_credentials.py | 8 ------- .../cisco/common/cisco_nova_configuration.py | 7 ------ quantum/plugins/cisco/l2network_model.py | 7 ------ quantum/plugins/cisco/l2network_plugin.py | 13 ----------- .../cisco/l2network_plugin_configuration.py | 7 ------ .../cisco/nexus/cisco_nexus_configuration.py | 7 ------ .../cisco/nexus/cisco_nexus_network_driver.py | 7 ------ quantum/plugins/cisco/ucs/cisco_getvif.py | 5 ----- .../cisco/ucs/cisco_ucs_configuration.py | 7 ------ .../cisco/ucs/cisco_ucs_network_driver.py | 22 ------------------- 12 files changed, 2 insertions(+), 99 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 68d1b5cf424..7b214202e06 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -32,6 +32,8 @@ Let's leap in! Pre-requisites -------------- +(The following are necessary only when using the UCS and/or Nexus devices in your system. +If you plan to just leverage the plugin framework, you do not need these.) * One or more UCS B200 series blade servers with M81KR VIC (aka Palo adapters) installed. * UCSM 2.0 (Capitola) Build 230 or above. diff --git a/quantum/plugins/cisco/common/cisco_configparser.py b/quantum/plugins/cisco/common/cisco_configparser.py index 312c230acba..ec7bae3cf1d 100644 --- a/quantum/plugins/cisco/common/cisco_configparser.py +++ b/quantum/plugins/cisco/common/cisco_configparser.py @@ -38,12 +38,3 @@ class CiscoConfigParser(ConfigObj): def dummy(self, section, key): return section[key] - - -def main(): - cp = CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ - + "/" + "test.ini") - print ("%s\n") % cp['PLUGIN']['provider'] - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/common/cisco_credentials.py b/quantum/plugins/cisco/common/cisco_credentials.py index edc2288cdea..74948cd207a 100644 --- a/quantum/plugins/cisco/common/cisco_credentials.py +++ b/quantum/plugins/cisco/common/cisco_credentials.py @@ -58,11 +58,3 @@ class Store(object): @staticmethod def deleteCredential(id): return _creds_dictionary.pop(id) - - -def main(): - Store.putCredential("10.10.10.10", "foo", "bar") - print ("%s\n") % Store.getCredentials() - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/common/cisco_nova_configuration.py b/quantum/plugins/cisco/common/cisco_nova_configuration.py index f1cbf2a1ec2..5e6d9e48518 100644 --- a/quantum/plugins/cisco/common/cisco_nova_configuration.py +++ b/quantum/plugins/cisco/common/cisco_nova_configuration.py @@ -33,10 +33,3 @@ DB_USERNAME = section['db_username'] DB_PASSWORD = section['db_password'] NOVA_HOST_NAME = section['nova_host_name'] NOVA_PROJ_NAME = section['nova_proj_name'] - - -def main(): - print NOVA_PROJ_NAME - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/l2network_model.py b/quantum/plugins/cisco/l2network_model.py index e24e0a372ff..ba787019952 100644 --- a/quantum/plugins/cisco/l2network_model.py +++ b/quantum/plugins/cisco/l2network_model.py @@ -98,10 +98,3 @@ class L2NetworkModel(L2NetworkModelBase): def unplug_interface(self, args): deviceParams = {const.DEVICE_IP: ""} self._invokeUCSPlugin(self._funcName(), args, deviceParams) - - -def main(): - client = L2NetworkModel() - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py index f627de25414..3c365d19e74 100644 --- a/quantum/plugins/cisco/l2network_plugin.py +++ b/quantum/plugins/cisco/l2network_plugin.py @@ -388,19 +388,6 @@ class L2Network(QuantumPluginBase): def _funcName(self, offset=0): return inspect.stack()[1 + offset][3] - -def main(): - client = L2Network() - """ - client.create_portprofile("12345", "tpp1", "2") - client.create_portprofile("12345", "tpp2", "3") - print ("%s\n") % client.get_all_portprofiles("12345") - """ - - -if __name__ == '__main__': - main() - """ TODO (Sumit): (1) Persistent storage diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index 81f221f0393..25df54d3817 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -48,10 +48,3 @@ CONF_FILE = "conf/plugins.ini" cp = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \ + "/" + CONF_FILE) plugins = cp.walk(cp.dummy) - - -def main(): - print plugins['PLUGINS'] - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py index f1ad1490281..d43d193f081 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py @@ -33,10 +33,3 @@ NEXUS_PORT = section['nexus_port'] section = cp['DRIVER'] NEXUS_DRIVER = section['name'] - - -def main(): - print NEXUS_PORT - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py index 78090d5f9cf..02380dc9e3f 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -234,10 +234,3 @@ class CiscoNEXUSDriver(): nexus_password) as m: self.disable_vlan(m, vlan_id) self.disable_switch_port(m, nexus_interface) - - -def main(): - client = CiscoNEXUSDriver() - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/ucs/cisco_getvif.py b/quantum/plugins/cisco/ucs/cisco_getvif.py index f00116ee1fc..b80d99a9869 100644 --- a/quantum/plugins/cisco/ucs/cisco_getvif.py +++ b/quantum/plugins/cisco/ucs/cisco_getvif.py @@ -49,8 +49,3 @@ def get_next_dynic(argv=[]): if not used: break return eth - -if __name__ == '__main__': - #nic = get_next_dynic(sys.argv) - nic = get_next_dynic() - print nic diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py index 30537417124..1ff57237c1a 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_configuration.py @@ -35,10 +35,3 @@ PROFILE_NAME_PREFIX = section['profile_name_prefix'] section = cp['DRIVER'] UCSM_DRIVER = section['name'] - - -def main(): - print MAX_UCSM_PORT_PROFILES - -if __name__ == '__main__': - main() diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py index acf0df100de..5d860ddca60 100644 --- a/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py +++ b/quantum/plugins/cisco/ucs/cisco_ucs_network_driver.py @@ -233,25 +233,3 @@ class CiscoUCSMDriver(): def release_dynamic_nic(self, host): # TODO (Sumit): Release on a specific host pass - - -def main(): - client = CiscoUCSMDriver() - #client.create_vlan("quantum-vlan-3", "3","172.20.231.27","admin", - # "c3l12345") - #client.create_profile("q-prof-3", "quantum-vlan-3","172.20.231.27", - # "admin", "c3l12345") - #client.get_dynamic_nic("dummy") - #client.get_dynamic_nic("dummy") - #client.release_dynamic_nic("dummy") - print client.get_dynamic_nic("dummy") - """ - client.change_vlan_in_profile("br100", "default", "test-2", - "172.20.231.27","admin", - "c3l12345") - client.change_vlan_in_profile("br100", "test-2", "default", - "172.20.231.27", "admin", "c3l12345") - """ - -if __name__ == '__main__': - main() From ff74c1deb0914530231582081ab2e736fe9ce443 Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Mon, 15 Aug 2011 09:02:24 -0700 Subject: [PATCH 75/76] Removing extra testing function on Nexus Driver --- .../plugins/cisco/nexus/cisco_nexus_network_driver.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py index 02380dc9e3f..c7b4d801a4d 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver.py @@ -210,16 +210,6 @@ class CiscoNEXUSDriver(): print confstr mgr.edit_config(target='running', config=confstr) - def test_nxos_api(self, host, user, password): - with self.nxos_connect(host, port=22, user=user, - password=password) as m: - #enable_vlan(m, '100', 'ccn1') - #enable_vlan_on_trunk_int(m, '2/1', '100') - #disable_vlan_on_trunk_int(m, '2/1', '100') - #disable_vlan(m, '100') - result = m.get(("subtree", filter_show_vlan_brief_snippet)) - print result - def create_vlan(self, vlan_name, vlan_id, nexus_host, nexus_user, nexus_password, nexus_interface): #TODO (Edgar) Move the SSH port to the configuration file From 9c6bfadfaeec0dee815df345bd5f36d696724fda Mon Sep 17 00:00:00 2001 From: Sumit Naiksatam Date: Mon, 15 Aug 2011 10:13:08 -0700 Subject: [PATCH 76/76] Changes in the README file to incorporate Somik's comments. --- quantum/plugins/cisco/README | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index 7b214202e06..b2ecf449a9a 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -1,6 +1,6 @@ -============================================ -README: Quantum L2 Network Plugin Framework -============================================ +===================================================================== +README: A Framework for a Quantum Plugin Supporting Multiple Switches +===================================================================== :Author: Sumit Naiksatam, Ram Durairaj, Mark Voelker, Edgar Magana, Shweta Padubidri, Rohit Agarwalla, Ying Liu :Contact: netstack@lists.launchpad.net @@ -15,8 +15,8 @@ Introduction This plugin implementation provides the following capabilities to help you take your Layer 2 network for a Quantum leap: -* A reference implementation of plugin framework for L2 network -* Supports multiple switches in the network +* A reference implementation a framework for a Quantum Plugin +to use multiple devices/switches in a L2 network * Supports multiple models of switches concurrently * Supports Cisco UCS blade servers with M81KR Virtual Interface Cards (aka "Palo adapters") via 802.1Qbh.