]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Adds the new IBM SDN-VE plugin
authorMohammad Banikazemi <mb@us.ibm.com>
Thu, 27 Feb 2014 11:45:17 +0000 (06:45 -0500)
committerThomas Goirand <thomas@goirand.fr>
Thu, 13 Mar 2014 07:20:37 +0000 (15:20 +0800)
It adds a new plugin for SDN-VE, the IBM SDN
controller. The plugin supports the core API
and the port binding and L3 extensions.

Implements: blueprint ibm-sdn-ve-plugin
DocImpact

Change-Id: I92619a95bca2ae0c37e7fdd39da30119b43d1ad6

21 files changed:
etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/176a85fc7d79_add_portbindings_db.py
neutron/db/migration/alembic_migrations/versions/1fcfc149aca4_agents_unique_by_type_and_host.py
neutron/db/migration/alembic_migrations/versions/511471cc46b_agent_ext_model_supp.py
neutron/db/migration/alembic_migrations/versions/folsom_initial.py
neutron/plugins/ibm/README [new file with mode: 0644]
neutron/plugins/ibm/__init__.py [new file with mode: 0644]
neutron/plugins/ibm/agent/__init__.py [new file with mode: 0644]
neutron/plugins/ibm/agent/sdnve_neutron_agent.py [new file with mode: 0644]
neutron/plugins/ibm/common/__init__.py [new file with mode: 0644]
neutron/plugins/ibm/common/config.py [new file with mode: 0644]
neutron/plugins/ibm/common/constants.py [new file with mode: 0644]
neutron/plugins/ibm/common/exceptions.py [new file with mode: 0644]
neutron/plugins/ibm/sdnve_api.py [new file with mode: 0644]
neutron/plugins/ibm/sdnve_api_fake.py [new file with mode: 0644]
neutron/plugins/ibm/sdnve_neutron_plugin.py [new file with mode: 0644]
neutron/tests/unit/ibm/__init__.py [new file with mode: 0644]
neutron/tests/unit/ibm/test_sdnve_agent.py [new file with mode: 0644]
neutron/tests/unit/ibm/test_sdnve_api.py [new file with mode: 0644]
neutron/tests/unit/ibm/test_sdnve_plugin.py [new file with mode: 0644]
setup.cfg

diff --git a/etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini b/etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini
new file mode 100644 (file)
index 0000000..8cb32ea
--- /dev/null
@@ -0,0 +1,49 @@
+[sdnve]
+# (ListOpt) The IP address of one (or more) SDN-VE controllers
+# Default value is : controller_ips = 127.0.0.1
+# Example: controller_ips = 127.0.0.1,127.0.0.2
+# (StrOpt) The integration bridge for OF based implementation
+# The default value for integration_bridge is None
+# Example: integration_bridge = br-int
+# (ListOpt) The interface mapping connecting the integration
+# bridge to external network as a list of physical network names and
+# interfaces: <physical_network_name>:<interface_name>
+# Example: interface_mappings = default:eth2
+# (BoolOpt) Used to reset the integration bridge, if exists
+# The default value for reset_bridge is True
+# Example: reset_bridge = False
+# (BoolOpt) Used to set the OVS controller as out-of-band
+# The default value for out_of_band is True
+# Example: out_of_band = False
+#
+# (BoolOpt) The fake controller for testing purposes
+# Default value is : use_fake_controller = False
+# (StrOpt) The port number for use with controller
+# The default value for the port is 8443
+# Example: port = 8443
+# (StrOpt) The userid for use with controller
+# The default value for the userid is admin
+# Example: userid = sdnve_user
+# (StrOpt) The password for use with controller
+# The default value for the password is admin
+# Example: password = sdnve_password
+#
+# (StrOpt) The default type of tenants (and associated resources)
+# Default value for OF is: default_tenant = OF
+# Example: default_tenant = OVERLAY
+# (StrOpt) The string in tenant description that indicates
+# Default value for OF tenants: of_signature = SDNVE-OF
+# (StrOpt) The string in tenant description that indicates
+# Default value for OVERLAY tenants: overlay_signature = SDNVE-OVERLAY
+
+[sdnve_agent]
+# (IntOpt) Agent's polling interval in seconds
+# polling_interval = 2
+# (StrOpt) What to use for root helper
+# The default value: root_helper = 'sudo'
+# (BoolOpt) Whether to use rpc or not
+# The default value: rpc = True
+
+[securitygroup]
+# The security group is not supported:
+# firewall_driver = neutron.agent.firewall.NoopFirewallDriver
index 82e40a1cee3a1a8e1548579453ff1742e9badbcc..fdf62e03753a99c520751824599f4ec42e45148e 100644 (file)
@@ -36,7 +36,8 @@ migration_for_plugins = [
     'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
     'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
     'neutron.plugins.vmware.plugin.NsxPlugin',
-    'neutron.plugins.vmware.plugin.NsxServicePlugin'
+    'neutron.plugins.vmware.plugin.NsxServicePlugin',
+    'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
 ]
 
 from alembic import op
index b563f9c893a3906ec0b01277dafe8a9df5d64ab4..4fe6e1398bac9fbef7839a697d976c2589b35f14 100644 (file)
@@ -37,6 +37,7 @@ migration_for_plugins = [
     'neutron.plugins.vmware.plugin.NsxPlugin',
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
     'neutron.services.loadbalancer.plugin.LoadBalancerPlugin',
+    'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
 ]
 
 from alembic import op
index ec21abda50f4626898962f3a5461c6bd4bac9534..a1154af57ff5ee862cc0f56efd3fe9c24c12ed13 100644 (file)
@@ -38,7 +38,8 @@ migration_for_plugins = [
     'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
     'neutron.plugins.vmware.plugin.NsxPlugin',
     'neutron.plugins.vmware.plugin.NsxServicePlugin',
-    'neutron.services.loadbalancer.plugin.LoadBalancerPlugin'
+    'neutron.services.loadbalancer.plugin.LoadBalancerPlugin',
+    'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
 ]
 
 from alembic import op
