From f6315adcd2b4130126202073a417eff160b12c0d Mon Sep 17 00:00:00 2001 From: Edgar Magana Date: Fri, 8 Feb 2013 09:01:34 -0800 Subject: [PATCH] PLUMgrid quantum plugin PLUMgrid plugin supports Quantum Core V2 APIs over an infrastructure running PLUMgrid Network Virtualization Platform. The plugin will interact directly with the Hypervisor layer to provide all the networking functionality requested by Quantum APIs. It will be based on a controller-mode implementation were all resources state will be controlled and handled by the plugin. Implements: blueprint plumgrid-quantum-plugin Change-Id: I6bc8d32b83982e10f661913e85494468cf10e0e2 --- etc/quantum/plugins/plumgrid/plumgrid.ini | 39 ++ quantum/plugins/plumgrid/README | 7 + quantum/plugins/plumgrid/__init__.py | 17 + quantum/plugins/plumgrid/common/__init__.py | 17 + quantum/plugins/plumgrid/common/exceptions.py | 31 ++ .../plumgrid/plumgrid_nos_plugin/__init__.py | 17 + .../plumgrid_nos_plugin/plugin_ver.py | 19 + .../plumgrid_nos_snippets.py | 44 +++ .../plumgrid_nos_plugin/plumgrid_plugin.py | 344 ++++++++++++++++++ .../plumgrid_nos_plugin/rest_connection.py | 92 +++++ quantum/tests/unit/plumgrid/__init__.py | 17 + .../unit/plumgrid/test_plumgrid_plugin.py | 77 ++++ setup.py | 3 + 13 files changed, 724 insertions(+) create mode 100644 etc/quantum/plugins/plumgrid/plumgrid.ini create mode 100644 quantum/plugins/plumgrid/README create mode 100644 quantum/plugins/plumgrid/__init__.py create mode 100644 quantum/plugins/plumgrid/common/__init__.py create mode 100644 quantum/plugins/plumgrid/common/exceptions.py create mode 100644 quantum/plugins/plumgrid/plumgrid_nos_plugin/__init__.py create mode 100644 quantum/plugins/plumgrid/plumgrid_nos_plugin/plugin_ver.py create mode 100644 quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_nos_snippets.py create mode 100644 quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_plugin.py create mode 100644 quantum/plugins/plumgrid/plumgrid_nos_plugin/rest_connection.py create mode 100644 quantum/tests/unit/plumgrid/__init__.py create mode 100644 quantum/tests/unit/plumgrid/test_plumgrid_plugin.py diff --git a/etc/quantum/plugins/plumgrid/plumgrid.ini b/etc/quantum/plugins/plumgrid/plumgrid.ini new file mode 100644 index 0000000000..4a1068b225 --- /dev/null +++ b/etc/quantum/plugins/plumgrid/plumgrid.ini @@ -0,0 +1,39 @@ +# Config file for Quantum PLUMgrid plugin + +[DATABASE] +# This line MUST be changed to actually run the plugin. +# Example: +# sql_connection = mysql://:@:3306/plumgrid_quantum +# Replace above with the IP address of the database used by the +# main quantum server. +# sql_connection = sqlite:// +# Database reconnection retry times - in event connectivity is lost +# set to -1 implies an infinite retry count +# sql_max_retries = 10 +# Database reconnection interval in seconds - if the initial connection to the +# database fails +# reconnect_interval = 2 +# Enable the use of eventlet's db_pool for MySQL. The flags sql_min_pool_size, +# sql_max_pool_size and sql_idle_timeout are relevant only if this is enabled. +# sql_dbpool_enable = False +# Minimum number of SQL connections to keep open in a pool +# sql_min_pool_size = 1 +# Maximum number of SQL connections to keep open in a pool +# sql_max_pool_size = 5 +# Timeout in seconds before idle sql connections are reaped +# sql_idle_timeout = 3600 + +[PLUMgridNOS] +# This line should be pointing to the NOS server, +# for the PLUMgrid platform. In other deployments, +# this is known as controller +# nos_server= +# nos_server_port= +# Authentification parameters for the NOS server. +# These are the admin credentials to manage and control +# the NOS server. +# username= +# password= +# servertimeout=5 +# Name of the network topology to be deployed by NOS +# topologyname= diff --git a/quantum/plugins/plumgrid/README b/quantum/plugins/plumgrid/README new file mode 100644 index 0000000000..0d1de33258 --- /dev/null +++ b/quantum/plugins/plumgrid/README @@ -0,0 +1,7 @@ +PLUMgrid Quantum Virtual Network Plugin + +This plugin implements Quantum v2 APIs and helps configure +L2/L3 virtual networks consisting of PLUMgrid Platform. + +For more details on use please refer to: +http://wiki.openstack.org/plumgrid-quantum diff --git a/quantum/plugins/plumgrid/__init__.py b/quantum/plugins/plumgrid/__init__.py new file mode 100644 index 0000000000..39e9b8d130 --- /dev/null +++ b/quantum/plugins/plumgrid/__init__.py @@ -0,0 +1,17 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. diff --git a/quantum/plugins/plumgrid/common/__init__.py b/quantum/plugins/plumgrid/common/__init__.py new file mode 100644 index 0000000000..39e9b8d130 --- /dev/null +++ b/quantum/plugins/plumgrid/common/__init__.py @@ -0,0 +1,17 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. diff --git a/quantum/plugins/plumgrid/common/exceptions.py b/quantum/plugins/plumgrid/common/exceptions.py new file mode 100644 index 0000000000..4201cce168 --- /dev/null +++ b/quantum/plugins/plumgrid/common/exceptions.py @@ -0,0 +1,31 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. + + +""" Quantum PLUMgrid Plugin exceptions """ + +from quantum.common import exceptions as base_exec + + +class PLUMgridException(base_exec.QuantumException): + message = _("An unexpected error occurred in the PLUMgrid Plugin: " + "%(err_msg)s") + + +class PLUMgridConnectionFailed(PLUMgridException): + message = _("Connection failed with PLUMgrid NOS: %(err_msg)s") diff --git a/quantum/plugins/plumgrid/plumgrid_nos_plugin/__init__.py b/quantum/plugins/plumgrid/plumgrid_nos_plugin/__init__.py new file mode 100644 index 0000000000..39e9b8d130 --- /dev/null +++ b/quantum/plugins/plumgrid/plumgrid_nos_plugin/__init__.py @@ -0,0 +1,17 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. diff --git a/quantum/plugins/plumgrid/plumgrid_nos_plugin/plugin_ver.py b/quantum/plugins/plumgrid/plumgrid_nos_plugin/plugin_ver.py new file mode 100644 index 0000000000..d9286de9cf --- /dev/null +++ b/quantum/plugins/plumgrid/plumgrid_nos_plugin/plugin_ver.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. + +VERSION = "0.1" diff --git a/quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_nos_snippets.py b/quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_nos_snippets.py new file mode 100644 index 0000000000..02faa5a758 --- /dev/null +++ b/quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_nos_snippets.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. +# @author: Brenden Blanco, bblanco@plumgrid.com, PLUMgrid, Inc. + +""" +Snippets needed by the PLUMgrid Plugin +""" + +from quantum.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +class DataNOSPLUMgrid(): + + BASE_NOS_URL = '/0/connectivity/domain/' + + def __init__(self): + LOG.info(_('QuantumPluginPLUMgrid Status: NOS Body Data Creation')) + + def create_domain_body_data(self, tenant_id): + body_data = {"container_group": tenant_id} + return body_data + + def create_network_body_data(self, tenant_id, topology_name): + body_data = {"config_template": "single_bridge", + "container_group": tenant_id, + "topology_name": topology_name} + return body_data diff --git a/quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_plugin.py b/quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_plugin.py new file mode 100644 index 0000000000..22e31f9626 --- /dev/null +++ b/quantum/plugins/plumgrid/plumgrid_nos_plugin/plumgrid_plugin.py @@ -0,0 +1,344 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. + +""" +Quantum PLUMgrid Plug-in for PLUMgrid Virtual Technology +This plugin will forward authenticated REST API calls +to the Network Operating System by PLUMgrid called NOS +""" + +import sys + +from quantum.db import api as db +from quantum.db import db_base_plugin_v2 +from quantum.openstack.common import cfg +from quantum.openstack.common import log as logging +from quantum.plugins.plumgrid.common import exceptions as plum_excep +from quantum.plugins.plumgrid.plumgrid_nos_plugin import plumgrid_nos_snippets +from quantum.plugins.plumgrid.plumgrid_nos_plugin import rest_connection +from quantum.plugins.plumgrid.plumgrid_nos_plugin.plugin_ver import VERSION + + +LOG = logging.getLogger(__name__) + + +nos_server_opts = [ + cfg.StrOpt('nos_server', default='localhost', + help=_("PLUMgrid NOS server to connect to")), + cfg.StrOpt('nos_server_port', default='8080', + help=_("PLUMgrid NOS server port to connect to")), + cfg.StrOpt('username', default='username', + help=_("PLUMgrid NOS admin username")), + cfg.StrOpt('password', default='password', secret=True, + help=_("PLUMgrid NOS admin password")), + cfg.IntOpt('servertimeout', default=5, + help=_("PLUMgrid NOS server timeout")), + cfg.IntOpt('topologyname', default='t1', + help=_("PLUMgrid NOS topology name")), ] + + +cfg.CONF.register_opts(nos_server_opts, "PLUMgridNOS") + + +class QuantumPluginPLUMgridV2(db_base_plugin_v2.QuantumDbPluginV2): + + def __init__(self): + LOG.info(_('QuantumPluginPLUMgrid Status: Starting Plugin')) + + # PLUMgrid NOS configuration + nos_plumgrid = cfg.CONF.PLUMgridNOS.nos_server + nos_port = cfg.CONF.PLUMgridNOS.nos_server_port + timeout = cfg.CONF.PLUMgridNOS.servertimeout + self.topology_name = cfg.CONF.PLUMgridNOS.topologyname + self.snippets = plumgrid_nos_snippets.DataNOSPLUMgrid() + + # TODO: (Edgar) These are placeholders for next PLUMgrid release + nos_username = cfg.CONF.PLUMgridNOS.username + nos_password = cfg.CONF.PLUMgridNOS.password + self.rest_conn = rest_connection.RestConnection(nos_plumgrid, + nos_port, timeout) + if self.rest_conn is None: + raise SystemExit(_('QuantumPluginPLUMgrid Status: ' + 'Aborting Plugin')) + + else: + # Plugin DB initialization + db.configure_db() + + # PLUMgrid NOS info validation + LOG.info(_('QuantumPluginPLUMgrid NOS: %s'), nos_plumgrid) + if not nos_plumgrid: + raise SystemExit(_('QuantumPluginPLUMgrid Status: ' + 'NOS value is missing in config file')) + + LOG.debug(_('QuantumPluginPLUMgrid Status: Quantum server with ' + 'PLUMgrid Plugin has started')) + + def create_network(self, context, network): + """ + Create network core Quantum API + """ + + LOG.debug(_('QuantumPluginPLUMgrid Status: create_network() called')) + + # Plugin DB - Network Create and validation + tenant_id = self._get_tenant_id_for_create(context, + network["network"]) + self._network_admin_state(network) + + with context.session.begin(subtransactions=True): + net = super(QuantumPluginPLUMgridV2, self).create_network(context, + network) + + try: + LOG.debug(_('QuantumPluginPLUMgrid Status: %s, %s, %s'), + tenant_id, network["network"], net["id"]) + nos_url = self.snippets.BASE_NOS_URL + net["id"] + headers = {} + body_data = self.snippets.create_domain_body_data(tenant_id) + self.rest_conn.nos_rest_conn(nos_url, + 'PUT', body_data, headers) + + except: + err_message = _("PLUMgrid NOS communication failed") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + # return created network + return net + + def update_network(self, context, net_id, network): + """ + Update network core Quantum API + """ + + LOG.debug(_("QuantumPluginPLUMgridV2.update_network() called")) + self._network_admin_state(network) + tenant_id = self._get_tenant_id_for_create(context, network["network"]) + + # Get initial network details + original_net = super(QuantumPluginPLUMgridV2, self).get_network( + context, net_id) + + with context.session.begin(subtransactions=True): + # Plugin DB - Network Update + new_network = super( + QuantumPluginPLUMgridV2, self).update_network(context, + net_id, network) + + try: + # PLUMgrid Server does not support updating resources yet + nos_url = self.snippets.BASE_NOS_URL + net_id + headers = {} + body_data = {} + self.rest_conn.nos_rest_conn(nos_url, + 'DELETE', body_data, headers) + nos_url = self.snippets.BASE_NOS_URL + new_network["id"] + body_data = self.snippets.create_domain_body_data(tenant_id) + self.rest_conn.nos_rest_conn(nos_url, + 'PUT', body_data, headers) + except: + err_message = _("PLUMgrid NOS communication failed") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + # return updated network + return new_network + + def delete_network(self, context, net_id): + """ + Delete network core Quantum API + """ + LOG.debug(_("QuantumPluginPLUMgrid Status: delete_network() called")) + super(QuantumPluginPLUMgridV2, self).get_network(context, net_id) + + with context.session.begin(subtransactions=True): + # Plugin DB - Network Delete + net_deleted = super(QuantumPluginPLUMgridV2, + self).delete_network(context, net_id) + + try: + nos_url = self.snippets.BASE_NOS_URL + net_id + headers = {} + body_data = {} + self.rest_conn.nos_rest_conn(nos_url, + 'DELETE', body_data, headers) + except: + err_message = _("PLUMgrid NOS communication failed") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + def create_port(self, context, port): + """ + Create port core Quantum API + """ + LOG.debug(_("QuantumPluginPLUMgrid Status: create_port() called")) + + # Port operations on PLUMgrid NOS is an automatic operation from the + # VIF driver operations in Nova. It requires admin_state_up to be True + port["port"]["admin_state_up"] = True + + # Plugin DB - Port Create and Return port + return super(QuantumPluginPLUMgridV2, self).create_port(context, + port) + + def update_port(self, context, port_id, port): + """ + Update port core Quantum API + + """ + LOG.debug(_("QuantumPluginPLUMgrid Status: update_port() called")) + + # Port operations on PLUMgrid NOS is an automatic operation from the + # VIF driver operations in Nova. + + # Plugin DB - Port Update + return super(QuantumPluginPLUMgridV2, self).update_port( + context, port_id, port) + + def delete_port(self, context, port_id): + """ + Delete port core Quantum API + """ + + LOG.debug(_("QuantumPluginPLUMgrid Status: delete_port() called")) + + # Port operations on PLUMgrid NOS is an automatic operation from the + # VIF driver operations in Nova. + + # Plugin DB - Port Delete + super(QuantumPluginPLUMgridV2, self).delete_port(context, port_id) + + def create_subnet(self, context, subnet): + """ + Create subnet core Quantum API + """ + + LOG.debug(_("QuantumPluginPLUMgrid Status: create_subnet() called")) + + with context.session.begin(subtransactions=True): + # Plugin DB - Subnet Create + subnet = super(QuantumPluginPLUMgridV2, self).create_subnet( + context, subnet) + subnet_details = self._get_subnet(context, subnet["id"]) + net_id = subnet_details["network_id"] + tenant_id = subnet_details["tenant_id"] + + try: + nos_url = self.snippets.BASE_NOS_URL + net_id + headers = {} + body_data = self.snippets.create_network_body_data( + tenant_id, self.topology_name) + self.rest_conn.nos_rest_conn(nos_url, + 'PUT', body_data, headers) + except: + err_message = _("PLUMgrid NOS communication failed: ") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + return subnet + + def delete_subnet(self, context, subnet_id): + """ + Delete subnet core Quantum API + """ + + LOG.debug(_("QuantumPluginPLUMgrid Status: delete_subnet() called")) + #Collecting subnet info + subnet_details = self._get_subnet(context, subnet_id) + + with context.session.begin(subtransactions=True): + # Plugin DB - Subnet Delete + del_subnet = super(QuantumPluginPLUMgridV2, self).delete_subnet( + context, subnet_id) + try: + headers = {} + body_data = {} + net_id = subnet_details["network_id"] + self._cleaning_nos_subnet_structure(body_data, headers, net_id) + except: + err_message = _("PLUMgrid NOS communication failed: ") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + return del_subnet + + def update_subnet(self, context, subnet_id, subnet): + """ + Update subnet core Quantum API + """ + + LOG.debug(_("update_subnet() called")) + #Collecting subnet info + initial_subnet = self._get_subnet(context, subnet_id) + net_id = initial_subnet["network_id"] + tenant_id = initial_subnet["tenant_id"] + + with context.session.begin(subtransactions=True): + # Plugin DB - Subnet Update + new_subnet = super(QuantumPluginPLUMgridV2, self).update_subnet( + context, subnet_id, subnet) + + try: + # PLUMgrid Server does not support updating resources yet + headers = {} + body_data = {} + self._cleaning_nos_subnet_structure(body_data, headers, net_id) + nos_url = self.snippets.BASE_NOS_URL + net_id + body_data = self.snippets.create_network_body_data( + tenant_id, self.topology_name) + self.rest_conn.nos_rest_conn(nos_url, + 'PUT', body_data, headers) + + except: + err_message = _("PLUMgrid NOS communication failed: ") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + return new_subnet + + """ + Extension API implementation + """ + # TODO: (Edgar) Complete extensions for PLUMgrid + + """ + Internal PLUMgrid fuctions + """ + + def _get_plugin_version(self): + return VERSION + + def _cleaning_nos_subnet_structure(self, body_data, headers, net_id): + domain_structure = ['/properties', '/link', '/ne'] + for structure in domain_structure: + nos_url = self.snippets.BASE_NOS_URL + net_id + structure + self.rest_conn.nos_rest_conn(nos_url, 'DELETE', body_data, headers) + + def _network_admin_state(self, network): + try: + if network["network"].get("admin_state_up"): + network_name = network["network"]["name"] + if network["network"]["admin_state_up"] is False: + LOG.warning(_("Network with admin_state_up=False are not " + "supported yet by this plugin. Ignoring " + "setting for network %s"), network_name) + except: + err_message = _("Network Admin State Validation Falied: ") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + return network diff --git a/quantum/plugins/plumgrid/plumgrid_nos_plugin/rest_connection.py b/quantum/plugins/plumgrid/plumgrid_nos_plugin/rest_connection.py new file mode 100644 index 0000000000..9916958fff --- /dev/null +++ b/quantum/plugins/plumgrid/plumgrid_nos_plugin/rest_connection.py @@ -0,0 +1,92 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. +# @author: Brenden Blanco, bblanco@plumgrid.com, PLUMgrid, Inc. + +""" +Quantum PLUMgrid Plug-in for PLUMgrid Virtual Technology +This plugin will forward authenticated REST API calls +to the Network Operating System by PLUMgrid called NOS +""" + +import httplib +import urllib2 + +from quantum.openstack.common import jsonutils as json +from quantum.openstack.common import log as logging +from quantum.plugins.plumgrid.common import exceptions as plum_excep + + +LOG = logging.getLogger(__name__) + + +class RestConnection(object): + """REST Connection to PLUMgrid NOS Server.""" + + def __init__(self, server, port, timeout): + LOG.debug(_('QuantumPluginPLUMgrid Status: REST Connection Started')) + self.server = server + self.port = port + self.timeout = timeout + + def nos_rest_conn(self, nos_url, action, data, headers): + self.nos_url = nos_url + body_data = json.dumps(data) + if not headers: + headers = {} + headers['Content-type'] = 'application/json' + headers['Accept'] = 'application/json' + + LOG.debug(_("PLUMgrid_NOS_Server: %s %s %s"), self.server, self.port, + action) + + conn = httplib.HTTPConnection(self.server, self.port, + timeout=self.timeout) + if conn is None: + LOG.error(_('PLUMgrid_NOS_Server: Could not establish HTTP ' + 'connection')) + return + + try: + LOG.debug(_("PLUMgrid_NOS_Server Sending Data: %s %s %s"), + nos_url, body_data, headers) + conn.request(action, nos_url, body_data, headers) + resp = conn.getresponse() + resp_str = resp.read() + + LOG.debug(_("PLUMgrid_NOS_Server Connection Data: %s, %s"), + resp, resp_str) + + if resp.status is httplib.OK: + try: + respdata = json.loads(resp_str) + LOG.debug(_("PLUMgrid_NOS_Server Connection RESP: %s"), + respdata) + pass + except ValueError: + err_message = _("PLUMgrid HTTP Connection Failed: ") + LOG.Exception(err_message) + raise plum_excep.PLUMgridException(err_message) + + ret = (resp.status, resp.reason, resp_str) + except urllib2.HTTPError, e: + LOG.error(_('PLUMgrid_NOS_Server: %(action)s failure, %(e)r')) + ret = 0, None, None, None + conn.close() + LOG.debug(_("PLUMgrid_NOS_Server: status=%(status)d, " + "reason=%(reason)r, ret=%(ret)s"), + {'status': ret[0], 'reason': ret[1], 'ret': ret[2]}) + return ret diff --git a/quantum/tests/unit/plumgrid/__init__.py b/quantum/tests/unit/plumgrid/__init__.py new file mode 100644 index 0000000000..39e9b8d130 --- /dev/null +++ b/quantum/tests/unit/plumgrid/__init__.py @@ -0,0 +1,17 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. diff --git a/quantum/tests/unit/plumgrid/test_plumgrid_plugin.py b/quantum/tests/unit/plumgrid/test_plumgrid_plugin.py new file mode 100644 index 0000000000..301bf6af58 --- /dev/null +++ b/quantum/tests/unit/plumgrid/test_plumgrid_plugin.py @@ -0,0 +1,77 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc. + +""" +Test cases for Quantum PLUMgrid Plug-in +""" + +from mock import patch + +from quantum.manager import QuantumManager +from quantum.tests.unit import test_db_plugin as test_plugin + + +class PLUMgridPluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase): + + _plugin_name = ('quantum.plugins.plumgrid.plumgrid_nos_plugin.' + 'plumgrid_plugin.QuantumPluginPLUMgridV2') + + def setUp(self): + self.restHTTPConnection = patch('httplib.HTTPConnection') + self.restHTTPConnection.start() + super(PLUMgridPluginV2TestCase, self).setUp(self._plugin_name) + + def tearDown(self): + super(PLUMgridPluginV2TestCase, self).tearDown() + self.restHTTPConnection.stop() + + +class TestPlumgridPluginV2HTTPResponse(test_plugin.TestV2HTTPResponse, + PLUMgridPluginV2TestCase): + + pass + + +class TestPlumgridPluginPortsV2(test_plugin.TestPortsV2, + PLUMgridPluginV2TestCase): + + pass + + +class TestPlumgridPluginNetworksV2(test_plugin.TestNetworksV2, + PLUMgridPluginV2TestCase): + + pass + + +class TestPlumgridPluginSubnetsV2(test_plugin.TestSubnetsV2, + PLUMgridPluginV2TestCase): + + pass + + +class TestPlumgridNetworkAdminState(PLUMgridPluginV2TestCase): + + def test_network_admin_state(self): + name = 'network_test' + admin_status_up = False + tenant_id = 'tenant_test' + network = {'network': {'name': name, + 'admin_state_up': admin_status_up, + 'tenant_id': tenant_id}} + plugin = QuantumManager.get_plugin() + self.assertEqual(plugin._network_admin_state(network), network) diff --git a/setup.py b/setup.py index b46dbb5be4..f24e267122 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ ryu_plugin_config_path = 'etc/quantum/plugins/ryu' meta_plugin_config_path = 'etc/quantum/plugins/metaplugin' nec_plugin_config_path = 'etc/quantum/plugins/nec' hyperv_plugin_config_path = 'etc/quantum/plugins/hyperv' +plumgrid_plugin_config_path = 'etc/quantum/plugins/plumgrid' if sys.platform == 'win32': # Windows doesn't have an "/etc" directory equivalent @@ -104,6 +105,8 @@ else: (nec_plugin_config_path, ['etc/quantum/plugins/nec/nec.ini']), (hyperv_plugin_config_path, ['etc/quantum/plugins/hyperv/hyperv_quantum_plugin.ini']), + (plumgrid_plugin_config_path, + ['etc/quantum/plugins/plumgrid/plumgrid.ini']), ] ConsoleScripts = [