From 89a574196783f93e32d81244f37f726be11bbf30 Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Fri, 30 Oct 2015 09:20:45 -0700 Subject: New ML2/L3 plugin to support SFC Change-Id: Ie778a2b2e09a29972e28d70c8eedee407b1d8eb6 Signed-off-by: Ashlee Young --- .../networking-onos/networking_onos/__init__.py | 19 ++ .../networking_onos/common/__init__.py | 0 .../networking_onos/common/config.py | 31 +++ .../networking_onos/common/utils.py | 44 ++++ .../networking_onos/plugins/__init__.py | 0 .../networking_onos/plugins/l3/README | 29 +++ .../networking_onos/plugins/l3/__init__.py | 0 .../networking_onos/plugins/l3/driver.py | 126 +++++++++++ .../networking_onos/plugins/l3/floating_ip.py | 40 ++++ .../networking_onos/plugins/l3/router.py | 74 +++++++ .../networking_onos/plugins/ml2/README | 33 +++ .../networking_onos/plugins/ml2/__init__.py | 0 .../networking_onos/plugins/ml2/driver.py | 140 ++++++++++++ .../networking_onos/tests/__init__.py | 0 .../networking_onos/tests/unit/__init__.py | 0 .../networking_onos/tests/unit/plugins/__init__.py | 0 .../tests/unit/plugins/l3/__init__.py | 0 .../tests/unit/plugins/l3/test_driver.py | 230 ++++++++++++++++++++ .../tests/unit/plugins/ml2/__init__.py | 0 .../tests/unit/plugins/ml2/test_driver.py | 237 +++++++++++++++++++++ 20 files changed, 1003 insertions(+) create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/config.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/utils.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/README create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/driver.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/floating_ip.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/router.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/README create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/driver.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/test_driver.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/__init__.py create mode 100644 framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/test_driver.py (limited to 'framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos') diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/__init__.py new file mode 100644 index 00000000..d0b36107 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import pbr.version + + +__version__ = pbr.version.VersionInfo( + 'networking-onos').version_string() diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/config.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/config.py new file mode 100644 index 00000000..8dc72286 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/config.py @@ -0,0 +1,31 @@ +# Copyright (c) 2015 Huawei Technologies India Pvt Ltd +# 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. + +from oslo_config import cfg + +ONOS_DRIVER_OPTS = [ + cfg.StrOpt('url_path', + default='', + help=_('ONOS ReST interface URL')), + cfg.StrOpt('username', + default='', + help=_('Username for authentication.')), + cfg.StrOpt('password', + default='', + secret=True, # do not expose value in the logs + help=_('Password for authentication.')) +] + +cfg.CONF.register_opts(ONOS_DRIVER_OPTS, "onos") diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/utils.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/utils.py new file mode 100644 index 00000000..49477149 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/common/utils.py @@ -0,0 +1,44 @@ +# Copyright (c) 2015 Huawei Technologies India Pvt Ltd +# 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. + +from oslo_log import log as logging +from oslo_serialization import jsonutils +import requests + + +LOG = logging.getLogger(__name__) + + +def send_msg(onos_path, onos_auth, msg_type, entity_path, entity=None): + """Send message to the ONOS controller.""" + + path = '/'.join([onos_path, entity_path]) + hdr = {'Content-Type': 'application/json'} + body = jsonutils.dumps(entity, indent=2) if entity else None + LOG.debug("Sending MSG_TYPE (%(msg)s) URL (%(path)s) " + "OBJECT (%(entity)s) BODY (%(body)s)", + {'msg': msg_type, 'path': path, + 'entity': entity, 'body': body}) + req = requests.request(method=msg_type, url=path, + headers=hdr, data=body, + auth=onos_auth) + # Let's raise voice for an error + req.raise_for_status() + + +def safe_delete_from_dict(dict, keys): + """Ignore key errors when deleting from a dictionary.""" + for key in keys: + dict.pop(key, None) diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/README b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/README new file mode 100644 index 00000000..04ca224b --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/README @@ -0,0 +1,29 @@ +Open Networking Operating System (ONOS) L3 Plugin +================================================= +ONOS is a carrier grade SDN open operating system designed for +High Availability, scale-out and better performance. + + http://www.onosproject.org/ + +Mode of Working: +================ +networking-onos.plugins.l3 define onos plug-in for supporting neutron's router +functionality. This shim layer makes the communication between ONOS and +Openstack neutron possible via ReST call. +The driver code can be downloaded from: + + https://git.openstack.org/cgit/openstack/networking-onos + +Using ONOS L3 Plugin +==================== +To use ONOS L3 Plugin one should +1. Make sure networking-onos code is downloaded and installed. If doing + mannually then download the code, go inside networking_onos folder + and finally run "sudo python setup.py install" otherwise download the + required package version from "https://pypi.python.org/pypi/networking-onos/" + and install using pip. + +2. Configure ONOS credentials in networking_onos/etc/conf_onos.ini. + +3. Start neutron server mentioning networking_onos/etc/conf_onos.ini as + one of the config-file with ONOS L3 Plugin support. diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/driver.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/driver.py new file mode 100644 index 00000000..2db3ad35 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/driver.py @@ -0,0 +1,126 @@ +# Copyright (C) 2015 Huawei Technologies India Pvt Ltd. +# 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. +# + +from oslo_config import cfg + +from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api +from neutron.api.rpc.handlers import l3_rpc +from neutron.common import constants as q_const +from neutron.common import rpc as n_rpc +from neutron.common import topics +from neutron.db import db_base_plugin_v2 +from neutron.db import extraroute_db +from neutron.db import l3_agentschedulers_db +from neutron.db import l3_gwmode_db +from neutron.plugins.common import constants + +from networking_onos.common import config # noqa +from networking_onos.plugins.l3 import floating_ip as onos_fip +from networking_onos.plugins.l3 import router as onos_router + + +class ONOSL3Plugin(db_base_plugin_v2.NeutronDbPluginV2, + extraroute_db.ExtraRoute_db_mixin, + l3_gwmode_db.L3_NAT_db_mixin, + l3_agentschedulers_db.L3AgentSchedulerDbMixin, + onos_router.ONOSRouter, + onos_fip.ONOSFloatingIP): + + """Implementation of the ONOS L3 Router Service Plugin. + + This class implements a L3 service plugin that provides + router and floatingip resources and manages associated + request/response. + """ + supported_extension_aliases = ["router", "ext-gw-mode", "extraroute"] + + def __init__(self): + self.setup_rpc() + self.onos_path = cfg.CONF.onos.url_path + self.onos_auth = (cfg.CONF.onos.username, cfg.CONF.onos.password) + + def setup_rpc(self): + self.topic = topics.L3PLUGIN + self.conn = n_rpc.create_connection(new=True) + self.agent_notifiers.update( + {q_const.AGENT_TYPE_L3: l3_rpc_agent_api.L3AgentNotifyAPI()}) + self.endpoints = [l3_rpc.L3RpcCallback()] + self.conn.create_consumer(self.topic, self.endpoints, fanout=False) + self.conn.consume_in_threads() + + def get_plugin_type(self): + return constants.L3_ROUTER_NAT + + def get_plugin_description(self): + """returns plug-in description""" + return ("L3 Router Service Plug-in for basic L3 forwarding using ONOS") + + def create_router(self, context, router): + router_dict = super(ONOSL3Plugin, self).create_router(context, router) + self.handle_create_router(router_dict) + return router_dict + + def update_router(self, context, id, router): + router_dict = super(ONOSL3Plugin, self).update_router(context, id, + router) + self.handle_update_router(router_dict, id) + return router_dict + + def delete_router(self, context, id): + super(ONOSL3Plugin, self).delete_router(context, id) + self.handle_delete_router(id) + + def create_floatingip(self, context, floatingip, + initial_status=q_const.FLOATINGIP_STATUS_ACTIVE): + fip_dict = super(ONOSL3Plugin, self).create_floatingip(context, + floatingip, + initial_status) + self.handle_create_floatingip(fip_dict) + return fip_dict + + def update_floatingip(self, context, id, floatingip): + fip_dict = super(ONOSL3Plugin, self).update_floatingip(context, id, + floatingip) + self.handle_update_floatingip(id, fip_dict) + return fip_dict + + def delete_floatingip(self, context, id): + super(ONOSL3Plugin, self).delete_floatingip(context, id) + self.handle_delete_floatingip(id) + + def add_router_interface(self, context, router_id, interface_info): + router = super(ONOSL3Plugin, self).add_router_interface(context, + router_id, + interface_info) + intf_add_type = self._get_intf_add_type(router, interface_info) + self.handle_add_router_interface(router, router_id, + interface_info, intf_add_type) + return router + + def remove_router_interface(self, context, router_id, interface_info): + router = super(ONOSL3Plugin, self).remove_router_interface( + context, router_id, interface_info) + intf_add_type = self._get_intf_add_type(router, interface_info) + self.handle_remove_router_interface(router, router_id, + interface_info, intf_add_type) + return router + + def _get_intf_add_type(self, router_info, intf_info): + add_by_port, add_by_sub = self._validate_interface_info(intf_info) + if add_by_sub: + return onos_router.ADD_INTF_BY_SUBNET + + return onos_router.ADD_INTF_BY_PORT diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/floating_ip.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/floating_ip.py new file mode 100644 index 00000000..0748724e --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/floating_ip.py @@ -0,0 +1,40 @@ +# Copyright (C) 2015 Huawei Technologies India Pvt Ltd. +# +# 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. +# + +from networking_onos.common import utils as onos_utils + + +class ONOSFloatingIP(object): + + """Implementation of ONOS L3 Floating IP Service. + + This class sends Neutron's L3 Floating IP messages to ONOS. + """ + def send_floatingip_msg(self, msg_type, entity_path, entity): + onos_utils.send_msg(self.onos_path, self.onos_auth, + msg_type, entity_path, entity) + + def handle_create_floatingip(self, fip_dict): + self.send_floatingip_msg('post', 'floatingips', + {'floatingip': fip_dict}) + + def handle_update_floatingip(self, id, fip_dict): + url_path = 'floatingips' + '/' + id + self.send_floatingip_msg('put', url_path, + {'floatingip': fip_dict}) + + def handle_delete_floatingip(self, id): + url_path = 'floatingips' + '/' + id + self.send_floatingip_msg('delete', url_path, None) diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/router.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/router.py new file mode 100644 index 00000000..495dacd6 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/l3/router.py @@ -0,0 +1,74 @@ +# Copyright (C) 2015 Huawei Technologies India Pvt Ltd. +# +# 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. +# + +from networking_onos.common import utils as onos_utils + +ADD_INTF_BY_PORT = 1 +ADD_INTF_BY_SUBNET = 2 + + +class ONOSRouter(object): + + """Implementation of ONOS L3 Router Service. + + This class sends Neutron's L3 router messages to ONOS. + """ + def send_router_msg(self, msg_type, entity_path, entity): + onos_utils.send_msg(self.onos_path, self.onos_auth, + msg_type, entity_path, entity) + + def handle_create_router(self, router_dict): + self.send_router_msg('post', 'routers', + {'router': router_dict}) + + def handle_update_router(self, router_dict, id): + url_path = 'routers' + '/' + id + resource = router_dict.copy() + onos_utils.safe_delete_from_dict(resource, + ['id', 'tenant_id', 'status']) + self.send_router_msg('put', url_path, {'router': resource}) + + def handle_delete_router(self, id): + url_path = 'routers' + '/' + id + self.send_router_msg('delete', url_path, None) + + def handle_add_router_interface(self, new_router, router_id, + interface_info, intf_add_type): + url_path = 'routers' + '/' + router_id + '/add_router_interface' + router_dict = self._prepare_router_dict(router_id, interface_info, + new_router, intf_add_type) + self.send_router_msg('put', url_path, router_dict) + + def handle_remove_router_interface(self, new_router, router_id, + interface_info, intf_add_type): + url_path = 'routers' + '/' + router_id + '/remove_router_interface' + router_dict = self._prepare_router_dict(router_id, interface_info, + new_router, intf_add_type) + self.send_router_msg('put', url_path, router_dict) + + def _prepare_router_dict(self, router_id, interface_info, + new_router, add_type): + if add_type == ADD_INTF_BY_SUBNET: + _port_id = new_router['port_id'] + _subnet_id = interface_info['subnet_id'] + else: + _port_id = interface_info['port_id'] + _subnet_id = new_router['subnet_id'] + + router_dict = {'subnet_id': _subnet_id, + 'port_id': _port_id, + 'id': router_id, + 'tenant_id': new_router['tenant_id']} + return router_dict diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/README b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/README new file mode 100644 index 00000000..c3c722c8 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/README @@ -0,0 +1,33 @@ +Open Networking Operating System (ONOS) ML2 MechanismDriver +========================================================== +ONOS is a carrier grade SDN open operating system designed for +High Availability, scale-out and better performance. + + http://www.onosproject.org/ + +Mode of Working: +================ +The networking-onos project provides a thin layer which makes the +communication between ONOS and OpenStack neutron possible via ReST +call. The driver code can be downloaded from: + + https://git.openstack.org/cgit/openstack/networking-onos + +Using ONOS ML2 MechanismDriver +============================== +To use ONOS ML2 MechanismDriver one should +1. Make sure networking-onos code is downloaded and installed. If doing + mannually then download the code, go inside networking_onos folder + and finally run "sudo python setup.py install" otherwise download the + required package version from "https://pypi.python.org/pypi/networking-onos/" + and install using pip. + +2. Configure ONOS as the required ML2 "mechanism_drivers" in + neutron/plugins/ml2/ml2_conf.ini: + + mechanism_drivers=onos_ml2 + +3. Configure ONOS credentials in networking_onos/etc/conf_onos.ini. + +4. Start neutron server mentioning networking_onos/etc/conf_onos.ini as + one of the config-file. diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/driver.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/driver.py new file mode 100644 index 00000000..b78775f4 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/plugins/ml2/driver.py @@ -0,0 +1,140 @@ +# Copyright (c) 2015 Huawei Technologies India Pvt Ltd +# 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. + +from oslo_config import cfg +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from neutron.common import constants as n_const +from neutron.extensions import portbindings +from neutron.plugins.common import constants +from neutron.plugins.ml2 import driver_api as api + +from networking_onos.common import config # noqa +from networking_onos.common import utils as onos_utils + +LOG = logging.getLogger(__name__) + + +class ONOSMechanismDriver(api.MechanismDriver): + + """Open Networking Operating System ML2 Driver for Neutron. + + Code which makes communication between ONOS and OpenStack Neutron + possible. + """ + def __init__(self): + self.onos_path = cfg.CONF.onos.url_path + self.onos_auth = (cfg.CONF.onos.username, cfg.CONF.onos.password) + self.vif_type = portbindings.VIF_TYPE_OVS + self.vif_details = {portbindings.CAP_PORT_FILTER: True} + + def initialize(self): + # No action required as of now. Can be extended in + # the future if required. + pass + + @log_helpers.log_method_call + def create_network_postcommit(self, context): + entity_path = 'networks' + resource = context.current.copy() + onos_utils.send_msg(self.onos_path, self.onos_auth, 'post', + entity_path, {'network': resource}) + + @log_helpers.log_method_call + def update_network_postcommit(self, context): + entity_path = 'networks/' + context.current['id'] + resource = context.current.copy() + onos_utils.send_msg(self.onos_path, self.onos_auth, 'put', + entity_path, {'network': resource}) + + @log_helpers.log_method_call + def delete_network_postcommit(self, context): + entity_path = 'networks/' + context.current['id'] + onos_utils.send_msg(self.onos_path, self.onos_auth, 'delete', + entity_path) + + @log_helpers.log_method_call + def create_subnet_postcommit(self, context): + entity_path = 'subnets' + resource = context.current.copy() + onos_utils.send_msg(self.onos_path, self.onos_auth, 'post', + entity_path, {'subnet': resource}) + + @log_helpers.log_method_call + def update_subnet_postcommit(self, context): + entity_path = 'subnets/' + context.current['id'] + resource = context.current.copy() + onos_utils.send_msg(self.onos_path, self.onos_auth, 'put', + entity_path, {'subnet': resource}) + + @log_helpers.log_method_call + def delete_subnet_postcommit(self, context): + entity_path = 'subnets/' + context.current['id'] + onos_utils.send_msg(self.onos_path, self.onos_auth, 'delete', + entity_path) + + @log_helpers.log_method_call + def create_port_postcommit(self, context): + entity_path = 'ports' + resource = context.current.copy() + onos_utils.send_msg(self.onos_path, self.onos_auth, 'post', + entity_path, {'port': resource}) + + @log_helpers.log_method_call + def update_port_postcommit(self, context): + entity_path = 'ports/' + context.current['id'] + resource = context.current.copy() + onos_utils.send_msg(self.onos_path, self.onos_auth, 'put', + entity_path, {'port': resource}) + + @log_helpers.log_method_call + def delete_port_postcommit(self, context): + entity_path = 'ports/' + context.current['id'] + onos_utils.send_msg(self.onos_path, self.onos_auth, 'delete', + entity_path) + + @log_helpers.log_method_call + def bind_port(self, context): + """Set port binding data for use with nova.""" + LOG.debug("Attempting to bind port %(port)s on network %(network)s", + {'port': context.current['id'], + 'network': context.network.current['id']}) + # Prepared porting binding data + for segment in context.segments_to_bind: + if self.check_segment(segment): + context.set_binding(segment[api.ID], + self.vif_type, + self.vif_details, + status=n_const.PORT_STATUS_ACTIVE) + LOG.debug("Port bound successful for segment: %s", segment) + return + else: + LOG.debug("Port bound un-successfult for segment ID %(id)s, " + "segment %(seg)s, phys net %(physnet)s, and " + "network type %(nettype)s", + {'id': segment[api.ID], + 'seg': segment[api.SEGMENTATION_ID], + 'physnet': segment[api.PHYSICAL_NETWORK], + 'nettype': segment[api.NETWORK_TYPE]}) + + @log_helpers.log_method_call + def check_segment(self, segment): + """Check whether segment is valid for the ONOS MechanismDriver.""" + + return segment[api.NETWORK_TYPE] in [constants.TYPE_LOCAL, + constants.TYPE_GRE, + constants.TYPE_VXLAN, + constants.TYPE_VLAN] diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/test_driver.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/test_driver.py new file mode 100644 index 00000000..c0bba85a --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/l3/test_driver.py @@ -0,0 +1,230 @@ +# Copyright (C) 2015 Huawei Technologies India Pvt Ltd. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from oslotest import base + +from neutron.extensions import l3 +from neutron.tests.unit.api.v2 import test_base +from neutron.tests.unit.extensions import base as test_neutron_extensions +from webob import exc + +import networking_onos.plugins.l3.driver as onos_driver + +fake_tenant_id = '048aa98a3ec345dc8b14427c81e276cf' + +fake_router_uuid = '292f7967-c5e7-47d8-8265-dc2160678b75' +fake_router_object = {'router': {'name': 'router_abc', + 'external_gateway_info': None, + 'admin_state_up': True, + 'tenant_id': fake_tenant_id}} + +fake_network_id = '7464aaf0-27ea-448a-97df-51732f9e0e27' +fake_router_external_info = {'external_gateway_info': + {'network_id': fake_network_id, + 'enable_snat': False}} + +fake_floating_ip_id = '7464aaf0-27ea-448a-97df-51732f9e0e25' +fake_floating_ip = {'floatingip': + {'fixed_ip_address': '10.1.1.1', + 'id': fake_floating_ip_id, + 'router_id': fake_router_uuid, + 'port_id': None, + 'status': None, + 'tenant_id': fake_tenant_id}} + +fake_port_id = '7db560e9-76d4-4bf9-9c28-43efa7afa45d' +fake_subnet_id = 'dc2b8071-c24c-4a8e-b471-dbf3fbe55830' +fake_port = {'id': fake_port_id, + 'network_id': fake_network_id, + 'fixed_ips': [{'ip_address': '21.41.4.5', + 'prefixlen': 28, + 'subnet_id': fake_subnet_id}], + 'subnets': [{'id': fake_subnet_id, + 'cidr': '21.41.4.0/28', + 'gateway_ip': '21.41.4.1'}]} + +fake_floating_ip_update_info = {'floating_network_id': fake_network_id, + 'tenant_id': fake_tenant_id, + 'fixed_ip_address': '20.1.1.11', + 'subnet_id': fake_port['subnets'][0]['id'], + 'port_id': fake_port_id, + 'floating_ip_address': '198.1.2.3'} + +fake_interface_add = {'subnet_id': fake_subnet_id} + +fake_interface_remove = {'subnet_id': fake_subnet_id, + 'port_id': fake_port_id} + + +class ONOSL3PluginTestCase(base.BaseTestCase, + test_neutron_extensions.ExtensionTestCase, + onos_driver.ONOSL3Plugin): + + def setUp(self): + super(ONOSL3PluginTestCase, self).setUp() + self._setUpExtension( + 'neutron.extensions.l3.RouterPluginBase', None, + l3.RESOURCE_ATTRIBUTE_MAP, l3.L3, None, + allow_pagination=True, allow_sorting=True, + supported_extension_aliases=['router'], + use_quota=True) + self.instance = self.plugin.return_value + + def _mock_req_res(self, status_code): + response = mock.Mock(status_code=status_code) + response.raise_for_status = mock.Mock() + return response + + def _test_send_msg(self, dict_info, oper_type, url): + if oper_type == 'post': + resp = self.api.post(url, self.serialize(dict_info)) + elif oper_type == 'put': + resp = self.api.put(url, self.serialize(dict_info)) + else: + resp = self.api.delete(url) + return resp + + def test_create_router(self): + router_info = copy.deepcopy(fake_router_object['router']) + router_info.update({'status': 'ACTIVE', 'id': fake_router_uuid}) + self.instance.create_router.return_value = router_info + self.instance.get_routers_count.return_value = 0 + url = test_base._get_path('routers', fmt=self.fmt) + resp = self._test_send_msg(fake_router_object, 'post', url) + self.instance.create_router.\ + assert_called_once_with(mock.ANY, router=fake_router_object) + self._verify_resp(resp, exc.HTTPCreated.code, + 'router', fake_router_uuid) + + def test_update_router(self): + router_info = copy.deepcopy(fake_router_object['router']) + router_info.update(fake_router_external_info) + router_info.update({'status': 'ACTIVE', 'id': fake_router_uuid}) + self.instance.update_router.return_value = router_info + router_request = {'router': fake_router_external_info} + url = test_base._get_path('routers', id=fake_router_uuid, fmt=self.fmt) + resp = self._test_send_msg(router_request, 'put', url) + self.instance.update_router.\ + assert_called_once_with(mock.ANY, fake_router_uuid, + router=router_request) + self._verify_resp(resp, exc.HTTPOk.code, 'router', fake_router_uuid) + + def test_delete_router(self): + url = test_base._get_path('routers', id=fake_router_uuid, fmt=self.fmt) + resp = self._test_send_msg(None, 'delete', url) + self.instance.delete_router.assert_called_once_with(mock.ANY, + fake_router_uuid) + self.assertEqual(resp.status_int, exc.HTTPNoContent.code) + + def test_create_floating_ip(self): + floatingip_info = copy.deepcopy(fake_floating_ip['floatingip']) + floatingip_info.update(fake_floating_ip_update_info) + floatingip_info.update({'status': 'ACTIVE', 'fixed_ip_address': None}) + + self.instance.create_floatingip.return_value = floatingip_info + self.instance.get_floatingips_count.return_value = 0 + self.instance.get_port = mock.Mock(return_value=fake_port) + + floating_ip_request = {'floatingip': fake_floating_ip_update_info} + url = test_base._get_path('floatingips', fmt=self.fmt) + resp = self._test_send_msg(floating_ip_request, 'post', url) + self.instance.create_floatingip.\ + assert_called_once_with(mock.ANY, + floatingip=floating_ip_request) + self._verify_resp(resp, exc.HTTPCreated.code, + 'floatingip', fake_floating_ip_id) + + def test_update_floating_ip(self): + fake_floating_ip_update_info = {'port_id': None} + floatingip_info = copy.deepcopy(fake_floating_ip['floatingip']) + floatingip_info.update(fake_floating_ip_update_info) + floatingip_info.update({'status': 'ACTIVE', + 'tenant_id': fake_tenant_id, + 'floating_network_id': fake_network_id, + 'fixed_ip_address': None, + 'floating_ip_address': '172.24.4.228'}) + + self.instance.update_floatingip.return_value = floatingip_info + self.instance.get_port = mock.Mock(return_value=fake_port) + floating_ip_request = {'floatingip': fake_floating_ip_update_info} + url = test_base._get_path('floatingips', + id=fake_floating_ip_id, fmt=self.fmt) + resp = self._test_send_msg(floating_ip_request, 'put', url) + self.instance.update_floatingip.\ + assert_called_once_with(mock.ANY, + fake_floating_ip_id, + floatingip=floating_ip_request) + self._verify_resp(resp, exc.HTTPOk.code, + 'floatingip', fake_floating_ip_id) + + def test_delete_floating_ip(self): + self.instance.get_port = mock.Mock(return_value=fake_port) + url = test_base._get_path('floatingips', id=fake_floating_ip_id) + resp = self._test_send_msg(None, 'delete', url) + self.instance.delete_floatingip.\ + assert_called_once_with(mock.ANY, fake_floating_ip_id) + self.assertEqual(resp.status_int, exc.HTTPNoContent.code) + + def test_add_router_interface(self): + interface_info = {'tenant_id': fake_tenant_id, + 'port_id': fake_port_id, + 'id': fake_router_uuid} + interface_info.update(fake_interface_add) + self.instance.add_router_interface.return_value = interface_info + url = test_base._get_path('routers', id=fake_router_uuid, + action='add_router_interface', + fmt=self.fmt) + resp = self._test_send_msg(fake_interface_add, 'put', url) + self.instance.add_router_interface.\ + assert_called_once_with(mock.ANY, fake_router_uuid, + fake_interface_add) + self._verify_resp(resp, exc.HTTPOk.code, None, fake_router_uuid) + + def test_remove_router_interface(self): + interface_info = {'tenant_id': fake_tenant_id, + 'id': fake_router_uuid} + interface_info.update(fake_interface_remove) + self.instance.remove_router_interface.return_value = interface_info + url = test_base._get_path('routers', id=fake_router_uuid, + action='remove_router_interface', + fmt=self.fmt) + resp = self._test_send_msg(fake_interface_remove, 'put', url) + self.instance.remove_router_interface.\ + assert_called_once_with(mock.ANY, fake_router_uuid, + fake_interface_remove) + self._verify_resp(resp, exc.HTTPOk.code, None, fake_router_uuid) + + def _verify_resp(self, resp, return_code, context, id): + self.assertEqual(resp.status_int, return_code) + resp = self.deserialize(resp) + + if context is None: + self.assertEqual(resp['id'], id) + self.assertEqual(resp['subnet_id'], fake_subnet_id) + return + + self.assertIn(context, resp) + resource = resp[context] + self.assertEqual(resource['id'], id) + if context == 'router': + self.assertEqual(resource['status'], 'ACTIVE') + self.assertEqual(resource['admin_state_up'], True) + elif context == 'floatingip': + self.assertEqual(resource['status'], 'ACTIVE') + self.assertEqual(resource['fixed_ip_address'], None) diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/__init__.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/test_driver.py b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/test_driver.py new file mode 100644 index 00000000..89969239 --- /dev/null +++ b/framework/src/openstack/neutron/plugin/networking-onos/networking-onos/networking_onos/tests/unit/plugins/ml2/test_driver.py @@ -0,0 +1,237 @@ +# Copyright (c) 2015 Huawei Technologies India Pvt Ltd +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +import requests + +from oslo_config import cfg +from oslo_serialization import jsonutils +from oslotest import base + +from neutron.common import constants as n_const +from neutron.plugins.common import constants +from neutron.plugins.ml2 import driver_api as api +from neutron.plugins.ml2 import driver_context as ctx + +import networking_onos.plugins.ml2.driver as onos_ml2_driver + + +fake_network_uuid = 'd897e21a-dfd6-4331-a5dd-7524fa421c3e' +fake_network_object = {'status': 'ACTIVE', + 'subnets': [], + 'name': 'net1', + 'provider:physical_network': None, + 'admin_state_up': True, + 'tenant_id': 'test-tenant', + 'provider:network_type': 'local', + 'router:external': False, + 'shared': False, + 'id': fake_network_uuid, + 'provider:segmentation_id': None} + +fake_subnet_uuid = 'd897e21a-dfd6-4331-a5dd-7524fa421c3e' +fake_subnet_object = {'ipv6_ra_mode': None, + 'allocation_pools': [{'start': '10.0.0.2', + 'end': '10.0.1.254'}], + 'host_routes': [], + 'ipv6_address_mode': None, + 'cidr': '10.0.0.0/23', + 'id': fake_subnet_uuid, + 'name': '', + 'enable_dhcp': True, + 'network_id': fake_network_uuid, + 'tenant_id': 'test-tenant', + 'dns_nameservers': [], + 'gateway_ip': '10.0.0.1', + 'ip_version': 4, + 'shared': False} + +fake_port_uuid = '72c56c48-e9b8-4dcf-b3a7-0813bb3bd839' +fake_port_object = {'status': 'DOWN', + 'binding:host_id': '', + 'allowed_address_pairs': [], + 'device_owner': 'fake_owner', + 'binding:profile': {}, + 'fixed_ips': [], + 'id': fake_port_uuid, + 'security_groups': + ['2f9244b4-9bee-4e81-bc4a-3f3c2045b3d7'], + 'device_id': 'fake_device', + 'name': '', + 'admin_state_up': True, + 'network_id': fake_network_uuid, + 'tenant_id': 'test-tenant', + 'binding:vif_details': {}, + 'binding:vnic_type': 'normal', + 'binding:vif_type': 'unbound', + 'mac_address': '12:34:56 :78:21:b6'} + + +class ONOSMechanismDriverTestCase(base.BaseTestCase, + onos_ml2_driver.ONOSMechanismDriver): + + def setUp(self): + super(ONOSMechanismDriverTestCase, self).setUp() + self.set_test_config() + + def set_test_config(self): + cfg.CONF.set_override('url_path', 'http://127.0.0.1:1111', 'onos') + cfg.CONF.set_override('username', 'onos_user', 'onos') + cfg.CONF.set_override('password', 'awesome', 'onos') + self.onos_path = cfg.CONF.onos.url_path + self.onos_auth = (cfg.CONF.onos.username, + cfg.CONF.onos.password) + + def _mock_req_resp(self, status_code): + response = mock.Mock(status_code=status_code) + response.raise_for_status = mock.Mock() + return response + + def _test_response(self, context, oper_type, obj_type, mock_method): + body = None + if oper_type is not 'delete': + entity = {obj_type: context.current.copy()} + body = jsonutils.dumps(entity, indent=2) + if oper_type == 'post': + url = '%s/%s' % (self.onos_path, obj_type + 's') + else: + url = '%s/%s/%s' % (self.onos_path, obj_type + 's', + context.current['id']) + kwargs = {'url': url, 'data': body} + mock_method.assert_called_once_with( + method=oper_type, + headers={'Content-Type': 'application/json'}, + auth=self.onos_auth, **kwargs) + + def test_create_network_postcommit(self): + context = mock.Mock(current=fake_network_object) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.create_network_postcommit(context) + self._test_response(context, 'post', 'network', mock_method) + + def test_update_network_postcommit(self): + context = mock.Mock(current=fake_network_object) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.update_network_postcommit(context) + self._test_response(context, 'put', 'network', mock_method) + + def test_delete_network_postcommit(self): + context = mock.Mock(current={'id': fake_network_uuid}) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.delete_network_postcommit(context) + self._test_response(context, 'delete', 'network', mock_method) + + def test_create_subnet_postcommit(self): + context = mock.Mock(current=fake_subnet_object) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.create_subnet_postcommit(context) + self._test_response(context, 'post', 'subnet', mock_method) + + def test_update_subnet_postcommit(self): + context = mock.Mock(current=fake_subnet_object) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.update_subnet_postcommit(context) + self._test_response(context, 'put', 'subnet', mock_method) + + def test_delete_subnet_postcommit(self): + context = mock.Mock(current={'id': fake_subnet_uuid}) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.delete_subnet_postcommit(context) + self._test_response(context, 'delete', 'subnet', mock_method) + + def test_create_port_postcommit(self): + context = mock.Mock(current=fake_port_object) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.create_port_postcommit(context) + self._test_response(context, 'post', 'port', mock_method) + + def test_update_port_postcommit(self): + context = mock.Mock(current=fake_port_object) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.update_port_postcommit(context) + self._test_response(context, 'put', 'port', mock_method) + + def test_delete_port_postcommit(self): + context = mock.Mock(current={'id': fake_port_uuid}) + resp = self._mock_req_resp(requests.codes.created) + with mock.patch('requests.request', + return_value=resp) as mock_method: + self.delete_port_postcommit(context) + self._test_response(context, 'delete', 'port', mock_method) + + # given valid and invalid segments + valid_segment = { + api.ID: 'API_ID', + api.NETWORK_TYPE: constants.TYPE_LOCAL, + api.SEGMENTATION_ID: 'API_SEGMENTATION_ID', + api.PHYSICAL_NETWORK: 'API_PHYSICAL_NETWORK'} + + invalid_segment = { + api.ID: 'API_ID', + api.NETWORK_TYPE: constants.TYPE_NONE, + api.SEGMENTATION_ID: 'API_SEGMENTATION_ID', + api.PHYSICAL_NETWORK: 'API_PHYSICAL_NETWORK'} + + def test_check_segment(self): + """Validate the check_segment method.""" + + # given driver and all network types + all_network_types = [constants.TYPE_FLAT, constants.TYPE_GRE, + constants.TYPE_LOCAL, constants.TYPE_VXLAN, + constants.TYPE_VLAN, constants.TYPE_NONE] + + # when checking segments network type + valid_types = {network_type + for network_type in all_network_types + if self.check_segment({api.NETWORK_TYPE: network_type})} + + # then true is returned only for valid network types + self.assertEqual({constants.TYPE_LOCAL, constants.TYPE_GRE, + constants.TYPE_VXLAN, constants.TYPE_VLAN}, + valid_types) + + def test_bind_port(self): + self.vif_type = "MY_VIF_TYPE" + self.vif_details = "MY_VIF_DETAILS" + network = mock.MagicMock(spec=api.NetworkContext) + port_context = mock.MagicMock( + spec=ctx.PortContext, current={'id': 'CURRENT_CONTEXT_ID'}, + segments_to_bind=[self.valid_segment, self.invalid_segment], + network=network) + + # when port is bound + self.bind_port(port_context) + + # then context binding is setup with returned vif_type and valid + # segment api ID + port_context.set_binding.assert_called_once_with( + self.valid_segment[api.ID], self.vif_type, + self.vif_details, status=n_const.PORT_STATUS_ACTIVE) -- cgit 1.2.3-korg