index 7289e9c271d5e7cd4dfffadd73bbfaa63d86c874..e75b63c91cb684965f4b5b75b87fbba1386f00d0 100644 (file)
@@ -37,6 +37,7 @@ PLUGINS = {
     'plumgrid': 'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.'
                 'NeutronPluginPLUMgridV2',
     'ryu': 'neutron.plugins.ryu.ryu_neutron_plugin.RyuNeutronPluginV2',
+    'ibm': 'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
 }
 
 L3_CAPABLE = [
@@ -48,6 +49,7 @@ L3_CAPABLE = [
     PLUGINS['ryu'],
     PLUGINS['brocade'],
     PLUGINS['plumgrid'],
+    PLUGINS['ibm'],
 ]
 
 FOLSOM_QUOTA = [
diff --git a/neutron/plugins/ibm/README b/neutron/plugins/ibm/README
new file mode 100644 (file)
index 0000000..732fd77
--- /dev/null
@@ -0,0 +1,6 @@
+IBM SDN-VE Neutron Plugin
+
+This plugin implements Neutron v2 APIs.
+
+For more details on how to use it please refer to the following page:
+http://wiki.openstack.org/wiki/IBM-Neutron
diff --git a/neutron/plugins/ibm/__init__.py b/neutron/plugins/ibm/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/ibm/agent/__init__.py b/neutron/plugins/ibm/agent/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/ibm/agent/sdnve_neutron_agent.py b/neutron/plugins/ibm/agent/sdnve_neutron_agent.py
new file mode 100644 (file)
index 0000000..a67976e
--- /dev/null
@@ -0,0 +1,247 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+
+import socket
+import time
+
+import eventlet
+from oslo.config import cfg
+
+from neutron.agent.linux import ip_lib
+from neutron.agent.linux import ovs_lib
+from neutron.agent import rpc as agent_rpc
+from neutron.common import config as logging_config
+from neutron.common import legacy
+from neutron.common import topics
+from neutron.common import utils as q_utils
+from neutron import context
+from neutron.openstack.common import log as logging
+from neutron.openstack.common.rpc import dispatcher
+from neutron.plugins.ibm.common import config  # noqa
+from neutron.plugins.ibm.common import constants
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SdnvePluginApi(agent_rpc.PluginApi):
+
+    def sdnve_info(self, context, info):
+        return self.call(context,
+                         self.make_msg('sdnve_info', info=info),
+                         topic=self.topic)
+
+
+class SdnveNeutronAgent():
+
+    RPC_API_VERSION = '1.1'
+
+    def __init__(self, integ_br, interface_mappings,
+                 info, root_helper, polling_interval,
+                 controller_ip, reset_br, out_of_band):
+        '''The agent initialization.
+
+        Sets the following parameters and sets up the integration
+        bridge and physical interfaces if need be.
+        :param integ_br: name of the integration bridge.
+        :param interface_mappings: interfaces to physical networks.
+        :param info: local IP address of this hypervisor.
+        :param root_helper: utility to use when running shell cmds.
+        :param polling_interval: interval (secs) to poll DB.
+        :param controller_ip: Ip address of SDN-VE controller.
+        '''
+
+        self.root_helper = root_helper
+        self.int_bridge_name = integ_br
+        self.controller_ip = controller_ip
+        self.interface_mappings = interface_mappings
+        self.polling_interval = polling_interval
+        self.info = info
+        self.reset_br = reset_br
+        self.out_of_band = out_of_band
+
+        if self.int_bridge_name:
+            self.int_br = self.setup_integration_br(integ_br, reset_br,
+                                                    out_of_band,
+                                                    self.controller_ip)
+            self.setup_physical_interfaces(self.interface_mappings)
+        else:
+            self.int_br = None
+
+        self.setup_rpc()
+
+    def setup_rpc(self):
+        if self.int_br:
+            mac = self.int_br.get_local_port_mac()
+            self.agent_id = '%s%s' % ('sdnve', (mac.replace(":", "")))
+        else:
+            nameaddr = socket.gethostbyname(socket.gethostname())
+            self.agent_id = '%s%s' % ('sdnve_', (nameaddr.replace(".", "_")))
+
+        self.topic = topics.AGENT
+        self.plugin_rpc = SdnvePluginApi(topics.PLUGIN)
+        self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
+
+        self.context = context.get_admin_context_without_session()
+        self.dispatcher = self.create_rpc_dispatcher()
+        consumers = [[constants.INFO, topics.UPDATE]]
+
+        self.connection = agent_rpc.create_consumers(self.dispatcher,
+                                                     self.topic,
+                                                     consumers)
+
+    # Plugin calls the agents through the following
+    def info_update(self, context, **kwargs):
+        LOG.debug(_("info_update received"))
+        info = kwargs.get('info', {})
+        new_controller = info.get('new_controller')
+        out_of_band = info.get('out_of_band')
+        if self.int_br and new_controller:
+            LOG.debug(_("info_update received. New controller"
+                        "is to be set to: %s"), new_controller)
+            self.int_br.run_vsctl(["set-controller",
+                                   self.int_bridge_name,
+                                   "tcp:" + new_controller])
+            if out_of_band:
+                LOG.debug(_("info_update received. New controller"
+                            "is set to be out of band"))
+                self.int_br.set_db_attribute("controller",
+                                             self.int_bridge_name,
+                                             "connection-mode",
+                                             "out-of-band")
+
+    def create_rpc_dispatcher(self):
+        return dispatcher.RpcDispatcher([self])
+
+    def setup_integration_br(self, bridge_name, reset_br, out_of_band,
+                             controller_ip=None):
+        '''Sets up the integration bridge.
+
+        Create the bridge and remove all existing flows if reset_br is True.
+        Otherwise, creates the bridge if not already existing.
+        :param bridge_name: the name of the integration bridge.
+        :param reset_br: A boolean to rest the bridge if True.
+        :param out_of_band: A boolean inidicating controller is out of band.
+        :param controller_ip: IP address to use as the bridge controller.
+        :returns: the integration bridge
+        '''
+
+        int_br = ovs_lib.OVSBridge(bridge_name, self.root_helper)
+        if reset_br:
+            int_br.reset_bridge()
+            int_br.remove_all_flows()
+        else:
+            int_br.create()
+
+        # set the controller
+        if controller_ip:
+            int_br.run_vsctl(
+                ["set-controller", bridge_name, "tcp:" + controller_ip])
+        if out_of_band:
+            int_br.set_db_attribute("controller", bridge_name,
+                                    "connection-mode", "out-of-band")
+
+        return int_br
+
+    def setup_physical_interfaces(self, interface_mappings):
+        '''Sets up the physical network interfaces.
+
+        Link physical interfaces to the integration bridge.
+        :param interface_mappings: map physical net names to interface names.
+        '''
+
+        for physical_network, interface in interface_mappings.iteritems():
+            LOG.info(_("Mapping physical network %(physical_network)s to "
+                       "interface %(interface)s"),
+                     {'physical_network': physical_network,
+                      'interface': interface})
+            # Connect the physical interface to the bridge
+            if not ip_lib.device_exists(interface, self.root_helper):
+                LOG.error(_("Interface %(interface)s for physical network "
+                            "%(physical_network)s does not exist. Agent "
+                            "terminated!"),
+                          {'physical_network': physical_network,
+                           'interface': interface})
+                raise SystemExit(1)
+            self.int_br.add_port(interface)
+
+    def sdnve_info(self):
+        details = self.plugin_rpc.sdnve_info(
+            self.context,
+            {'info': self.info})
+        return details
+
+    def rpc_loop(self):
+
+        while True:
+            start = time.time()
+            LOG.debug(_("Agent in the rpc loop."))
+
+            # sleep till end of polling interval
+            elapsed = (time.time() - start)
+            if (elapsed < self.polling_interval):
+                time.sleep(self.polling_interval - elapsed)
+            else:
+                LOG.info(_("Loop iteration exceeded interval "
+                           "(%(polling_interval)s vs. %(elapsed)s)!"),
+                         {'polling_interval': self.polling_interval,
+                          'elapsed': elapsed})
+
+    def daemon_loop(self):
+        self.rpc_loop()
+
+
+def create_agent_config_map(config):
+
+    interface_mappings = q_utils.parse_mappings(
+        config.SDNVE.interface_mappings)
+
+    controller_ips = config.SDNVE.controller_ips
+    LOG.info(_("Controller IPs: %s"), controller_ips)
+    controller_ip = controller_ips[0]
+
+    return {
+        'integ_br': config.SDNVE.integration_bridge,
+        'interface_mappings': interface_mappings,
+        'controller_ip': controller_ip,
+        'info': config.SDNVE.info,
+        'root_helper': config.SDNVE_AGENT.root_helper,
+        'polling_interval': config.SDNVE_AGENT.polling_interval,
+        'reset_br': config.SDNVE.reset_bridge,
+        'out_of_band': config.SDNVE.out_of_band}
+
+
+def main():
+    eventlet.monkey_patch()
+    cfg.CONF.register_opts(ip_lib.OPTS)
+    cfg.CONF(project='neutron')
+    logging_config.setup_logging(cfg.CONF)
+    legacy.modernize_quantum_config(cfg.CONF)
+
+    try:
+        agent_config = create_agent_config_map(cfg.CONF)
+    except ValueError as e:
+        LOG.exception(_("%s Agent terminated!"), e)
+        raise SystemExit(1)
+
+    plugin = SdnveNeutronAgent(**agent_config)
+
+    # Start everything.
+    LOG.info(_("Agent initialized successfully, now running... "))
+    plugin.daemon_loop()
diff --git a/neutron/plugins/ibm/common/__init__.py b/neutron/plugins/ibm/common/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/ibm/common/config.py b/neutron/plugins/ibm/common/config.py
new file mode 100644 (file)
index 0000000..b32c7b6
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+
+from oslo.config import cfg
+
+
+DEFAULT_INTERFACE_MAPPINGS = []
+DEFAULT_CONTROLLER_IPS = ['127.0.0.1']
+
+sdnve_opts = [
+    cfg.BoolOpt('use_fake_controller', default=False,
+                help=_("If set to True uses a fake controller.")),
+    cfg.StrOpt('base_url', default='/one/nb/v2/',
+               help=_("Base URL for SDN-VE controller REST API")),
+    cfg.ListOpt('controller_ips', default=DEFAULT_CONTROLLER_IPS,
+                help=_("List of IP addresses of SDN-VE controller(s)")),
+    cfg.StrOpt('info', default='sdnve_info_string',
+               help=_("SDN-VE RPC subject")),
+    cfg.StrOpt('port', default='8443',
+               help=_("SDN-VE controller port number")),
+    cfg.StrOpt('format', default='json',
+               help=_("SDN-VE request/response format")),
+    cfg.StrOpt('userid', default='admin',
+               help=_("SDN-VE administrator user id")),
+    cfg.StrOpt('password', default='admin',
+               help=_("SDN-VE administrator password")),
+    cfg.StrOpt('integration_bridge', default=None,
+               help=_("Integration bridge to use")),
+    cfg.BoolOpt('reset_bridge', default=True,
+                help=_("Reset the integration bridge before use")),
+    cfg.BoolOpt('out_of_band', default=True,
+                help=_("Indicating if controller is out of band or not")),
+    cfg.ListOpt('interface_mappings',
+                default=DEFAULT_INTERFACE_MAPPINGS,
+                help=_("List of <physical_network_name>:<interface_name>")),
+    cfg.StrOpt('default_tenant_type', default='OF',
+               help=_("Tenant type: OF (default) and OVERLAY")),
+    cfg.StrOpt('overlay_signature', default='SDNVE-OVERLAY',
+               help=_("The string in tenant description that indicates "
+                      "the tenant is a OVERLAY tenant")),
+    cfg.StrOpt('of_signature', default='SDNVE-OF',
+               help=_("The string in tenant description that indicates "
+                      "the tenant is a OF tenant")),
+]
+
+sdnve_agent_opts = [
+    cfg.IntOpt('polling_interval', default=2,
+               help=_("Agent polling interval if necessary")),
+    cfg.StrOpt('root_helper', default='sudo',
+               help=_("Using root helper")),
+    cfg.BoolOpt('rpc', default=True,
+                help=_("Whether using rpc")),
+
+]
+
+
+cfg.CONF.register_opts(sdnve_opts, "SDNVE")
+cfg.CONF.register_opts(sdnve_agent_opts, "SDNVE_AGENT")
diff --git a/neutron/plugins/ibm/common/constants.py b/neutron/plugins/ibm/common/constants.py
new file mode 100644 (file)
index 0000000..3acf9ba
--- /dev/null
@@ -0,0 +1,32 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+
+import httplib
+
+# Topic for info notifications between the plugin and agent
+INFO = 'info'
+
+TENANT_TYPE_OF = 'OF'
+TENANT_TYPE_OVERLAY = 'OVERLAY'
+
+HTTP_ACCEPTABLE = [httplib.OK,
+                   httplib.CREATED,
+                   httplib.ACCEPTED,
+                   httplib.NO_CONTENT
+                   ]
diff --git a/neutron/plugins/ibm/common/exceptions.py b/neutron/plugins/ibm/common/exceptions.py
new file mode 100644 (file)
index 0000000..d2e5e7e
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+from neutron.common import exceptions
+
+
+class SdnveException(exceptions.NeutronException):
+    message = _("An unexpected error occurred in the SDN-VE Plugin. "
+                "Here is the error message: %(msg)s")
+
+
+class BadInputException(exceptions.BadRequest):
+    message = _("The input does not contain nececessary info: %(msg)s")
diff --git a/neutron/plugins/ibm/sdnve_api.py b/neutron/plugins/ibm/sdnve_api.py
new file mode 100644 (file)
index 0000000..43ba132
--- /dev/null
@@ -0,0 +1,387 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+
+import httplib
+import urllib
+
+import httplib2
+from keystoneclient.v2_0 import client as keyclient
+from oslo.config import cfg
+
+from neutron.api.v2 import attributes
+from neutron.openstack.common import log as logging
+from neutron.plugins.ibm.common import config  # noqa
+from neutron.plugins.ibm.common import constants
+from neutron.wsgi import Serializer
+
+LOG = logging.getLogger(__name__)
+
+SDNVE_VERSION = '2.0'
+SDNVE_ACTION_PREFIX = '/sdnve'
+SDNVE_RETRIES = 0
+SDNVE_RETRIY_INTERVAL = 1
+SDNVE_TENANT_TYPE_OVERLAY = u'DOVE'
+SDNVE_URL = 'https://%s:%s%s'
+
+
+class RequestHandler(object):
+    '''Handles processeing requests to and responses from controller.'''
+
+    def __init__(self, controller_ips=None, port=None, ssl=None,
+                 base_url=None, userid=None, password=None,
+                 timeout=10, formats=None):
+        '''Initializes the RequestHandler for communication with controller
+
+        Following keyword arguments are used; if not specified, default
+        values are used.
+        :param port: Username for authentication.
+        :param timeout: Time out for http requests.
+        :param userid: User id for accessing controller.
+        :param password: Password for accessing the controlelr.
+        :param base_url: The base url for the controller.
+        :param controller_ips: List of controller IP addresses.
+        :param formats: Supported formats.
+        '''
+        self.port = port or cfg.CONF.SDNVE.port
+        self.timeout = timeout
+        self._s_meta = None
+        self.connection = None
+        self.httpclient = httplib2.Http(
+            disable_ssl_certificate_validation=True)
+        self.cookie = None
+
+        userid = userid or cfg.CONF.SDNVE.userid
+        password = password or cfg.CONF.SDNVE.password
+        if (userid and password):
+            self.httpclient.add_credentials(userid, password)
+
+        self.base_url = base_url or cfg.CONF.SDNVE.base_url
+        self.controller_ips = controller_ips or cfg.CONF.SDNVE.controller_ips
+
+        LOG.info(_("The IP addr of available SDN-VE controllers: %s"),
+                 self.controller_ips)
+        self.controller_ip = self.controller_ips[0]
+        LOG.info(_("The SDN-VE controller IP address: %s"),
+                 self.controller_ip)
+
+        self.new_controller = False
+        self.format = formats or cfg.CONF.SDNVE.format
+
+        self.version = SDNVE_VERSION
+        self.action_prefix = SDNVE_ACTION_PREFIX
+        self.retries = SDNVE_RETRIES
+        self.retry_interval = SDNVE_RETRIY_INTERVAL
+
+    def serialize(self, data):
+        '''Serializes a dictionary with a single key.'''
+
+        if isinstance(data, dict):
+            return Serializer().serialize(data, self.content_type())
+        elif data:
+            raise TypeError(_("unable to serialize object type: '%s'") %
+                            type(data))
+
+    def deserialize(self, data, status_code):
+        '''Deserializes an xml or json string into a dictionary.'''
+
+        # NOTE(mb): Temporary fix for backend controller requirement
+        data = data.replace("router_external", "router:external")
+
+        if status_code == httplib.NO_CONTENT:
+            return data
+        try:
+            deserialized_data = Serializer(
+                metadata=self._s_meta).deserialize(data, self.content_type())
+            deserialized_data = deserialized_data['body']
+        except Exception:
+            deserialized_data = data
+
+        return deserialized_data
+
+    def content_type(self, format=None):
+        '''Returns the mime-type for either 'xml' or 'json'.'''
+
+        return 'application/%s' % (format or self.format)
+
+    def delete(self, url, body=None, headers=None, params=None):
+        return self.do_request("DELETE", url, body=body,
+                               headers=headers, params=params)
+
+    def get(self, url, body=None, headers=None, params=None):
+        return self.do_request("GET", url, body=body,
+                               headers=headers, params=params)
+
+    def post(self, url, body=None, headers=None, params=None):
+        return self.do_request("POST", url, body=body,
+                               headers=headers, params=params)
+
+    def put(self, url, body=None, headers=None, params=None):
+        return self.do_request("PUT", url, body=body,
+                               headers=headers, params=params)
+
+    def do_request(self, method, url, body=None, headers=None,
+                   params=None, connection_type=None):
+
+        status_code = -1
+        replybody_deserialized = ''
+
+        if body:
+            body = self.serialize(body)
+
+        self.headers = headers or {'Content-Type': self.content_type()}
+        if self.cookie:
+            self.headers['cookie'] = self.cookie
+
+        if self.controller_ip != self.controller_ips[0]:
+            controllers = [self.controller_ip]
+        else:
+            controllers = []
+        controllers.extend(self.controller_ips)
+
+        for controller_ip in controllers:
+            serverurl = SDNVE_URL % (controller_ip, self.port, self.base_url)
+            myurl = serverurl + url
+            if params and isinstance(params, dict):
+                myurl += '?' + urllib.urlencode(params, doseq=1)
+
+            try:
+                LOG.debug(_("Sending request to SDN-VE. url: "
+                            "%(myurl)s method: %(method)s body: "
+                            "%(body)s header: %(header)s "),
+                          {'myurl': myurl, 'method': method,
+                           'body': body, 'header': self.headers})
+                resp, replybody = self.httpclient.request(
+                    myurl, method=method, body=body, headers=self.headers)
+                LOG.debug(("Response recd from SDN-VE. resp: %(resp)s"
+                           "body: %(body)s"),
+                          {'resp': resp.status, 'body': replybody})
+                status_code = resp.status
+
+            except Exception as e:
+                LOG.error(_("Error: Could not reach server: %(url)s "
+                            "Exception: %(excp)s."),
+                          {'url': myurl, 'excp': e})
+                self.cookie = None
+                continue
+
+            if status_code not in constants.HTTP_ACCEPTABLE:
+                LOG.debug(_("Error message: %(reply)s --  Status: %(status)s"),
+                          {'reply': replybody, 'status': status_code})
+            else:
+                LOG.debug(_("Received response status: %s"), status_code)
+
+            if resp.get('set-cookie'):
+                self.cookie = resp['set-cookie']
+            replybody_deserialized = self.deserialize(
+                replybody,
+                status_code)
+            LOG.debug(_("Deserialized body: %s"), replybody_deserialized)
+            if controller_ip != self.controller_ip:
+                # bcast the change of controller
+                self.new_controller = True
+                self.controller_ip = controller_ip
+
+            return (status_code, replybody_deserialized)
+
+        return (httplib.REQUEST_TIMEOUT, 'Could not reach server(s)')
+
+
+class Client(RequestHandler):
+    '''Client for SDNVE controller.'''
+
+    def __init__(self):
+        '''Initialize a new SDNVE client.'''
+        super(Client, self).__init__()
+
+        self.keystoneclient = KeystoneClient()
+
+    resource_path = {
+        'network': "ln/networks/",
+        'subnet': "ln/subnets/",
+        'port': "ln/ports/",
+        'tenant': "ln/tenants/",
+        'router': "ln/routers/",
+        'floatingip': "ln/floatingips/",
+    }
+
+    def process_request(self, body):
+        '''Processes requests according to requirements of controller.'''
+        if self.format == 'json':
+            body = dict(
+                (k.replace(':', '_'), v) for k, v in body.items()
+                if attributes.is_attr_set(v))
+
+    def sdnve_list(self, resource, **params):
+        '''Fetches a list of resources.'''
+
+        res = self.resource_path.get(resource, None)
+        if not res:
+            LOG.info(_("Bad resource for forming a list request"))
+            return 0, ''
+
+        return self.get(res, params=params)
+
+    def sdnve_show(self, resource, specific, **params):
+        '''Fetches information of a certain resource.'''
+
+        res = self.resource_path.get(resource, None)
+        if not res:
+            LOG.info(_("Bad resource for forming a show request"))
+            return 0, ''
+
+        return self.get(res + specific, params=params)
+
+    def sdnve_create(self, resource, body):
+        '''Creates a new resource.'''
+
+        res = self.resource_path.get(resource, None)
+        if not res:
+            LOG.info(_("Bad resource for forming a create request"))
+            return 0, ''
+
+        self.process_request(body)
+        status, data = self.post(res, body=body)
+        return (status, data)
+
+    def sdnve_update(self, resource, specific, body=None):
+        '''Updates a resource.'''
+
+        res = self.resource_path.get(resource, None)
+        if not res:
+            LOG.info(_("Bad resource for forming a update request"))
+            return 0, ''
+
+        self.process_request(body)
+        return self.put(res + specific, body=body)
+
+    def sdnve_delete(self, resource, specific):
+        '''Deletes the specified resource.'''
+
+        res = self.resource_path.get(resource, None)
+        if not res:
+            LOG.info(_("Bad resource for forming a delete request"))
+            return 0, ''
+
+        return self.delete(res + specific)
+
+    def _tenant_id_conversion(self, osid):
+        return osid
+
+    def sdnve_get_tenant_byid(self, os_tenant_id):
+        sdnve_tenant_id = self._tenant_id_conversion(os_tenant_id)
+        resp, content = self.sdnve_show('tenant', sdnve_tenant_id)
+        if resp in constants.HTTP_ACCEPTABLE:
+            tenant_id = content.get('id')
+            tenant_type = content.get('network_type')
+            if tenant_type == SDNVE_TENANT_TYPE_OVERLAY:
+                tenant_type = constants.TENANT_TYPE_OVERLAY
+            return tenant_id, tenant_type
+        return None, None
+
+    def sdnve_check_and_create_tenant(self, os_tenant_id, network_type=None):
+
+        if not os_tenant_id:
+            return
+        tenant_id, tenant_type = self.sdnve_get_tenant_byid(os_tenant_id)
+        if tenant_id:
+            if not network_type:
+                return tenant_id
+            if tenant_type != network_type:
+                LOG.info(_("Non matching tenant and network types: "
+                           "%(ttype)s %(ntype)s"),
+                         {'ttype': tenant_type, 'ntype': network_type})
+                return
+            return tenant_id
+
+        # Have to create a new tenant
+        sdnve_tenant_id = self._tenant_id_conversion(os_tenant_id)
+        if not network_type:
+            network_type = self.keystoneclient.get_tenant_type(os_tenant_id)
+        if network_type == constants.TENANT_TYPE_OVERLAY:
+            network_type = SDNVE_TENANT_TYPE_OVERLAY
+
+        pinn_desc = ("Created by SDN-VE Neutron Plugin, OS project name = " +
+                     self.keystoneclient.get_tenant_name(os_tenant_id))
+
+        res, content = self.sdnve_create('tenant',
+                                         {'id': sdnve_tenant_id,
+                                          'name': os_tenant_id,
+                                          'network_type': network_type,
+                                          'description': pinn_desc})
+        if res not in constants.HTTP_ACCEPTABLE:
+            return
+
+        return sdnve_tenant_id
+
+    def sdnve_get_controller(self):
+        if self.new_controller:
+            self.new_controller = False
+            return self.controller_ip
+
+
+class KeystoneClient(object):
+
+    def __init__(self, username=None, tenant_name=None, password=None,
+                 auth_url=None):
+
+        keystone_conf = cfg.CONF.keystone_authtoken
+        keystone_auth_url = ('%s://%s:%s/v2.0/' %
+                             (keystone_conf.auth_protocol,
+                              keystone_conf.auth_host,
+                              keystone_conf.auth_port))
+
+        username = username or keystone_conf.admin_user
+        tenant_name = tenant_name or keystone_conf.admin_tenant_name
+        password = password or keystone_conf.admin_password
+        auth_url = auth_url or keystone_auth_url
+
+        self.overlay_signature = cfg.CONF.SDNVE.overlay_signature
+        self.of_signature = cfg.CONF.SDNVE.of_signature
+        self.default_tenant_type = cfg.CONF.SDNVE.default_tenant_type
+
+        self.client = keyclient.Client(username=username,
+                                       password=password,
+                                       tenant_name=tenant_name,
+                                       auth_url=auth_url)
+
+    def get_tenant_byid(self, id):
+
+        try:
+            return self.client.tenants.get(id)
+        except Exception:
+            LOG.exception(_("Did not find tenant: %r"), id)
+
+    def get_tenant_type(self, id):
+
+        tenant = self.get_tenant_byid(id)
+        if tenant:
+            description = tenant.description
+            if description:
+                if (description.find(self.overlay_signature) >= 0):
+                    return constants.TENANT_TYPE_OVERLAY
+                if (description.find(self.of_signature) >= 0):
+                    return constants.TENANT_TYPE_OF
+        return self.default_tenant_type
+
+    def get_tenant_name(self, id):
+
+        tenant = self.get_tenant_byid(id)
+        if tenant:
+            return tenant.name
+        return 'not found'
diff --git a/neutron/plugins/ibm/sdnve_api_fake.py b/neutron/plugins/ibm/sdnve_api_fake.py
new file mode 100644 (file)
index 0000000..74cfc83
--- /dev/null
@@ -0,0 +1,64 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+from neutron.openstack.common import log as logging
+from neutron.plugins.ibm.common import constants
+
+LOG = logging.getLogger(__name__)
+
+HTTP_OK = 200
+
+
+class FakeClient():
+
+    '''Fake Client for SDNVE controller.'''
+
+    def __init__(self, **kwargs):
+        LOG.info(_('Fake SDNVE controller initialized'))
+
+    def sdnve_list(self, resource, **_params):
+        LOG.info(_('Fake SDNVE controller: list'))
+        return (HTTP_OK, None)
+
+    def sdnve_show(self, resource, specific, **_params):
+        LOG.info(_('Fake SDNVE controller: show'))
+        return (HTTP_OK, None)
+
+    def sdnve_create(self, resource, body):
+        LOG.info(_('Fake SDNVE controller: create'))
+        return (HTTP_OK, None)
+
+    def sdnve_update(self, resource, specific, body=None):
+        LOG.info(_('Fake SDNVE controller: update'))
+        return (HTTP_OK, None)
+
+    def sdnve_delete(self, resource, specific):
+        LOG.info(_('Fake SDNVE controller: delete'))
+        return (HTTP_OK, None)
+
+    def sdnve_get_tenant_byid(self, id):
+        LOG.info(_('Fake SDNVE controller: get tenant by id'))
+        return id, constants.TENANT_TYPE_OF
+
+    def sdnve_check_and_create_tenant(self, id, network_type=None):
+        LOG.info(_('Fake SDNVE controller: check and create tenant'))
+        return id
+
+    def sdnve_get_controller(self):
+        LOG.info(_('Fake SDNVE controller: get controller'))
+        return None
diff --git a/neutron/plugins/ibm/sdnve_neutron_plugin.py b/neutron/plugins/ibm/sdnve_neutron_plugin.py
new file mode 100644 (file)
index 0000000..1e4afe2
--- /dev/null
@@ -0,0 +1,649 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp.
+
+
+import functools
+
+from oslo.config import cfg
+
+from neutron.common import constants as q_const
+from neutron.common import exceptions as q_exc
+from neutron.common import rpc as q_rpc
+from neutron.common import topics
+from neutron.db import agents_db
+from neutron.db import db_base_plugin_v2
+from neutron.db import external_net_db
+from neutron.db import l3_gwmode_db
+from neutron.db import portbindings_db
+from neutron.extensions import portbindings
+from neutron.openstack.common import excutils
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import rpc
+from neutron.openstack.common.rpc import proxy
+from neutron.plugins.ibm.common import config  # noqa
+from neutron.plugins.ibm.common import constants
+from neutron.plugins.ibm.common import exceptions as sdnve_exc
+from neutron.plugins.ibm import sdnve_api as sdnve
+from neutron.plugins.ibm import sdnve_api_fake as sdnve_fake
+
+LOG = logging.getLogger(__name__)
+
+
+class SdnveRpcCallbacks():
+
+    def __init__(self, notifier):
+        self.notifier = notifier  # used to notify the agent
+
+    def create_rpc_dispatcher(self):
+        '''Get the rpc dispatcher for this manager.
+        If a manager would like to set an rpc API version, or support more than
+        one class as the target of rpc messages, override this method.
+        '''
+        return q_rpc.PluginRpcDispatcher([self,
+                                          agents_db.AgentExtRpcCallback()])
+
+    def sdnve_info(self, rpc_context, **kwargs):
+        '''Update new information.'''
+        info = kwargs.get('info')
+        # Notify all other listening agents
+        self.notifier.info_update(rpc_context, info)
+        return info
+
+
+class AgentNotifierApi(proxy.RpcProxy):
+    '''Agent side of the SDN-VE rpc API.'''
+
+    BASE_RPC_API_VERSION = '1.0'
+
+    def __init__(self, topic):
+        super(AgentNotifierApi, self).__init__(
+            topic=topic, default_version=self.BASE_RPC_API_VERSION)
+
+        self.topic_info_update = topics.get_topic_name(topic,
+                                                       constants.INFO,
+                                                       topics.UPDATE)
+
+    def info_update(self, context, info):
+        self.fanout_cast(context,
+                         self.make_msg('info_update',
+                                       info=info),
+                         topic=self.topic_info_update)
+
+
+def _ha(func):
+    '''Supports the high availability feature of the controller.'''
+
+    @functools.wraps(func)
+    def hawrapper(self, *args, **kwargs):
+        '''This wrapper sets the new controller if necessary
+
+        When a controller is detected to be not responding, and a
+        new controller is chosen to be used in its place, this decorator
+        makes sure the existing integration bridges are set to point
+        to the new controleer by calling the set_controller method.
+        '''
+        ret_func = func(self, *args, **kwargs)
+        self.set_controller(args[0])
+        return ret_func
+    return hawrapper
+
+
+class SdnvePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
+                    external_net_db.External_net_db_mixin,
+                    portbindings_db.PortBindingMixin,
+                    l3_gwmode_db.L3_NAT_db_mixin,
+                    ):
+
+    '''
+    Implement the Neutron abstractions using SDN-VE SDN Controller.
+    '''
+
+    __native_bulk_support = False
+    __native_pagination_support = False
+    __native_sorting_support = False
+
+    supported_extension_aliases = ["binding", "router", "external-net"]
+
+    def __init__(self, configfile=None):
+        self.base_binding_dict = {
+            portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
+            portbindings.VIF_DETAILS: {portbindings.CAP_PORT_FILTER: False}}
+
+        super(SdnvePluginV2, self).__init__()
+        self.setup_rpc()
+        self.sdnve_controller_select()
+        if self.fake_controller:
+            self.sdnve_client = sdnve_fake.FakeClient()
+        else:
+            self.sdnve_client = sdnve.Client()
+
+    def sdnve_controller_select(self):
+        self.fake_controller = cfg.CONF.SDNVE.use_fake_controller
+
+    def setup_rpc(self):
+        # RPC support
+        self.topic = topics.PLUGIN
+        self.conn = rpc.create_connection(new=True)
+        self.notifier = AgentNotifierApi(topics.AGENT)
+        self.callbacks = SdnveRpcCallbacks(self.notifier)
+        self.dispatcher = self.callbacks.create_rpc_dispatcher()
+        self.conn.create_consumer(self.topic, self.dispatcher,
+                                  fanout=False)
+        # Consume from all consumers in a thread
+        self.conn.consume_in_thread()
+
+    def _update_base_binding_dict(self, tenant_type):
+        if tenant_type == constants.TENANT_TYPE_OVERLAY:
+            self.base_binding_dict[
+                portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE
+        if tenant_type == constants.TENANT_TYPE_OF:
+            self.base_binding_dict[
+                portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS
+
+    def set_controller(self, context):
+        LOG.info(_("Set a new controller if needed."))
+        new_controller = self.sdnve_client.sdnve_get_controller()
+        if new_controller:
+            self.notifier.info_update(
+                context,
+                {'new_controller': new_controller})
+            LOG.info(_("Set the controller to a new controller: %s"),
+                     new_controller)
+
+    def _process_request(self, request, current):
+        new_request = dict(
+            (k, v) for k, v in request.items()
+            if v != current.get(k))
+
+        msg = _("Original SDN-VE HTTP request: %(orig)s; New request: %(new)s")
+        LOG.debug(msg, {'orig': request, 'new': new_request})
+        return new_request
+
+    #
+    # Network
+    #
+
+    @_ha
+    def create_network(self, context, network):
+        LOG.debug(_("Create network in progress: %r"), network)
+        session = context.session
+
+        tenant_id = self._get_tenant_id_for_create(context, network['network'])
+        # Create a new SDN-VE tenant if need be
+        sdnve_tenant = self.sdnve_client.sdnve_check_and_create_tenant(
+            tenant_id)
+        if sdnve_tenant is None:
+            raise sdnve_exc.SdnveException(
+                msg=_('Create net failed: no SDN-VE tenant.'))
+
+        with session.begin(subtransactions=True):
+            net = super(SdnvePluginV2, self).create_network(context, network)
+            self._process_l3_create(context, net, network['network'])
+
+        # Create SDN-VE network
+        (res, data) = self.sdnve_client.sdnve_create('network', net)
+        if res not in constants.HTTP_ACCEPTABLE:
+            super(SdnvePluginV2, self).delete_network(context, net['id'])
+            raise sdnve_exc.SdnveException(
+                msg=(_('Create net failed in SDN-VE: %s') % res))
+
+        LOG.debug(_("Created network: %s"), net['id'])
+        return net
+
+    @_ha
+    def update_network(self, context, id, network):
+        LOG.debug(_("Update network in progress: %r"), network)
+        session = context.session
+
+        processed_request = {}
+        with session.begin(subtransactions=True):
+            original_network = super(SdnvePluginV2, self).get_network(
+                context, id)
+            processed_request['network'] = self._process_request(
+                network['network'], original_network)
+            net = super(SdnvePluginV2, self).update_network(
+                context, id, network)
+            self._process_l3_update(context, net, network['network'])
+
+        if processed_request['network']:
+            (res, data) = self.sdnve_client.sdnve_update(
+                'network', id, processed_request['network'])
+            if res not in constants.HTTP_ACCEPTABLE:
+                net = super(SdnvePluginV2, self).update_network(
+                    context, id, {'network': original_network})
+                raise sdnve_exc.SdnveException(
+                    msg=(_('Update net failed in SDN-VE: %s') % res))
+
+        return net
+
+    @_ha
+    def delete_network(self, context, id):
+        LOG.debug(_("Delete network in progress: %s"), id)
+        super(SdnvePluginV2, self).delete_network(context, id)
+
+        (res, data) = self.sdnve_client.sdnve_delete('network', id)
+        if res not in constants.HTTP_ACCEPTABLE:
+            LOG.error(
+                _("Delete net failed after deleting the network in DB: %s"),
+                res)
+
+    @_ha
+    def get_network(self, context, id, fields=None):
+        LOG.debug(_("Get network in progress: %s"), id)
+        return super(SdnvePluginV2, self).get_network(context, id, fields)
+
+    @_ha
+    def get_networks(self, context, filters=None, fields=None, sorts=None,
+                     limit=None, marker=None, page_reverse=False):
+        LOG.debug(_("Get networks in progress"))
+        return super(SdnvePluginV2, self).get_networks(
+            context, filters, fields, sorts, limit, marker, page_reverse)
+
+    #
+    # Port
+    #
+
+    @_ha
+    def create_port(self, context, port):
+        LOG.debug(_("Create port in progress: %r"), port)
+        session = context.session
+
+        # Set port status as 'ACTIVE' to avoid needing the agent
+        port['port']['status'] = q_const.PORT_STATUS_ACTIVE
+        port_data = port['port']
+
+        with session.begin(subtransactions=True):
+            port = super(SdnvePluginV2, self).create_port(context, port)
+            if 'id' not in port:
+                return port
+            # If the tenant_id is set to '' by create_port, add the id to
+            # the request being sent to the controller as the controller
+            # requires a tenant id
+            tenant_id = port.get('tenant_id')
+            if not tenant_id:
+                LOG.debug(_("Create port does not have tenant id info"))
+                original_network = super(SdnvePluginV2, self).get_network(
+                    context, port['network_id'])
+                original_tenant_id = original_network['tenant_id']
+                port['tenant_id'] = original_tenant_id
+                LOG.debug(
+                    _("Create port does not have tenant id info; "
+                      "obtained is: %s"),
+                    port['tenant_id'])
+
+            os_tenant_id = tenant_id
+            id_na, tenant_type = self.sdnve_client.sdnve_get_tenant_byid(
+                os_tenant_id)
+            self._update_base_binding_dict(tenant_type)
+            self._process_portbindings_create_and_update(context,
+                                                         port_data, port)
+
+        # NOTE(mb): Remove this block when controller is updated
+        # Remove the information that the controller does not accept
+        sdnve_port = port.copy()
+        sdnve_port.pop('device_id', None)
+        sdnve_port.pop('device_owner', None)
+
+        (res, data) = self.sdnve_client.sdnve_create('port', sdnve_port)
+        if res not in constants.HTTP_ACCEPTABLE:
+            super(SdnvePluginV2, self).delete_port(context, port['id'])
+            raise sdnve_exc.SdnveException(
+                msg=(_('Create port failed in SDN-VE: %s') % res))
+
+        LOG.debug(_("Created port: %s"), port.get('id', 'id not found'))
+        return port
+
+    @_ha
+    def update_port(self, context, id, port):
+        LOG.debug(_("Update port in progress: %r"), port)
+        session = context.session
+
+        processed_request = {}
+        with session.begin(subtransactions=True):
+            original_port = super(SdnvePluginV2, self).get_port(
+                context, id)
+            processed_request['port'] = self._process_request(
+                port['port'], original_port)
+            updated_port = super(SdnvePluginV2, self).update_port(
+                context, id, port)
+
+            os_tenant_id = updated_port['tenant_id']
+            id_na, tenant_type = self.sdnve_client.sdnve_get_tenant_byid(
+                os_tenant_id)
+            self._update_base_binding_dict(tenant_type)
+            self._process_portbindings_create_and_update(context,
+                                                         port['port'],
+                                                         updated_port)
+
+        if processed_request['port']:
+            (res, data) = self.sdnve_client.sdnve_update(
+                'port', id, processed_request['port'])
+            if res not in constants.HTTP_ACCEPTABLE:
+                updated_port = super(SdnvePluginV2, self).update_port(
+                    context, id, {'port': original_port})
+                raise sdnve_exc.SdnveException(
+                    msg=(_('Update port failed in SDN-VE: %s') % res))
+
+        return updated_port
+
+    @_ha
+    def delete_port(self, context, id, l3_port_check=True):
+        LOG.debug(_("Delete port in progress: %s"), id)
+
+        # if needed, check to see if this is a port owned by
+        # an l3-router.  If so, we should prevent deletion.
+        if l3_port_check:
+            self.prevent_l3_port_deletion(context, id)
+        self.disassociate_floatingips(context, id)
+
+        super(SdnvePluginV2, self).delete_port(context, id)
+
+        (res, data) = self.sdnve_client.sdnve_delete('port', id)
+        if res not in constants.HTTP_ACCEPTABLE:
+            LOG.error(
+                _("Delete port operation failed in SDN-VE "
+                  "after deleting the port from DB: %s"), res)
+
+    #
+    # Subnet
+    #
+
+    @_ha
+    def create_subnet(self, context, subnet):
+        LOG.debug(_("Create subnet in progress: %r"), subnet)
+        new_subnet = super(SdnvePluginV2, self).create_subnet(context, subnet)
+
+        # Note(mb): Use of null string currently required by controller
+        sdnve_subnet = new_subnet.copy()
+        if subnet.get('gateway_ip') is None:
+            sdnve_subnet['gateway_ip'] = 'null'
+        (res, data) = self.sdnve_client.sdnve_create('subnet', sdnve_subnet)
+        if res not in constants.HTTP_ACCEPTABLE:
+            super(SdnvePluginV2, self).delete_subnet(context,
+                                                     new_subnet['id'])
+            raise sdnve_exc.SdnveException(
+                msg=(_('Create subnet failed in SDN-VE: %s') % res))
+
+        LOG.debug(_("Subnet created: %s"), new_subnet['id'])
+
+        return new_subnet
+
+    @_ha
+    def update_subnet(self, context, id, subnet):
+        LOG.debug(_("Update subnet in progress: %r"), subnet)
+        session = context.session
+
+        processed_request = {}
+        with session.begin(subtransactions=True):
+            original_subnet = super(SdnvePluginV2, self).get_subnet(
+                context, id)
+            processed_request['subnet'] = self._process_request(
+                subnet['subnet'], original_subnet)
+            updated_subnet = super(SdnvePluginV2, self).update_subnet(
+                context, id, subnet)
+
+        if processed_request['subnet']:
+            # Note(mb): Use of string containing null required by controller
+            if 'gateway_ip' in processed_request['subnet']:
+                if processed_request['subnet'].get('gateway_ip') is None:
+                    processed_request['subnet']['gateway_ip'] = 'null'
+            (res, data) = self.sdnve_client.sdnve_update(
+                'subnet', id, processed_request['subnet'])
+            if res not in constants.HTTP_ACCEPTABLE:
+                for key in subnet['subnet'].keys():
+                    subnet['subnet'][key] = original_subnet[key]
+                super(SdnvePluginV2, self).update_subnet(
+                    context, id, subnet)
+                raise sdnve_exc.SdnveException(
+                    msg=(_('Update subnet failed in SDN-VE: %s') % res))
+
+        return updated_subnet
+
+    @_ha
+    def delete_subnet(self, context, id):
+        LOG.debug(_("Delete subnet in progress: %s"), id)
+        super(SdnvePluginV2, self).delete_subnet(context, id)
+
+        (res, data) = self.sdnve_client.sdnve_delete('subnet', id)
+        if res not in constants.HTTP_ACCEPTABLE:
+            LOG.error(_("Delete subnet operation failed in SDN-VE after "
+                        "deleting the subnet from DB: %s"), res)
+
+    #
+    # Router
+    #
+
+    @_ha
+    def create_router(self, context, router):
+        LOG.debug(_("Create router in progress: %r"), router)
+
+        if router['router']['admin_state_up'] is False:
+            LOG.warning(_('Ignoring admin_state_up=False for router=%r.  '
+                          'Overriding with True'), router)
+            router['router']['admin_state_up'] = True
+
+        tenant_id = self._get_tenant_id_for_create(context, router['router'])
+        # Create a new Pinnaacles tenant if need be
+        sdnve_tenant = self.sdnve_client.sdnve_check_and_create_tenant(
+            tenant_id)
+        if sdnve_tenant is None:
+            raise sdnve_exc.SdnveException(
+                msg=_('Create router failed: no SDN-VE tenant.'))
+
+        new_router = super(SdnvePluginV2, self).create_router(context, router)
+        # Create Sdnve router
+        (res, data) = self.sdnve_client.sdnve_create('router', new_router)
+        if res not in constants.HTTP_ACCEPTABLE:
+            super(SdnvePluginV2, self).delete_router(context, new_router['id'])
+            raise sdnve_exc.SdnveException(
+                msg=(_('Create router failed in SDN-VE: %s') % res))
+
+        LOG.debug(_("Router created: %r"), new_router)
+        return new_router
+
+    @_ha
+    def update_router(self, context, id, router):
+        LOG.debug(_("Update router in progress: id=%(id)s "
+                    "router=%(router)r"),
+                  {'id': id, 'router': router})
+        session = context.session
+
+        processed_request = {}
+        if not router['router'].get('admin_state_up', True):
+            raise q_exc.NotImplementedError(_('admin_state_up=False '
+                                              'routers are not '
+                                              'supported.'))
+
+        with session.begin(subtransactions=True):
+            original_router = super(SdnvePluginV2, self).get_router(
+                context, id)
+            processed_request['router'] = self._process_request(
+                router['router'], original_router)
+            updated_router = super(SdnvePluginV2, self).update_router(
+                context, id, router)
+
+        if processed_request['router']:
+            (res, data) = self.sdnve_client.sdnve_update(
+                'router', id, processed_request['router'])
+            if res not in constants.HTTP_ACCEPTABLE:
+                super(SdnvePluginV2, self).update_router(
+                    context, id, {'router': original_router})
+                raise sdnve_exc.SdnveException(
+                    msg=(_('Update router failed in SDN-VE: %s') % res))
+
+        return updated_router
+
+    @_ha
+    def delete_router(self, context, id):
+        LOG.debug(_("Delete router in progress: %s"), id)
+
+        super(SdnvePluginV2, self).delete_router(context, id)
+
+        (res, data) = self.sdnve_client.sdnve_delete('router', id)
+        if res not in constants.HTTP_ACCEPTABLE:
+            LOG.error(
+                _("Delete router operation failed in SDN-VE after "
+                  "deleting the router in DB: %s"), res)
+
+    @_ha
+    def add_router_interface(self, context, router_id, interface_info):
+        LOG.debug(_("Add router interface in progress: "
+                    "router_id=%(router_id)s "
+                    "interface_info=%(interface_info)r"),
+                  {'router_id': router_id, 'interface_info': interface_info})
+
+        new_interface = super(SdnvePluginV2, self).add_router_interface(
+            context, router_id, interface_info)
+        LOG.debug(
+            _("SdnvePluginV2.add_router_interface called. Port info: %s"),
+            new_interface)
+        request_info = interface_info.copy()
+        request_info['port_id'] = new_interface['port_id']
+        # Add the subnet_id to the request sent to the controller
+        if 'subnet_id' not in interface_info:
+            request_info['subnet_id'] = new_interface['subnet_id']
+
+        (res, data) = self.sdnve_client.sdnve_update(
+            'router', router_id + '/add_router_interface', request_info)
+        if res not in constants.HTTP_ACCEPTABLE:
+            super(SdnvePluginV2, self).remove_router_interface(
+                context, router_id, interface_info)
+            raise sdnve_exc.SdnveException(
+                msg=(_('Update router-add-interface failed in SDN-VE: %s') %
+                     res))
+
+        LOG.debug(_("Added router interface: %r"), new_interface)
+        return new_interface
+
+    def _add_router_interface_only(self, context, router_id, interface_info):
+        LOG.debug(_("Add router interface only called: "
+                    "router_id=%(router_id)s "
+                    "interface_info=%(interface_info)r"),
+                  {'router_id': router_id, 'interface_info': interface_info})
+
+        port_id = interface_info.get('port_id')
+        if port_id:
+            (res, data) = self.sdnve_client.sdnve_update(
+                'router', router_id + '/add_router_interface', interface_info)
+            if res not in constants.HTTP_ACCEPTABLE:
+                LOG.error(_("SdnvePluginV2._add_router_interface_only: "
+                            "failed to add the interface in the roll back."
+                            " of a remove_router_interface operation"))
+
+    @_ha
+    def remove_router_interface(self, context, router_id, interface_info):
+        LOG.debug(_("Remove router interface in progress: "
+                    "router_id=%(router_id)s "
+                    "interface_info=%(interface_info)r"),
+                  {'router_id': router_id, 'interface_info': interface_info})
+
+        subnet_id = interface_info.get('subnet_id')
+        if not subnet_id:
+            portid = interface_info.get('port_id')
+            if not portid:
+                raise sdnve_exc.BadInputException(msg=_('No port ID'))
+            myport = super(SdnvePluginV2, self).get_port(context, portid)
+            LOG.debug(_("SdnvePluginV2.remove_router_interface port: %s"),
+                      myport)
+            myfixed_ips = myport.get('fixed_ips')
+            if not myfixed_ips:
+                raise sdnve_exc.BadInputException(msg=_('No fixed IP'))
+            subnet_id = myfixed_ips[0].get('subnet_id')
+            if subnet_id:
+                interface_info['subnet_id'] = subnet_id
+                LOG.debug(
+                    _("SdnvePluginV2.remove_router_interface subnet_id: %s"),
+                    subnet_id)
+
+        (res, data) = self.sdnve_client.sdnve_update(
+            'router', router_id + '/remove_router_interface', interface_info)
+
+        if res not in constants.HTTP_ACCEPTABLE:
+            raise sdnve_exc.SdnveException(
+                msg=(_('Update router-remove-interface failed SDN-VE: %s') %
+                     res))
+
+        session = context.session
+        with session.begin(subtransactions=True):
+            try:
+                info = super(SdnvePluginV2, self).remove_router_interface(
+                    context, router_id, interface_info)
+            except Exception:
+                with excutils.save_and_reraise_exception():
+                    self._add_router_interface_only(context,
+                                                    router_id, interface_info)
+
+        return info
+
+    #
+    # Floating Ip
+    #
+
+    @_ha
+    def create_floatingip(self, context, floatingip):
+        LOG.debug(_("Create floatingip in progress: %r"),
+                  floatingip)
+        new_floatingip = super(SdnvePluginV2, self).create_floatingip(
+            context, floatingip)
+
+        (res, data) = self.sdnve_client.sdnve_create(
+            'floatingip', {'floatingip': new_floatingip})
+        if res not in constants.HTTP_ACCEPTABLE:
+            super(SdnvePluginV2, self).delete_floatingip(
+                context, new_floatingip['id'])
+            raise sdnve_exc.SdnveException(
+                msg=(_('Creating floating ip operation failed '
+                       'in SDN-VE controller: %s') % res))
+
+        LOG.debug(_("Created floatingip : %r"), new_floatingip)
+        return new_floatingip
+
+    @_ha
+    def update_floatingip(self, context, id, floatingip):
+        LOG.debug(_("Update floatingip in progress: %r"), floatingip)
+        session = context.session
+
+        processed_request = {}
+        with session.begin(subtransactions=True):
+            original_floatingip = super(
+                SdnvePluginV2, self).get_floatingip(context, id)
+            processed_request['floatingip'] = self._process_request(
+                floatingip['floatingip'], original_floatingip)
+            updated_floatingip = super(
+                SdnvePluginV2, self).update_floatingip(context, id, floatingip)
+
+        if processed_request['floatingip']:
+            (res, data) = self.sdnve_client.sdnve_update(
+                'floatingip', id,
+                {'floatingip': processed_request['floatingip']})
+            if res not in constants.HTTP_ACCEPTABLE:
+                super(SdnvePluginV2, self).update_floatingip(
+                    context, id, {'floatingip': original_floatingip})
+                raise sdnve_exc.SdnveException(
+                    msg=(_('Update floating ip failed in SDN-VE: %s') % res))
+
+        return updated_floatingip
+
+    @_ha
+    def delete_floatingip(self, context, id):
+        LOG.debug(_("Delete floatingip in progress: %s"), id)
+        super(SdnvePluginV2, self).delete_floatingip(context, id)
+
+        (res, data) = self.sdnve_client.sdnve_delete('floatingip', id)
+        if res not in constants.HTTP_ACCEPTABLE:
+            LOG.error(_("Delete floatingip failed in SDN-VE: %s"), res)
diff --git a/neutron/tests/unit/ibm/__init__.py b/neutron/tests/unit/ibm/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/ibm/test_sdnve_agent.py b/neutron/tests/unit/ibm/test_sdnve_agent.py
new file mode 100644 (file)
index 0000000..3042d3d
--- /dev/null
@@ -0,0 +1,121 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp
+
+
+import contextlib
+
+import mock
+from oslo.config import cfg
+
+from neutron.agent.linux import ip_lib
+from neutron.plugins.ibm.agent import sdnve_neutron_agent
+from neutron.tests import base
+
+
+NOTIFIER = ('neutron.plugins.ibm.'
+            'sdnve_neutron_plugin.AgentNotifierApi')
+
+
+class CreateAgentConfigMap(base.BaseTestCase):
+
+    def test_create_agent_config_map_succeeds(self):
+        self.assertTrue(sdnve_neutron_agent.create_agent_config_map(cfg.CONF))
+
+    def test_create_agent_config_using_controller_ips(self):
+        self.addCleanup(cfg.CONF.reset)
+        cfg.CONF.set_override('controller_ips',
+                              ['10.10.10.1', '10.10.10.2'], group='SDNVE')
+        cfgmap = sdnve_neutron_agent.create_agent_config_map(cfg.CONF)
+        self.assertEqual(cfgmap['controller_ip'], '10.10.10.1')
+
+    def test_create_agent_config_using_interface_mappings(self):
+        self.addCleanup(cfg.CONF.reset)
+        cfg.CONF.set_override('interface_mappings',
+                              ['interface1 : eth1', 'interface2 : eth2'],
+                              group='SDNVE')
+        cfgmap = sdnve_neutron_agent.create_agent_config_map(cfg.CONF)
+        self.assertEqual(cfgmap['interface_mappings'],
+                         {'interface1': 'eth1', 'interface2': 'eth2'})
+
+
+class TestSdnveNeutronAgent(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestSdnveNeutronAgent, self).setUp()
+        self.addCleanup(cfg.CONF.reset)
+        notifier_p = mock.patch(NOTIFIER)
+        notifier_cls = notifier_p.start()
+        self.notifier = mock.Mock()
+        notifier_cls.return_value = self.notifier
+        # Avoid rpc initialization for unit tests
+        cfg.CONF.set_override('rpc_backend',
+                              'neutron.openstack.common.rpc.impl_fake')
+        cfg.CONF.set_override('integration_bridge',
+                              'br_int', group='SDNVE')
+        kwargs = sdnve_neutron_agent.create_agent_config_map(cfg.CONF)
+
+        class MockFixedIntervalLoopingCall(object):
+            def __init__(self, f):
+                self.f = f
+
+            def start(self, interval=0):
+                self.f()
+
+        with contextlib.nested(
+            mock.patch('neutron.plugins.ibm.agent.sdnve_neutron_agent.'
+                       'SdnveNeutronAgent.setup_integration_br',
+                       return_value=mock.Mock()),
+            mock.patch('neutron.openstack.common.loopingcall.'
+                       'FixedIntervalLoopingCall',
+                       new=MockFixedIntervalLoopingCall)):
+            self.agent = sdnve_neutron_agent.SdnveNeutronAgent(**kwargs)
+
+    def test_setup_physical_interfaces(self):
+        with mock.patch.object(self.agent.int_br,
+                               'add_port') as add_port_func:
+            with mock.patch.object(ip_lib,
+                                   'device_exists',
+                                   return_valxue=True):
+                self.agent.setup_physical_interfaces({"interface1": "eth1"})
+        add_port_func.assert_called_once_with('eth1')
+
+    def test_setup_physical_interfaces_none(self):
+        with mock.patch.object(self.agent.int_br,
+                               'add_port') as add_port_func:
+            with mock.patch.object(ip_lib,
+                                   'device_exists',
+                                   return_valxue=True):
+                self.agent.setup_physical_interfaces({})
+        self.assertFalse(add_port_func.called)
+
+    def test_get_info_set_controller(self):
+        with mock.patch.object(self.agent.int_br,
+                               'run_vsctl') as run_vsctl_func:
+            kwargs = {}
+            kwargs['info'] = {'new_controller': '10.10.10.1'}
+            self.agent.info_update('dummy', **kwargs)
+        run_vsctl_func.assert_called_one_with(['set-controller',
+                                               'br_int',
+                                               'tcp:10.10.10.1'])
+
+    def test_get_info(self):
+        with mock.patch.object(self.agent.int_br,
+                               'run_vsctl') as run_vsctl_func:
+            kwargs = {}
+            self.agent.info_update('dummy', **kwargs)
+        self.assertFalse(run_vsctl_func.called)
diff --git a/neutron/tests/unit/ibm/test_sdnve_api.py b/neutron/tests/unit/ibm/test_sdnve_api.py
new file mode 100644 (file)
index 0000000..ba76e05
--- /dev/null
@@ -0,0 +1,139 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp
+
+
+import mock
+from oslo.config import cfg
+
+from neutron.openstack.common import uuidutils
+from neutron.plugins.ibm.common import constants
+from neutron.plugins.ibm import sdnve_api
+from neutron.tests import base
+
+RESOURCE_PATH = {
+    'network': "ln/networks/",
+}
+RESOURCE = 'network'
+HTTP_OK = 200
+TENANT_ID = uuidutils.generate_uuid()
+
+
+class TestSdnveApi(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestSdnveApi, self).setUp()
+        self.addCleanup(cfg.CONF.reset)
+
+        class MockKeystoneClient(object):
+            def __init__(self, **kwargs):
+                pass
+
+            def get_tenant_name(self, id):
+                return 'test tenant name'
+
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'KeystoneClient',
+                        new=MockKeystoneClient):
+            self.api = sdnve_api.Client()
+
+    def mock_do_request(self, method, url, body=None, headers=None,
+                        params=None, connection_type=None):
+        return (HTTP_OK, url)
+
+    def mock_do_request_tenant(self, method, url, body=None, headers=None,
+                               params=None, connection_type=None):
+        return (HTTP_OK, {'id': TENANT_ID,
+                          'network_type': constants.TENANT_TYPE_OF})
+
+    def mock_do_request_no_tenant(self, method, url, body=None, headers=None,
+                                  params=None, connection_type=None):
+        return (None, None)
+
+    def mock_process_request(self, body):
+        return body
+
+    def test_sdnve_api_list(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request):
+            result = self.api.sdnve_list(RESOURCE)
+            self.assertEqual(result, (HTTP_OK, RESOURCE_PATH[RESOURCE]))
+
+    def test_sdnve_api_show(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request):
+            result = self.api.sdnve_show(RESOURCE, TENANT_ID)
+            self.assertEqual(result,
+                             (HTTP_OK, RESOURCE_PATH[RESOURCE] + TENANT_ID))
+
+    def test_sdnve_api_create(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request):
+            with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                            'Client.process_request',
+                            new=self.mock_process_request):
+                result = self.api.sdnve_create(RESOURCE, '')
+                self.assertEqual(result, (HTTP_OK, RESOURCE_PATH[RESOURCE]))
+
+    def test_sdnve_api_update(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request):
+            with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                            'Client.process_request',
+                            new=self.mock_process_request):
+                result = self.api.sdnve_update(RESOURCE, TENANT_ID, '')
+                self.assertEqual(result,
+                                 (HTTP_OK,
+                                  RESOURCE_PATH[RESOURCE] + TENANT_ID))
+
+    def test_sdnve_api_delete(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request):
+            result = self.api.sdnve_delete(RESOURCE, TENANT_ID)
+            self.assertEqual(result,
+                             (HTTP_OK, RESOURCE_PATH[RESOURCE] + TENANT_ID))
+
+    def test_sdnve_get_tenant_by_id(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request_tenant):
+            id = TENANT_ID
+            result = self.api.sdnve_get_tenant_byid(id)
+            self.assertEqual(result,
+                             (TENANT_ID, constants.TENANT_TYPE_OF))
+
+    def test_sdnve_check_and_create_tenant(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request_tenant):
+            id = TENANT_ID
+            result = self.api.sdnve_check_and_create_tenant(id)
+            self.assertEqual(result, TENANT_ID)
+
+    def test_sdnve_check_and_create_tenant_fail(self):
+        with mock.patch('neutron.plugins.ibm.sdnve_api.'
+                        'Client.do_request',
+                        new=self.mock_do_request_no_tenant):
+            id = TENANT_ID
+            result = self.api.sdnve_check_and_create_tenant(
+                id, constants.TENANT_TYPE_OF)
+            self.assertIsNone(result)
diff --git a/neutron/tests/unit/ibm/test_sdnve_plugin.py b/neutron/tests/unit/ibm/test_sdnve_plugin.py
new file mode 100644 (file)
index 0000000..4e4c967
--- /dev/null
@@ -0,0 +1,126 @@
+# Copyright 2014 IBM Corp.
+#
+# 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: Mohammad Banikazemi, IBM Corp
+
+
+import contextlib
+import mock
+
+from neutron.extensions import portbindings
+from neutron.tests.unit import _test_extension_portbindings as test_bindings
+from neutron.tests.unit import test_db_plugin as test_plugin
+from neutron.tests.unit import test_l3_plugin as test_l3_plugin
+
+from neutron.plugins.ibm.common import constants
+
+
+_plugin_name = ('neutron.plugins.ibm.'
+                'sdnve_neutron_plugin.SdnvePluginV2')
+HTTP_OK = 200
+
+
+class MockClient(object):
+    def sdnve_list(self, resource, **params):
+        return (HTTP_OK, 'body')
+
+    def sdnve_show(self, resource, specific, **params):
+        return (HTTP_OK, 'body')
+
+    def sdnve_create(self, resource, body):
+        return (HTTP_OK, 'body')
+
+    def sdnve_update(self, resource, specific, body=None):
+        return (HTTP_OK, 'body')
+
+    def sdnve_delete(self, resource, specific):
+        return (HTTP_OK, 'body')
+
+    def sdnve_get_tenant_byid(self, os_tenant_id):
+        return (os_tenant_id, constants.TENANT_TYPE_OF)
+
+    def sdnve_check_and_create_tenant(
+        self, os_tenant_id, network_type=None):
+        return os_tenant_id
+
+    def sdnve_get_controller(self):
+        return
+
+
+class MockKeystoneClient(object):
+    def __init__(self, **kwargs):
+        pass
+
+    def get_tenant_type(self, id):
+        return constants.TENANT_TYPE_OF
+
+    def get_tenant_name(self, id):
+        return "tenant name"
+
+
+class IBMPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
+    def setUp(self):
+        with contextlib.nested(
+            mock.patch('neutron.plugins.ibm.sdnve_api.'
+                       'KeystoneClient',
+                       new=MockKeystoneClient),
+            mock.patch('neutron.plugins.ibm.sdnve_api.'
+                       'Client',
+                       new=MockClient)):
+            super(IBMPluginV2TestCase, self).setUp(plugin=_plugin_name)
+
+
+class TestIBMBasicGet(test_plugin.TestBasicGet,
+                      IBMPluginV2TestCase):
+    pass
+
+
+class TestIBMV2HTTPResponse(test_plugin.TestV2HTTPResponse,
+                            IBMPluginV2TestCase):
+    pass
+
+
+class TestIBMNetworksV2(test_plugin.TestNetworksV2,
+                        IBMPluginV2TestCase):
+    pass
+
+
+class TestIBMPortsV2(test_plugin.TestPortsV2,
+                     IBMPluginV2TestCase):
+    pass
+
+
+class TestIBMSubnetsV2(test_plugin.TestSubnetsV2,
+                       IBMPluginV2TestCase):
+    pass
+
+
+class TestIBMPortBinding(IBMPluginV2TestCase,
+                         test_bindings.PortBindingsTestCase):
+    VIF_TYPE = portbindings.VIF_TYPE_OVS
+
+
+class IBMPluginRouterTestCase(test_l3_plugin.L3NatDBIntTestCase):
+
+    def setUp(self):
+        with contextlib.nested(
+            mock.patch('neutron.plugins.ibm.sdnve_api.'
+                       'KeystoneClient',
+                       new=MockKeystoneClient),
+            mock.patch('neutron.plugins.ibm.sdnve_api.'
+                       'Client',
+                       new=MockClient)):
+            super(IBMPluginRouterTestCase, self).setUp(plugin=_plugin_name)
index 27f70b6e0e63de51526291bf67a88b3d95e8cba0..885ebde4c912417990089eab27b03fdef6c00c4c 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,6 +51,7 @@ data_files =
     etc/neutron/plugins/brocade = etc/neutron/plugins/brocade/brocade.ini
     etc/neutron/plugins/cisco = etc/neutron/plugins/cisco/cisco_plugins.ini
     etc/neutron/plugins/hyperv = etc/neutron/plugins/hyperv/hyperv_neutron_plugin.ini
