PLUMgrid plugin v2

This commit implements blueprint plumgrid-plugin-v2

Includes PLUMlib Library
Fake PLUMLib library for Unit Tests
Remove the use of topologies
Includes IOVISOR VIF driver
Implements External Networks

Change-Id: I8ba90862e5a416d04d3327b46fcb0f6f3fa65248
This commit is contained in:
Edgar Magana 2013-07-10 15:11:16 -07:00
parent ca421e7e62
commit 744ffcf141
14 changed files with 807 additions and 502 deletions

View File

@ -1,16 +1,13 @@
# Config file for Neutron PLUMgrid plugin
# Config file for Neutron PLUMgrid Plugin
[plumgridnos]
# This line should be pointing to the NOS server,
# for the PLUMgrid platform. In other deployments,
# this is known as controller
# nos_server=<nos-ip-address>
# nos_server_port=<nos-port>
# Authentification parameters for the NOS server.
[PLUMgridDirector]
# This line should be pointing to the PLUMgrid Director,
# for the PLUMgrid platform.
# director_server=<director-ip-address>
# director_server_port=<director-port>
# Authentification parameters for the Director.
# These are the admin credentials to manage and control
# the NOS server.
# username=<nos-admin-username>
# password=<nos-admin-password>
# the PLUMgrid Director server.
# username=<director-admin-username>
# password=<director-admin-password>
# servertimeout=5
# Name of the network topology to be deployed by NOS
# topologyname=<nos-topology-name>

View File

@ -37,6 +37,7 @@ CAP_PORT_FILTER = 'port_filter'
VIF_TYPE_UNBOUND = 'unbound'
VIF_TYPE_BINDING_FAILED = 'binding_failed'
VIF_TYPE_IOVISOR = 'iovisor'
VIF_TYPE_OVS = 'ovs'
VIF_TYPE_IVS = 'ivs'
VIF_TYPE_BRIDGE = 'bridge'

View File

@ -1,7 +1,8 @@
PLUMgrid Neutron Virtual Network Plugin
PLUMgrid Neutron Plugin for Virtual Network Infrastructure (VNI)
This plugin implements Neutron v2 APIs and helps configure
L2/L3 virtual networks consisting of PLUMgrid Platform.
Implements External Networks and Port Binding Extension
For more details on use please refer to:
http://wiki.openstack.org/plumgrid-neutron
http://wiki.openstack.org/PLUMgrid-Neutron

View File

@ -28,4 +28,4 @@ class PLUMgridException(base_exec.NeutronException):
class PLUMgridConnectionFailed(PLUMgridException):
message = _("Connection failed with PLUMgrid NOS: %(err_msg)s")
message = _("Connection failed with PLUMgrid Director: %(err_msg)s")

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.

View File

@ -0,0 +1,79 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class Plumlib():
"""
Class PLUMgrid Fake Library. This library is a by-pass implementation
for the PLUMgrid Library. This class is being used by the unit test
integration in Neutron.
"""
def __init__(self):
LOG.info('Python PLUMgrid Fake Library Started ')
pass
def director_conn(self, director_plumgrid, director_port, timeout):
LOG.info('Fake Director: %s', director_plumgrid + ':' + director_port)
pass
def create_network(self, tenant_id, net_db):
pass
def update_network(self, tenant_id, net_id):
pass
def delete_network(self, net_db, net_id):
pass
def create_subnet(self, sub_db, net_db, ipnet):
pass
def update_subnet(self, org_sub_db, new_sub_db, ipnet):
pass
def delete_subnet(self, tenant_id, net_db, net_id):
pass
def create_port(self, port_db, router_db):
pass
def update_port(self, port_db, router_db):
pass
def delete_port(self, port_db, router_db):
pass
def create_router(self, tenant_id, router_db):
pass
def update_router(self, router_db, router_id):
pass
def delete_router(self, tenant_id, router_id):
pass
def add_router_interface(self, tenant_id, router_id, port_db, ipnet):
pass
def remove_router_interface(self, tenant_id, net_id, router_id):
pass

View File

