--- /dev/null
+# 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
--- /dev/null
+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
--- /dev/null
+# 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.
--- /dev/null
+# 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.
--- /dev/null
+#!/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()
--- /dev/null
+# 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.
--- /dev/null
+# 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
--- /dev/null
+# 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")
--- /dev/null
+# 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)
--- /dev/null
+# 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.
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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.
--- /dev/null
+# 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)]
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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)
--- /dev/null
+#!/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)
--- /dev/null
+# 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.
--- /dev/null
+# 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.
--- /dev/null
+# 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
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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))
--- /dev/null
+# 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()
--- /dev/null
+# 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()
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,
['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(
'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',
]
},