+    etc/neutron/plugins/ibm = etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini
     etc/neutron/plugins/linuxbridge = etc/neutron/plugins/linuxbridge/linuxbridge_conf.ini
     etc/neutron/plugins/metaplugin = etc/neutron/plugins/metaplugin/metaplugin.ini
     etc/neutron/plugins/midonet = etc/neutron/plugins/midonet/midonet.ini
@@ -85,6 +86,7 @@ console_scripts =
     neutron-debug = neutron.debug.shell:main
     neutron-dhcp-agent = neutron.agent.dhcp_agent:main
     neutron-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
+    neutron-ibm-agent = neutron.plugins.ibm.agent.sdnve_neutron_agent:main
     neutron-l3-agent = neutron.agent.l3_agent:main
     neutron-lbaas-agent = neutron.services.loadbalancer.agent.agent:main
     neutron-linuxbridge-agent = neutron.plugins.linuxbridge.agent.linuxbridge_neutron_agent:main
@@ -106,6 +108,7 @@ console_scripts =
     quantum-debug = neutron.debug.shell:main
     quantum-dhcp-agent = neutron.agent.dhcp_agent:main
     quantum-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
+    quantum-ibm-agent = neutron.plugins.ibm.agent.sdnve_neutron_agent:main
     quantum-l3-agent = neutron.agent.l3_agent:main
     quantum-lbaas-agent = neutron.services.loadbalancer.agent.agent:main
     quantum-linuxbridge-agent = neutron.plugins.linuxbridge.agent.linuxbridge_neutron_agent:main
@@ -127,6 +130,7 @@ neutron.core_plugins =
     cisco = neutron.plugins.cisco.network_plugin:PluginV2
     embrane = neutron.plugins.embrane.plugins.embrane_ovs_plugin:EmbraneOvsPlugin
     hyperv = neutron.plugins.hyperv.hyperv_neutron_plugin:HyperVNeutronPlugin
+    ibm = neutron.plugins.ibm.sdnve_neutron_plugin:SdnvePluginV2
     linuxbridge = neutron.plugins.linuxbridge.lb_neutron_plugin:LinuxBridgePluginV2
     midonet = neutron.plugins.midonet.plugin:MidonetPluginV2
     ml2 = neutron.plugins.ml2.plugin:Ml2Plugin