]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
NEC OpenFlow plugin support.
authorRyota MIBU <r-mibu@cq.jp.nec.com>
Wed, 1 Aug 2012 16:42:20 +0000 (01:42 +0900)
committerRyota MIBU <r-mibu@cq.jp.nec.com>
Tue, 14 Aug 2012 10:01:39 +0000 (19:01 +0900)
blueprint quantum-nec-openflow-plugin

Change-Id: Ib6d6f658bbf6d653527fa7820685f9bed1412a18

32 files changed:
etc/quantum/plugins/nec/nec.ini [new file with mode: 0644]
quantum/plugins/nec/README [new file with mode: 0644]
quantum/plugins/nec/__init__.py [new file with mode: 0644]
quantum/plugins/nec/agent/__init__.py [new file with mode: 0644]
quantum/plugins/nec/agent/nec_quantum_agent.py [new file with mode: 0755]
quantum/plugins/nec/common/__init__.py [new file with mode: 0644]
quantum/plugins/nec/common/config.py [new file with mode: 0644]
quantum/plugins/nec/common/exceptions.py [new file with mode: 0644]
quantum/plugins/nec/common/ofc_client.py [new file with mode: 0644]
quantum/plugins/nec/db/__init__.py [new file with mode: 0644]
quantum/plugins/nec/db/api.py [new file with mode: 0644]
quantum/plugins/nec/db/models.py [new file with mode: 0644]
quantum/plugins/nec/db/nec_plugin_base.py [new file with mode: 0644]
quantum/plugins/nec/drivers/__init__.py [new file with mode: 0644]
quantum/plugins/nec/drivers/pfc.py [new file with mode: 0644]
quantum/plugins/nec/drivers/trema.py [new file with mode: 0644]
quantum/plugins/nec/extensions/__init__.py [new file with mode: 0644]
quantum/plugins/nec/extensions/packetfilter.py [new file with mode: 0644]
quantum/plugins/nec/nec_plugin.py [new file with mode: 0644]
quantum/plugins/nec/ofc_driver_base.py [new file with mode: 0644]
quantum/plugins/nec/ofc_manager.py [new file with mode: 0644]
quantum/plugins/nec/run_tests.py [new file with mode: 0755]
quantum/plugins/nec/tests/__init__.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/__init__.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/stub_ofc_driver.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/test_config.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/test_db.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/test_nec_plugin_v2.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/test_ofc_manager.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/test_pfc_driver.py [new file with mode: 0644]
quantum/plugins/nec/tests/unit/test_trema_driver.py [new file with mode: 0644]
setup.py

