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