Merge "One Convergence Neutron Plugin Implementation"

This commit is contained in:
Jenkins 2014-03-06 21:03:26 +00:00 committed by Gerrit Code Review
commit 005fec677c
23 changed files with 1269 additions and 2 deletions

View File

@ -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 = <ip address of nvsd controller>
# Port number of NVSD controller api server
# nvsd_port = 8082
# Authentication credentials to access the api server
# nvsd_user = <nvsd controller username>
# nvsd_passwd = <password>
# API request timeout in seconds
# request_timeout = <default 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:<passwd>@127.0.0.1/<neutron_db>?charset=utf8

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'],
]

View File

@ -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.

View File

@ -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")

View File

@ -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")

View File

@ -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()

View File

@ -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,
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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