@ -0,0 +1,85 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
"""
Neutron Plug-in for PLUMgrid Virtual Networking Infrastructure (VNI)
This plugin will forward authenticated REST API calls
to the PLUMgrid Network Management System called Director
"""
from plumgridlib import plumlib
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class Plumlib(object):
"""
Class PLUMgrid Python Library. This library is a third-party tool
needed by PLUMgrid plugin to implement all core API in Neutron.
"""
def __init__(self):
LOG.info('Python PLUMgrid Library Started ')
def director_conn(self, director_plumgrid, director_port, timeout):
self.plumlib = plumlib.Plumlib(director_plumgrid,
director_port,
timeout)
def create_network(self, tenant_id, net_db):
self.plumlib.create_network(tenant_id, net_db)
def update_network(self, tenant_id, net_id):
self.plumlib.update_network(tenant_id, net_id)
def delete_network(self, net_db, net_id):
self.plumlib.delete_network(net_db, net_id)
def create_subnet(self, sub_db, net_db, ipnet):
self.plumlib.create_subnet(sub_db, net_db, ipnet)
def update_subnet(self, org_sub_db, new_sub_db, ipnet):
self.plumlib.update_subnet(org_sub_db, new_sub_db, ipnet)
def delete_subnet(self, tenant_id, net_db, net_id):
self.plumlib.delete_subnet(tenant_id, net_db, net_id)
def create_port(self, port_db, router_db):
self.plumlib.create_port(port_db, router_db)
def update_port(self, port_db, router_db):
self.plumlib.update_port(port_db, router_db)
def delete_port(self, port_db, router_db):
self.plumlib.delete_port(port_db, router_db)
def create_router(self, tenant_id, router_db):
self.plumlib.create_router(tenant_id, router_db)
def update_router(self, router_db, router_id):
self.plumlib.update_router(router_db, router_id)
def delete_router(self, tenant_id, router_id):
self.plumlib.delete_router(tenant_id, router_id)
def add_router_interface(self, tenant_id, router_id, port_db, ipnet):
self.plumlib.add_router_interface(tenant_id, router_id, port_db, ipnet)
def remove_router_interface(self, tenant_id, net_id, router_id):
self.plumlib.remove_router_interface(tenant_id, net_id, router_id)

View File

@ -1,44 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
# @author: Brenden Blanco, bblanco@plumgrid.com, PLUMgrid, Inc.
"""
Snippets needed by the PLUMgrid Plugin
"""
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class DataNOSPLUMgrid():
BASE_NOS_URL = '/0/connectivity/domain/'
def __init__(self):
LOG.info(_('NeutronPluginPLUMgrid Status: NOS Body Data Creation'))
def create_domain_body_data(self, tenant_id):
body_data = {"container_group": tenant_id}
return body_data
def create_network_body_data(self, tenant_id, topology_name):
body_data = {"config_template": "single_bridge",
"container_group": tenant_id,
"topology_name": topology_name}
return body_data

View File