diff --git a/etc/quantum/plugins/nec/nec.ini b/etc/quantum/plugins/nec/nec.ini
new file mode 100644 (file)
index 0000000..e21a2b7
--- /dev/null
@@ -0,0 +1,39 @@
+# Sample Configurations
+
+[DATABASE]
+# This line MUST be changed to actually run the plugin.
+# Example:
+# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
+# Replace 127.0.0.1 above with the IP address of the database used by the
+# main quantum server. (Leave it as is if the database runs on this host.)
+sql_connection = sqlite://
+# Database reconnection retry times - in event connectivity is lost
+# set to -1 implies an infinite retry count
+# sql_max_retries = 10
+# Database reconnection interval in seconds - in event connectivity is lost
+reconnect_interval = 2
+
+[OVS]
+# Do not change this parameter unless you have a good reason to.
+# This is the name of the OVS integration bridge. There is one per hypervisor.
+# The integration bridge acts as a virtual "patch port". All VM VIFs are
+# attached to this bridge and then "patched" according to their network
+# connectivity.
+integration_bridge = br-int
+
+[AGENT]
+# Agent's polling interval in seconds
+polling_interval = 2
+# Change to "sudo quantum-rootwrap" to limit commands that can be run
+# as root.
+root_helper = sudo
+
+[OFC]
+# Specify OpenFlow Controller Host, Port and Driver to connect.
+host = 127.0.0.1
+port = 8888
+# Drivers are in quantum/plugins/nec/drivers/ .
+driver = trema
+# PacketFilter is available when it's enabled in this configuration
+# and supported by the driver.
+enable_packet_filter = true
diff --git a/quantum/plugins/nec/README b/quantum/plugins/nec/README
new file mode 100644 (file)
index 0000000..c219364
--- /dev/null
@@ -0,0 +1,20 @@
+Quantum NEC OpenFlow Plugin
+
+
+# -- What's this?
+
+http://wiki.openstack.org/Quantum-NEC-OpenFlow-Plugin
+
+
+# -- Installation
+
+Use QuickStart Script for this plugin.  This provides you auto installation and
+configuration of Nova, Quantum and Trema.
+https://github.com/nec-openstack/quantum-openflow-plugin/tree/folsom
+
+
+# -- Running Tests
+
+To run tests of this plugin (run the following from the top level Quantum
+directory):
+PLUGIN_DIR=quantum/plugins/nec ./run_tests.sh -N
diff --git a/quantum/plugins/nec/__init__.py b/quantum/plugins/nec/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/agent/__init__.py b/quantum/plugins/nec/agent/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/agent/nec_quantum_agent.py b/quantum/plugins/nec/agent/nec_quantum_agent.py
new file mode 100755 (executable)
index 0000000..4e4d824
--- /dev/null
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.
+# Based on ryu/openvswitch agents.
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# Copyright 2011 Nicira Networks, 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: Ryota MIBU
+
+import logging
+import sys
+import time
+import socket
+
+from quantum.agent.linux import ovs_lib
+from quantum.common import config as logging_config
+from quantum.common import topics
+from quantum.openstack.common import context
+from quantum.openstack.common import rpc
+from quantum.plugins.nec.common import config
+
+
+logging.basicConfig()
+LOG = logging.getLogger(__name__)
+
+
+class NECQuantumAgent(object):
+
+    def __init__(self, integ_br, root_helper, polling_interval):
+        '''Constructor.
+
+        :param integ_br: name of the integration bridge.
+        :param root_helper: utility to use when running shell cmds.
+        :param polling_interval: interval (secs) to check the bridge.
+        '''
+        self.int_br = ovs_lib.OVSBridge(integ_br, root_helper)
+        self.polling_interval = polling_interval
+
+        self.host = socket.gethostname()
+        self.agent_id = 'nec-q-agent.%s' % self.host
+        self.datapath_id = "0x%s" % self.int_br.get_datapath_id()
+
+        # RPC network init
+        self.context = context.RequestContext('quantum', 'quantum',
+                                              is_admin=False)
+        self.conn = rpc.create_connection(new=True)
+
+    def update_ports(self, port_added=[], port_removed=[]):
+        """RPC to update information of ports on Quantum Server"""
+        LOG.info("update ports: added=%s, removed=%s" %
+                 (port_added, port_removed))
+        try:
+            rpc.call(self.context,
+                     topics.PLUGIN,
+                     {'method': 'update_ports',
+                      'args': {'topic': topics.AGENT,
+                               'agent_id': self.agent_id,
+                               'datapath_id': self.datapath_id,
+                               'port_added': port_added,
+                               'port_removed': port_removed}})
+        except Exception as e:
+            LOG.warn("update_ports() failed.")
+            return
+
+    def _vif_port_to_port_info(self, vif_port):
+        return dict(id=vif_port.vif_id, port_no=vif_port.ofport,
+                    mac=vif_port.vif_mac)
+
+    def daemon_loop(self):
+        """Main processing loop for NEC Plugin Agent."""
+        old_ports = []
+        while True:
+            new_ports = []
+
+            port_added = []
+            for vif_port in self.int_br.get_vif_ports():
+                port_id = vif_port.vif_id
+                new_ports.append(port_id)
+                if port_id not in old_ports:
+                    port_info = self._vif_port_to_port_info(vif_port)
+                    port_added.append(port_info)
+
+            port_removed = []
+            for port_id in old_ports:
+                if port_id not in new_ports:
+                    port_removed.append(port_id)
+
+            if port_added or port_removed:
+                self.update_ports(port_added, port_removed)
+            else:
+                LOG.debug("No port changed.")
+
+            old_ports = new_ports
+            time.sleep(self.polling_interval)
+
+
+def main():
+    config.CONF(args=sys.argv, project='quantum')
+
+    logging_config.setup_logging(config.CONF)
+
+    # Determine which agent type to use.
+    integ_br = config.OVS.integration_bridge
+    root_helper = config.AGENT.root_helper
+    polling_interval = config.AGENT.polling_interval
+
+    agent = NECQuantumAgent(integ_br, root_helper, polling_interval)
+
+    # Start everything.
+    agent.daemon_loop()
+
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/quantum/plugins/nec/common/__init__.py b/quantum/plugins/nec/common/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/common/config.py b/quantum/plugins/nec/common/config.py
new file mode 100644 (file)
index 0000000..925e8bf
--- /dev/null
@@ -0,0 +1,59 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.openstack.common import cfg
+# import rpc config options
+from quantum.openstack.common import rpc
+
+
+database_opts = [
+    cfg.StrOpt('sql_connection', default='sqlite://'),
+    cfg.IntOpt('sql_max_retries', default=-1),
+    cfg.IntOpt('reconnect_interval', default=2),
+]
+
+ovs_opts = [
+    cfg.StrOpt('integration_bridge', default='br-int'),
+]
+
+agent_opts = [
+    cfg.IntOpt('polling_interval', default=2),
+    cfg.StrOpt('root_helper', default='sudo'),
+]
+
+ofc_opts = [
+    cfg.StrOpt('host', default='127.0.0.1'),
+    cfg.StrOpt('port', default='8888'),
+    cfg.StrOpt('driver', default='trema'),
+    cfg.BoolOpt('enable_packet_filter', default=True),
+    cfg.BoolOpt('use_ssl', default=False),
+    cfg.StrOpt('key_file', default=None),
+    cfg.StrOpt('cert_file', default=None),
+]
+
+
+cfg.CONF.register_opts(database_opts, "DATABASE")
+cfg.CONF.register_opts(ovs_opts, "OVS")
+cfg.CONF.register_opts(agent_opts, "AGENT")
+cfg.CONF.register_opts(ofc_opts, "OFC")
+
+# shortcuts
+CONF = cfg.CONF
+DATABASE = cfg.CONF.DATABASE
+OVS = cfg.CONF.OVS
+AGENT = cfg.CONF.AGENT
+OFC = cfg.CONF.OFC
diff --git a/quantum/plugins/nec/common/exceptions.py b/quantum/plugins/nec/common/exceptions.py
new file mode 100644 (file)
index 0000000..407dab2
--- /dev/null
@@ -0,0 +1,39 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.common import exceptions as qexc
+
+
+class OFCException(qexc.QuantumException):
+    message = _("An OFC exception has occurred: %(reason)s")
+
+
+class NECDBException(qexc.QuantumException):
+    message = _("An exception occurred in NECPluginV2 DB: %(reason)s")
+
+
+class OFCConsistencyBroken(qexc.QuantumException):
+    message = _("Consistency of Quantum-OFC resource map is broken: "
+                "%(reason)s")
+
+
+class PortInfoNotFound(qexc.NotFound):
+    message = _("PortInfo %(id)s could not be found")
+
+
+class PacketFilterNotFound(qexc.NotFound):
+    message = _("PacketFilter %(id)s could not be found")
diff --git a/quantum/plugins/nec/common/ofc_client.py b/quantum/plugins/nec/common/ofc_client.py
new file mode 100644 (file)
index 0000000..f8db74d
--- /dev/null
@@ -0,0 +1,99 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import httplib
+import json
+import logging
+import socket
+
+from quantum.plugins.nec.common import exceptions as nexc
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OFCClient(object):
+    """A HTTP/HTTPS client for OFC Drivers"""
+
+    def __init__(self, host="127.0.0.1", port=8888, use_ssl=False,
+                 key_file=None, cert_file=None):
+        """Creates a new client to some OFC.
+
+        :param host: The host where service resides
+        :param port: The port where service resides
+        :param use_ssl: True to use SSL, False to use HTTP
+        :param key_file: The SSL key file to use if use_ssl is true
+        :param cert_file: The SSL cert file to use if use_ssl is true
+        """
+        self.host = host
+        self.port = port
+        self.use_ssl = use_ssl
+        self.key_file = key_file
+        self.cert_file = cert_file
+        self.connection = None
+
+    def get_connection_type(self):
+        """Returns the proper connection type"""
+        if self.use_ssl:
+            return httplib.HTTPSConnection
+        else:
+            return httplib.HTTPConnection
+
+    def do_request(self, method, action, body=None):
+        LOG.debug("Client request: %s %s [%s]" % (method, action, str(body)))
+
+        if type(body) is dict:
+            body = json.dumps(body)
+        try:
+            connection_type = self.get_connection_type()
+            headers = {"Content-Type": "application/json"}
+            # Open connection and send request, handling SSL certs
+            certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
+            certs = dict((x, certs[x]) for x in certs if certs[x] is not None)
+            if self.use_ssl and len(certs):
+                conn = connection_type(self.host, self.port, **certs)
+            else:
+                conn = connection_type(self.host, self.port)
+            conn.request(method, action, body, headers)
+            res = conn.getresponse()
+            data = res.read()
+            LOG.debug("OFC returns [%s:%s]" % (str(res.status), data))
+            if res.status in (httplib.OK,
+                              httplib.CREATED,
+                              httplib.ACCEPTED,
+                              httplib.NO_CONTENT):
+                if data and len(data) > 1:
+                    return json.loads(data)
+            else:
+                reason = _("An operation on OFC is failed.")
+                raise nexc.OFCException(reason=reason)
+        except (socket.error, IOError), e:
+            reason = _("Failed to connect OFC : %s" % str(e))
+            LOG.error(reason)
+            raise nexc.OFCException(reason=reason)
+
+    def get(self, action):
+        return self.do_request("GET", action)
+
+    def post(self, action, body=None):
+        return self.do_request("POST", action, body=body)
+
+    def put(self, action, body=None):
+        return self.do_request("PUT", action, body=body)
+
+    def delete(self, action):
+        return self.do_request("DELETE", action)
diff --git a/quantum/plugins/nec/db/__init__.py b/quantum/plugins/nec/db/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/db/api.py b/quantum/plugins/nec/db/api.py
new file mode 100644 (file)
index 0000000..853f294
--- /dev/null
@@ -0,0 +1,123 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import logging
+
+import sqlalchemy as sa
+
+from quantum.db import api as db
+from quantum.db import model_base
+from quantum.plugins.nec.common import config
+from quantum.plugins.nec.common import exceptions as nexc
+from quantum.plugins.nec.db import models as nmodels
+
+
+LOG = logging.getLogger(__name__)
+OFP_VLAN_NONE = 0xffff
+
+
+def initialize():
+    options = {"sql_connection": "%s" % config.DATABASE.sql_connection,
+               "sql_max_retries": config.DATABASE.sql_max_retries,
+               "reconnect_interval": config.DATABASE.reconnect_interval,
+               "base": model_base.BASEV2}
+    db.configure_db(options)
+
+
+def clear_db(base=model_base.BASEV2):
+    db.clear_db(base)
+
+
+def get_ofc_item(model, id):
+    session = db.get_session()
+    try:
+        return (session.query(model).
+                filter_by(id=id).
+                one())
+    except sa.orm.exc.NoResultFound:
+        return None
+
+
+def find_ofc_item(model, quantum_id):
+    session = db.get_session()
+    try:
+        return (session.query(model).
+                filter_by(quantum_id=quantum_id).
+                one())
+    except sa.orm.exc.NoResultFound:
+        return None
+
+
+def add_ofc_item(model, id, quantum_id):
+    session = db.get_session()
+    try:
+        item = model(id=id, quantum_id=quantum_id)
+        session.add(item)
+        session.flush()
+    except sa.exc.IntegrityError as exc:
+        LOG.exception(exc)
+        raise nexc.NECDBException
+    return item
+
+
+def del_ofc_item(model, id):
+    session = db.get_session()
+    try:
+        item = (session.query(model).
+                filter_by(id=id).
+                one())
+        session.delete(item)
+        session.flush()
+    except sa.orm.exc.NoResultFound:
+        LOG.warning("_del_ofc_item(): NotFound item "
+                    "(model=%s, id=%s) " % (model, id))
+
+
+def get_portinfo(id):
+    session = db.get_session()
+    try:
+        return (session.query(nmodels.PortInfo).
+                filter_by(id=id).
+                one())
+    except sa.orm.exc.NoResultFound:
+        return None
+
+
+def add_portinfo(id, datapath_id='', port_no=0, vlan_id=OFP_VLAN_NONE, mac=''):
+    session = db.get_session()
+    try:
+        portinfo = nmodels.PortInfo(id=id, datapath_id=datapath_id,
+                                    port_no=port_no, vlan_id=vlan_id, mac=mac)
+        session.add(portinfo)
+        session.flush()
+    except sa.exc.IntegrityError as exc:
+        LOG.exception(exc)
+        raise nexc.NECDBException
+    return portinfo
+
+
+def del_portinfo(id):
+    session = db.get_session()
+    try:
+        portinfo = (session.query(nmodels.PortInfo).
+                    filter_by(id=id).
+                    one())
+        session.delete(portinfo)
+        session.flush()
+    except sa.orm.exc.NoResultFound:
+        LOG.warning("del_portinfo(): NotFound portinfo for "
+                    "port_id: %s" % id)
diff --git a/quantum/plugins/nec/db/models.py b/quantum/plugins/nec/db/models.py
new file mode 100644 (file)
index 0000000..ee20616
--- /dev/null
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import sqlalchemy as sa
+
+from quantum.db import model_base
+from quantum.db import models_v2
+
+
+class HasQuantumId(object):
+    """Logical ID on Quantum"""
+    quantum_id = sa.Column(sa.String(36), nullable=False)
+
+
+class OFCTenant(model_base.BASEV2, models_v2.HasId, HasQuantumId):
+    """Represents a Tenant on OpenFlow Network/Controller."""
+
+
+class OFCNetwork(model_base.BASEV2, models_v2.HasId, HasQuantumId):
+    """Represents a Network on OpenFlow Network/Controller."""
+
+
+class OFCPort(model_base.BASEV2, models_v2.HasId, HasQuantumId):
+    """Represents a Port on OpenFlow Network/Controller."""
+
+
+class OFCFilter(model_base.BASEV2, models_v2.HasId, HasQuantumId):
+    """Represents a Filter on OpenFlow Network/Controller."""
+
+
+class PortInfo(model_base.BASEV2, models_v2.HasId):
+    """Represents a Virtual Interface."""
+    datapath_id = sa.Column(sa.String(36), nullable=False)
+    port_no = sa.Column(sa.Integer, nullable=False)
+    vlan_id = sa.Column(sa.Integer, nullable=False)
+    mac = sa.Column(sa.String(32), nullable=False)
+
+
+class PacketFilter(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
+    """Represents a packet filter"""
+    network_id = sa.Column(sa.String(36),
+                           sa.ForeignKey('networks.id', ondelete="CASCADE"),
+                           nullable=False)
+    priority = sa.Column(sa.Integer, nullable=False)
+    action = sa.Column(sa.String(16), nullable=False)
+    # condition
+    in_port = sa.Column(sa.String(36), nullable=False)
+    src_mac = sa.Column(sa.String(32), nullable=False)
+    dst_mac = sa.Column(sa.String(32), nullable=False)
+    eth_type = sa.Column(sa.Integer, nullable=False)
+    src_cidr = sa.Column(sa.String(64), nullable=False)
+    dst_cidr = sa.Column(sa.String(64), nullable=False)
+    protocol = sa.Column(sa.String(16), nullable=False)
+    src_port = sa.Column(sa.Integer, nullable=False)
+    dst_port = sa.Column(sa.Integer, nullable=False)
+    # status
+    admin_state_up = sa.Column(sa.Boolean(), nullable=False)
+    status = sa.Column(sa.String(16), nullable=False)
diff --git a/quantum/plugins/nec/db/nec_plugin_base.py b/quantum/plugins/nec/db/nec_plugin_base.py
new file mode 100644 (file)
index 0000000..d99e2e3
--- /dev/null
@@ -0,0 +1,118 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.api.v2 import attributes
+from quantum.common import utils
+from quantum.db import db_base_plugin_v2
+from quantum.plugins.nec.db import models as nmodels
+
+
+class NECPluginV2Base(db_base_plugin_v2.QuantumDbPluginV2):
+
+    """ Base class of plugins that handle packet filters. """
+
+    def _make_packet_filter_dict(self, packet_filter, fields=None):
+        res = {'id': packet_filter['id'],
+               'tenant_id': packet_filter['tenant_id'],
+               'network_id': packet_filter['network_id'],
+               'action': packet_filter['action'],
+               'priority': packet_filter['priority'],
+               'in_port': packet_filter['in_port'],
+               'src_mac': packet_filter['src_mac'],
+               'dst_mac': packet_filter['dst_mac'],
+               'eth_type': packet_filter['eth_type'],
+               'src_cidr': packet_filter['src_cidr'],
+               'dst_cidr': packet_filter['dst_cidr'],
+               'protocol': packet_filter['protocol'],
+               'src_port': packet_filter['src_port'],
+               'dst_port': packet_filter['dst_port'],
+               'admin_state_up': packet_filter['admin_state_up'],
+               'status': packet_filter['status']}
+        return self._fields(res, fields)
+
+    def _get_packet_filter(self, context, id, verbose=None):
+        try:
+            packet_filter = self._get_by_id(context, nmodels.PacketFilter, id,
+                                            verbose=verbose)
+        except exc.NoResultFound:
+            raise q_exc.PacketFilterNotFound(id=id)
+        except exc.MultipleResultsFound:
+            LOG.error('Multiple packet_filters match for %s' % id)
+            raise q_exc.PacketFilterNotFound(id=id)
+        return packet_filter
+
+    def get_packet_filter(self, context, id, fields=None, verbose=None):
+        packet_filter = self._get_packet_filter(context, id, verbose=verbose)
+        return self._make_packet_filter_dict(packet_filter, fields)
+
+    def get_packet_filters(self, context, filters=None, fields=None,
+                           verbose=None):
+        return self._get_collection(context,
+                                    nmodels.PacketFilter,
+                                    self._make_packet_filter_dict,
+                                    filters=filters,
+                                    fields=fields,
+                                    verbose=verbose)
+
+    def create_packet_filter(self, context, packet_filter):
+        pf = packet_filter['packet_filter']
+        tenant_id = self._get_tenant_id_for_create(context, pf)
+
+        # validate network ownership
+        super(NECPluginV2Base, self).get_network(context, pf['network_id'])
+        if pf.get('in_port') != attributes.ATTR_NOT_SPECIFIED:
+            # validate port ownership
+            super(NECPluginV2Base, self).get_port(context, pf['in_port'])
+
+        params = {'tenant_id': tenant_id,
+                  'id': pf.get('id') or utils.str_uuid(),
+                  'network_id': pf['network_id'],
+                  'priority': pf['priority'],
+                  'action': pf['action'],
+                  'admin_state_up': pf.get('admin_state_up', True),
+                  'status': "ACTIVE"}
+        conditions = {'in_port': '',
+                      'src_mac': '',
+                      'dst_mac': '',
+                      'eth_type': 0,
+                      'src_cidr': '',
+                      'dst_cidr': '',
+                      'src_port': 0,
+                      'dst_port': 0,
+                      'protocol': ''}
+        for key, default in conditions.items():
+            if pf.get(key) == attributes.ATTR_NOT_SPECIFIED:
+                params.update({key: default})
+            else:
+                params.update({key: pf.get(key)})
+
+        with context.session.begin():
+            pf_entry = nmodels.PacketFilter(**params)
+            context.session.add(pf_entry)
+        return self._make_packet_filter_dict(pf_entry)
+
+    def update_packet_filter(self, context, id, packet_filter):
+        pf = packet_filter['packet_filter']
+        with context.session.begin():
+            pf_entry = self._get_packet_filter(context, id)
+            pf_entry.update(pf)
+        return self._make_packet_filter_dict(pf_entry)
+
+    def delete_packet_filter(self, context, id):
+        with context.session.begin():
+            packet_filter = self._get_packet_filter(context, id)
+            context.session.delete(packet_filter)
diff --git a/quantum/plugins/nec/drivers/__init__.py b/quantum/plugins/nec/drivers/__init__.py
new file mode 100644 (file)
index 0000000..09e803b
--- /dev/null
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import logging
+
+from quantum.openstack.common import importutils
+
+
+LOG = logging.getLogger(__name__)
+DRIVER_PATH = "quantum.plugins.nec.drivers.%s"
+DRIVER_LIST = {
+    'trema': DRIVER_PATH % "trema.TremaPortBaseDriver",
+    'trema_port': DRIVER_PATH % "trema.TremaPortBaseDriver",
+    'trema_portmac': DRIVER_PATH % "trema.TremaPortMACBaseDriver",
+    'trema_mac': DRIVER_PATH % "trema.TremaMACBaseDriver",
+    'pfc': DRIVER_PATH % "pfc.PFCDriver"}
+
+
+def get_driver(driver_name):
+    LOG.info("Loading OFC driver: %s" % driver_name)
+    driver_klass = DRIVER_LIST.get(driver_name) or driver_name
+    return importutils.import_class(driver_klass)
diff --git a/quantum/plugins/nec/drivers/pfc.py b/quantum/plugins/nec/drivers/pfc.py
new file mode 100644 (file)
index 0000000..c3315a1
--- /dev/null
@@ -0,0 +1,101 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.plugins.nec.common import ofc_client
+from quantum.plugins.nec import ofc_driver_base
+
+
+TENANTS_PATH = "/tenants"
+TENANT_PATH = "/tenants/%s"
+NETWORKS_PATH = "/tenants/%s/networks"
+NETWORK_PATH = "/tenants/%s/networks/%s"
+PORTS_PATH = "/tenants/%s/networks/%s/ports"
+PORT_PATH = "/tenants/%s/networks/%s/ports/%s"
+
+
+class PFCDriver(ofc_driver_base.OFCDriverBase):
+
+    def __init__(self, conf_ofc):
+        self.client = ofc_client.OFCClient(host=conf_ofc.host,
+                                           port=conf_ofc.port,
+                                           use_ssl=conf_ofc.use_ssl,
+                                           key_file=conf_ofc.key_file,
+                                           cert_file=conf_ofc.cert_file)
+
+    @classmethod
+    def filter_supported(cls):
+        return False
+
+    def create_tenant(self, description, tenant_id=None):
+        body = {'description': description}
+        if tenant_id:
+            body.update({'id': tenant_id})
+        res = self.client.post(TENANTS_PATH, body=body)
+        ofc_tenant_id = res['id']
+        return ofc_tenant_id
+
+    def update_tenant(self, ofc_tenant_id, description):
+        path = TENANT_PATH % ofc_tenant_id
+        body = {'description': description}
+        res = self.client.put(path, body=body)
+
+    def delete_tenant(self, ofc_tenant_id):
+        path = TENANT_PATH % ofc_tenant_id
+        return self.client.delete(path)
+
+    def create_network(self, ofc_tenant_id, description, network_id=None):
+        path = NETWORKS_PATH % ofc_tenant_id
+        body = {'description': description}
+        if network_id:
+            body.update({'id': network_id})
+        res = self.client.post(path, body=body)
+        ofc_network_id = res['id']
+        return ofc_network_id
+
+    def update_network(self, ofc_tenant_id, ofc_network_id, description):
+        path = NETWORK_PATH % (ofc_tenant_id, ofc_network_id)
+        body = {'description': description}
+        return self.client.put(path, body=body)
+
+    def delete_network(self, ofc_tenant_id, ofc_network_id):
+        path = NETWORK_PATH % (ofc_tenant_id, ofc_network_id)
+        return self.client.delete(path)
+
+    def create_port(self, ofc_tenant_id, ofc_network_id, portinfo,
+                    port_id=None):
+        path = PORTS_PATH % (ofc_tenant_id, ofc_network_id)
+        body = {'datapath_id': portinfo.datapath_id,
+                'port': str(portinfo.port_no),
+                'vid': str(portinfo.vlan_id)}
+        if port_id:
+            body.update({'id': port_id})
+        res = self.client.post(path, body=body)
+        ofc_port_id = res['id']
+        return ofc_port_id
+
+    def update_port(self, ofc_tenant_id, ofc_network_id, portinfo, port_id):
+        path = PORT_PATH % (ofc_tenant_id, ofc_network_id, ofc_port_id)
+        body = {'datapath_id': portinfo.datapath_id,
+                'port': str(portinfo.port_no),
+                'vid': str(portinfo.vlan_id)}
+        res = self.client.put(path, body=body)
+        ofc_port_id = res['id']
+        return ofc_port_id
+
+    def delete_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id):
+        path = PORT_PATH % (ofc_tenant_id, ofc_network_id, ofc_port_id)
+        return self.client.delete(path)
diff --git a/quantum/plugins/nec/drivers/trema.py b/quantum/plugins/nec/drivers/trema.py
new file mode 100644 (file)
index 0000000..845b380
--- /dev/null
@@ -0,0 +1,248 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import uuid
+
+from quantum.plugins.nec.common import ofc_client
+from quantum.plugins.nec import ofc_driver_base
+
+
+class TremaDriverBase(ofc_driver_base.OFCDriverBase):
+    """Common class for Trema (Sliceable Switch) Drivers"""
+    networks_path = "/networks"
+    network_path = "/networks/%s"
+
+    def __init__(self, conf_ofc):
+        # Trema sliceable REST API does not support HTTPS
+        self.client = ofc_client.OFCClient(host=conf_ofc.host,
+                                           port=conf_ofc.port)
+
+    def create_tenant(self, description, tenant_id=None):
+        return tenant_id or str(uuid.uuid4())
+
+    def update_tenant(self, ofc_tenant_id, description):
+        pass
+
+    def delete_tenant(self, ofc_tenant_id):
+        pass
+
+    def create_network(self, ofc_tenant_id, description, network_id=None):
+        ofc_network_id = network_id or str(uuid.uuid4())
+        body = {'id': ofc_network_id, 'description': description}
+        self.client.post(self.networks_path, body=body)
+        return ofc_network_id
+
+    def update_network(self, ofc_tenant_id, ofc_network_id, description):
+        path = self.network_path % ofc_network_id
+        body = {'description': description}
+        return self.client.put(path, body=body)
+
+    def delete_network(self, ofc_tenant_id, ofc_network_id):
+        path = self.network_path % ofc_network_id
+        return self.client.delete(path)
+
+    def update_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id,
+                    portinfo):
+        self.delete_port(ofc_tenant_id, ofc_network_id, ofc_port_id)
+        self.create_port(ofc_tenant_id, ofc_network_id, portinfo, ofc_port_id)
+
+
+class TremaFilterDriver(object):
+    """Trema (Sliceable Switch) PacketFilter Driver"""
+    filters_path = "/filters"
+    filter_path = "/filters/%s"
+
+    @classmethod
+    def filter_supported(cls):
+        return True
+
+    def create_filter(self, ofc_tenant_id, ofc_network_id, filter_dict,
+                      portinfo=None, filter_id=None):
+        if filter_dict['action'].upper() in ["ACCEPT", "ALLOW"]:
+            ofc_action = "ALLOW"
+        elif filter_dict['action'].upper() in ["DROP", "DENY"]:
+            ofc_action = "DENY"
+
+        body = {'priority': filter_dict['priority'],
+                'slice': ofc_network_id,
+                'action': ofc_action}
+        ofp_wildcards = ["dl_vlan", "dl_vlan_pcp", "nw_tos"]
+
+        if portinfo:
+            body['in_datapath_id'] = portinfo.datapath_id
+            body['in_port'] = portinfo.port_no
+        else:
+            body['wildcards'] = "in_datapath_id"
+            ofp_wildcards.append("in_port")
+
+        if filter_dict['src_mac']:
+            body['dl_src'] = filter_dict['src_mac']
+        else:
+            ofp_wildcards.append("dl_src")
+
+        if filter_dict['dst_mac']:
+            body['dl_dst'] = filter_dict['dst_mac']
+        else:
+            ofp_wildcards.append("dl_dst")
+
+        if filter_dict['src_cidr']:
+            body['nw_src'] = filter_dict['src_cidr']
+        else:
+            ofp_wildcards.append("nw_src:32")
+
+        if filter_dict['dst_cidr']:
+            body['nw_dst'] = filter_dict['dst_cidr']
+        else:
+            ofp_wildcards.append("nw_dst:32")
+
+        if filter_dict['protocol']:
+            if filter_dict['protocol'].upper() in "ICMP":
+                body['dl_type'] = "0x800"
+                body['nw_proto'] = hex(1)
+            elif filter_dict['protocol'].upper() in "TCP":
+                body['dl_type'] = "0x800"
+                body['nw_proto'] = hex(6)
+            elif filter_dict['protocol'].upper() in "UDP":
+                body['dl_type'] = "0x800"
+                body['nw_proto'] = hex(17)
+            elif filter_dict['protocol'].upper() in "ARP":
+                body['dl_type'] = "0x806"
+                ofp_wildcards.append("nw_proto")
+            else:
+                body['nw_proto'] = filter_dict['protocol']
+                if filter_dict['eth_type']:
+                    body['dl_type'] = filter_dict['eth_type']
+                else:
+                    ofp_wildcards.append("dl_type")
+        else:
+            ofp_wildcards.append("dl_type")
+            ofp_wildcards.append("nw_proto")
+
+        if filter_dict['src_port']:
+            body['tp_src'] = hex(filter_dict['src_port'])
+        else:
+            ofp_wildcards.append("tp_src")
+
+        if filter_dict['dst_port']:
+            body['tp_dst'] = hex(filter_dict['dst_port'])
+        else:
+            ofp_wildcards.append("tp_dst")
+
+        ofc_filter_id = filter_id or str(uuid.uuid4())
+        body['id'] = ofc_filter_id
+
+        body['ofp_wildcards'] = ','.join(ofp_wildcards)
+
+        self.client.post(self.filters_path, body=body)
+        return ofc_filter_id
+
+    def delete_filter(self, ofc_tenant_id, ofc_network_id, ofc_filter_id):
+        path = self.filter_path % ofc_filter_id
+        return self.client.delete(path)
+
+
+class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
+    """Trema (Sliceable Switch) Driver for port base binding
+
+    TremaPortBaseDriver uses port base binding.
+    Ports are identified by datapath_id, port_no and vlan_id.
+    """
+    ports_path = "/networks/%s/ports"
+    port_path = "/networks/%s/ports/%s"
+
+    def create_port(self, ofc_tenant_id, ofc_network_id, portinfo,
+                    port_id=None):
+        ofc_port_id = port_id or str(uuid.uuid4())
+        path = self.ports_path % ofc_network_id
+        body = {'id': ofc_port_id,
+                'datapath_id': portinfo.datapath_id,
+                'port': str(portinfo.port_no),
+                'vid': str(portinfo.vlan_id)}
+        self.client.post(path, body=body)
+        return ofc_port_id
+
+    def delete_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id):
+        path = self.port_path % (ofc_network_id, ofc_port_id)
+        return self.client.delete(path)
+
+
+class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriver):
+    """Trema (Sliceable Switch) Driver for port-mac base binding
+
+    TremaPortBaseDriver uses port-mac base binding.
+    Ports are identified by datapath_id, port_no, vlan_id and mac.
+    """
+    ports_path = "/networks/%s/ports"
+    port_path = "/networks/%s/ports/%s"
+    attachments_path = "/networks/%s/ports/%s/attachments"
+    attachment_path = "/networks/%s/ports/%s/attachments/%s"
+
+    def create_port(self, ofc_tenant_id, ofc_network_id, portinfo,
+                    port_id=None):
+        #NOTE: This Driver create slices with Port-MAC Based bindings on Trema
+        #      Sliceable.  It's REST API requires Port Based binding before you
+        #      define Port-MAC Based binding.
+        ofc_port_id = port_id or str(uuid.uuid4())
+        dummy_port_id = "dummy-%s" % ofc_port_id
+
+        path = self.ports_path % ofc_network_id
+        body = {'id': dummy_port_id,
+                'datapath_id': portinfo.datapath_id,
+                'port': str(portinfo.port_no),
+                'vid': str(portinfo.vlan_id)}
+        self.client.post(path, body=body)
+
+        path = self.attachments_path % (ofc_network_id, dummy_port_id)
+        body = {'id': ofc_port_id, 'mac': portinfo.mac}
+        self.client.post(path, body=body)
+
+        path = self.port_path % (ofc_network_id, dummy_port_id)
+        self.client.delete(path)
+
+        return ofc_port_id
+
+    def delete_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id):
+        dummy_port_id = "dummy-%s" % ofc_port_id
+        path = self.attachment_path % (ofc_network_id, dummy_port_id,
+                                       ofc_port_id)
+        return self.client.delete(path)
+
+
+class TremaMACBaseDriver(TremaDriverBase):
+    """Trema (Sliceable Switch) Driver for mac base binding
+
+    TremaPortBaseDriver uses mac base binding.
+    Ports are identified by mac.
+    """
+    attachments_path = "/networks/%s/attachments"
+    attachment_path = "/networks/%s/attachments/%s"
+
+    @classmethod
+    def filter_supported(cls):
+        return False
+
+    def create_port(self, ofc_tenant_id, ofc_network_id, portinfo,
+                    port_id=None):
+        ofc_port_id = port_id or str(uuid.uuid4())
+        path = self.attachments_path % ofc_network_id
+        body = {'id': ofc_port_id, 'mac': portinfo.mac}
+        self.client.post(path, body=body)
+        return ofc_port_id
+
+    def delete_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id):
+        path = self.attachment_path % (ofc_network_id, ofc_port_id)
+        return self.client.delete(path)
diff --git a/quantum/plugins/nec/extensions/__init__.py b/quantum/plugins/nec/extensions/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/extensions/packetfilter.py b/quantum/plugins/nec/extensions/packetfilter.py
new file mode 100644 (file)
index 0000000..d92e8aa
--- /dev/null
@@ -0,0 +1,131 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.api.v2 import attributes
+from quantum.api.v2 import base
+from quantum.extensions import extensions
+from quantum.manager import QuantumManager
+from quantum import quota
+from quantum.openstack.common import cfg
+
+
+quota_packet_filter_opts = [
+    cfg.IntOpt('quota_packet_filter',
+               default=100,
+               help="number of packet_filters allowed per tenant, "
+                    "-1 for unlimited")
+]
+# Register the configuration options
+cfg.CONF.register_opts(quota_packet_filter_opts, 'QUOTAS')
+
+
+PACKET_FILTER_ACTION_REGEX = "(?i)^(allow|accept|drop|deny)$"
+PACKET_FILTER_NUMBER_REGEX = "(?i)^(0x[0-9a-fA-F]+|[0-9]+)$"
+PACKET_FILTER_PROTOCOL_REGEX = "(?i)^(icmp|tcp|udp|arp|0x[0-9a-fA-F]+|[0-9]+)$"
+PACKET_FILTER_ATTR_MAP = {
+    'id': {'allow_post': False, 'allow_put': False,
+           'validate': {'type:regex': attributes.UUID_PATTERN},
+           'is_visible': True},
+    'name': {'allow_post': True, 'allow_put': True, 'default': '',
+             'is_visible': True},
+    'tenant_id': {'allow_post': True, 'allow_put': False,
+                  'required_by_policy': True,
+                  'is_visible': True},
+    'network_id': {'allow_post': True, 'allow_put': False,
+                   'validate': {'type:regex': attributes.UUID_PATTERN},
+                   'is_visible': True},
+    'admin_state_up': {'allow_post': True, 'allow_put': True,
+                       'default': True,
+                       'convert_to': attributes.convert_to_boolean,
+                       'validate': {'type:boolean': None},
+                       'is_visible': True},
+    'status': {'allow_post': False, 'allow_put': False,
+               'is_visible': True},
+    'action': {'allow_post': True, 'allow_put': True,
+               'validate': {'type:regex': PACKET_FILTER_ACTION_REGEX},
+               'is_visible': True},
+    'priority': {'allow_post': True, 'allow_put': True,
+                 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX},
+                 'is_visible': True},
+    'in_port': {'allow_post': True, 'allow_put': True,
+                'default': attributes.ATTR_NOT_SPECIFIED,
+                'validate': {'type:regex': attributes.UUID_PATTERN},
+                'is_visible': True},
+    'src_mac': {'allow_post': True, 'allow_put': True,
+                'default': attributes.ATTR_NOT_SPECIFIED,
+                'validate': {'type:mac_address': None},
+                'is_visible': True},
+    'dst_mac': {'allow_post': True, 'allow_put': True,
+                'default': attributes.ATTR_NOT_SPECIFIED,
+                'validate': {'type:mac_address': None},
+                'is_visible': True},
+    'eth_type': {'allow_post': True, 'allow_put': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED,
+                 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX},
+                 'is_visible': True},
+    'src_cidr': {'allow_post': True, 'allow_put': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED,
+                 'validate': {'type:subnet': None},
+                 'is_visible': True},
+    'dst_cidr': {'allow_post': True, 'allow_put': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED,
+                 'validate': {'type:subnet': None},
+                 'is_visible': True},
+    'protocol': {'allow_post': True, 'allow_put': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED,
+                 'validate': {'type:regex': PACKET_FILTER_PROTOCOL_REGEX},
+                 'is_visible': True},
+    'src_port': {'allow_post': True, 'allow_put': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED,
+                 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX},
+                 'is_visible': True},
+    'dst_port': {'allow_post': True, 'allow_put': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED,
+                 'validate': {'type:regex': PACKET_FILTER_NUMBER_REGEX},
+                 'is_visible': True},
+}
+
+
+class Packetfilter(object):
+
+    def __init__(self):
+        pass
+
+    def get_name(self):
+        return "PacketFilters"
+
+    def get_alias(self):
+        return "PacketFilters"
+
+    def get_description(self):
+        return "PacketFilters"
+
+    def get_namespace(self):
+        return "http://www.nec.co.jp/api/ext/packet_filter/v2.0"
+
+    def get_updated(self):
+        return "2012-07-24T00:00:00+09:00"
+
+    def get_resources(self):
+        resource = base.create_resource('packet_filters', 'packet_filter',
+                                        QuantumManager.get_plugin(),
+                                        PACKET_FILTER_ATTR_MAP)
+        qresource = quota.CountableResource('packet_filter',
+                                            quota._count_resource,
+                                            'quota_packet_filter')
+        quota.QUOTAS.register_resource(qresource)
+        return [extensions.ResourceExtension('packet_filters', resource)]
diff --git a/quantum/plugins/nec/nec_plugin.py b/quantum/plugins/nec/nec_plugin.py
new file mode 100644 (file)
index 0000000..d4d8523
--- /dev/null
@@ -0,0 +1,505 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import logging
+
+from quantum import context
+from quantum.common import topics
+from quantum.openstack.common import rpc
+from quantum.openstack.common.rpc import dispatcher
+from quantum.plugins.nec import ofc_manager
+from quantum.plugins.nec.common import config
+from quantum.plugins.nec.common import exceptions as nexc
+from quantum.plugins.nec.db import api as ndb
+from quantum.plugins.nec.db import nec_plugin_base
+
+LOG = logging.getLogger(__name__)
+
+
+class OperationalStatus:
+    """Enumeration for operational status.
+
+       ACTIVE: The resource is available.
+       DOWN: The resource is not operational.  This might indicate
+             admin_status_up=False, or lack of OpenFlow info for the port.
+       BUILD: The plugin is creating the resource.
+       ERROR: Some error occured.
+    """
+    ACTIVE = "ACTIVE"
+    DOWN = "DOWN"
+    BUILD = "BUILD"
+    ERROR = "ERROR"
+
+
+class NECPluginV2(nec_plugin_base.NECPluginV2Base):
+    """NECPluginV2 controls an OpenFlow Controller.
+
+    The Quantum NECPluginV2 maps L2 logical networks to L2 virtualized networks
+    on an OpenFlow enabled network.  An OpenFlow Controller (OFC) provides
+    L2 network isolation without VLAN and this plugin controls the OFC.
+
+    NOTE: This is for Quantum API V2.  Codes for V1.0 and V1.1 are available
+          at https://github.com/nec-openstack/quantum-openflow-plugin .
+    """
+
+    def __init__(self):
+        ndb.initialize()
+        self.ofc = ofc_manager.OFCManager()
+
+        self.packet_filter_enabled = (config.OFC.enable_packet_filter and
+                                      self.ofc.driver.filter_supported)
+        if self.packet_filter_enabled:
+            self.supported_extension_aliases = ["PacketFilters"]
+
+        self.setup_rpc()
+
+    def setup_rpc(self):
+        self.topic = topics.PLUGIN
+        self.conn = rpc.create_connection(new=True)
+        self.callbacks = NECPluginV2RPCCallbacks(self)
+        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_resource_status(self, context, resource, id, status):
+        """Update status of specified resource."""
+        request = {}
+        request[resource] = dict(status=status)
+        obj_updater = getattr(super(NECPluginV2, self), "update_%s" % resource)
+        obj_updater(context, id, request)
+
+    def activate_port_if_ready(self, context, port, network=None):
+        """Activate port by creating port on OFC if ready.
+
+        Activate port and packet_filters associated with the port.
+        Conditions to activate port on OFC are:
+            * port admin_state is UP
+            * network admin_state is UP
+            * portinfo are available (to identify port on OFC)
+        """
+        if not network:
+            network = super(NECPluginV2, self).get_network(context,
+                                                           port['network_id'])
+
+        port_status = OperationalStatus.ACTIVE
+        if not port['admin_state_up']:
+            LOG.debug("activate_port_if_ready(): skip, "
+                      "port.admin_state_up is False.")
+            port_status = OperationalStatus.DOWN
+        elif not network['admin_state_up']:
+            LOG.debug("activate_port_if_ready(): skip, "
+                      "network.admin_state_up is False.")
+            port_status = OperationalStatus.DOWN
+        elif not ndb.get_portinfo(port['id']):
+            LOG.debug("activate_port_if_ready(): skip, "
+                      "no portinfo for this port.")
+            port_status = OperationalStatus.DOWN
+
+        # activate packet_filters before creating port on OFC.
+        if self.packet_filter_enabled:
+            if port_status is OperationalStatus.ACTIVE:
+                filters = dict(in_port=[port['id']],
+                               status=[OperationalStatus.DOWN],
+                               admin_state_up=[True])
+                pfs = (super(NECPluginV2, self).
+                       get_packet_filters(context, filters=filters))
+                for pf in pfs:
+                    self._activate_packet_filter_if_ready(context, pf,
+                                                          network=network,
+                                                          in_port=port)
+
+        if port_status in [OperationalStatus.ACTIVE]:
+            if self.ofc.exists_ofc_port(port['id']):
+                LOG.debug("activate_port_if_ready(): skip, "
+                          "ofc_port already exists.")
+            else:
+                try:
+                    self.ofc.create_ofc_port(port['tenant_id'],
+                                             port['network_id'],
+                                             port['id'])
+                except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                    reason = "create_ofc_port() failed due to %s" % exc
+                    LOG.error(reason)
+                    port_status = OperationalStatus.ERROR
+
+        if port_status is not port['status']:
+            self._update_resource_status(context, "port", port['id'],
+                                         port_status)
+
+    def deactivate_port(self, context, port):
+        """Deactivate port by deleting port from OFC if exists.
+
+        Deactivate port and packet_filters associated with the port.
+        """
+        port_status = OperationalStatus.DOWN
+        if self.ofc.exists_ofc_port(port['id']):
+            try:
+                self.ofc.delete_ofc_port(port['tenant_id'],
+                                         port['network_id'],
+                                         port['id'])
+            except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                reason = "delete_ofc_port() failed due to %s" % exc
+                LOG.error(reason)
+                port_status = OperationalStatus.ERROR
+        else:
+            LOG.debug("deactivate_port(): skip, ofc_port does not "
+                      "exist.")
+
+        if port_status is not port['status']:
+            self._update_resource_status(context, "port", port['id'],
+                                         port_status)
+
+        # deactivate packet_filters after the port has deleted from OFC.
+        if self.packet_filter_enabled:
+            filters = dict(in_port=[port['id']],
+                           status=[OperationalStatus.ACTIVE])
+            pfs = super(NECPluginV2, self).get_packet_filters(context,
+                                                              filters=filters)
+            for pf in pfs:
+                self._deactivate_packet_filter(context, pf)
+
+    # Quantm Plugin Basic methods
+
+    def create_network(self, context, network):
+        """Create a new network entry on DB, and create it on OFC."""
+        LOG.debug("NECPluginV2.create_network() called, "
+                  "network=%s ." % network)
+        new_net = super(NECPluginV2, self).create_network(context, network)
+        self._update_resource_status(context, "network", new_net['id'],
+                                     OperationalStatus.BUILD)
+
+        try:
+            if not self.ofc.exists_ofc_tenant(new_net['tenant_id']):
+                self.ofc.create_ofc_tenant(new_net['tenant_id'])
+            self.ofc.create_ofc_network(new_net['tenant_id'], new_net['id'],
+                                        new_net['name'])
+        except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+            reason = "create_network() failed due to %s" % exc
+            LOG.error(reason)
+            self._update_resource_status(context, "network", new_net['id'],
+                                         OperationalStatus.ERROR)
+        else:
+            self._update_resource_status(context, "network", new_net['id'],
+                                         OperationalStatus.ACTIVE)
+
+        return new_net
+
+    def update_network(self, context, id, network):
+        """Update network and handle resources associated with the network.
+
+        Update network entry on DB. If 'admin_state_up' was changed, activate
+        or deactivate ports and packetfilters associated with the network.
+        """
+        LOG.debug("NECPluginV2.update_network() called, "
+                  "id=%s network=%s ." % (id, network))
+        old_net = super(NECPluginV2, self).get_network(context, id)
+        new_net = super(NECPluginV2, self).update_network(context, id, network)
+
+        changed = (old_net['admin_state_up'] is not new_net['admin_state_up'])
+        if changed and not new_net['admin_state_up']:
+            self._update_resource_status(context, "network", id,
+                                         OperationalStatus.DOWN)
+            # disable all active ports and packet_filters of the network
+            filters = dict(network_id=[id], status=[OperationalStatus.ACTIVE])
+            ports = super(NECPluginV2, self).get_ports(context,
+                                                       filters=filters)
+            for port in ports:
+                self.deactivate_port(context, port)
+            if self.packet_filter_enabled:
+                pfs = (super(NECPluginV2, self).
+                       get_packet_filters(context, filters=filters))
+                for pf in pfs:
+                    self._deactivate_packet_filter(context, pf)
+        elif changed and new_net['admin_state_up']:
+            self._update_resource_status(context, "network", id,
+                                         OperationalStatus.ACTIVE)
+            # enable ports and packet_filters of the network
+            filters = dict(network_id=[id], status=[OperationalStatus.DOWN],
+                           admin_state_up=[True])
+            ports = super(NECPluginV2, self).get_ports(context,
+                                                       filters=filters)
+            for port in ports:
+                self.activate_port_if_ready(context, port, new_net)
+            if self.packet_filter_enabled:
+                pfs = (super(NECPluginV2, self).
+                       get_packet_filters(context, filters=filters))
+                for pf in pfs:
+                    self._activate_packet_filter_if_ready(context, pf, new_net)
+
+        return new_net
+
+    def delete_network(self, context, id):
+        """Delete network and packet_filters associated with the network.
+
+        Delete network entry from DB and OFC. Then delete packet_filters
+        associated with the network. If the network is the last resource
+        of the tenant, delete unnessary ofc_tenant.
+        """
+        LOG.debug("NECPluginV2.delete_network() called, id=%s .", id)
+        net = super(NECPluginV2, self).get_network(context, id)
+        tenant_id = net['tenant_id']
+
+        # get packet_filters associated with the network
+        if self.packet_filter_enabled:
+            filters = dict(network_id=[id])
+            pfs = (super(NECPluginV2, self).
+                   get_packet_filters(context, filters=filters))
+
+        super(NECPluginV2, self).delete_network(context, id)
+        try:
+            self.ofc.delete_ofc_network(tenant_id, id)
+        except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+            reason = "delete_network() failed due to %s" % exc
+            # NOTE: The OFC configuration of this network could be remained
+            #       as an orphan resource. But, it does NOT harm any other
+            #       resources, so this plugin just warns.
+            LOG.warn(reason)
+
+        # delete all packet_filters of the network
+        if self.packet_filter_enabled:
+            for pf in pfs:
+                self.delete_packet_filter(context, pf['id'])
+
+        # delete unnessary ofc_tenant
+        filters = dict(tenant_id=[tenant_id])
+        nets = super(NECPluginV2, self).get_networks(context, filters=filters)
+        if len(nets) == 0:
+            try:
+                self.ofc.delete_ofc_tenant(tenant_id)
+            except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                reason = "delete_ofc_tenant() failed due to %s" % exc
+                LOG.warn(reason)
+
+    def create_port(self, context, port):
+        """Create a new port entry on DB, then try to activate it."""
+        LOG.debug("NECPluginV2.create_port() called, port=%s ." % port)
+        new_port = super(NECPluginV2, self).create_port(context, port)
+        self._update_resource_status(context, "port", new_port['id'],
+                                     OperationalStatus.BUILD)
+
+        self.activate_port_if_ready(context, new_port)
+
+        return new_port
+
+    def update_port(self, context, id, port):
+        """Update port, and handle packetfilters associated with the port.
+
+        Update network entry on DB. If admin_state_up was changed, activate
+        or deactivate the port and packetfilters associated with it.
+        """
+        LOG.debug("NECPluginV2.update_port() called, "
+                  "id=%s port=%s ." % (id, port))
+        old_port = super(NECPluginV2, self).get_port(context, id)
+        new_port = super(NECPluginV2, self).update_port(context, id, port)
+
+        changed = (old_port['admin_state_up'] is
+                   not new_port['admin_state_up'])
+        if changed:
+            if new_port['admin_state_up']:
+                self.activate_port_if_ready(context, new_port)
+            else:
+                self.deactivate_port(context, old_port)
+
+        return new_port
+
+    def delete_port(self, context, id):
+        """Delete port and packet_filters associated with the port."""
+        LOG.debug("NECPluginV2.delete_port() called, id=%s ." % id)
+        port = super(NECPluginV2, self).get_port(context, id)
+
+        self.deactivate_port(context, port)
+
+        # delete all packet_filters of the port
+        if self.packet_filter_enabled:
+            filters = dict(port_id=[id])
+            pfs = (super(NECPluginV2, self).
+                   get_packet_filters(context, filters=filters))
+            for packet_filter in pfs:
+                self.delete_packet_filter(context, packet_filter['id'])
+
+        super(NECPluginV2, self).delete_port(context, id)
+
+    # For PacketFilter Extension
+
+    def _activate_packet_filter_if_ready(self, context, packet_filter,
+                                         network=None, in_port=None):
+        """Activate packet_filter by creating filter on OFC if ready.
+
+        Conditions to create packet_filter on OFC are:
+            * packet_filter admin_state is UP
+            * network admin_state is UP
+            * (if 'in_port' is specified) portinfo is available
+        """
+        net_id = packet_filter['network_id']
+        if not network:
+            network = super(NECPluginV2, self).get_network(context, net_id)
+        in_port_id = packet_filter.get("in_port")
+        if in_port_id and not in_port:
+            in_port = super(NECPluginV2, self).get_port(context, in_port_id)
+
+        pf_status = OperationalStatus.ACTIVE
+        if not packet_filter['admin_state_up']:
+            LOG.debug("_activate_packet_filter_if_ready(): skip, "
+                      "packet_filter.admin_state_up is False.")
+            pf_status = OperationalStatus.DOWN
+        elif not network['admin_state_up']:
+            LOG.debug("_activate_packet_filter_if_ready(): skip, "
+                      "network.admin_state_up is False.")
+            pf_status = OperationalStatus.DOWN
+        elif in_port_id and in_port_id is in_port.get('id'):
+            LOG.debug("_activate_packet_filter_if_ready(): skip, "
+                      "invalid in_port_id.")
+            pf_status = OperationalStatus.DOWN
+        elif in_port_id and not ndb.get_portinfo(in_port_id):
+            LOG.debug("_activate_packet_filter_if_ready(): skip, "
+                      "no portinfo for in_port.")
+            pf_status = OperationalStatus.DOWN
+
+        if pf_status in [OperationalStatus.ACTIVE]:
+            if self.ofc.exists_ofc_packet_filter(packet_filter['id']):
+                LOG.debug("_activate_packet_filter_if_ready(): skip, "
+                          "ofc_packet_filter already exists.")
+            else:
+                try:
+                    (self.ofc.
+                     create_ofc_packet_filter(packet_filter['tenant_id'],
+                                              packet_filter['network_id'],
+                                              packet_filter['id'],
+                                              packet_filter))
+                except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                    reason = ("create_ofc_packet_filter() failed due to "
+                              "%s" % exc)
+                    LOG.error(reason)
+                    pf_status = OperationalStatus.ERROR
+
+        if pf_status is not packet_filter['status']:
+            self._update_resource_status(context, "packet_filter",
+                                         packet_filter['id'], pf_status)
+
+    def _deactivate_packet_filter(self, context, packet_filter):
+        """Deactivate packet_filter by deleting filter from OFC if exixts."""
+        pf_status = OperationalStatus.DOWN
+        if not self.ofc.exists_ofc_packet_filter(packet_filter['id']):
+            LOG.debug("_deactivate_packet_filter(): skip, "
+                      "ofc_packet_filter does not exist.")
+        else:
+            try:
+                self.ofc.delete_ofc_packet_filter(packet_filter['tenant_id'],
+                                                  packet_filter['network_id'],
+                                                  packet_filter['id'])
+            except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                reason = ("delete_ofc_packet_filter() failed due to "
+                          "%s" % exc)
+                LOG.error(reason)
+                pf_status = OperationalStatus.ERROR
+
+        if pf_status is not packet_filter['status']:
+            self._update_resource_status(context, "packet_filter",
+                                         packet_filter['id'], pf_status)
+
+    def create_packet_filter(self, context, packet_filter):
+        """Create a new packet_filter entry on DB, then try to activate it."""
+        LOG.debug("NECPluginV2.create_packet_filter() called, "
+                  "packet_filter=%s ." % packet_filter)
+        new_pf = super(NECPluginV2, self).create_packet_filter(context,
+                                                               packet_filter)
+        self._update_resource_status(context, "packet_filter", new_pf['id'],
+                                     OperationalStatus.BUILD)
+
+        self._activate_packet_filter_if_ready(context, new_pf)
+
+        return new_pf
+
+    def update_packet_filter(self, context, id, packet_filter):
+        """Update packet_filter entry on DB, and recreate it if changed.
+
+        If any rule of the packet_filter was changed, recreate it on OFC.
+        """
+        LOG.debug("NECPluginV2.update_packet_filter() called, "
+                  "id=%s packet_filter=%s ." % (id, packet_filter))
+        old_pf = super(NECPluginV2, self).get_packet_filter(context, id)
+        new_pf = super(NECPluginV2, self).update_packet_filter(context, id,
+                                                               packet_filter)
+
+        changed = False
+        exclude_items = ["id", "name", "tenant_id", "network_id", "status"]
+        for key in new_pf['packet_filter'].keys():
+            if key not in exclude_items:
+                if old_pf[key] is not new_pf[key]:
+                    changed = True
+                    break
+
+        if changed:
+            self._deactivate_packet_filter(context, old_pf)
+            self._activate_packet_filter_if_ready(context, new_pf)
+
+        return new_pf
+
+    def delete_packet_filter(self, context, id):
+        """Deactivate and delete packet_filter."""
+        LOG.debug("NECPluginV2.delete_packet_filter() called, id=%s ." % id)
+        pf = super(NECPluginV2, self).get_packet_filter(context, id)
+        self._deactivate_packet_filter(context, pf)
+
+        super(NECPluginV2, self).delete_packet_filter(context, id)
+
+
+class NECPluginV2RPCCallbacks():
+
+    RPC_API_VERSION = '1.0'
+
+    def __init__(self, plugin):
+        self.plugin = plugin
+        self.admin_context = context.get_admin_context()
+
+    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 dispatcher.RpcDispatcher([self])
+
+    def update_ports(self, rpc_context, **kwargs):
+        """Update ports' information and activate/deavtivate them.
+
+        Expected input format is:
+            {'topic': 'q-agent-notifier',
+             'agent_id': 'nec-q-agent.' + <hostname>,
+             'datapath_id': <datapath_id of br-int on remote host>,
+             'port_added': [<new PortInfo>,...],
+             'port_removed': [<removed Port ID>,...]}
+        """
+        LOG.debug("NECPluginV2RPCCallbacks.update_ports() called, "
+                  "kwargs=%s ." % kwargs)
+        topic = kwargs['topic']
+        datapath_id = kwargs['datapath_id']
+        for p in kwargs.get('port_added', []):
+            id = p['id']
+            port = self.plugin.get_port(self.admin_context, id)
+            if port and ndb.get_portinfo(id):
+                ndb.del_portinfo(id)
+                self.plugin.deactivate_port(self.admin_context, port)
+            ndb.add_portinfo(id, datapath_id, p['port_no'],
+                             mac=p.get('mac', ''))
+            self.plugin.activate_port_if_ready(self.admin_context, port)
+        for id in kwargs.get('port_removed', []):
+            port = self.plugin.get_port(self.admin_context, id)
+            if port and ndb.get_portinfo(id):
+                ndb.del_portinfo(id)
+                self.plugin.deactivate_port(self.admin_context, port)
diff --git a/quantum/plugins/nec/ofc_driver_base.py b/quantum/plugins/nec/ofc_driver_base.py
new file mode 100644 (file)
index 0000000..d1c0996
--- /dev/null
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from abc import ABCMeta, abstractmethod
+
+
+class OFCDriverBase(object):
+    """OpenFlow Controller (OFC) Driver Specification.
+
+    OFCDriverBase defines the minimum set of methods required by this plugin.
+    It would be better that other methods like update_* are implemented.
+    """
+
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def create_tenant(self, description, tenant_id=None):
+        """Create a new tenant at OpenFlow Controller.
+
+        :param description: A description of this tenant.
+        :param tenant_id: A hint of OFC tenant ID.
+                          A driver could use this id as a OFC id or ignore it.
+        :returns: ID of the tenant created at OpenFlow Controller.
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
+
+    @abstractmethod
+    def delete_tenant(self, ofc_tenant_id):
+        """Delete a tenant at OpenFlow Controller.
+
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
+
+    @abstractmethod
+    def create_network(self, ofc_tenant_id, description, network_id=None):
+        """Create a new network on specified OFC tenant at OpenFlow Controller.
+
+        :param ofc_tenant_id: a OFC tenant ID in which a new network belongs.
+        :param description: A description of this network.
+        :param network_id: A hint of a OFC network ID.
+        :returns: ID of the network created at OpenFlow Controller.
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
+
+    @abstractmethod
+    def update_network(self, ofc_tenant_id, ofc_network_id, description):
+        """Update description of specified network.
+
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
+
+    @abstractmethod
+    def delete_network(self, ofc_tenant_id, ofc_network_id):
+        """Delete a netwrok at OpenFlow Controller.
+
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
+
+    @abstractmethod
+    def create_port(self, ofc_tenant_id, ofc_network_id, portinfo,
+                    port_id=None):
+        """Create a new port on specified tenant and network at OFC.
+
+        :param ofc_network_id: a OFC tenant ID in which a new port belongs.
+        :param portinfo: An OpenFlow information of this port.
+                    {'datapath_id': Switch ID that a port connected.
+                     'port_no': Port Number that a port connected on a Swtich.
+                     'vlan_id': VLAN ID that a port tagging.
+                     'mac': Mac address.
+                    }
+        :param port_id: A hint of a OFC port ID.
+
+        :returns: ID of the port created at OpenFlow Controller.
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
+
+    @abstractmethod
+    def delete_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id):
+        """Delete a port at OpenFlow Controller.
+
+        :raises: quantum.plugin.nec.common.exceptions.OFCException
+        """
+        pass
diff --git a/quantum/plugins/nec/ofc_manager.py b/quantum/plugins/nec/ofc_manager.py
new file mode 100644 (file)
index 0000000..a775097
--- /dev/null
@@ -0,0 +1,150 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.plugins.nec import drivers
+from quantum.plugins.nec.common import config
+from quantum.plugins.nec.common import exceptions as nexc
+from quantum.plugins.nec.db import api as ndb
+from quantum.plugins.nec.db import models as nmodels
+
+
+class OFCManager(object):
+    """This class manages an OpenFlow Controller and map resources.
+
+    This class manage an OpenFlow Controller (OFC) with a driver specified in
+    a configuration of this plugin.  This keeps mappings between IDs on Quantum
+    and OFC for various entities such as Tenant, Network and Filter.  A Port on
+    OFC is identified by a switch ID 'datapath_id' and a port number 'port_no'
+    of the switch.  An ID named as 'ofc_*' is used to identify resource on OFC.
+    """
+    resource_map = {'ofc_tenant': nmodels.OFCTenant,
+                    'ofc_network': nmodels.OFCNetwork,
+                    'ofc_port': nmodels.OFCPort,
+                    'ofc_packet_filter': nmodels.OFCFilter}
+
+    def __init__(self):
+        self.driver = drivers.get_driver(config.OFC.driver)(config.OFC)
+
+    def _get_ofc_id(self, resource, quantum_id):
+        model = self.resource_map[resource]
+        ofc_item = ndb.find_ofc_item(model, quantum_id)
+        if not ofc_item:
+            reason = "NotFound %s for quantum_id=%s." % (resource, quantum_id)
+            raise nexc.OFCConsistencyBroken(reason=reason)
+        return ofc_item.id
+
+    def _exists_ofc_item(self, resource, quantum_id):
+        model = self.resource_map[resource]
+        if ndb.find_ofc_item(model, quantum_id):
+            return True
+        else:
+            return False
+
+    # Tenant
+
+    def create_ofc_tenant(self, tenant_id):
+        desc = "ID=%s at OpenStack." % tenant_id
+        ofc_tenant_id = self.driver.create_tenant(desc, tenant_id)
+        ndb.add_ofc_item(nmodels.OFCTenant, ofc_tenant_id, tenant_id)
+
+    def exists_ofc_tenant(self, tenant_id):
+        return self._exists_ofc_item("ofc_tenant", tenant_id)
+
+    def delete_ofc_tenant(self, tenant_id):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+
+        self.driver.delete_tenant(ofc_tenant_id)
+        ndb.del_ofc_item(nmodels.OFCTenant, ofc_tenant_id)
+
+    # Network
+
+    def create_ofc_network(self, tenant_id, network_id, network_name=None):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+
+        desc = "ID=%s Name=%s at Quantum." % (network_id, network_name)
+        ofc_net_id = self.driver.create_network(ofc_tenant_id, desc,
+                                                network_id)
+        ndb.add_ofc_item(nmodels.OFCNetwork, ofc_net_id, network_id)
+
+    def update_ofc_network(self, tenant_id, network_id, network_name):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+        ofc_net_id = self._get_ofc_id("ofc_network", network_id)
+
+        desc = "ID=%s Name=%s at Quantum." % (network_id, network_name)
+        self.driver.update_network(ofc_tenant_id, ofc_net_id, desc)
+
+    def exists_ofc_network(self, network_id):
+        return self._exists_ofc_item("ofc_network", network_id)
+
+    def delete_ofc_network(self, tenant_id, network_id):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+        ofc_net_id = self._get_ofc_id("ofc_network", network_id)
+
+        self.driver.delete_network(ofc_tenant_id, ofc_net_id)
+        ndb.del_ofc_item(nmodels.OFCNetwork, ofc_net_id)
+
+    # Port
+
+    def create_ofc_port(self, tenant_id, network_id, port_id):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+        ofc_net_id = self._get_ofc_id("ofc_network", network_id)
+        portinfo = ndb.get_portinfo(port_id)
+        if not portinfo:
+            raise nexc.PortInfoNotFound(id=port_id)
+
+        ofc_port_id = self.driver.create_port(ofc_tenant_id, ofc_net_id,
+                                              portinfo, port_id)
+        ndb.add_ofc_item(nmodels.OFCPort, ofc_port_id, port_id)
+
+    def exists_ofc_port(self, port_id):
+        return self._exists_ofc_item("ofc_port", port_id)
+
+    def delete_ofc_port(self, tenant_id, network_id, port_id):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+        ofc_net_id = self._get_ofc_id("ofc_network", network_id)
+        ofc_port_id = self._get_ofc_id("ofc_port", port_id)
+
+        self.driver.delete_port(ofc_tenant_id, ofc_net_id, ofc_port_id)
+        ndb.del_ofc_item(nmodels.OFCPort, ofc_port_id)
+
+    # PacketFilter
+
+    def create_ofc_packet_filter(self, tenant_id, network_id, filter_id,
+                                 filter_dict):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+        ofc_net_id = self._get_ofc_id("ofc_network", network_id)
+        in_port_id = filter_dict.get('in_port')
+        portinfo = None
+        if in_port_id:
+            portinfo = ndb.get_portinfo(in_port_id)
+            if not portinfo:
+                raise nexc.PortInfoNotFound(id=in_port_id)
+
+        ofc_pf_id = self.driver.create_filter(ofc_tenant_id, ofc_net_id,
+                                              filter_dict, portinfo, filter_id)
+        ndb.add_ofc_item(nmodels.OFCFilter, ofc_pf_id, filter_id)
+
+    def exists_ofc_packet_filter(self, filter_id):
+        return self._exists_ofc_item("ofc_packet_filter", filter_id)
+
+    def delete_ofc_packet_filter(self, tenant_id, network_id, filter_id):
+        ofc_tenant_id = self._get_ofc_id("ofc_tenant", tenant_id)
+        ofc_net_id = self._get_ofc_id("ofc_network", network_id)
+        ofc_pf_id = self._get_ofc_id("ofc_packet_filter", filter_id)
+
+        res = self.driver.delete_filter(ofc_tenant_id, ofc_net_id, ofc_pf_id)
+        ndb.del_ofc_item(nmodels.OFCFilter, ofc_pf_id)
diff --git a/quantum/plugins/nec/run_tests.py b/quantum/plugins/nec/run_tests.py
new file mode 100755 (executable)
index 0000000..5ef4086
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+
+"""Unittest runner for quantum NEC OpenFlow plugin
+
+This file should be run from the top dir in the quantum directory
+
+To run all tests::
+    PLUGIN_DIR=quantum/plugins/nec ./run_tests.sh
+"""
+
+import os
+import sys
+
+from nose import config
+from nose import core
+
+sys.path.append(os.getcwd())
+sys.path.append(os.path.dirname(__file__))
+
+from quantum.common.test_lib import run_tests, test_config
+import quantum.tests.unit
+
+
+if __name__ == '__main__':
+    exit_status = False
+
+    # if a single test case was specified,
+    # we should only invoked the tests once
+    invoke_once = len(sys.argv) > 1
+
+    test_config['plugin_name'] = "nec_plugin.NECPluginV2"
+
+    cwd = os.getcwd()
+    c = config.Config(stream=sys.stdout,
+                      env=os.environ,
+                      verbosity=3,
+                      includeExe=True,
+                      traverseNamespace=True,
+                      plugins=core.DefaultPluginManager())
+    c.configureWhere(quantum.tests.unit.__path__)
+    exit_status = run_tests(c)
+
+    if invoke_once:
+        sys.exit(0)
+
+    os.chdir(cwd)
+
+    working_dir = os.path.abspath("quantum/plugins/nec")
+    c = config.Config(stream=sys.stdout,
+                      env=os.environ,
+                      verbosity=3,
+                      workingDir=working_dir)
+    exit_status = exit_status or run_tests(c)
+
+    sys.exit(exit_status)
diff --git a/quantum/plugins/nec/tests/__init__.py b/quantum/plugins/nec/tests/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/tests/unit/__init__.py b/quantum/plugins/nec/tests/unit/__init__.py
new file mode 100644 (file)
index 0000000..362a360
--- /dev/null
@@ -0,0 +1,15 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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.
diff --git a/quantum/plugins/nec/tests/unit/stub_ofc_driver.py b/quantum/plugins/nec/tests/unit/stub_ofc_driver.py
new file mode 100644 (file)
index 0000000..c5f7332
--- /dev/null
@@ -0,0 +1,56 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+from quantum.plugins.nec import ofc_driver_base
+
+
+class StubOFCDriver(ofc_driver_base.OFCDriverBase):
+
+    def __init__(self, conf):
+        pass
+
+    def create_tenant(self, description, tenant_id=None):
+        return "ofc-" + tenant_id[:-4]
+
+    def delete_tenant(self, ofc_tenant_id):
+        pass
+
+    def create_network(self, ofc_tenant_id, description, network_id=None):
+        return "ofc-" + network_id[:-4]
+
+    def update_network(self, ofc_tenant_id, ofc_network_id, description):
+        pass
+
+    def delete_network(self, ofc_tenant_id, ofc_network_id):
+        pass
+
+    def create_port(self, ofc_tenant_id, ofc_network_id, info, port_id=None):
+        return "ofc-" + port_id[:-4]
+
+    def delete_port(self, ofc_tenant_id, ofc_network_id, ofc_port_id):
+        pass
+
+    @classmethod
+    def filter_supported(cls):
+        return True
+
+    def create_filter(self, ofc_tenant_id, ofc_network_id, filter_dict,
+                      portinfo=None, filter_id=None):
+        return "ofc-" + filter_id[:-4]
+
+    def delete_filter(self, ofc_tenant_id, ofc_network_id, ofc_filter_id):
+        pass
diff --git a/quantum/plugins/nec/tests/unit/test_config.py b/quantum/plugins/nec/tests/unit/test_config.py
new file mode 100644 (file)
index 0000000..ac8d758
--- /dev/null
@@ -0,0 +1,39 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import unittest
+
+from quantum.plugins.nec.common import config
+
+
+class ConfigurationTest(unittest.TestCase):
+
+    def test_defaults(self):
+        self.assertEqual('sqlite://', config.DATABASE.sql_connection)
+        self.assertEqual('sqlite://', config.CONF.DATABASE.sql_connection)
+        self.assertEqual(-1, config.CONF.DATABASE.sql_max_retries)
+        self.assertEqual(2, config.CONF.DATABASE.reconnect_interval)
+        self.assertEqual('br-int', config.CONF.OVS.integration_bridge)
+        self.assertEqual(2, config.CONF.AGENT.polling_interval)
+        self.assertEqual('sudo', config.CONF.AGENT.root_helper)
+        self.assertEqual('127.0.0.1', config.CONF.OFC.host)
+        self.assertEqual('8888', config.CONF.OFC.port)
+        self.assertEqual('trema', config.CONF.OFC.driver)
+        self.assertTrue(config.CONF.OFC.enable_packet_filter)
+        self.assertFalse(config.CONF.OFC.use_ssl)
+        self.assertEqual(None, config.CONF.OFC.key_file)
+        self.assertEqual(None, config.CONF.OFC.cert_file)
diff --git a/quantum/plugins/nec/tests/unit/test_db.py b/quantum/plugins/nec/tests/unit/test_db.py
new file mode 100644 (file)
index 0000000..d531e3f
--- /dev/null
@@ -0,0 +1,141 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import random
+import unittest
+
+from quantum.common import utils
+from quantum.plugins.nec.common import exceptions as nexc
+from quantum.plugins.nec.db import api as ndb
+from quantum.plugins.nec.db import models as nmodels
+
+
+class NECPluginV2DBTest(unittest.TestCase):
+    """Class conisting of NECPluginV2 DB unit tests"""
+
+    def setUp(self):
+        """Setup for tests"""
+        ndb.initialize()
+
+    def tearDown(self):
+        """Tear Down"""
+        ndb.clear_db()
+
+    def get_ofc_item_random_params(self):
+        """create random parameters for ofc_item test"""
+        ofc_id = utils.str_uuid()
+        quantum_id = utils.str_uuid()
+        none = utils.str_uuid()
+        return ofc_id, quantum_id, none
+
+    def testa_add_ofc_item(self):
+        """test add OFC item"""
+        o, q, n = self.get_ofc_item_random_params()
+        tenant = ndb.add_ofc_item(nmodels.OFCTenant, o, q)
+        self.assertEqual(tenant.id, o)
+        self.assertEqual(tenant.quantum_id, q)
+
+        exception_raised = False
+        try:
+            ndb.add_ofc_item(nmodels.OFCTenant, o, q)
+        except nexc.NECDBException:
+            exception_raised = True
+        self.assertTrue(exception_raised)
+
+    def testb_get_ofc_item(self):
+        """test get OFC item"""
+        o, q, n = self.get_ofc_item_random_params()
+        ndb.add_ofc_item(nmodels.OFCTenant, o, q)
+        tenant = ndb.get_ofc_item(nmodels.OFCTenant, o)
+        self.assertEqual(tenant.id, o)
+        self.assertEqual(tenant.quantum_id, q)
+
+        tenant_none = ndb.get_ofc_item(nmodels.OFCTenant, n)
+        self.assertEqual(None, tenant_none)
+
+    def testc_find_ofc_item(self):
+        """test find OFC item"""
+        o, q, n = self.get_ofc_item_random_params()
+        ndb.add_ofc_item(nmodels.OFCTenant, o, q)
+        tenant = ndb.find_ofc_item(nmodels.OFCTenant, q)
+        self.assertEqual(tenant.id, o)
+        self.assertEqual(tenant.quantum_id, q)
+
+        tenant_none = ndb.find_ofc_item(nmodels.OFCTenant, n)
+        self.assertEqual(None, tenant_none)
+
+    def testc_del_ofc_item(self):
+        """test delete OFC item"""
+        o, q, n = self.get_ofc_item_random_params()
+        ndb.add_ofc_item(nmodels.OFCTenant, o, q)
+        ndb.del_ofc_item(nmodels.OFCTenant, o)
+
+        tenant_none = ndb.get_ofc_item(nmodels.OFCTenant, q)
+        self.assertEqual(None, tenant_none)
+        tenant_none = ndb.find_ofc_item(nmodels.OFCTenant, q)
+        self.assertEqual(None, tenant_none)
+
+    def get_portinfo_random_params(self):
+        """create random parameters for portinfo test"""
+        port_id = utils.str_uuid()
+        datapath_id = hex(random.randint(0, 0xffffffff))
+        port_no = random.randint(1, 100)
+        vlan_id = random.randint(0, 4095)
+        mac = ':'.join(["%02x" % random.randint(0, 0xff) for x in range(6)])
+        none = utils.str_uuid()
+        return port_id, datapath_id, port_no, vlan_id, mac, none
+
+    def testd_add_portinfo(self):
+        """test add portinfo"""
+        i, d, p, v, m, n = self.get_portinfo_random_params()
+        portinfo = ndb.add_portinfo(i, d, p, v, m)
+        self.assertEqual(portinfo.id, i)
+        self.assertEqual(portinfo.datapath_id, d)
+        self.assertEqual(portinfo.port_no, p)
+        self.assertEqual(portinfo.vlan_id, v)
+        self.assertEqual(portinfo.mac, m)
+
+        exception_raised = False
+        try:
+            ndb.add_portinfo(i, d, p, v, m)
+        except nexc.NECDBException:
+            exception_raised = True
+        self.assertTrue(exception_raised)
+
+    def teste_get_portinfo(self):
+        """test get portinfo"""
+        i, d, p, v, m, n = self.get_portinfo_random_params()
+        ndb.add_portinfo(i, d, p, v, m)
+        portinfo = ndb.get_portinfo(i)
+        self.assertEqual(portinfo.id, i)
+        self.assertEqual(portinfo.datapath_id, d)
+        self.assertEqual(portinfo.port_no, p)
+        self.assertEqual(portinfo.vlan_id, v)
+        self.assertEqual(portinfo.mac, m)
+
+        portinfo_none = ndb.get_portinfo(n)
+        self.assertEqual(None, portinfo_none)
+
+    def testf_del_portinfo(self):
+        """test delete portinfo"""
+        i, d, p, v, m, n = self.get_portinfo_random_params()
+        ndb.add_portinfo(i, d, p, v, m)
+        portinfo = ndb.get_portinfo(i)
+        self.assertEqual(portinfo.id, i)
+        ndb.del_portinfo(i)
+        portinfo_none = ndb.get_portinfo(i)
+        self.assertEqual(None, portinfo_none)
diff --git a/quantum/plugins/nec/tests/unit/test_nec_plugin_v2.py b/quantum/plugins/nec/tests/unit/test_nec_plugin_v2.py
new file mode 100644 (file)
index 0000000..66fada4
--- /dev/null
@@ -0,0 +1,62 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import quantum.openstack.common.rpc
+from quantum.plugins.nec.common import config
+from quantum.tests.unit import test_db_plugin
+
+
+class NECPluginTestBase(object):
+
+    def setUp(self):
+        # Make sure at each test a new instance of the plugin is returned
+        test_db_plugin.QuantumManager._instance = None
+
+        self._tenant_id = 'test-tenant'
+
+        json_deserializer = test_db_plugin.JSONDeserializer()
+        self._deserializers = {
+            'application/json': json_deserializer,
+        }
+
+        plugin = 'quantum.plugins.nec.nec_plugin.NECPluginV2'
+        config.CONF.set_override('core_plugin', plugin)
+        driver = "quantum.plugins.nec.tests.unit.stub_ofc_driver.StubOFCDriver"
+        config.CONF.set_override('driver', driver, 'OFC')
+        config.CONF.set_override('rpc_backend',
+                                 'quantum.openstack.common.rpc.impl_fake')
+        self.api = test_db_plugin.APIRouter()
+        self._skip_native_bulk = False
+
+
+class TestPortsV2(NECPluginTestBase, test_db_plugin.TestPortsV2):
+    pass
+
+
+class TestNetworksV2(NECPluginTestBase, test_db_plugin.TestNetworksV2):
+    pass
+
+
+# NOTE: This plugin does not override methods for subnet.
+#class TestSubnetsV2(NECPluginTestBase, test_db_plugin.TestSubnetsV2):
+#    pass
+
+
+# TODO(r-mibu): write UT for packet_filters.
+class TestPacketFiltersV2(NECPluginTestBase,
+                          test_db_plugin.QuantumDbPluginV2TestCase):
+    pass
diff --git a/quantum/plugins/nec/tests/unit/test_ofc_manager.py b/quantum/plugins/nec/tests/unit/test_ofc_manager.py
new file mode 100644 (file)
index 0000000..0eb2b79
--- /dev/null
@@ -0,0 +1,160 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import unittest
+
+from quantum.common import utils
+from quantum.plugins.nec.common import config
+from quantum.plugins.nec.db import api as ndb
+from quantum.plugins.nec.db import models as nmodels
+from quantum.plugins.nec import ofc_manager
+
+
+class OFCManagerTest(unittest.TestCase):
+    """Class conisting of OFCManager unit tests"""
+
+    def setUp(self):
+        driver = "quantum.plugins.nec.tests.unit.stub_ofc_driver.StubOFCDriver"
+        config.CONF.set_override('driver', driver, 'OFC')
+        ndb.initialize()
+        self.ofc = ofc_manager.OFCManager()
+
+    def tearDown(self):
+        ndb.clear_db()
+
+    def get_random_params(self):
+        """create random parameters for portinfo test"""
+        tenant = utils.str_uuid()
+        network = utils.str_uuid()
+        port = utils.str_uuid()
+        _filter = utils.str_uuid()
+        none = utils.str_uuid()
+        return tenant, network, port, _filter, none
+
+    def testa_create_ofc_tenant(self):
+        """test create ofc_tenant"""
+        t, n, p, f, none = self.get_random_params()
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCTenant, t))
+        self.ofc.create_ofc_tenant(t)
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCTenant, t))
+        tenant = ndb.find_ofc_item(nmodels.OFCTenant, t)
+        self.assertEqual(tenant.id, "ofc-" + t[:-4])
+
+    def testb_exists_ofc_tenant(self):
+        """test exists_ofc_tenant"""
+        t, n, p, f, none = self.get_random_params()
+        self.assertFalse(self.ofc.exists_ofc_tenant(t))
+        self.ofc.create_ofc_tenant(t)
+        self.assertTrue(self.ofc.exists_ofc_tenant(t))
+
+    def testc_delete_ofc_tenant(self):
+        """test delete ofc_tenant"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCTenant, t))
+        self.ofc.delete_ofc_tenant(t)
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCTenant, t))
+
+    def testd_create_ofc_network(self):
+        """test create ofc_network"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCNetwork, n))
+        self.ofc.create_ofc_network(t, n)
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCNetwork, n))
+        network = ndb.find_ofc_item(nmodels.OFCNetwork, n)
+        self.assertEqual(network.id, "ofc-" + n[:-4])
+
+    def teste_exists_ofc_network(self):
+        """test exists_ofc_network"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.assertFalse(self.ofc.exists_ofc_network(n))
+        self.ofc.create_ofc_network(t, n)
+        self.assertTrue(self.ofc.exists_ofc_network(n))
+
+    def testf_delete_ofc_network(self):
+        """test delete ofc_network"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCNetwork, n))
+        self.ofc.delete_ofc_network(t, n)
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCNetwork, n))
+
+    def testg_create_ofc_port(self):
+        """test create ofc_port"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        ndb.add_portinfo(p, "0xabc", 1, 65535, "00:11:22:33:44:55")
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCPort, p))
+        self.ofc.create_ofc_port(t, n, p)
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCPort, p))
+        port = ndb.find_ofc_item(nmodels.OFCPort, p)
+        self.assertEqual(port.id, "ofc-" + p[:-4])
+
+    def testh_exists_ofc_port(self):
+        """test exists_ofc_port"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        ndb.add_portinfo(p, "0xabc", 2, 65535, "00:12:22:33:44:55")
+        self.assertFalse(self.ofc.exists_ofc_port(p))
+        self.ofc.create_ofc_port(t, n, p)
+        self.assertTrue(self.ofc.exists_ofc_port(p))
+
+    def testi_delete_ofc_port(self):
+        """test delete ofc_port"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        ndb.add_portinfo(p, "0xabc", 3, 65535, "00:13:22:33:44:55")
+        self.ofc.create_ofc_port(t, n, p)
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCPort, p))
+        self.ofc.delete_ofc_port(t, n, p)
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCPort, p))
+
+    def testj_create_ofc_packet_filter(self):
+        """test create ofc_filter"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCFilter, f))
+        self.ofc.create_ofc_packet_filter(t, n, f, {})
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCFilter, f))
+        _filter = ndb.find_ofc_item(nmodels.OFCFilter, f)
+        self.assertEqual(_filter.id, "ofc-" + f[:-4])
+
+    def testk_exists_ofc_packet_filter(self):
+        """test exists_ofc_packet_filter"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        self.assertFalse(self.ofc.exists_ofc_packet_filter(f))
+        self.ofc.create_ofc_packet_filter(t, n, f, {})
+        self.assertTrue(self.ofc.exists_ofc_packet_filter(f))
+
+    def testl_delete_ofc_packet_filter(self):
+        """test delete ofc_filter"""
+        t, n, p, f, none = self.get_random_params()
+        self.ofc.create_ofc_tenant(t)
+        self.ofc.create_ofc_network(t, n)
+        self.ofc.create_ofc_packet_filter(t, n, f, {})
+        self.assertTrue(ndb.find_ofc_item(nmodels.OFCFilter, f))
+        self.ofc.delete_ofc_packet_filter(t, n, f)
+        self.assertFalse(ndb.find_ofc_item(nmodels.OFCFilter, f))
diff --git a/quantum/plugins/nec/tests/unit/test_pfc_driver.py b/quantum/plugins/nec/tests/unit/test_pfc_driver.py
new file mode 100644 (file)
index 0000000..eb2f064
--- /dev/null
@@ -0,0 +1,158 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import mox
+import unittest
+
+from quantum.common import utils
+from quantum.plugins.nec import drivers
+from quantum.plugins.nec.db import models as nmodels
+from quantum.plugins.nec.common import ofc_client as ofc
+
+
+class TestConfig(object):
+    """Configuration for this test"""
+    host = '127.0.0.1'
+    port = 8888
+    use_ssl = False
+    key_file = None
+    cert_file = None
+
+
+def _ofc(id):
+    """OFC ID converter"""
+    return "ofc-%s" % id
+
+
+class PFCDriverTestBase(unittest.TestCase):
+
+    def setUp(self):
+        self.mox = mox.Mox()
+        self.driver = drivers.get_driver("pfc")(TestConfig)
+        self.mox.StubOutWithMock(ofc.OFCClient, 'do_request')
+
+    def tearDown(self):
+        self.mox.UnsetStubs()
+
+    def get_ofc_item_random_params(self):
+        """create random parameters for ofc_item test"""
+        tenant_id = utils.str_uuid()
+        network_id = utils.str_uuid()
+        port_id = utils.str_uuid()
+        portinfo = nmodels.PortInfo(id=port_id, datapath_id="0x123456789",
+                                    port_no=1234, vlan_id=321,
+                                    mac="11:22:33:44:55:66")
+        return tenant_id, network_id, portinfo
+
+    def testa_create_tenant(self):
+        t, n, p = self.get_ofc_item_random_params()
+        description = "desc of %s" % t
+
+        path = "/tenants"
+        body = {'id': t, 'description': description}
+        tenant = {'id': _ofc(t)}
+        ofc.OFCClient.do_request("POST", path, body=body).AndReturn(tenant)
+        self.mox.ReplayAll()
+
+        ret = self.driver.create_tenant(description, t)
+        self.mox.VerifyAll()
+        self.assertEqual(ret, _ofc(t))
+
+    def testb_update_tenant(self):
+        t, n, p = self.get_ofc_item_random_params()
+        description = "new desc of %s" % t
+
+        path = "/tenants/%s" % _ofc(t)
+        body = {'description': description}
+        ofc.OFCClient.do_request("PUT", path, body=body)
+        self.mox.ReplayAll()
+
+        self.driver.update_tenant(_ofc(t), description)
+        self.mox.VerifyAll()
+
+    def testc_delete_tenant(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        path = "/tenants/%s" % _ofc(t)
+        ofc.OFCClient.do_request("DELETE", path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_tenant(_ofc(t))
+        self.mox.VerifyAll()
+
+    def testd_create_network(self):
+        t, n, p = self.get_ofc_item_random_params()
+        description = "desc of %s" % n
+
+        path = "/tenants/%s/networks" % _ofc(t)
+        body = {'id': n, 'description': description}
+        network = {'id': _ofc(n)}
+        ofc.OFCClient.do_request("POST", path, body=body).AndReturn(network)
+        self.mox.ReplayAll()
+
+        ret = self.driver.create_network(_ofc(t), description, n)
+        self.mox.VerifyAll()
+        self.assertEqual(ret, _ofc(n))
+
+    def teste_update_network(self):
+        t, n, p = self.get_ofc_item_random_params()
+        description = "desc of %s" % n
+
+        path = "/tenants/%s/networks/%s" % (_ofc(t), _ofc(n))
+        body = {'description': description}
+        ofc.OFCClient.do_request("PUT", path, body=body)
+        self.mox.ReplayAll()
+
+        self.driver.update_network(_ofc(t), _ofc(n), description)
+        self.mox.VerifyAll()
+
+    def testf_delete_network(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        path = "/tenants/%s/networks/%s" % (_ofc(t), _ofc(n))
+        ofc.OFCClient.do_request("DELETE", path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_network(_ofc(t), _ofc(n))
+        self.mox.VerifyAll()
+
+    def testg_create_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        path = "/tenants/%s/networks/%s/ports" % (_ofc(t), _ofc(n))
+        body = {'id': p.id,
+                'datapath_id': p.datapath_id,
+                'port': str(p.port_no),
+                'vid': str(p.vlan_id)}
+        port = {'id': _ofc(p.id)}
+        ofc.OFCClient.do_request("POST", path, body=body).AndReturn(port)
+        self.mox.ReplayAll()
+
+        ret = self.driver.create_port(_ofc(t), _ofc(n), p, p.id)
+        self.mox.VerifyAll()
+        self.assertEqual(ret, _ofc(p.id))
+
+    def testh_delete_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        path = "/tenants/%s/networks/%s/ports/%s" % (_ofc(t), _ofc(n),
+                                                     _ofc(p.id))
+        ofc.OFCClient.do_request("DELETE", path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_port(_ofc(t), _ofc(n), _ofc(p.id))
+        self.mox.VerifyAll()
diff --git a/quantum/plugins/nec/tests/unit/test_trema_driver.py b/quantum/plugins/nec/tests/unit/test_trema_driver.py
new file mode 100644 (file)
index 0000000..2929386
--- /dev/null
@@ -0,0 +1,236 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NEC Corporation.  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: Ryota MIBU
+
+import mox
+import unittest
+
+from quantum.common import utils
+from quantum.plugins.nec import drivers
+from quantum.plugins.nec.db import models as nmodels
+from quantum.plugins.nec.common import ofc_client
+
+
+class TestConfig(object):
+    """Configuration for this test"""
+    host = '127.0.0.1'
+    port = 8888
+
+
+class TremaDriverTestBase():
+
+    driver_name = "trema"
+
+    def setUp(self):
+        self.mox = mox.Mox()
+        self.driver = drivers.get_driver(self.driver_name)(TestConfig)
+        self.mox.StubOutWithMock(ofc_client.OFCClient, 'do_request')
+
+    def tearDown(self):
+        self.mox.UnsetStubs()
+
+    def get_ofc_item_random_params(self):
+        """create random parameters for ofc_item test"""
+        tenant_id = utils.str_uuid()
+        network_id = utils.str_uuid()
+        port_id = utils.str_uuid()
+        portinfo = nmodels.PortInfo(id=port_id, datapath_id="0x123456789",
+                                    port_no=1234, vlan_id=321,
+                                    mac="11:22:33:44:55:66")
+        return tenant_id, network_id, portinfo
+
+
+class TremaDriverNetworkTestBase(TremaDriverTestBase):
+
+    def testa_create_network(self):
+        t, n, p = self.get_ofc_item_random_params()
+        description = "desc of %s" % n
+
+        body = {'id': n, 'description': description}
+        ofc_client.OFCClient.do_request("POST", "/networks", body=body)
+        self.mox.ReplayAll()
+
+        self.driver.create_network(t, description, n)
+        self.mox.VerifyAll()
+
+    def testb_update_network(self):
+        t, n, p = self.get_ofc_item_random_params()
+        description = "desc of %s" % n
+
+        body = {'description': description}
+        ofc_client.OFCClient.do_request("PUT", "/networks/%s" % n, body=body)
+        self.mox.ReplayAll()
+
+        self.driver.update_network(t, n, description)
+        self.mox.VerifyAll()
+
+    def testc_delete_network(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        ofc_client.OFCClient.do_request("DELETE", "/networks/%s" % n)
+        self.mox.ReplayAll()
+
+        self.driver.delete_network(t, n)
+        self.mox.VerifyAll()
+
+
+class TremaPortBaseDriverTest(TremaDriverNetworkTestBase, unittest.TestCase):
+
+    driver_name = "trema_port"
+
+    def testd_create_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        body = {'id': p.id,
+                'datapath_id': p.datapath_id,
+                'port': str(p.port_no),
+                'vid': str(p.vlan_id)}
+        ofc_client.OFCClient.do_request("POST",
+                                        "/networks/%s/ports" % n, body=body)
+        self.mox.ReplayAll()
+
+        self.driver.create_port(t, n, p, p.id)
+        self.mox.VerifyAll()
+
+    def testd_delete_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        ofc_client.OFCClient.do_request("DELETE",
+                                        "/networks/%s/ports/%s" % (n, p.id))
+        self.mox.ReplayAll()
+
+        self.driver.delete_port(t, n, p.id)
+        self.mox.VerifyAll()
+
+
+class TremaPortMACBaseDriverTest(TremaDriverNetworkTestBase,
+                                 unittest.TestCase):
+
+    driver_name = "trema_portmac"
+
+    def testd_create_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+        dummy_port = "dummy-%s" % p.id
+
+        path_1 = "/networks/%s/ports" % n
+        body_1 = {'id': dummy_port,
+                  'datapath_id': p.datapath_id,
+                  'port': str(p.port_no),
+                  'vid': str(p.vlan_id)}
+        ofc_client.OFCClient.do_request("POST", path_1, body=body_1)
+        path_2 = "/networks/%s/ports/%s/attachments" % (n, dummy_port)
+        body_2 = {'id': p.id, 'mac': p.mac}
+        ofc_client.OFCClient.do_request("POST", path_2, body=body_2)
+        path_3 = "/networks/%s/ports/%s" % (n, dummy_port)
+        ofc_client.OFCClient.do_request("DELETE", path_3)
+        self.mox.ReplayAll()
+
+        self.driver.create_port(t, n, p, p.id)
+        self.mox.VerifyAll()
+
+    def testd_delete_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+        dummy_port = "dummy-%s" % p.id
+
+        path = "/networks/%s/ports/%s/attachments/%s" % (n, dummy_port, p.id)
+        ofc_client.OFCClient.do_request("DELETE", path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_port(t, n, p.id)
+        self.mox.VerifyAll()
+
+
+class TremaMACBaseDriverTest(TremaDriverNetworkTestBase, unittest.TestCase):
+
+    driver_name = "trema_mac"
+
+    def testd_create_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        path = "/networks/%s/attachments" % n
+        body = {'id': p.id, 'mac': p.mac}
+        ofc_client.OFCClient.do_request("POST", path, body=body)
+        self.mox.ReplayAll()
+
+        self.driver.create_port(t, n, p, p.id)
+        self.mox.VerifyAll()
+
+    def testd_delete_port(self):
+        t, n, p = self.get_ofc_item_random_params()
+
+        path = "/networks/%s/attachments/%s" % (n, p.id)
+        ofc_client.OFCClient.do_request("DELETE", path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_port(t, n, p.id)
+        self.mox.VerifyAll()
+
+
+class TremaFilterDriverTest(TremaDriverTestBase, unittest.TestCase):
+
+    def get_ofc_item_random_params(self):
+        """create random parameters for ofc_item test"""
+        t, n, p = (super(TremaFilterDriverTest, self).
+                   get_ofc_item_random_params())
+        filter_id = utils.str_uuid()
+        filter_dict = {'tenant_id': t,
+                       'id': filter_id,
+                       'network_id': n,
+                       'priority': 123,
+                       'action': "ACCEPT",
+                       'in_port': p.id,
+                       'src_mac': p.mac,
+                       'dst_mac': "",
+                       'eth_type': 0,
+                       'src_cidr': "",
+                       'dst_cidr': "",
+                       'src_port': 0,
+                       'dst_port': 0,
+                       'protocol': "TCP",
+                       'admin_state_up': True,
+                       'status': "ACTIVE"}
+        filter_item = nmodels.PacketFilter(**filter_dict)
+        return t, n, p, filter_item
+
+    def testa_create_filter(self):
+        t, n, p, f = self.get_ofc_item_random_params()
+
+        ofp_wildcards = 'dl_vlan,dl_vlan_pcp,nw_tos,dl_dst,' + \
+                        'nw_src:32,nw_dst:32,tp_src,tp_dst'
+        body = {'id': f.id,
+                'action': 'ALLOW',
+                'priority': 123,
+                'slice': n,
+                'in_datapath_id': '0x123456789',
+                'in_port': 1234,
+                'nw_proto': '0x6',
+                'dl_type': '0x800',
+                'dl_src': p.mac,
+                'ofp_wildcards': ofp_wildcards}
+        ofc_client.OFCClient.do_request("POST", "/filters", body=body)
+        self.mox.ReplayAll()
+
+        self.driver.create_filter(t, n, f, p, f.id)
+        self.mox.VerifyAll()
+
+    def testb_delete_filter(self):
+        t, n, p, f = self.get_ofc_item_random_params()
+
+        ofc_client.OFCClient.do_request("DELETE", "/filters/%s" % f.id)
+        self.mox.ReplayAll()
+
+        self.driver.delete_filter(t, n, f.id)
+        self.mox.VerifyAll()
index 78238fd6f5819f920bbbd361624fe4466f4d1333..143c2c6c9113528ec390cb2b099b7c12037da296 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -49,6 +49,7 @@ linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
 nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
 ryu_plugin_config_path = 'etc/quantum/plugins/ryu'
 meta_plugin_config_path = 'etc/quantum/plugins/metaplugin'
+nec_plugin_config_path = 'etc/quantum/plugins/nec'
 
 DataFiles = [
     (config_path,
@@ -72,7 +73,8 @@ DataFiles = [
         ['etc/quantum/plugins/nicira/nvp.ini']),
     (ryu_plugin_config_path, ['etc/quantum/plugins/ryu/ryu.ini']),
     (meta_plugin_config_path,
-        ['etc/quantum/plugins/metaplugin/metaplugin.ini'])
+        ['etc/quantum/plugins/metaplugin/metaplugin.ini']),
+    (nec_plugin_config_path, ['etc/quantum/plugins/nec/nec.ini']),
 ]
 
 setuptools.setup(
@@ -102,6 +104,8 @@ setuptools.setup(
             'quantum.plugins.openvswitch.agent.ovs_quantum_agent:main',
             'quantum-ryu-agent = '
             'quantum.plugins.ryu.agent.ryu_quantum_agent:main',
+            'quantum-nec-agent = '
+            'quantum.plugins.nec.agent.nec_quantum_agent:main',
             'quantum-server = quantum.server:main',
         ]
     },