]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
One Convergence Neutron Plugin Implementation
authorHemanth Ravi <hemanth.ravi@oneconvergence.com>
Mon, 27 Jan 2014 00:51:06 +0000 (16:51 -0800)
committerHemanth Ravi <hemanth.ravi@oneconvergence.com>
Thu, 6 Mar 2014 16:10:07 +0000 (08:10 -0800)
One Convergence Neutron Plugin implements Neutron API to provide a network
virtualization solution. The plugin works with One Convergence NVSD controller
to provide the functionality. This checkin implements the Neutron core APIs
and the plugin will be extended to support the L3 and service plugin extension
APIs.

Change-Id: Ic8a0dc0f5950d41b9b253c0d61b6812dbfd161c7
Implements: blueprint oc-nvsd-neutron-plugin

23 files changed:
etc/neutron/plugins/oneconvergence/nvsdplugin.ini [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/128e042a2b68_ext_gw_mode.py
neutron/db/migration/alembic_migrations/versions/176a85fc7d79_add_portbindings_db.py
neutron/db/migration/alembic_migrations/versions/1c33fa3cd1a1_extra_route_config.py
neutron/db/migration/alembic_migrations/versions/1fcfc149aca4_agents_unique_by_type_and_host.py
neutron/db/migration/alembic_migrations/versions/2eeaf963a447_floatingip_status.py
neutron/db/migration/alembic_migrations/versions/3cb5d900c5de_security_groups.py
neutron/db/migration/alembic_migrations/versions/4692d074d587_agent_scheduler.py
neutron/db/migration/alembic_migrations/versions/511471cc46b_agent_ext_model_supp.py
neutron/db/migration/alembic_migrations/versions/folsom_initial.py
neutron/plugins/oneconvergence/README [new file with mode: 0644]
neutron/plugins/oneconvergence/__init__.py [new file with mode: 0644]
neutron/plugins/oneconvergence/lib/__init__.py [new file with mode: 0644]
neutron/plugins/oneconvergence/lib/config.py [new file with mode: 0644]
neutron/plugins/oneconvergence/lib/exception.py [new file with mode: 0644]
neutron/plugins/oneconvergence/lib/nvsdlib.py [new file with mode: 0644]
neutron/plugins/oneconvergence/lib/plugin_helper.py [new file with mode: 0644]
neutron/plugins/oneconvergence/plugin.py [new file with mode: 0644]
neutron/tests/unit/oneconvergence/__init__.py [new file with mode: 0644]
neutron/tests/unit/oneconvergence/test_nvsd_plugin.py [new file with mode: 0644]
neutron/tests/unit/oneconvergence/test_nvsdlib.py [new file with mode: 0644]
neutron/tests/unit/oneconvergence/test_plugin_helper.py [new file with mode: 0644]
setup.cfg

diff --git a/etc/neutron/plugins/oneconvergence/nvsdplugin.ini b/etc/neutron/plugins/oneconvergence/nvsdplugin.ini
new file mode 100644 (file)
index 0000000..67335e0
--- /dev/null
@@ -0,0 +1,23 @@
+[nvsd]
+# Configure the NVSD controller. The plugin proxies the api calls using
+# to NVSD controller which implements the required functionality.
+
+# IP address of NVSD controller api server
+# nvsd_ip = <ip address of nvsd controller>
+
+# Port number of NVSD controller api server
+# nvsd_port = 8082
+
+# Authentication credentials to access the api server
+# nvsd_user = <nvsd controller username>
+# nvsd_passwd = <password>
+
+# API request timeout in seconds
+# request_timeout = <default request timeout>
+
+# Maximum number of retry attempts to login to the NVSD controller
+# Specify 0 to retry until success (default)
+# nvsd_retries = 0
+
+[database]
+# connection = mysql://root:<passwd>@127.0.0.1/<neutron_db>?charset=utf8
index 28f6108e22ce98f5b3247d50d87b5eba38d0c5b1..b2aa6443d4a5e511c026c5164766975e02c7388c 100644 (file)
@@ -42,7 +42,8 @@ migration_for_plugins = [
     'neutron.plugins.vmware.plugin.NsxPlugin',
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
     'neutron.plugins.embrane.plugins.embrane_ovs_plugin.EmbraneOvsPlugin',
-    'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2'
+    'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index fdf62e03753a99c520751824599f4ec42e45148e..c1289250d7bd275a4d36857d47f26e73089297ee 100644 (file)
@@ -38,6 +38,7 @@ migration_for_plugins = [
     'neutron.plugins.vmware.plugin.NsxPlugin',
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
     'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index 07dd4a945812ebfd061d5b02855a3f12da675d17..8c1f2a5630a42bf0ac0d28261f37ea8bf3ff5e09 100644 (file)
@@ -38,7 +38,8 @@ migration_for_plugins = [
     'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
     'neutron.plugins.ryu.ryu_neutron_plugin.RyuNeutronPluginV2',
     'neutron.plugins.vmware.plugin.NsxPlugin',
-    'neutron.plugins.vmware.plugin.NsxServicePlugin'
+    'neutron.plugins.vmware.plugin.NsxServicePlugin',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index 4fe6e1398bac9fbef7839a697d976c2589b35f14..a55f0b49eea11fd47ab1f3c5a2c92f056094acbc 100644 (file)
@@ -38,6 +38,7 @@ migration_for_plugins = [
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
     'neutron.services.loadbalancer.plugin.LoadBalancerPlugin',
     'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index c9068397f5a2d63a0c5f6f77db1920b9ccecbc79..006b5282f2d7b5f1dddc6d5d3f0ad78ae273df64 100644 (file)
@@ -44,6 +44,7 @@ migration_for_plugins = [
     'neutron.plugins.nec.nec_plugin.NECPluginV2',
     'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
     'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
     'neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2',
     'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.'
     'NeutronPluginPLUMgridV2',
index 5959efd3d020680099eb0524a1067556e93a93cc..057a360aa6d78c0d3a77fc717357bdb44b38958c 100644 (file)
@@ -38,6 +38,7 @@ migration_for_plugins = [
     'neutron.plugins.ryu.ryu_neutron_plugin.RyuNeutronPluginV2',
     'neutron.plugins.vmware.plugin.NsxPlugin',
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index 86d14f7907d1dd0438cc6175b10e59ebde6979ea..fdf13639fccd7faaa0a99f2d44ac4d6f590308b1 100644 (file)
@@ -38,6 +38,7 @@ migration_for_plugins = [
     'neutron.plugins.nec.nec_plugin.NECPluginV2',
     'neutron.plugins.vmware.plugin.NsxPlugin',
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index a1154af57ff5ee862cc0f56efd3fe9c24c12ed13..12d75aa9ef69f5449e078f745f5fea6dcc110a35 100644 (file)
@@ -40,6 +40,7 @@ migration_for_plugins = [
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
     'neutron.services.loadbalancer.plugin.LoadBalancerPlugin',
     'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
+    'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
 ]
 
 from alembic import op
index e75b63c91cb684965f4b5b75b87fbba1386f00d0..15a0e69046acf29ee30cd670dc2ccffca14495e5 100644 (file)
@@ -33,6 +33,7 @@ PLUGINS = {
     'ml2': 'neutron.plugins.ml2.plugin.Ml2Plugin',
     'nec': 'neutron.plugins.nec.nec_plugin.NECPluginV2',
     'nvp': 'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
+    'ocnvsd': 'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2',
     'ovs': 'neutron.plugins.openvswitch.ovs_neutron_plugin.OVSNeutronPluginV2',
     'plumgrid': 'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.'
                 'NeutronPluginPLUMgridV2',
@@ -45,6 +46,7 @@ L3_CAPABLE = [
     PLUGINS['meta'],
     PLUGINS['ml2'],
     PLUGINS['nec'],
+    PLUGINS['ocnvsd'],
     PLUGINS['ovs'],
     PLUGINS['ryu'],
     PLUGINS['brocade'],
@@ -56,6 +58,7 @@ FOLSOM_QUOTA = [
     PLUGINS['lbr'],
     PLUGINS['ml2'],
     PLUGINS['nvp'],
+    PLUGINS['ocnvsd'],
     PLUGINS['ovs'],
 ]
 
diff --git a/neutron/plugins/oneconvergence/README b/neutron/plugins/oneconvergence/README
new file mode 100644 (file)
index 0000000..0169624
--- /dev/null
@@ -0,0 +1,32 @@
+One Convergence Neutron Plugin to implement the Neutron v2.0 API. The plugin
+works with One Convergence NVSD controller to provide network virtualization
+functionality.
+
+The plugin is enabled with the following configuration line in neutron.conf:
+
+core_plugin = neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2
+
+The configuration parameters required for the plugin are specified in the file
+etc/neutron/plugins/oneconvergence/nvsdplugin.ini. The configuration file contains
+description of the different parameters.
+
+To enable One Convergence Neutron Plugin with devstack and configure the required
+parameters, use the following lines in localrc:
+
+Q_PLUGIN=oneconvergence
+
+disable_service n-net
+disable_service q-agt
+enable_service q-dhcp
+enable_service q-svc
+enable_service q-l3
+enable_service q-meta
+enable_service neutron
+
+NVSD_IP=
+NVSD_PORT=
+NVSD_USER=
+NVSD_PASSWD=
+
+The NVSD controller configuration should be specified in nvsdplugin.ini before
+invoking stack.sh.
diff --git a/neutron/plugins/oneconvergence/__init__.py b/neutron/plugins/oneconvergence/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/oneconvergence/lib/__init__.py b/neutron/plugins/oneconvergence/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/oneconvergence/lib/config.py b/neutron/plugins/oneconvergence/lib/config.py
new file mode 100644 (file)
index 0000000..f6eae26
--- /dev/null
@@ -0,0 +1,41 @@
+# Copyright 2014 OneConvergence, 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.
+#
+
+""" Register the configuration options"""
+
+from oslo.config import cfg
+
+
+NVSD_OPT = [
+    cfg.StrOpt('nvsd_ip',
+               default='127.0.0.1',
+               help=_("NVSD Controller IP address")),
+    cfg.IntOpt('nvsd_port',
+               default=8082,
+               help=_("NVSD Controller Port number")),
+    cfg.StrOpt('nvsd_user',
+               default='ocplugin',
+               help=_("NVSD Controller username")),
+    cfg.StrOpt('nvsd_passwd',
+               default='oc123', secret=True,
+               help=_("NVSD Controller password")),
+    cfg.IntOpt('request_timeout',
+               default=30,
+               help=_("NVSD controller REST API request timeout in seconds")),
+    cfg.IntOpt('nvsd_retries', default=0,
+               help=_("Number of login retries to NVSD controller"))
+]
+
+cfg.CONF.register_opts(NVSD_OPT, "nvsd")
diff --git a/neutron/plugins/oneconvergence/lib/exception.py b/neutron/plugins/oneconvergence/lib/exception.py
new file mode 100644 (file)
index 0000000..b6864b1
--- /dev/null
@@ -0,0 +1,55 @@
+# Copyright 2014 OneConvergence, 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.
+#
+
+"""NVSD Exception Definitions."""
+
+from neutron.common import exceptions as n_exc
+
+
+class NVSDAPIException(n_exc.NeutronException):
+    '''Base NVSDplugin Exception.'''
+    message = _("An unknown nvsd plugin exception occurred: %(reason)s")
+
+
+class RequestTimeout(NVSDAPIException):
+    message = _("The request has timed out.")
+
+
+class UnAuthorizedException(NVSDAPIException):
+    message = _("Invalid access credentials to the Server.")
+
+
+class NotFoundException(NVSDAPIException):
+    message = _("A resource is not found: %(reason)s")
+
+
+class BadRequestException(NVSDAPIException):
+    message = _("Request sent to server is invalid: %(reason)s")
+
+
+class ServerException(NVSDAPIException):
+    message = _("Internal Server Error: %(reason)s")
+
+
+class ConnectionClosedException(NVSDAPIException):
+    message = _("Connection is closed by the server.")
+
+
+class ForbiddenException(NVSDAPIException):
+    message = _("The request is forbidden access to the resource: %(reason)s")
+
+
+class InternalServerError(NVSDAPIException):
+    message = _("Internal Server Error from NVSD controller: %(reason)s")
diff --git a/neutron/plugins/oneconvergence/lib/nvsdlib.py b/neutron/plugins/oneconvergence/lib/nvsdlib.py
new file mode 100644 (file)
index 0000000..bbf4e5b
--- /dev/null
@@ -0,0 +1,262 @@
+# Copyright 2014 OneConvergence, 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: Kedar Kulkarni, One Convergence, Inc.
+
+"""Intermidiate NVSD Library."""
+
+from neutron.openstack.common import excutils
+from neutron.openstack.common import jsonutils as json
+from neutron.openstack.common import log as logging
+import neutron.plugins.oneconvergence.lib.exception as nvsdexception
+from neutron.plugins.oneconvergence.lib import plugin_helper
+
+LOG = logging.getLogger(__name__)
+
+NETWORKS_URI = "/pluginhandler/ocplugin/tenant/%s/lnetwork/"
+NETWORK_URI = NETWORKS_URI + "%s"
+GET_ALL_NETWORKS = "/pluginhandler/ocplugin/tenant/getallnetworks"
+
+SUBNETS_URI = NETWORK_URI + "/lsubnet/"
+SUBNET_URI = SUBNETS_URI + "%s"
+GET_ALL_SUBNETS = "/pluginhandler/ocplugin/tenant/getallsubnets"
+
+PORTS_URI = NETWORK_URI + "/lport/"
+PORT_URI = PORTS_URI + "%s"
+
+METHODS = {"POST": "create",
+           "PUT": "update",
+           "DELETE": "delete",
+           "GET": "get"}
+
+
+class NVSDApi(object):
+
+    def build_error_msg(self, method, resource, tenant_id, resource_id):
+        if method == "POST":
+            msg = _("Could not create a %(resource)s under tenant "
+                    "%(tenant_id)s") % {'resource': resource,
+                                        'tenant_id': tenant_id}
+        elif resource_id:
+            msg = _("Failed to %(method)s %(resource)s "
+                    "id=%(resource_id)s") % {'method': METHODS[method],
+                                             'resource': resource,
+                                             'resource_id': resource_id
+                                             }
+        else:
+            msg = _("Failed to %(method)s %(resource)s") % {
+                'method': METHODS[method], 'resource': resource}
+        return msg
+
+    def set_connection(self):
+        self.nvsdcontroller = plugin_helper.initialize_plugin_helper()
+        self.nvsdcontroller.login()
+
+    def send_request(self, method, uri, body=None, resource=None,
+                     tenant_id='', resource_id=None):
+        """Issue a request to NVSD controller."""
+
+        try:
+            result = self.nvsdcontroller.request(method, uri, body=body)
+        except nvsdexception.NVSDAPIException as e:
+            with excutils.save_and_reraise_exception() as ctxt:
+                msg = self.build_error_msg(method, resource, tenant_id,
+                                           resource_id)
+                LOG.error(msg)
+                # Modifying the reason message without disturbing the exception
+                # info
+                ctxt.value = type(e)(reason=msg)
+        return result
+
+    def create_network(self, network):
+
+        tenant_id = network['tenant_id']
+        router_external = network['router:external'] is True
+
+        network_obj = {
+            "name": network['name'],
+            "tenant_id": tenant_id,
+            "shared": network['shared'],
+            "admin_state_up": network['admin_state_up'],
+            "router:external": router_external
+        }
+
+        uri = NETWORKS_URI % tenant_id
+
+        response = self.send_request("POST", uri, body=json.dumps(network_obj),
+                                     resource='network', tenant_id=tenant_id)
+
+        nvsd_net = response.json()
+
+        LOG.debug(_("Network %(id)s created under tenant %(tenant_id)s"),
+                  {'id': nvsd_net['id'], 'tenant_id': tenant_id})
+
+        return nvsd_net
+
+    def update_network(self, network, network_update):
+
+        tenant_id = network['tenant_id']
+        network_id = network['id']
+
+        uri = NETWORK_URI % (tenant_id, network_id)
+
+        self.send_request("PUT", uri,
+                          body=json.dumps(network_update),
+                          resource='network', tenant_id=tenant_id,
+                          resource_id=network_id)
+
+        LOG.debug(_("Network %(id)s updated under tenant %(tenant_id)s"),
+                  {'id': network_id, 'tenant_id': tenant_id})
+
+    def delete_network(self, network, subnets=[]):
+
+        tenant_id = network['tenant_id']
+        network_id = network['id']
+
+        ports = self._get_ports(tenant_id, network_id)
+
+        for port in ports:
+            self.delete_port(port['id'], port)
+
+        for subnet in subnets:
+            self.delete_subnet(subnet)
+
+        path = NETWORK_URI % (tenant_id, network_id)
+
+        self.send_request("DELETE", path, resource='network',
+                          tenant_id=tenant_id, resource_id=network_id)
+
+        LOG.debug(_("Network %(id)s deleted under tenant %(tenant_id)s"),
+                  {'id': network_id, 'tenant_id': tenant_id})
+
+    def create_subnet(self, subnet):
+
+        tenant_id = subnet['tenant_id']
+        network_id = subnet['network_id']
+
+        uri = SUBNETS_URI % (tenant_id, network_id)
+
+        self.send_request("POST", uri, body=json.dumps(subnet),
+                          resource='subnet', tenant_id=tenant_id)
+
+        LOG.debug(_("Subnet %(id)s created under tenant %(tenant_id)s"),
+                  {'id': subnet['id'], 'tenant_id': tenant_id})
+
+    def delete_subnet(self, subnet):
+
+        tenant_id = subnet['tenant_id']
+        network_id = subnet['network_id']
+        subnet_id = subnet['id']
+
+        uri = SUBNET_URI % (tenant_id, network_id, subnet_id)
+
+        self.send_request("DELETE", uri, resource='subnet',
+                          tenant_id=tenant_id, resource_id=subnet_id)
+
+        LOG.debug(_("Subnet %(id)s deleted under tenant %(tenant_id)s"),
+                  {'id': subnet_id, 'tenant_id': tenant_id})
+
+    def update_subnet(self, subnet, subnet_update):
+
+        tenant_id = subnet['tenant_id']
+        network_id = subnet['network_id']
+        subnet_id = subnet['id']
+
+        uri = SUBNET_URI % (tenant_id, network_id, subnet_id)
+
+        self.send_request("PUT", uri,
+                          body=json.dumps(subnet_update),
+                          resource='subnet', tenant_id=tenant_id,
+                          resource_id=subnet_id)
+
+        LOG.debug(_("Subnet %(id)s updated under tenant %(tenant_id)s"),
+                  {'id': subnet_id, 'tenant_id': tenant_id})
+
+    def create_port(self, tenant_id, port):
+
+        network_id = port["network_id"]
+        fixed_ips = port.get("fixed_ips")
+        ip_address = None
+        subnet_id = None
+
+        if fixed_ips:
+            ip_address = fixed_ips[0].get("ip_address")
+            subnet_id = fixed_ips[0].get("subnet_id")
+
+        lport = {
+            "id": port["id"],
+            "name": port["name"],
+            "device_id": port["device_id"],
+            "device_owner": port["device_owner"],
+            "mac_address": port["mac_address"],
+            "ip_address": ip_address,
+            "subnet_id": subnet_id,
+            "admin_state_up": port["admin_state_up"],
+            "network_id": network_id,
+            "status": port["status"]
+        }
+
+        path = PORTS_URI % (tenant_id, network_id)
+
+        self.send_request("POST", path, body=json.dumps(lport),
+                          resource='port', tenant_id=tenant_id)
+
+        LOG.debug(_("Port %(id)s created under tenant %(tenant_id)s"),
+                  {'id': port['id'], 'tenant_id': tenant_id})
+
+    def update_port(self, tenant_id, port, port_update):
+
+        network_id = port['network_id']
+        port_id = port['id']
+
+        lport = {}
+        for k in ('admin_state_up', 'name', 'device_id', 'device_owner'):
+            if k in port_update:
+                lport[k] = port_update[k]
+
+        fixed_ips = port_update.get('fixed_ips', None)
+        if fixed_ips:
+            lport["ip_address"] = fixed_ips[0].get("ip_address")
+            lport["subnet_id"] = fixed_ips[0].get("subnet_id")
+
+        uri = PORT_URI % (tenant_id, network_id, port_id)
+
+        self.send_request("PUT", uri, body=json.dumps(lport),
+                          resource='port', tenant_id=tenant_id,
+                          resource_id=port_id)
+
+        LOG.debug(_("Port %(id)s updated under tenant %(tenant_id)s"),
+                  {'id': port_id, 'tenant_id': tenant_id})
+
+    def delete_port(self, port_id, port):
+
+        tenant_id = port['tenant_id']
+        network_id = port['network_id']
+
+        uri = PORT_URI % (tenant_id, network_id, port_id)
+
+        self.send_request("DELETE", uri, resource='port', tenant_id=tenant_id,
+                          resource_id=port_id)
+
+        LOG.debug(_("Port %(id)s deleted under tenant %(tenant_id)s"),
+                  {'id': port_id, 'tenant_id': tenant_id})
+
+    def _get_ports(self, tenant_id, network_id):
+
+        uri = PORTS_URI % (tenant_id, network_id)
+
+        response = self.send_request("GET", uri, resource='ports',
+                                     tenant_id=tenant_id)
+
+        return response.json()
diff --git a/neutron/plugins/oneconvergence/lib/plugin_helper.py b/neutron/plugins/oneconvergence/lib/plugin_helper.py
new file mode 100644 (file)
index 0000000..060f606
--- /dev/null
@@ -0,0 +1,186 @@
+# Copyright 2014 OneConvergence, 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: Kedar Kulkarni, One Convergence, Inc.
+
+"""Library to talk to NVSD controller."""
+
+import httplib
+import time
+from urlparse import urljoin
+
+from oslo.config import cfg
+import requests
+
+from neutron.openstack.common import jsonutils as json
+from neutron.openstack.common import log as logging
+import neutron.plugins.oneconvergence.lib.exception as exception
+
+LOG = logging.getLogger(__name__)
+
+
+def initialize_plugin_helper():
+    nvsdcontroller = NVSDController()
+    return nvsdcontroller
+
+
+class NVSDController(object):
+
+    """Encapsulates the NVSD Controller details."""
+
+    def __init__(self):
+
+        self._host = cfg.CONF.nvsd.nvsd_ip
+        self._port = cfg.CONF.nvsd.nvsd_port
+        self._user = cfg.CONF.nvsd.nvsd_user
+        self._password = cfg.CONF.nvsd.nvsd_passwd
+        self._retries = cfg.CONF.nvsd.nvsd_retries
+        self._request_timeout = float(cfg.CONF.nvsd.request_timeout)
+        self.api_url = 'http://' + self._host + ':' + str(self._port)
+
+        self.pool = requests.Session()
+
+        self.auth_token = None
+
+    def do_request(self, method, url=None, headers=None, data=None,
+                   timeout=10):
+        response = self.pool.request(method, url=url,
+                                     headers=headers, data=data,
+                                     timeout=self._request_timeout)
+        return response
+
+    def login(self):
+        """Login to NVSD Controller."""
+
+        headers = {"Content-Type": "application/json"}
+
+        login_url = urljoin(self.api_url,
+                            "/pluginhandler/ocplugin/authmgmt/login")
+
+        data = json.dumps({"user_name": self._user, "passwd": self._password})
+
+        attempts = 0
+
+        while True:
+            if attempts < self._retries:
+                attempts += 1
+            elif self._retries == 0:
+                attempts = 0
+            else:
+                msg = _("Unable to connect to NVSD controller. Exiting after "
+                        "%(retries)s attempts") % {'retries': self._retries}
+                LOG.error(msg)
+                raise exception.ServerException(reason=msg)
+            try:
+                response = self.do_request("POST", url=login_url,
+                                           headers=headers, data=data,
+                                           timeout=self._request_timeout)
+                break
+            except Exception as e:
+                LOG.error(_("Login Failed: %s"), e)
+                LOG.error(_("Unable to establish connection"
+                            " with Controller %s"), self.api_url)
+                LOG.error(_("Retrying after 1 second..."))
+                time.sleep(1)
+
+        if response.status_code == requests.codes.ok:
+            LOG.debug(_("Login Successful %(uri)s "
+                        "%(status)s"), {'uri': self.api_url,
+                                        'status': response.status_code})
+            self.auth_token = json.loads(response.content)["session_uuid"]
+            LOG.debug(_("AuthToken = %s"), self.auth_token)
+        else:
+            LOG.error(_("login failed"))
+
+        return
+
+    def request(self, method, url, body="", content_type="application/json"):
+        """Issue a request to NVSD controller."""
+
+        if self.auth_token is None:
+            LOG.warning(_("No Token, Re-login"))
+            self.login()
+
+        headers = {"Content-Type": content_type}
+
+        uri = urljoin(url, "?authToken=%s" % self.auth_token)
+
+        url = urljoin(self.api_url, uri)
+
+        request_ok = False
+        response = None
+
+        try:
+            response = self.do_request(method, url=url,
+                                       headers=headers, data=body,
+                                       timeout=self._request_timeout)
+
+            LOG.debug(_("request: %(method)s %(uri)s successful"),
+                      {'method': method, 'uri': self.api_url + uri})
+            request_ok = True
+        except httplib.IncompleteRead as e:
+            response = e.partial
+            request_ok = True
+        except Exception as e:
+            LOG.error(_("request: Request failed from "
+                        "Controller side :%s"), e)
+
+        if response is None:
+            # Timeout.
+            LOG.error(_("Response is Null, Request timed out: %(method)s to "
+                        "%(uri)s"), {'method': method, 'uri': uri})
+            self.auth_token = None
+            raise exception.RequestTimeout()
+
+        status = response.status_code
+        if status == requests.codes.unauthorized:
+            self.auth_token = None
+            # Raise an exception to inform that the request failed.
+            raise exception.UnAuthorizedException()
+
+        if status in self.error_codes:
+            LOG.error(_("Request %(method)s %(uri)s body = %(body)s failed "
+                        "with status %(status)s"), {'method': method,
+                                                    'uri': uri, 'body': body,
+                                                    'status': status})
+            LOG.error(_("%s"), response.reason)
+            raise self.error_codes[status]()
+        elif status not in (requests.codes.ok, requests.codes.created,
+                            requests.codes.no_content):
+            LOG.error(_("%(method)s to %(url)s, unexpected response code: "
+                        "%(status)d"), {'method': method, 'url': url,
+                                        'status': status})
+            return
+
+        if not request_ok:
+            LOG.error(_("Request failed from Controller side with "
+                        "Status=%s"), status)
+            raise exception.ServerException()
+        else:
+            LOG.debug(_("Success: %(method)s %(url)s status=%(status)s"),
+                      {'method': method, 'url': self.api_url + uri,
+                       'status': status})
+        response.body = response.content
+        return response
+
+    error_codes = {
+        404: exception.NotFoundException,
+        409: exception.BadRequestException,
+        500: exception.InternalServerError,
+        503: exception.ServerException,
+        403: exception.ForbiddenException,
+        301: exception.NVSDAPIException,
+        307: exception.NVSDAPIException,
+        400: exception.NVSDAPIException,
+    }
diff --git a/neutron/plugins/oneconvergence/plugin.py b/neutron/plugins/oneconvergence/plugin.py
new file mode 100644 (file)
index 0000000..59646cb
--- /dev/null
@@ -0,0 +1,300 @@
+# Copyright 2014 OneConvergence, 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: Kedar Kulkarni, One Convergence, Inc.
+
+"""Implementation of OneConvergence Neutron Plugin."""
+
+from oslo.config import cfg
+
+from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
+from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
+from neutron.common import constants as q_const
+from neutron.common import exceptions as nexception
+from neutron.common import rpc as q_rpc
+from neutron.common import topics
+from neutron.db import agents_db
+from neutron.db import agentschedulers_db
+from neutron.db import db_base_plugin_v2
+from neutron.db import dhcp_rpc_base
+from neutron.db import external_net_db
+from neutron.db import extraroute_db
+from neutron.db import l3_agentschedulers_db
+from neutron.db import l3_gwmode_db
+from neutron.db import l3_rpc_base
+from neutron.db import portbindings_base
+from neutron.db import quota_db  # noqa
+from neutron.extensions import portbindings
+from neutron.openstack.common import excutils
+from neutron.openstack.common import importutils
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import rpc
+from neutron.plugins.common import constants as svc_constants
+import neutron.plugins.oneconvergence.lib.config  # noqa
+import neutron.plugins.oneconvergence.lib.exception as nvsdexception
+from neutron.plugins.oneconvergence.lib import nvsdlib as nvsd_lib
+
+LOG = logging.getLogger(__name__)
+IPv6 = 6
+
+
+class NVSDRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
+                       l3_rpc_base.L3RpcCallbackMixin):
+
+    """Agent callback."""
+
+    RPC_API_VERSION = '1.1'
+
+    def create_rpc_dispatcher(self):
+        """Get the rpc dispatcher for this manager."""
+        return q_rpc.PluginRpcDispatcher([self,
+                                          agents_db.AgentExtRpcCallback()])
+
+
+class OneConvergencePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
+                             extraroute_db.ExtraRoute_db_mixin,
+                             l3_agentschedulers_db.L3AgentSchedulerDbMixin,
+                             agentschedulers_db.DhcpAgentSchedulerDbMixin,
+                             external_net_db.External_net_db_mixin,
+                             l3_gwmode_db.L3_NAT_db_mixin,
+                             portbindings_base.PortBindingBaseMixin):
+
+    """L2 Virtual Network Plugin.
+
+    OneConvergencePluginV2 is a Neutron plugin that provides L2 Virtual Network
+    functionality.
+    """
+
+    __native_bulk_support = True
+    __native_pagination_support = True
+    __native_sorting_support = True
+
+    supported_extension_aliases = ['agent',
+                                   'binding',
+                                   'dhcp_agent_scheduler',
+                                   'ext-gw-mode',
+                                   'external-net',
+                                   'extraroute',
+                                   'l3_agent_scheduler',
+                                   'quotas',
+                                   'router',
+                                   ]
+
+    def __init__(self):
+
+        super(OneConvergencePluginV2, self).__init__()
+
+        self.oneconvergence_init()
+
+        self.base_binding_dict = {
+            portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS}
+
+        portbindings_base.register_port_dict_function()
+
+        self.setup_rpc()
+
+        self.network_scheduler = importutils.import_object(
+            cfg.CONF.network_scheduler_driver)
+        self.router_scheduler = importutils.import_object(
+            cfg.CONF.router_scheduler_driver)
+
+    def oneconvergence_init(self):
+        """Initialize the connections and set the log levels for the plugin."""
+
+        self.nvsdlib = nvsd_lib.NVSDApi()
+        self.nvsdlib.set_connection()
+
+    def setup_rpc(self):
+        # RPC support
+        self.service_topics = {svc_constants.CORE: topics.PLUGIN,
+                               svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN}
+        self.conn = rpc.create_connection(new=True)
+        self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
+            dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+        )
+        self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
+            l3_rpc_agent_api.L3AgentNotify
+        )
+        self.callbacks = NVSDRpcCallbacks()
+        self.dispatcher = self.callbacks.create_rpc_dispatcher()
+        for svc_topic in self.service_topics.values():
+            self.conn.create_consumer(svc_topic, self.dispatcher, fanout=False)
+
+        # Consume from all consumers in a thread
+        self.conn.consume_in_thread()
+
+    def create_network(self, context, network):
+
+        net = self.nvsdlib.create_network(network['network'])
+
+        network['network']['id'] = net['id']
+
+        try:
+            neutron_net = super(OneConvergencePluginV2,
+                                self).create_network(context, network)
+
+            #following call checks whether the network is external or not and
+            #if it is external then adds this network to externalnetworks
+            #table of neutron db
+            self._process_l3_create(context, neutron_net, network['network'])
+        except nvsdexception.NVSDAPIException:
+            with excutils.save_and_reraise_exception():
+                self.nvsdlib.delete_network(net)
+
+        return neutron_net
+
+    def update_network(self, context, net_id, network):
+
+        with context.session.begin(subtransactions=True):
+
+            neutron_net = super(OneConvergencePluginV2,
+                                self).update_network(context, net_id, network)
+
+            self.nvsdlib.update_network(neutron_net, network['network'])
+            # updates neutron database e.g. externalnetworks table.
+            self._process_l3_update(context, neutron_net, network['network'])
+
+        return neutron_net
+
+    def delete_network(self, context, net_id):
+
+        with context.session.begin(subtransactions=True):
+            network = self._get_network(context, net_id)
+            #get all the subnets under the network to delete them
+            subnets = self._get_subnets_by_network(context, net_id)
+
+            super(OneConvergencePluginV2, self).delete_network(context,
+                                                               net_id)
+
+            self.nvsdlib.delete_network(network, subnets)
+
+    def create_subnet(self, context, subnet):
+
+        if subnet['subnet']['ip_version'] == IPv6:
+            raise nexception.InvalidInput(
+                error_message="NVSDPlugin doesn't support IPv6.")
+
+        neutron_subnet = super(OneConvergencePluginV2,
+                               self).create_subnet(context, subnet)
+
+        try:
+            self.nvsdlib.create_subnet(neutron_subnet)
+        except nvsdexception.NVSDAPIException:
+            with excutils.save_and_reraise_exception():
+                #Log the message and delete the subnet from the neutron
+                super(OneConvergencePluginV2,
+                      self).delete_subnet(context, neutron_subnet['id'])
+                LOG.error(_("Failed to create subnet, "
+                          "deleting it from neutron"))
+
+        return neutron_subnet
+
+    def delete_subnet(self, context, subnet_id):
+
+        neutron_subnet = self._get_subnet(context, subnet_id)
+
+        with context.session.begin(subtransactions=True):
+
+            super(OneConvergencePluginV2, self).delete_subnet(context,
+                                                              subnet_id)
+
+            self.nvsdlib.delete_subnet(neutron_subnet)
+
+    def update_subnet(self, context, subnet_id, subnet):
+
+        with context.session.begin(subtransactions=True):
+
+            neutron_subnet = super(OneConvergencePluginV2,
+                                   self).update_subnet(context, subnet_id,
+                                                       subnet)
+
+            self.nvsdlib.update_subnet(neutron_subnet, subnet)
+        return neutron_subnet
+
+    def create_port(self, context, port):
+
+        network = {}
+
+        network_id = port['port']['network_id']
+
+        with context.session.begin(subtransactions=True):
+
+            # Invoke the Neutron  API for creating port
+            neutron_port = super(OneConvergencePluginV2,
+                                 self).create_port(context, port)
+
+            self._process_portbindings_create_and_update(context,
+                                                         port['port'],
+                                                         neutron_port)
+
+            if port['port']['device_owner'] in ('network:router_gateway',
+                                                'network:floatingip'):
+                # for l3 requests, tenant_id will be None/''
+                network = self._get_network(context, network_id)
+
+                tenant_id = network['tenant_id']
+            else:
+                tenant_id = port['port']['tenant_id']
+
+        port_id = neutron_port['id']
+
+        try:
+            self.nvsdlib.create_port(tenant_id, neutron_port)
+        except nvsdexception.NVSDAPIException:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_("Deleting newly created "
+                          "neutron port %s"), port_id)
+                super(OneConvergencePluginV2, self).delete_port(context,
+                                                                port_id)
+
+        return neutron_port
+
+    def update_port(self, context, port_id, port):
+
+        with context.session.begin(subtransactions=True):
+
+            neutron_port = super(OneConvergencePluginV2,
+                                 self).update_port(context, port_id, port)
+
+            if neutron_port['tenant_id'] == '':
+                network = self._get_network(context,
+                                            neutron_port['network_id'])
+                tenant_id = network['tenant_id']
+            else:
+                tenant_id = neutron_port['tenant_id']
+
+            self.nvsdlib.update_port(tenant_id, neutron_port, port['port'])
+
+            self._process_portbindings_create_and_update(context,
+                                                         port['port'],
+                                                         neutron_port)
+        return neutron_port
+
+    def delete_port(self, context, port_id, l3_port_check=True):
+
+        if l3_port_check:
+            self.prevent_l3_port_deletion(context, port_id)
+
+        neutron_port = self._get_port(context, port_id)
+
+        with context.session.begin(subtransactions=True):
+
+            self.disassociate_floatingips(context, port_id)
+
+            super(OneConvergencePluginV2, self).delete_port(context, port_id)
+
+            network = self._get_network(context, neutron_port['network_id'])
+            neutron_port['tenant_id'] = network['tenant_id']
+
+            self.nvsdlib.delete_port(port_id, neutron_port)
diff --git a/neutron/tests/unit/oneconvergence/__init__.py b/neutron/tests/unit/oneconvergence/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/oneconvergence/test_nvsd_plugin.py b/neutron/tests/unit/oneconvergence/test_nvsd_plugin.py
new file mode 100644 (file)
index 0000000..e503a5e
--- /dev/null
@@ -0,0 +1,109 @@
+# Copyright 2014 OneConvergence, 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.
+#
+
+"""Test Library for OneConvergencePlugin."""
+
+import contextlib
+import uuid
+
+import mock
+from oslo.config import cfg
+
+from neutron import context
+from neutron.extensions import portbindings
+from neutron.manager import NeutronManager
+from neutron.plugins.oneconvergence import plugin as nvsd_plugin
+from neutron.tests.unit import _test_extension_portbindings as test_bindings
+from neutron.tests.unit import test_db_plugin as test_plugin
+
+PLUGIN_NAME = 'neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2'
+
+
+class OneConvergencePluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
+    _plugin_name = PLUGIN_NAME
+
+    def setUp(self):
+        def mocked_oneconvergence_init(self):
+            def side_effect(*args, **kwargs):
+                return {'id': str(uuid.uuid4())}
+
+            self.nvsdlib = mock.Mock()
+            self.nvsdlib.create_network.side_effect = side_effect
+
+        self.addCleanup(mock.patch.stopall)
+
+        with mock.patch.object(nvsd_plugin.OneConvergencePluginV2,
+                               'oneconvergence_init',
+                               new=mocked_oneconvergence_init):
+            super(OneConvergencePluginV2TestCase,
+                  self).setUp(self._plugin_name)
+
+
+class TestOneConvergencePluginNetworksV2(test_plugin.TestNetworksV2,
+                                         OneConvergencePluginV2TestCase):
+    pass
+
+
+class TestOneConvergencePluginSubnetsV2(test_plugin.TestSubnetsV2,
+                                        OneConvergencePluginV2TestCase):
+    def test_update_subnet_inconsistent_ipv6_gatewayv4(self):
+        self.skipTest("NVSD Plugin does not support IPV6.")
+
+    def test_create_subnet_with_v6_allocation_pool(self):
+        self.skipTest("NVSD Plugin does not support IPV6.")
+
+    def test_update_subnet_inconsistent_ipv6_hostroute_dst_v4(self):
+        self.skipTest("NVSD Plugin does not support IPV6.")
+
+    def test_update_subnet_inconsistent_ipv6_hostroute_np_v4(self):
+        self.skipTest("NVSD Plugin does not support IPV6.")
+
+
+class TestOneConvergencePluginPortsV2(test_plugin.TestPortsV2,
+                                      test_bindings.PortBindingsTestCase,
+                                      OneConvergencePluginV2TestCase):
+    VIF_TYPE = portbindings.VIF_TYPE_OVS
+
+    def test_requested_subnet_id_v4_and_v6(self):
+        self.skipTest("NVSD Plugin does not support IPV6.")
+
+    def test_port_vif_details(self):
+        plugin = NeutronManager.get_plugin()
+        with self.port(name='name') as port1:
+            ctx = context.get_admin_context()
+            port = plugin.get_port(ctx, port1['port']['id'])
+            self.assertEqual(port['binding:vif_type'],
+                             portbindings.VIF_TYPE_OVS)
+
+    def test_ports_vif_details(self):
+        cfg.CONF.set_default('allow_overlapping_ips', True)
+        plugin = NeutronManager.get_plugin()
+        with contextlib.nested(self.port(), self.port()) as (port1, port2):
+            ctx = context.get_admin_context()
+            ports = plugin.get_ports(ctx)
+            self.assertEqual(len(ports), 2)
+            for port in ports:
+                self.assertEqual(port['binding:vif_type'],
+                                 portbindings.VIF_TYPE_OVS)
+
+
+class TestOneConvergenceBasicGet(test_plugin.TestBasicGet,
+                                 OneConvergencePluginV2TestCase):
+    pass
+
+
+class TestOneConvergenceV2HTTPResponse(test_plugin.TestV2HTTPResponse,
+                                       OneConvergencePluginV2TestCase):
+    pass
diff --git a/neutron/tests/unit/oneconvergence/test_nvsdlib.py b/neutron/tests/unit/oneconvergence/test_nvsdlib.py
new file mode 100644 (file)
index 0000000..3ebc6d9
--- /dev/null
@@ -0,0 +1,186 @@
+# Copyright 2014 OneConvergence, Inc. All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+import mock
+
+from neutron.openstack.common import jsonutils as json
+from neutron.plugins.oneconvergence.lib import nvsdlib
+from neutron.tests import base
+
+NETWORKS_URI = "/pluginhandler/ocplugin/tenant/%s/lnetwork/"
+NETWORK_URI = NETWORKS_URI + "%s"
+GET_ALL_NETWORKS = "/pluginhandler/ocplugin/tenant/getallnetworks"
+
+SUBNETS_URI = NETWORK_URI + "/lsubnet/"
+SUBNET_URI = SUBNETS_URI + "%s"
+GET_ALL_SUBNETS = "/pluginhandler/ocplugin/tenant/getallsubnets"
+
+PORTS_URI = NETWORK_URI + "/lport/"
+PORT_URI = PORTS_URI + "%s"
+
+TEST_NET = 'test-network'
+TEST_SUBNET = 'test-subnet'
+TEST_PORT = 'test-port'
+TEST_TENANT = 'test-tenant'
+
+
+class TestNVSDApi(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestNVSDApi, self).setUp()
+        self.nvsdlib = nvsdlib.NVSDApi()
+
+    def test_create_network(self):
+        network_obj = {
+            "name": 'test-net',
+            "tenant_id": TEST_TENANT,
+            "shared": False,
+            "admin_state_up": True,
+            "router:external": False
+        }
+        resp = mock.Mock()
+        resp.json.return_value = {'id': 'uuid'}
+        with mock.patch.object(self.nvsdlib, 'send_request',
+                               return_value=resp) as send_request:
+            uri = NETWORKS_URI % TEST_TENANT
+            net = self.nvsdlib.create_network(network_obj)
+            send_request.assert_called_once_with("POST", uri,
+                                                 body=json.dumps(network_obj),
+                                                 resource='network',
+                                                 tenant_id=TEST_TENANT)
+            self.assertEqual(net, {'id': 'uuid'})
+
+    def test_update_network(self):
+        network = {'id': TEST_NET,
+                   'tenant_id': TEST_TENANT}
+        update_network = {'name': 'new_name'}
+        uri = NETWORK_URI % (TEST_TENANT, TEST_NET)
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            self.nvsdlib.update_network(network, update_network)
+            send_request.assert_called_once_with(
+                "PUT", uri, body=json.dumps(update_network),
+                resource='network', tenant_id=TEST_TENANT,
+                resource_id=TEST_NET)
+
+    def test_delete_network(self):
+        network = {'id': TEST_NET,
+                   'tenant_id': TEST_TENANT}
+
+        uri = NETWORK_URI % (TEST_TENANT, TEST_NET)
+
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            with mock.patch.object(self.nvsdlib, '_get_ports'):
+                self.nvsdlib.delete_network(network)
+                send_request.assert_called_once_with(
+                    "DELETE", uri, resource='network',
+                    tenant_id=TEST_TENANT, resource_id=TEST_NET)
+
+    def test_create_port(self):
+        path = PORTS_URI % (TEST_TENANT, TEST_NET)
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            fixed_ips = [{'ip_address': '10.0.0.2',
+                          'subnet_id': TEST_SUBNET}]
+
+            lport = {
+                "id": TEST_PORT,
+                "name": 'test',
+                "device_id": "device_id",
+                "device_owner": "device_owner",
+                "mac_address": "mac_address",
+                "fixed_ips": fixed_ips,
+                "admin_state_up": True,
+                "network_id": TEST_NET,
+                "status": 'ACTIVE'
+            }
+            self.nvsdlib.create_port(TEST_TENANT, lport)
+            expected = {"id": TEST_PORT, "name": 'test',
+                        "device_id": "device_id",
+                        "device_owner": "device_owner",
+                        "mac_address": "mac_address",
+                        "ip_address": '10.0.0.2',
+                        "subnet_id": TEST_SUBNET,
+                        "admin_state_up": True,
+                        "network_id": TEST_NET,
+                        "status": 'ACTIVE'}
+            send_request.assert_called_once_with("POST", path,
+                                                 body=json.dumps(expected),
+                                                 resource='port',
+                                                 tenant_id=TEST_TENANT)
+
+    def test_update_port(self):
+        port = {'id': TEST_PORT,
+                'network_id': TEST_NET}
+
+        port_update = {'name': 'new-name'}
+        uri = PORT_URI % (TEST_TENANT, TEST_NET, TEST_PORT)
+
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            self.nvsdlib.update_port(TEST_TENANT, port, port_update)
+            send_request.assert_called_once_with("PUT", uri,
+                                                 body=json.dumps(port_update),
+                                                 resource='port',
+                                                 resource_id='test-port',
+                                                 tenant_id=TEST_TENANT)
+
+    def test_delete_port(self):
+        port = {'network_id': TEST_NET,
+                'tenant_id': TEST_TENANT}
+        uri = PORT_URI % (TEST_TENANT, TEST_NET, TEST_PORT)
+
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            self.nvsdlib.delete_port(TEST_PORT, port)
+            send_request.assert_called_once_with("DELETE", uri,
+                                                 resource='port',
+                                                 tenant_id=TEST_TENANT,
+                                                 resource_id=TEST_PORT)
+
+    def test_create_subnet(self):
+        subnet = {'id': TEST_SUBNET,
+                  'tenant_id': TEST_TENANT,
+                  'network_id': TEST_NET}
+        uri = SUBNETS_URI % (TEST_TENANT, TEST_NET)
+
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            self.nvsdlib.create_subnet(subnet)
+            send_request.assert_called_once_with("POST", uri,
+                                                 body=json.dumps(subnet),
+                                                 resource='subnet',
+                                                 tenant_id=TEST_TENANT)
+
+    def test_update_subnet(self):
+        subnet = {'id': TEST_SUBNET,
+                  'tenant_id': TEST_TENANT,
+                  'network_id': TEST_NET}
+        subnet_update = {'name': 'new-name'}
+        uri = SUBNET_URI % (TEST_TENANT, TEST_NET, TEST_SUBNET)
+
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            self.nvsdlib.update_subnet(subnet, subnet_update)
+            send_request.assert_called_once_with(
+                "PUT", uri, body=json.dumps(subnet_update), resource='subnet',
+                tenant_id=TEST_TENANT, resource_id=TEST_SUBNET)
+
+    def test_delete_subnet(self):
+        subnet = {'id': TEST_SUBNET,
+                  'tenant_id': TEST_TENANT,
+                  'network_id': TEST_NET}
+        uri = SUBNET_URI % (TEST_TENANT, TEST_NET, TEST_SUBNET)
+
+        with mock.patch.object(self.nvsdlib, 'send_request') as send_request:
+            self.nvsdlib.delete_subnet(subnet)
+            send_request.assert_called_once_with("DELETE", uri,
+                                                 resource='subnet',
+                                                 tenant_id=TEST_TENANT,
+                                                 resource_id=TEST_SUBNET)
diff --git a/neutron/tests/unit/oneconvergence/test_plugin_helper.py b/neutron/tests/unit/oneconvergence/test_plugin_helper.py
new file mode 100644 (file)
index 0000000..21031a7
--- /dev/null
@@ -0,0 +1,60 @@
+# Copyright 2014 OneConvergence, 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: Kedar Kulkarni, One Convergence, Inc.
+import mock
+import requests
+
+from neutron.openstack.common import jsonutils as json
+from neutron.plugins.oneconvergence.lib import config  # noqa
+from neutron.plugins.oneconvergence.lib import plugin_helper as client
+from neutron.tests import base
+
+
+class TestPluginHelper(base.BaseTestCase):
+    def setUp(self):
+        super(TestPluginHelper, self).setUp()
+        self.nvsdcontroller = client.NVSDController()
+
+    def get_response(self, *args, **kwargs):
+        response = mock.Mock()
+        response.status_code = requests.codes.ok
+        response.content = json.dumps({'session_uuid': 'new_auth_token'})
+        return response
+
+    def test_login(self):
+        login_url = ('http://127.0.0.1:8082/pluginhandler/ocplugin/'
+                     'authmgmt/login')
+        headers = {'Content-Type': 'application/json'}
+        data = json.dumps({"user_name": "ocplugin", "passwd": "oc123"})
+        timeout = 30.0
+
+        with mock.patch.object(self.nvsdcontroller, 'do_request',
+                               side_effect=self.get_response) as do_request:
+            self.nvsdcontroller.login()
+            do_request.assert_called_once_with('POST', url=login_url,
+                                               headers=headers, data=data,
+                                               timeout=timeout)
+
+    def test_request(self):
+        with mock.patch.object(self.nvsdcontroller, 'do_request',
+                               side_effect=self.get_response) as do_request:
+            self.nvsdcontroller.login()
+            self.nvsdcontroller.request("POST", "/some_url")
+            self.assertEqual(do_request.call_count, 2)
+            do_request.assert_called_with(
+                'POST',
+                url='http://127.0.0.1:8082/some_url?authToken=new_auth_token',
+                headers={'Content-Type': 'application/json'}, data='',
+                timeout=30.0)
index ef14663453a5125716dc172128cb26fcc74c5fc4..274a490df7b1c3531a188d3e2299398563a03932 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -68,6 +68,7 @@ data_files =
     etc/neutron/plugins/mlnx = etc/neutron/plugins/mlnx/mlnx_conf.ini
     etc/neutron/plugins/nec = etc/neutron/plugins/nec/nec.ini
     etc/neutron/plugins/nicira = etc/neutron/plugins/nicira/nvp.ini
+    etc/neutron/plugins/oneconvergence = etc/neutron/plugins/oneconvergence/nvsdplugin.ini
     etc/neutron/plugins/openvswitch = etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini
     etc/neutron/plugins/plumgrid = etc/neutron/plugins/plumgrid/plumgrid.ini
     etc/neutron/plugins/ryu = etc/neutron/plugins/ryu/ryu.ini
@@ -144,6 +145,7 @@ neutron.core_plugins =
     mlnx = neutron.plugins.mlnx.mlnx_plugin:MellanoxEswitchPlugin
     nec = neutron.plugins.nec.nec_plugin:NECPluginV2
     nicira = neutron.plugins.nicira.NeutronPlugin:NvpPluginV2
+    oneconvergence = neutron.plugins.oneconvergence.plugin.OneConvergencePluginV2
     openvswitch = neutron.plugins.openvswitch.ovs_neutron_plugin:OVSNeutronPluginV2
     plumgrid = neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin:NeutronPluginPLUMgridV2
     ryu = neutron.plugins.ryu.ryu_neutron_plugin:RyuNeutronPluginV2