@ -1,325 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
"""
Neutron PLUMgrid Plug-in for PLUMgrid Virtual Technology
This plugin will forward authenticated REST API calls
to the Network Operating System by PLUMgrid called NOS
"""
from oslo.config import cfg
from neutron.db import api as db
from neutron.db import db_base_plugin_v2
from neutron.openstack.common import log as logging
from neutron.plugins.plumgrid.common import exceptions as plum_excep
from neutron.plugins.plumgrid.plumgrid_nos_plugin.plugin_ver import VERSION
from neutron.plugins.plumgrid.plumgrid_nos_plugin import plumgrid_nos_snippets
from neutron.plugins.plumgrid.plumgrid_nos_plugin import rest_connection
LOG = logging.getLogger(__name__)
nos_server_opts = [
cfg.StrOpt('nos_server', default='localhost',
help=_("PLUMgrid NOS server to connect to")),
cfg.StrOpt('nos_server_port', default='8080',
help=_("PLUMgrid NOS server port to connect to")),
cfg.StrOpt('username', default='username',
help=_("PLUMgrid NOS admin username")),
cfg.StrOpt('password', default='password', secret=True,
help=_("PLUMgrid NOS admin password")),
cfg.IntOpt('servertimeout', default=5,
help=_("PLUMgrid NOS server timeout")),
cfg.StrOpt('topologyname', default='t1',
help=_("PLUMgrid NOS topology name")), ]
cfg.CONF.register_opts(nos_server_opts, "PLUMgridNOS")
class NeutronPluginPLUMgridV2(db_base_plugin_v2.NeutronDbPluginV2):
def __init__(self):
LOG.info(_('NeutronPluginPLUMgrid Status: Starting Plugin'))
# PLUMgrid NOS configuration
nos_plumgrid = cfg.CONF.PLUMgridNOS.nos_server
nos_port = cfg.CONF.PLUMgridNOS.nos_server_port
timeout = cfg.CONF.PLUMgridNOS.servertimeout
self.topology_name = cfg.CONF.PLUMgridNOS.topologyname
self.snippets = plumgrid_nos_snippets.DataNOSPLUMgrid()
# TODO(Edgar) These are placeholders for next PLUMgrid release
cfg.CONF.PLUMgridNOS.username
cfg.CONF.PLUMgridNOS.password
self.rest_conn = rest_connection.RestConnection(nos_plumgrid,
nos_port, timeout)
if self.rest_conn is None:
raise SystemExit(_('NeutronPluginPLUMgrid Status: '
'Aborting Plugin'))
else:
# Plugin DB initialization
db.configure_db()
# PLUMgrid NOS info validation
LOG.info(_('NeutronPluginPLUMgrid NOS: %s'), nos_plumgrid)
if not nos_plumgrid:
raise SystemExit(_('NeutronPluginPLUMgrid Status: '
'NOS value is missing in config file'))
LOG.debug(_('NeutronPluginPLUMgrid Status: Neutron server with '
'PLUMgrid Plugin has started'))
def create_network(self, context, network):
"""Create network core Neutron API."""
LOG.debug(_('NeutronPluginPLUMgrid Status: create_network() called'))
# Plugin DB - Network Create and validation
tenant_id = self._get_tenant_id_for_create(context,
network["network"])
self._network_admin_state(network)
with context.session.begin(subtransactions=True):
net = super(NeutronPluginPLUMgridV2, self).create_network(context,
network)
try:
LOG.debug(_('NeutronPluginPLUMgrid Status: %(tenant_id)s, '
'%(network)s, %(network_id)s'),
dict(
tenant_id=tenant_id,
network=network["network"],
network_id=net["id"],
))
nos_url = self.snippets.BASE_NOS_URL + net["id"]
headers = {}
body_data = self.snippets.create_domain_body_data(tenant_id)
self.rest_conn.nos_rest_conn(nos_url,
'PUT', body_data, headers)
except Exception:
err_message = _("PLUMgrid NOS communication failed")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
# return created network
return net
def update_network(self, context, net_id, network):
"""Update network core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgridV2.update_network() called"))
self._network_admin_state(network)
tenant_id = self._get_tenant_id_for_create(context, network["network"])
with context.session.begin(subtransactions=True):
# Plugin DB - Network Update
new_network = super(
NeutronPluginPLUMgridV2, self).update_network(context,
net_id, network)
try:
# PLUMgrid Server does not support updating resources yet
nos_url = self.snippets.BASE_NOS_URL + net_id
headers = {}
body_data = {}
self.rest_conn.nos_rest_conn(nos_url,
'DELETE', body_data, headers)
nos_url = self.snippets.BASE_NOS_URL + new_network["id"]
body_data = self.snippets.create_domain_body_data(tenant_id)
self.rest_conn.nos_rest_conn(nos_url,
'PUT', body_data, headers)
except Exception:
err_message = _("PLUMgrid NOS communication failed")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
# return updated network
return new_network
def delete_network(self, context, net_id):
"""Delete network core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgrid Status: delete_network() called"))
super(NeutronPluginPLUMgridV2, self).get_network(context, net_id)
with context.session.begin(subtransactions=True):
# Plugin DB - Network Delete
super(NeutronPluginPLUMgridV2, self).delete_network(context,
net_id)
try:
nos_url = self.snippets.BASE_NOS_URL + net_id
headers = {}
body_data = {}
self.rest_conn.nos_rest_conn(nos_url,
'DELETE', body_data, headers)
except Exception:
err_message = _("PLUMgrid NOS communication failed")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
def create_port(self, context, port):
"""Create port core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgrid Status: create_port() called"))
# Port operations on PLUMgrid NOS is an automatic operation from the
# VIF driver operations in Nova. It requires admin_state_up to be True
port["port"]["admin_state_up"] = True
# Plugin DB - Port Create and Return port
return super(NeutronPluginPLUMgridV2, self).create_port(context,
port)
def update_port(self, context, port_id, port):
"""Update port core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgrid Status: update_port() called"))
# Port operations on PLUMgrid NOS is an automatic operation from the
# VIF driver operations in Nova.
# Plugin DB - Port Update
return super(NeutronPluginPLUMgridV2, self).update_port(
context, port_id, port)
def delete_port(self, context, port_id):
"""Delete port core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgrid Status: delete_port() called"))
# Port operations on PLUMgrid NOS is an automatic operation from the
# VIF driver operations in Nova.
# Plugin DB - Port Delete
super(NeutronPluginPLUMgridV2, self).delete_port(context, port_id)
def create_subnet(self, context, subnet):
"""Create subnet core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgrid Status: create_subnet() called"))
with context.session.begin(subtransactions=True):
# Plugin DB - Subnet Create
subnet = super(NeutronPluginPLUMgridV2, self).create_subnet(
context, subnet)
subnet_details = self._get_subnet(context, subnet["id"])
net_id = subnet_details["network_id"]
tenant_id = subnet_details["tenant_id"]
try:
nos_url = self.snippets.BASE_NOS_URL + net_id
headers = {}
body_data = self.snippets.create_network_body_data(
tenant_id, self.topology_name)
self.rest_conn.nos_rest_conn(nos_url,
'PUT', body_data, headers)
except Exception:
err_message = _("PLUMgrid NOS communication failed: ")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
return subnet
def delete_subnet(self, context, subnet_id):
"""Delete subnet core Neutron API."""
LOG.debug(_("NeutronPluginPLUMgrid Status: delete_subnet() called"))
#Collecting subnet info
subnet_details = self._get_subnet(context, subnet_id)
with context.session.begin(subtransactions=True):
# Plugin DB - Subnet Delete
del_subnet = super(NeutronPluginPLUMgridV2, self).delete_subnet(
context, subnet_id)
try:
headers = {}
body_data = {}
net_id = subnet_details["network_id"]
self._cleaning_nos_subnet_structure(body_data, headers, net_id)
except Exception:
err_message = _("PLUMgrid NOS communication failed: ")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
return del_subnet
def update_subnet(self, context, subnet_id, subnet):
"""Update subnet core Neutron API."""
LOG.debug(_("update_subnet() called"))
#Collecting subnet info
initial_subnet = self._get_subnet(context, subnet_id)
net_id = initial_subnet["network_id"]
tenant_id = initial_subnet["tenant_id"]
with context.session.begin(subtransactions=True):
# Plugin DB - Subnet Update
new_subnet = super(NeutronPluginPLUMgridV2, self).update_subnet(
context, subnet_id, subnet)
try:
# PLUMgrid Server does not support updating resources yet
headers = {}
body_data = {}
self._cleaning_nos_subnet_structure(body_data, headers, net_id)
nos_url = self.snippets.BASE_NOS_URL + net_id
body_data = self.snippets.create_network_body_data(
tenant_id, self.topology_name)
self.rest_conn.nos_rest_conn(nos_url,
'PUT', body_data, headers)
except Exception:
err_message = _("PLUMgrid NOS communication failed: ")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
return new_subnet
"""
Extension API implementation
"""
# TODO(Edgar) Complete extensions for PLUMgrid
"""
Internal PLUMgrid fuctions
"""
def _get_plugin_version(self):
return VERSION
def _cleaning_nos_subnet_structure(self, body_data, headers, net_id):
domain_structure = ['/properties', '/link', '/ne']
for structure in domain_structure:
nos_url = self.snippets.BASE_NOS_URL + net_id + structure
self.rest_conn.nos_rest_conn(nos_url, 'DELETE', body_data, headers)
def _network_admin_state(self, network):
try:
if network["network"].get("admin_state_up"):
network_name = network["network"]["name"]
if network["network"]["admin_state_up"] is False:
LOG.warning(_("Network with admin_state_up=False are not "
"supported yet by this plugin. Ignoring "
"setting for network %s"), network_name)
except Exception:
err_message = _("Network Admin State Validation Falied: ")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
return network

View File

@ -1,97 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
# @author: Brenden Blanco, bblanco@plumgrid.com, PLUMgrid, Inc.
"""
Neutron PLUMgrid Plug-in for PLUMgrid Virtual Technology
This plugin will forward authenticated REST API calls
to the Network Operating System by PLUMgrid called NOS
"""
import httplib
import urllib2
from neutron.openstack.common import jsonutils as json
from neutron.openstack.common import log as logging
from neutron.plugins.plumgrid.common import exceptions as plum_excep
LOG = logging.getLogger(__name__)
class RestConnection(object):
"""REST Connection to PLUMgrid NOS Server."""
def __init__(self, server, port, timeout):
LOG.debug(_('NeutronPluginPLUMgrid Status: REST Connection Started'))
self.server = server
self.port = port
self.timeout = timeout
def nos_rest_conn(self, nos_url, action, data, headers):
self.nos_url = nos_url
body_data = json.dumps(data)
if not headers:
headers = {}
headers['Content-type'] = 'application/json'
headers['Accept'] = 'application/json'
LOG.debug(_("PLUMgrid_NOS_Server: %(server)s %(port)s %(action)s"),
dict(server=self.server, port=self.port, action=action))
conn = httplib.HTTPConnection(self.server, self.port,
timeout=self.timeout)
if conn is None:
LOG.error(_('PLUMgrid_NOS_Server: Could not establish HTTP '
'connection'))
return
try:
LOG.debug(_("PLUMgrid_NOS_Server Sending Data: %(nos_url)s "
"%(body_data)s %(headers)s"),
dict(
nos_url=nos_url,
body_data=body_data,
headers=headers,
))
conn.request(action, nos_url, body_data, headers)
resp = conn.getresponse()
resp_str = resp.read()
LOG.debug(_("PLUMgrid_NOS_Server Connection Data: %(resp)s, "
"%(resp_str)s"), dict(resp=resp, resp_str=resp_str))
if resp.status is httplib.OK:
try:
respdata = json.loads(resp_str)
LOG.debug(_("PLUMgrid_NOS_Server Connection RESP: %s"),
respdata)
pass
except ValueError:
err_message = _("PLUMgrid HTTP Connection Failed: ")
LOG.Exception(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
ret = (resp.status, resp.reason, resp_str)
except urllib2.HTTPError:
LOG.error(_('PLUMgrid_NOS_Server: %(action)s failure, %(e)r'))
ret = 0, None, None, None
conn.close()
LOG.debug(_("PLUMgrid_NOS_Server: status=%(status)d, "
"reason=%(reason)r, ret=%(ret)s"),
{'status': ret[0], 'reason': ret[1], 'ret': ret[2]})
return ret

View File

@ -16,4 +16,4 @@
#
# @author: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
VERSION = "0.1"
VERSION = "0.2"

View File

@ -0,0 +1,546 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 PLUMgrid, 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: Edgar Magana, emagana@plumgrid.com, PLUMgrid, Inc.
"""
Neutron Plug-in for PLUMgrid Virtual Networking Infrastructure (VNI)
This plugin will forward authenticated REST API calls
to the PLUMgrid Network Management System called Director
"""
import netaddr
from oslo.config import cfg
from neutron.api.v2 import attributes
from neutron.db import api as db
from neutron.db import db_base_plugin_v2
from neutron.db import l3_db
from neutron.db import portbindings_db
from neutron.extensions import portbindings
from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging
from neutron.plugins.plumgrid.common import exceptions as plum_excep
from neutron.plugins.plumgrid.plumgrid_plugin.plugin_ver import VERSION
from neutron import policy
LOG = logging.getLogger(__name__)
PLUM_DRIVER = 'neutron.plugins.plumgrid.drivers.plumlib.Plumlib'
ERR_MESSAGE = 'PLUMgrid Director communication failed'
director_server_opts = [
cfg.StrOpt('director_server', default='localhost',
help=_("PLUMgrid Director server to connect to")),
cfg.StrOpt('director_server_port', default='8080',
help=_("PLUMgrid Director server port to connect to")),
cfg.StrOpt('username', default='username',
help=_("PLUMgrid Director admin username")),
cfg.StrOpt('password', default='password', secret=True,
help=_("PLUMgrid Director admin password")),
cfg.IntOpt('servertimeout', default=5,
help=_("PLUMgrid Director server timeout")), ]
cfg.CONF.register_opts(director_server_opts, "PLUMgridDirector")
class NeutronPluginPLUMgridV2(db_base_plugin_v2.NeutronDbPluginV2,
portbindings_db.PortBindingMixin,
l3_db.L3_NAT_db_mixin):
supported_extension_aliases = ["router", "binding"]
binding_view = "extension:port_binding:view"
binding_set = "extension:port_binding:set"
def __init__(self):
LOG.info(_('Neutron PLUMgrid Director: Starting Plugin'))
# Plugin DB initialization
db.configure_db()
self.plumgrid_init()
LOG.debug(_('Neutron PLUMgrid Director: Neutron server with '
'PLUMgrid Plugin has started'))
def plumgrid_init(self):
"""PLUMgrid initialization."""
director_plumgrid = cfg.CONF.PLUMgridDirector.director_server
director_port = cfg.CONF.PLUMgridDirector.director_server_port
timeout = cfg.CONF.PLUMgridDirector.servertimeout
# PLUMgrid Director info validation
LOG.info(_('Neutron PLUMgrid Director: %s'), director_plumgrid)
self._plumlib = importutils.import_object(PLUM_DRIVER)
self._plumlib.director_conn(director_plumgrid, director_port, timeout)
def create_network(self, context, network):
"""Create Neutron network.
Creates a PLUMgrid-based bridge.
"""
LOG.debug(_('Neutron PLUMgrid Director: create_network() called'))
# Plugin DB - Network Create and validation
tenant_id = self._get_tenant_id_for_create(context,
network["network"])
self._network_admin_state(network)
with context.session.begin(subtransactions=True):
net_db = super(NeutronPluginPLUMgridV2,
self).create_network(context, network)
# Propagate all L3 data into DB
self._process_l3_create(context, net_db, network['network'])
try:
LOG.debug(_('PLUMgrid Library: create_network() called'))
self._plumlib.create_network(tenant_id, net_db)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
# Return created network
return net_db
def update_network(self, context, net_id, network):
"""Update Neutron network.
Updates a PLUMgrid-based bridge.
"""
LOG.debug(_("Neutron PLUMgrid Director: update_network() called"))
self._network_admin_state(network)
tenant_id = self._get_tenant_id_for_create(context, network["network"])
with context.session.begin(subtransactions=True):
# Plugin DB - Network Update
net_db = super(
NeutronPluginPLUMgridV2, self).update_network(context,
net_id, network)
try:
LOG.debug(_("PLUMgrid Library: update_network() called"))
self._plumlib.update_network(tenant_id, net_id)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
# Return updated network
return net_db
def delete_network(self, context, net_id):
"""Delete Neutron network.
Deletes a PLUMgrid-based bridge.
"""
LOG.debug(_("Neutron PLUMgrid Director: delete_network() called"))
net_db = super(NeutronPluginPLUMgridV2,
self).get_network(context, net_id)
with context.session.begin(subtransactions=True):
# Plugin DB - Network Delete
super(NeutronPluginPLUMgridV2, self).delete_network(context,
net_id)
try:
LOG.debug(_("PLUMgrid Library: update_network() called"))
self._plumlib.delete_network(net_db, net_id)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
def create_port(self, context, port):
"""Create Neutron port.
Creates a PLUMgrid-based port on the specific Virtual Network
Function (VNF).
"""
LOG.debug(_("Neutron PLUMgrid Director: create_port() called"))
# Port operations on PLUMgrid Director is an automatic operation
# from the VIF driver operations in Nova.
# It requires admin_state_up to be True
port["port"]["admin_state_up"] = True
with context.session.begin(subtransactions=True):
# Plugin DB - Port Create and Return port
port_db = super(NeutronPluginPLUMgridV2, self).create_port(context,
port)
device_id = port_db["device_id"]
if port_db["device_owner"] == "network:router_gateway":
router_db = self._get_router(context, device_id)
else:
router_db = None
try:
LOG.debug(_("PLUMgrid Library: create_port() called"))
self._plumlib.create_port(port_db, router_db)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
# Plugin DB - Port Create and Return port
return self._port_viftype_binding(context, port_db)
def update_port(self, context, port_id, port):
"""Update Neutron port.
Updates a PLUMgrid-based port on the specific Virtual Network
Function (VNF).
"""
LOG.debug(_("Neutron PLUMgrid Director: update_port() called"))
with context.session.begin(subtransactions=True):
# Plugin DB - Port Create and Return port
port_db = super(NeutronPluginPLUMgridV2, self).update_port(
context, port_id, port)
device_id = port_db["device_id"]
if port_db["device_owner"] == "network:router_gateway":
router_db = self._get_router(context, device_id)
else:
router_db = None
try:
LOG.debug(_("PLUMgrid Library: create_port() called"))
self._plumlib.update_port(port_db, router_db)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
# Plugin DB - Port Update
return self._port_viftype_binding(context, port_db)
def delete_port(self, context, port_id, l3_port_check=True):
"""Delete Neutron port.
Deletes a PLUMgrid-based port on the specific Virtual Network
Function (VNF).
"""
LOG.debug(_("Neutron PLUMgrid Director: delete_port() called"))
with context.session.begin(subtransactions=True):
# Plugin DB - Port Create and Return port
port_db = super(NeutronPluginPLUMgridV2,
self).get_port(context, port_id)
super(NeutronPluginPLUMgridV2, self).delete_port(context, port_id)
if port_db["device_owner"] == "network:router_gateway":
device_id = port_db["device_id"]
router_db = self._get_router(context, device_id)
else:
router_db = None
try:
LOG.debug(_("PLUMgrid Library: delete_port() called"))
self._plumlib.delete_port(port_db, router_db)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
def get_port(self, context, id, fields=None):
with context.session.begin(subtransactions=True):
port_db = super(NeutronPluginPLUMgridV2,
self).get_port(context, id, fields)
self._port_viftype_binding(context, port_db)
return self._fields(port_db, fields)
def get_ports(self, context, filters=None, fields=None):
with context.session.begin(subtransactions=True):
ports_db = super(NeutronPluginPLUMgridV2,
self).get_ports(context, filters, fields)
for port_db in ports_db:
self._port_viftype_binding(context, port_db)
return [self._fields(port, fields) for port in ports_db]
def create_subnet(self, context, subnet):
"""Create Neutron subnet.
Creates a PLUMgrid-based DHCP and NAT Virtual Network
Functions (VNFs).
"""
LOG.debug(_("Neutron PLUMgrid Director: create_subnet() called"))
with context.session.begin(subtransactions=True):
# Plugin DB - Subnet Create
net_db = super(NeutronPluginPLUMgridV2, self).get_network(
context, subnet['subnet']['network_id'], fields=None)
s = subnet['subnet']
ipnet = netaddr.IPNetwork(s['cidr'])
# PLUMgrid Director reserves the last IP address for GW
# when is not defined
if s['gateway_ip'] is attributes.ATTR_NOT_SPECIFIED:
gw_ip = str(netaddr.IPAddress(ipnet.last - 1))
subnet['subnet']['gateway_ip'] = gw_ip
# PLUMgrid reserves the first IP
if s['allocation_pools'] == attributes.ATTR_NOT_SPECIFIED:
allocation_pool = self._allocate_pools_for_subnet(context, s)
subnet['subnet']['allocation_pools'] = allocation_pool
sub_db = super(NeutronPluginPLUMgridV2, self).create_subnet(
context, subnet)
try:
LOG.debug(_("PLUMgrid Library: create_subnet() called"))
self._plumlib.create_subnet(sub_db, net_db, ipnet)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
return sub_db
def delete_subnet(self, context, subnet_id):
"""Delete subnet core Neutron API."""
LOG.debug(_("Neutron PLUMgrid Director: delete_subnet() called"))
# Collecting subnet info
sub_db = self._get_subnet(context, subnet_id)
tenant_id = self._get_tenant_id_for_create(context, subnet_id)
net_id = sub_db["network_id"]
net_db = self.get_network(context, net_id)
with context.session.begin(subtransactions=True):
# Plugin DB - Subnet Delete
super(NeutronPluginPLUMgridV2, self).delete_subnet(
context, subnet_id)
try:
LOG.debug(_("PLUMgrid Library: delete_subnet() called"))
self._plumlib.delete_subnet(tenant_id, net_db, net_id)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
def update_subnet(self, context, subnet_id, subnet):
"""Update subnet core Neutron API."""
LOG.debug(_("update_subnet() called"))
# Collecting subnet info
org_sub_db = self._get_subnet(context, subnet_id)
with context.session.begin(subtransactions=True):
# Plugin DB - Subnet Update
new_sub_db = super(NeutronPluginPLUMgridV2,
self).update_subnet(context, subnet_id, subnet)
ipnet = netaddr.IPNetwork(new_sub_db['cidr'])
try:
# PLUMgrid Server does not support updating resources yet
LOG.debug(_("PLUMgrid Library: update_network() called"))
self._plumlib.update_subnet(org_sub_db, new_sub_db, ipnet)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
return new_sub_db
def create_router(self, context, router):
"""
Create router extension Neutron API
"""
LOG.debug(_("Neutron PLUMgrid Director: create_router() called"))
tenant_id = self._get_tenant_id_for_create(context, router["router"])
with context.session.begin(subtransactions=True):
# Create router in DB
router_db = super(NeutronPluginPLUMgridV2,
self).create_router(context, router)
# Create router on the network controller
try:
# Add Router to VND
LOG.debug(_("PLUMgrid Library: create_router() called"))
self._plumlib.create_router(tenant_id, router_db)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
# Return created router
return router_db
def update_router(self, context, router_id, router):
LOG.debug(_("Neutron PLUMgrid Director: update_router() called"))
with context.session.begin(subtransactions=True):
router_db = super(NeutronPluginPLUMgridV2,
self).update_router(context, router_id, router)
try:
LOG.debug(_("PLUMgrid Library: update_router() called"))
self._plumlib.update_router(router_db, router_id)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
# Return updated router
return router_db
def delete_router(self, context, router_id):
LOG.debug(_("Neutron PLUMgrid Director: delete_router() called"))
with context.session.begin(subtransactions=True):
orig_router = self._get_router(context, router_id)
tenant_id = orig_router["tenant_id"]
super(NeutronPluginPLUMgridV2, self).delete_router(context,
router_id)
try:
LOG.debug(_("PLUMgrid Library: delete_router() called"))
self._plumlib.delete_router(tenant_id, router_id)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
def add_router_interface(self, context, router_id, interface_info):
LOG.debug(_("Neutron PLUMgrid Director: "
"add_router_interface() called"))
with context.session.begin(subtransactions=True):
# Validate args
router_db = self._get_router(context, router_id)
tenant_id = router_db['tenant_id']
# Create interface in DB
int_router = super(NeutronPluginPLUMgridV2,
self).add_router_interface(context,
router_id,
interface_info)
port_db = self._get_port(context, int_router['port_id'])
subnet_id = port_db["fixed_ips"][0]["subnet_id"]
subnet_db = super(NeutronPluginPLUMgridV2,
self)._get_subnet(context, subnet_id)
ipnet = netaddr.IPNetwork(subnet_db['cidr'])
# Create interface on the network controller
try:
LOG.debug(_("PLUMgrid Library: add_router_interface() called"))
self._plumlib.add_router_interface(tenant_id, router_id,
port_db, ipnet)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
return int_router
def remove_router_interface(self, context, router_id, int_info):
LOG.debug(_("Neutron PLUMgrid Director: "
"remove_router_interface() called"))
with context.session.begin(subtransactions=True):
# Validate args
router_db = self._get_router(context, router_id)
tenant_id = router_db['tenant_id']
if 'port_id' in int_info:
port = self._get_port(context, int_info['port_id'])
net_id = port['network_id']
elif 'subnet_id' in int_info:
subnet_id = int_info['subnet_id']
subnet = self._get_subnet(context, subnet_id)
net_id = subnet['network_id']
# Remove router in DB
del_int_router = super(NeutronPluginPLUMgridV2,
self).remove_router_interface(context,
router_id,
int_info)
try:
LOG.debug(_("PLUMgrid Library: "
"remove_router_interface() called"))
self._plumlib.remove_router_interface(tenant_id,
net_id, router_id)
except Exception:
LOG.error(ERR_MESSAGE)
raise plum_excep.PLUMgridException(err_msg=ERR_MESSAGE)
return del_int_router
"""
Internal PLUMgrid Fuctions
"""
def _get_plugin_version(self):
return VERSION
def _port_viftype_binding(self, context, port):
if self._check_view_auth(context, port, self.binding_view):
port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_IOVISOR
port[portbindings.CAPABILITIES] = {
portbindings.CAP_PORT_FILTER:
'security-group' in self.supported_extension_aliases}
return port
def _check_view_auth(self, context, resource, action):
return policy.check(context, action, resource)
def _network_admin_state(self, network):
try:
if network["network"].get("admin_state_up"):
network_name = network["network"]["name"]
if network["network"]["admin_state_up"] is False:
LOG.warning(_("Network with admin_state_up=False are not "
"supported yet by this plugin. Ignoring "
"setting for network %s"), network_name)
except Exception:
err_message = _("Network Admin State Validation Falied: ")
LOG.error(err_message)
raise plum_excep.PLUMgridException(err_msg=err_message)
return network
def _allocate_pools_for_subnet(self, context, subnet):
"""Create IP allocation pools for a given subnet
Pools are defined by the 'allocation_pools' attribute,
a list of dict objects with 'start' and 'end' keys for
defining the pool range.
Modified from Neutron DB based class
"""
pools = []
# Auto allocate the pool around gateway_ip
net = netaddr.IPNetwork(subnet['cidr'])
first_ip = net.first + 2
last_ip = net.last - 1
gw_ip = int(netaddr.IPAddress(subnet['gateway_ip'] or net.last))
# Use the gw_ip to find a point for splitting allocation pools
# for this subnet
split_ip = min(max(gw_ip, net.first), net.last)
if split_ip > first_ip:
pools.append({'start': str(netaddr.IPAddress(first_ip)),
'end': str(netaddr.IPAddress(split_ip - 1))})
if split_ip < last_ip:
pools.append({'start': str(netaddr.IPAddress(split_ip + 1)),
'end': str(netaddr.IPAddress(last_ip))})
# return auto-generated pools
# no need to check for their validity
return pools

View File

@ -19,53 +19,84 @@
Test cases for Neutron PLUMgrid Plug-in
"""
from mock import patch
import mock
from neutron.extensions import portbindings
from neutron.manager import NeutronManager
from neutron.openstack.common import importutils
from neutron.plugins.plumgrid.plumgrid_plugin import plumgrid_plugin
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit import test_db_plugin as test_plugin
class PLUMgridPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
PLUM_DRIVER = ('neutron.plugins.plumgrid.drivers.fake_plumlib.Plumlib')
FAKE_DIRECTOR = '1.1.1.1'
FAKE_PORT = '1234'
FAKE_TIMEOUT = '0'
_plugin_name = ('neutron.plugins.plumgrid.plumgrid_nos_plugin.'
class PLUMgridPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
_plugin_name = ('neutron.plugins.plumgrid.plumgrid_plugin.'
'plumgrid_plugin.NeutronPluginPLUMgridV2')
def setUp(self):
self.restHTTPConnection = patch('httplib.HTTPConnection')
self.restHTTPConnection.start()
super(PLUMgridPluginV2TestCase, self).setUp(self._plugin_name)
def mocked_plumlib_init(self):
director_plumgrid = FAKE_DIRECTOR
director_port = FAKE_PORT
timeout = FAKE_TIMEOUT
self._plumlib = importutils.import_object(PLUM_DRIVER)
self._plumlib.director_conn(director_plumgrid,
director_port, timeout)
with mock.patch.object(plumgrid_plugin.NeutronPluginPLUMgridV2,
'plumgrid_init', new=mocked_plumlib_init):
super(PLUMgridPluginV2TestCase, self).setUp(self._plugin_name)
def tearDown(self):
super(PLUMgridPluginV2TestCase, self).tearDown()
self.restHTTPConnection.stop()
class TestPlumgridPluginV2HTTPResponse(test_plugin.TestV2HTTPResponse,
PLUMgridPluginV2TestCase):
class TestPlumgridPluginNetworksV2(test_plugin.TestNetworksV2,
PLUMgridPluginV2TestCase):
pass
class TestPlumgridV2HTTPResponse(test_plugin.TestV2HTTPResponse,
PLUMgridPluginV2TestCase):
pass
class TestPlumgridPluginPortsV2(test_plugin.TestPortsV2,
PLUMgridPluginV2TestCase):
pass
class TestPlumgridPluginNetworksV2(test_plugin.TestNetworksV2,
PLUMgridPluginV2TestCase):
pass
def test_range_allocation(self):
self.skipTest("Plugin does not support Neutron allocation process")
class TestPlumgridPluginSubnetsV2(test_plugin.TestSubnetsV2,
PLUMgridPluginV2TestCase):
def test_create_subnet_default_gw_conflict_allocation_pool_returns_409(
self):
self.skipTest("Plugin does not support Neutron allocation process")
pass
def test_create_subnet_defaults(self):
self.skipTest("Plugin does not support Neutron allocation process")
def test_create_subnet_gw_values(self):
self.skipTest("Plugin does not support Neutron allocation process")
def test_update_subnet_gateway_in_allocation_pool_returns_409(self):
self.skipTest("Plugin does not support Neutron allocation process")
class TestPlumgridPluginPortBinding(PLUMgridPluginV2TestCase,
test_bindings.PortBindingsTestCase):
VIF_TYPE = portbindings.VIF_TYPE_IOVISOR
def setUp(self):
super(TestPlumgridPluginPortBinding, self).setUp()
class TestPlumgridNetworkAdminState(PLUMgridPluginV2TestCase):
def test_network_admin_state(self):
name = 'network_test'
admin_status_up = False
@ -75,3 +106,18 @@ class TestPlumgridNetworkAdminState(PLUMgridPluginV2TestCase):
'tenant_id': tenant_id}}
plugin = NeutronManager.get_plugin()
self.assertEqual(plugin._network_admin_state(network), network)
class TestPlumgridAllocationPool(PLUMgridPluginV2TestCase):
def test_allocate_pools_for_subnet(self):
cidr = '10.0.0.0/24'
gateway_ip = '10.0.0.254'
subnet = {'gateway_ip': gateway_ip,
'cidr': cidr,
'ip_version': 4}
allocation_pool = [{"start": '10.0.0.2',
"end": '10.0.0.253'}]
context = None
plugin = NeutronManager.get_plugin()
pool = plugin._allocate_pools_for_subnet(context, subnet)
self.assertEqual(allocation_pool, pool)