Adds the new IBM SDN-VE plugin
It adds a new plugin for SDN-VE, the IBM SDN controller. The plugin supports the core API and the port binding and L3 extensions. Implements: blueprint ibm-sdn-ve-plugin DocImpact Change-Id: I92619a95bca2ae0c37e7fdd39da30119b43d1ad6
This commit is contained in:
parent
c624dc3230
commit
f2c5f5856b
49
etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini
Normal file
49
etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini
Normal file
@ -0,0 +1,49 @@
|
||||
[sdnve]
|
||||
# (ListOpt) The IP address of one (or more) SDN-VE controllers
|
||||
# Default value is : controller_ips = 127.0.0.1
|
||||
# Example: controller_ips = 127.0.0.1,127.0.0.2
|
||||
# (StrOpt) The integration bridge for OF based implementation
|
||||
# The default value for integration_bridge is None
|
||||
# Example: integration_bridge = br-int
|
||||
# (ListOpt) The interface mapping connecting the integration
|
||||
# bridge to external network as a list of physical network names and
|
||||
# interfaces: <physical_network_name>:<interface_name>
|
||||
# Example: interface_mappings = default:eth2
|
||||
# (BoolOpt) Used to reset the integration bridge, if exists
|
||||
# The default value for reset_bridge is True
|
||||
# Example: reset_bridge = False
|
||||
# (BoolOpt) Used to set the OVS controller as out-of-band
|
||||
# The default value for out_of_band is True
|
||||
# Example: out_of_band = False
|
||||
#
|
||||
# (BoolOpt) The fake controller for testing purposes
|
||||
# Default value is : use_fake_controller = False
|
||||
# (StrOpt) The port number for use with controller
|
||||
# The default value for the port is 8443
|
||||
# Example: port = 8443
|
||||
# (StrOpt) The userid for use with controller
|
||||
# The default value for the userid is admin
|
||||
# Example: userid = sdnve_user
|
||||
# (StrOpt) The password for use with controller
|
||||
# The default value for the password is admin
|
||||
# Example: password = sdnve_password
|
||||
#
|
||||
# (StrOpt) The default type of tenants (and associated resources)
|
||||
# Default value for OF is: default_tenant = OF
|
||||
# Example: default_tenant = OVERLAY
|
||||
# (StrOpt) The string in tenant description that indicates
|
||||
# Default value for OF tenants: of_signature = SDNVE-OF
|
||||
# (StrOpt) The string in tenant description that indicates
|
||||
# Default value for OVERLAY tenants: overlay_signature = SDNVE-OVERLAY
|
||||
|
||||
[sdnve_agent]
|
||||
# (IntOpt) Agent's polling interval in seconds
|
||||
# polling_interval = 2
|
||||
# (StrOpt) What to use for root helper
|
||||
# The default value: root_helper = 'sudo'
|
||||
# (BoolOpt) Whether to use rpc or not
|
||||
# The default value: rpc = True
|
||||
|
||||
[securitygroup]
|
||||
# The security group is not supported:
|
||||
# firewall_driver = neutron.agent.firewall.NoopFirewallDriver
|
@ -36,7 +36,8 @@ migration_for_plugins = [
|
||||
'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
|
||||
'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
|
||||
'neutron.plugins.vmware.plugin.NsxPlugin',
|
||||
'neutron.plugins.vmware.plugin.NsxServicePlugin'
|
||||
'neutron.plugins.vmware.plugin.NsxServicePlugin',
|
||||
'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
|
||||
]
|
||||
|
||||
from alembic import op
|
||||
|
@ -37,6 +37,7 @@ migration_for_plugins = [
|
||||
'neutron.plugins.vmware.plugin.NsxPlugin',
|
||||
'neutron.plugins.vmware.plugin.NsxServicePlugin',
|
||||
'neutron.services.loadbalancer.plugin.LoadBalancerPlugin',
|
||||
'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
|
||||
]
|
||||
|
||||
from alembic import op
|
||||
|
@ -38,7 +38,8 @@ migration_for_plugins = [
|
||||
'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin',
|
||||
'neutron.plugins.vmware.plugin.NsxPlugin',
|
||||
'neutron.plugins.vmware.plugin.NsxServicePlugin',
|
||||
'neutron.services.loadbalancer.plugin.LoadBalancerPlugin'
|
||||
'neutron.services.loadbalancer.plugin.LoadBalancerPlugin',
|
||||
'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
|
||||
]
|
||||
|
||||
from alembic import op
|
||||
|
@ -37,6 +37,7 @@ PLUGINS = {
|
||||
'plumgrid': 'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.'
|
||||
'NeutronPluginPLUMgridV2',
|
||||
'ryu': 'neutron.plugins.ryu.ryu_neutron_plugin.RyuNeutronPluginV2',
|
||||
'ibm': 'neutron.plugins.ibm.sdnve_neutron_plugin.SdnvePluginV2',
|
||||
}
|
||||
|
||||
L3_CAPABLE = [
|
||||
@ -48,6 +49,7 @@ L3_CAPABLE = [
|
||||
PLUGINS['ryu'],
|
||||
PLUGINS['brocade'],
|
||||
PLUGINS['plumgrid'],
|
||||
PLUGINS['ibm'],
|
||||
]
|
||||
|
||||
FOLSOM_QUOTA = [
|
||||
|
6
neutron/plugins/ibm/README
Normal file
6
neutron/plugins/ibm/README
Normal file
@ -0,0 +1,6 @@
|
||||
IBM SDN-VE Neutron Plugin
|
||||
|
||||
This plugin implements Neutron v2 APIs.
|
||||
|
||||
For more details on how to use it please refer to the following page:
|
||||
http://wiki.openstack.org/wiki/IBM-Neutron
|
0
neutron/plugins/ibm/__init__.py
Normal file
0
neutron/plugins/ibm/__init__.py
Normal file
0
neutron/plugins/ibm/agent/__init__.py
Normal file
0
neutron/plugins/ibm/agent/__init__.py
Normal file
247
neutron/plugins/ibm/agent/sdnve_neutron_agent.py
Normal file
247
neutron/plugins/ibm/agent/sdnve_neutron_agent.py
Normal file
@ -0,0 +1,247 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
|
||||
import socket
|
||||
import time
|
||||
|
||||
import eventlet
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import ovs_lib
|
||||
from neutron.agent import rpc as agent_rpc
|
||||
from neutron.common import config as logging_config
|
||||
from neutron.common import legacy
|
||||
from neutron.common import topics
|
||||
from neutron.common import utils as q_utils
|
||||
from neutron import context
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common.rpc import dispatcher
|
||||
from neutron.plugins.ibm.common import config # noqa
|
||||
from neutron.plugins.ibm.common import constants
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SdnvePluginApi(agent_rpc.PluginApi):
|
||||
|
||||
def sdnve_info(self, context, info):
|
||||
return self.call(context,
|
||||
self.make_msg('sdnve_info', info=info),
|
||||
topic=self.topic)
|
||||
|
||||
|
||||
class SdnveNeutronAgent():
|
||||
|
||||
RPC_API_VERSION = '1.1'
|
||||
|
||||
def __init__(self, integ_br, interface_mappings,
|
||||
info, root_helper, polling_interval,
|
||||
controller_ip, reset_br, out_of_band):
|
||||
'''The agent initialization.
|
||||
|
||||
Sets the following parameters and sets up the integration
|
||||
bridge and physical interfaces if need be.
|
||||
:param integ_br: name of the integration bridge.
|
||||
:param interface_mappings: interfaces to physical networks.
|
||||
:param info: local IP address of this hypervisor.
|
||||
:param root_helper: utility to use when running shell cmds.
|
||||
:param polling_interval: interval (secs) to poll DB.
|
||||
:param controller_ip: Ip address of SDN-VE controller.
|
||||
'''
|
||||
|
||||
self.root_helper = root_helper
|
||||
self.int_bridge_name = integ_br
|
||||
self.controller_ip = controller_ip
|
||||
self.interface_mappings = interface_mappings
|
||||
self.polling_interval = polling_interval
|
||||
self.info = info
|
||||
self.reset_br = reset_br
|
||||
self.out_of_band = out_of_band
|
||||
|
||||
if self.int_bridge_name:
|
||||
self.int_br = self.setup_integration_br(integ_br, reset_br,
|
||||
out_of_band,
|
||||
self.controller_ip)
|
||||
self.setup_physical_interfaces(self.interface_mappings)
|
||||
else:
|
||||
self.int_br = None
|
||||
|
||||
self.setup_rpc()
|
||||
|
||||
def setup_rpc(self):
|
||||
if self.int_br:
|
||||
mac = self.int_br.get_local_port_mac()
|
||||
self.agent_id = '%s%s' % ('sdnve', (mac.replace(":", "")))
|
||||
else:
|
||||
nameaddr = socket.gethostbyname(socket.gethostname())
|
||||
self.agent_id = '%s%s' % ('sdnve_', (nameaddr.replace(".", "_")))
|
||||
|
||||
self.topic = topics.AGENT
|
||||
self.plugin_rpc = SdnvePluginApi(topics.PLUGIN)
|
||||
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
|
||||
|
||||
self.context = context.get_admin_context_without_session()
|
||||
self.dispatcher = self.create_rpc_dispatcher()
|
||||
consumers = [[constants.INFO, topics.UPDATE]]
|
||||
|
||||
self.connection = agent_rpc.create_consumers(self.dispatcher,
|
||||
self.topic,
|
||||
consumers)
|
||||
|
||||
# Plugin calls the agents through the following
|
||||
def info_update(self, context, **kwargs):
|
||||
LOG.debug(_("info_update received"))
|
||||
info = kwargs.get('info', {})
|
||||
new_controller = info.get('new_controller')
|
||||
out_of_band = info.get('out_of_band')
|
||||
if self.int_br and new_controller:
|
||||
LOG.debug(_("info_update received. New controller"
|
||||
"is to be set to: %s"), new_controller)
|
||||
self.int_br.run_vsctl(["set-controller",
|
||||
self.int_bridge_name,
|
||||
"tcp:" + new_controller])
|
||||
if out_of_band:
|
||||
LOG.debug(_("info_update received. New controller"
|
||||
"is set to be out of band"))
|
||||
self.int_br.set_db_attribute("controller",
|
||||
self.int_bridge_name,
|
||||
"connection-mode",
|
||||
"out-of-band")
|
||||
|
||||
def create_rpc_dispatcher(self):
|
||||
return dispatcher.RpcDispatcher([self])
|
||||
|
||||
def setup_integration_br(self, bridge_name, reset_br, out_of_band,
|
||||
controller_ip=None):
|
||||
'''Sets up the integration bridge.
|
||||
|
||||
Create the bridge and remove all existing flows if reset_br is True.
|
||||
Otherwise, creates the bridge if not already existing.
|
||||
:param bridge_name: the name of the integration bridge.
|
||||
:param reset_br: A boolean to rest the bridge if True.
|
||||
:param out_of_band: A boolean inidicating controller is out of band.
|
||||
:param controller_ip: IP address to use as the bridge controller.
|
||||
:returns: the integration bridge
|
||||
'''
|
||||
|
||||
int_br = ovs_lib.OVSBridge(bridge_name, self.root_helper)
|
||||
if reset_br:
|
||||
int_br.reset_bridge()
|
||||
int_br.remove_all_flows()
|
||||
else:
|
||||
int_br.create()
|
||||
|
||||
# set the controller
|
||||
if controller_ip:
|
||||
int_br.run_vsctl(
|
||||
["set-controller", bridge_name, "tcp:" + controller_ip])
|
||||
if out_of_band:
|
||||
int_br.set_db_attribute("controller", bridge_name,
|
||||
"connection-mode", "out-of-band")
|
||||
|
||||
return int_br
|
||||
|
||||
def setup_physical_interfaces(self, interface_mappings):
|
||||
'''Sets up the physical network interfaces.
|
||||
|
||||
Link physical interfaces to the integration bridge.
|
||||
:param interface_mappings: map physical net names to interface names.
|
||||
'''
|
||||
|
||||
for physical_network, interface in interface_mappings.iteritems():
|
||||
LOG.info(_("Mapping physical network %(physical_network)s to "
|
||||
"interface %(interface)s"),
|
||||
{'physical_network': physical_network,
|
||||
'interface': interface})
|
||||
# Connect the physical interface to the bridge
|
||||
if not ip_lib.device_exists(interface, self.root_helper):
|
||||
LOG.error(_("Interface %(interface)s for physical network "
|
||||
"%(physical_network)s does not exist. Agent "
|
||||
"terminated!"),
|
||||
{'physical_network': physical_network,
|
||||
'interface': interface})
|
||||
raise SystemExit(1)
|
||||
self.int_br.add_port(interface)
|
||||
|
||||
def sdnve_info(self):
|
||||
details = self.plugin_rpc.sdnve_info(
|
||||
self.context,
|
||||
{'info': self.info})
|
||||
return details
|
||||
|
||||
def rpc_loop(self):
|
||||
|
||||
while True:
|
||||
start = time.time()
|
||||
LOG.debug(_("Agent in the rpc loop."))
|
||||
|
||||
# sleep till end of polling interval
|
||||
elapsed = (time.time() - start)
|
||||
if (elapsed < self.polling_interval):
|
||||
time.sleep(self.polling_interval - elapsed)
|
||||
else:
|
||||
LOG.info(_("Loop iteration exceeded interval "
|
||||
"(%(polling_interval)s vs. %(elapsed)s)!"),
|
||||
{'polling_interval': self.polling_interval,
|
||||
'elapsed': elapsed})
|
||||
|
||||
def daemon_loop(self):
|
||||
self.rpc_loop()
|
||||
|
||||
|
||||
def create_agent_config_map(config):
|
||||
|
||||
interface_mappings = q_utils.parse_mappings(
|
||||
config.SDNVE.interface_mappings)
|
||||
|
||||
controller_ips = config.SDNVE.controller_ips
|
||||
LOG.info(_("Controller IPs: %s"), controller_ips)
|
||||
controller_ip = controller_ips[0]
|
||||
|
||||
return {
|
||||
'integ_br': config.SDNVE.integration_bridge,
|
||||
'interface_mappings': interface_mappings,
|
||||
'controller_ip': controller_ip,
|
||||
'info': config.SDNVE.info,
|
||||
'root_helper': config.SDNVE_AGENT.root_helper,
|
||||
'polling_interval': config.SDNVE_AGENT.polling_interval,
|
||||
'reset_br': config.SDNVE.reset_bridge,
|
||||
'out_of_band': config.SDNVE.out_of_band}
|
||||
|
||||
|
||||
def main():
|
||||
eventlet.monkey_patch()
|
||||
cfg.CONF.register_opts(ip_lib.OPTS)
|
||||
cfg.CONF(project='neutron')
|
||||
logging_config.setup_logging(cfg.CONF)
|
||||
legacy.modernize_quantum_config(cfg.CONF)
|
||||
|
||||
try:
|
||||
agent_config = create_agent_config_map(cfg.CONF)
|
||||
except ValueError as e:
|
||||
LOG.exception(_("%s Agent terminated!"), e)
|
||||
raise SystemExit(1)
|
||||
|
||||
plugin = SdnveNeutronAgent(**agent_config)
|
||||
|
||||
# Start everything.
|
||||
LOG.info(_("Agent initialized successfully, now running... "))
|
||||
plugin.daemon_loop()
|
0
neutron/plugins/ibm/common/__init__.py
Normal file
0
neutron/plugins/ibm/common/__init__.py
Normal file
74
neutron/plugins/ibm/common/config.py
Normal file
74
neutron/plugins/ibm/common/config.py
Normal file
@ -0,0 +1,74 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
|
||||
DEFAULT_INTERFACE_MAPPINGS = []
|
||||
DEFAULT_CONTROLLER_IPS = ['127.0.0.1']
|
||||
|
||||
sdnve_opts = [
|
||||
cfg.BoolOpt('use_fake_controller', default=False,
|
||||
help=_("If set to True uses a fake controller.")),
|
||||
cfg.StrOpt('base_url', default='/one/nb/v2/',
|
||||
help=_("Base URL for SDN-VE controller REST API")),
|
||||
cfg.ListOpt('controller_ips', default=DEFAULT_CONTROLLER_IPS,
|
||||
help=_("List of IP addresses of SDN-VE controller(s)")),
|
||||
cfg.StrOpt('info', default='sdnve_info_string',
|
||||
help=_("SDN-VE RPC subject")),
|
||||
cfg.StrOpt('port', default='8443',
|
||||
help=_("SDN-VE controller port number")),
|
||||
cfg.StrOpt('format', default='json',
|
||||
help=_("SDN-VE request/response format")),
|
||||
cfg.StrOpt('userid', default='admin',
|
||||
help=_("SDN-VE administrator user id")),
|
||||
cfg.StrOpt('password', default='admin',
|
||||
help=_("SDN-VE administrator password")),
|
||||
cfg.StrOpt('integration_bridge', default=None,
|
||||
help=_("Integration bridge to use")),
|
||||
cfg.BoolOpt('reset_bridge', default=True,
|
||||
help=_("Reset the integration bridge before use")),
|
||||
cfg.BoolOpt('out_of_band', default=True,
|
||||
help=_("Indicating if controller is out of band or not")),
|
||||
cfg.ListOpt('interface_mappings',
|
||||
default=DEFAULT_INTERFACE_MAPPINGS,
|
||||
help=_("List of <physical_network_name>:<interface_name>")),
|
||||
cfg.StrOpt('default_tenant_type', default='OF',
|
||||
help=_("Tenant type: OF (default) and OVERLAY")),
|
||||
cfg.StrOpt('overlay_signature', default='SDNVE-OVERLAY',
|
||||
help=_("The string in tenant description that indicates "
|
||||
"the tenant is a OVERLAY tenant")),
|
||||
cfg.StrOpt('of_signature', default='SDNVE-OF',
|
||||
help=_("The string in tenant description that indicates "
|
||||
"the tenant is a OF tenant")),
|
||||
]
|
||||
|
||||
sdnve_agent_opts = [
|
||||
cfg.IntOpt('polling_interval', default=2,
|
||||
help=_("Agent polling interval if necessary")),
|
||||
cfg.StrOpt('root_helper', default='sudo',
|
||||
help=_("Using root helper")),
|
||||
cfg.BoolOpt('rpc', default=True,
|
||||
help=_("Whether using rpc")),
|
||||
|
||||
]
|
||||
|
||||
|
||||
cfg.CONF.register_opts(sdnve_opts, "SDNVE")
|
||||
cfg.CONF.register_opts(sdnve_agent_opts, "SDNVE_AGENT")
|
32
neutron/plugins/ibm/common/constants.py
Normal file
32
neutron/plugins/ibm/common/constants.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
|
||||
import httplib
|
||||
|
||||
# Topic for info notifications between the plugin and agent
|
||||
INFO = 'info'
|
||||
|
||||
TENANT_TYPE_OF = 'OF'
|
||||
TENANT_TYPE_OVERLAY = 'OVERLAY'
|
||||
|
||||
HTTP_ACCEPTABLE = [httplib.OK,
|
||||
httplib.CREATED,
|
||||
httplib.ACCEPTED,
|
||||
httplib.NO_CONTENT
|
||||
]
|
28
neutron/plugins/ibm/common/exceptions.py
Normal file
28
neutron/plugins/ibm/common/exceptions.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
from neutron.common import exceptions
|
||||
|
||||
|
||||
class SdnveException(exceptions.NeutronException):
|
||||
message = _("An unexpected error occurred in the SDN-VE Plugin. "
|
||||
"Here is the error message: %(msg)s")
|
||||
|
||||
|
||||
class BadInputException(exceptions.BadRequest):
|
||||
message = _("The input does not contain nececessary info: %(msg)s")
|
387
neutron/plugins/ibm/sdnve_api.py
Normal file
387
neutron/plugins/ibm/sdnve_api.py
Normal file
@ -0,0 +1,387 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
|
||||
import httplib
|
||||
import urllib
|
||||
|
||||
import httplib2
|
||||
from keystoneclient.v2_0 import client as keyclient
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.ibm.common import config # noqa
|
||||
from neutron.plugins.ibm.common import constants
|
||||
from neutron.wsgi import Serializer
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SDNVE_VERSION = '2.0'
|
||||
SDNVE_ACTION_PREFIX = '/sdnve'
|
||||
SDNVE_RETRIES = 0
|
||||
SDNVE_RETRIY_INTERVAL = 1
|
||||
SDNVE_TENANT_TYPE_OVERLAY = u'DOVE'
|
||||
SDNVE_URL = 'https://%s:%s%s'
|
||||
|
||||
|
||||
class RequestHandler(object):
|
||||
'''Handles processeing requests to and responses from controller.'''
|
||||
|
||||
def __init__(self, controller_ips=None, port=None, ssl=None,
|
||||
base_url=None, userid=None, password=None,
|
||||
timeout=10, formats=None):
|
||||
'''Initializes the RequestHandler for communication with controller
|
||||
|
||||
Following keyword arguments are used; if not specified, default
|
||||
values are used.
|
||||
:param port: Username for authentication.
|
||||
:param timeout: Time out for http requests.
|
||||
:param userid: User id for accessing controller.
|
||||
:param password: Password for accessing the controlelr.
|
||||
:param base_url: The base url for the controller.
|
||||
:param controller_ips: List of controller IP addresses.
|
||||
:param formats: Supported formats.
|
||||
'''
|
||||
self.port = port or cfg.CONF.SDNVE.port
|
||||
self.timeout = timeout
|
||||
self._s_meta = None
|
||||
self.connection = None
|
||||
self.httpclient = httplib2.Http(
|
||||
disable_ssl_certificate_validation=True)
|
||||
self.cookie = None
|
||||
|
||||
userid = userid or cfg.CONF.SDNVE.userid
|
||||
password = password or cfg.CONF.SDNVE.password
|
||||
if (userid and password):
|
||||
self.httpclient.add_credentials(userid, password)
|
||||
|
||||
self.base_url = base_url or cfg.CONF.SDNVE.base_url
|
||||
self.controller_ips = controller_ips or cfg.CONF.SDNVE.controller_ips
|
||||
|
||||
LOG.info(_("The IP addr of available SDN-VE controllers: %s"),
|
||||
self.controller_ips)
|
||||
self.controller_ip = self.controller_ips[0]
|
||||
LOG.info(_("The SDN-VE controller IP address: %s"),
|
||||
self.controller_ip)
|
||||
|
||||
self.new_controller = False
|
||||
self.format = formats or cfg.CONF.SDNVE.format
|
||||
|
||||
self.version = SDNVE_VERSION
|
||||
self.action_prefix = SDNVE_ACTION_PREFIX
|
||||
self.retries = SDNVE_RETRIES
|
||||
self.retry_interval = SDNVE_RETRIY_INTERVAL
|
||||
|
||||
def serialize(self, data):
|
||||
'''Serializes a dictionary with a single key.'''
|
||||
|
||||
if isinstance(data, dict):
|
||||
return Serializer().serialize(data, self.content_type())
|
||||
elif data:
|
||||
raise TypeError(_("unable to serialize object type: '%s'") %
|
||||
type(data))
|
||||
|
||||
def deserialize(self, data, status_code):
|
||||
'''Deserializes an xml or json string into a dictionary.'''
|
||||
|
||||
# NOTE(mb): Temporary fix for backend controller requirement
|
||||
data = data.replace("router_external", "router:external")
|
||||
|
||||
if status_code == httplib.NO_CONTENT:
|
||||
return data
|
||||
try:
|
||||
deserialized_data = Serializer(
|
||||
metadata=self._s_meta).deserialize(data, self.content_type())
|
||||
deserialized_data = deserialized_data['body']
|
||||
except Exception:
|
||||
deserialized_data = data
|
||||
|
||||
return deserialized_data
|
||||
|
||||
def content_type(self, format=None):
|
||||
'''Returns the mime-type for either 'xml' or 'json'.'''
|
||||
|
||||
return 'application/%s' % (format or self.format)
|
||||
|
||||
def delete(self, url, body=None, headers=None, params=None):
|
||||
return self.do_request("DELETE", url, body=body,
|
||||
headers=headers, params=params)
|
||||
|
||||
def get(self, url, body=None, headers=None, params=None):
|
||||
return self.do_request("GET", url, body=body,
|
||||
headers=headers, params=params)
|
||||
|
||||
def post(self, url, body=None, headers=None, params=None):
|
||||
return self.do_request("POST", url, body=body,
|
||||
headers=headers, params=params)
|
||||
|
||||
def put(self, url, body=None, headers=None, params=None):
|
||||
return self.do_request("PUT", url, body=body,
|
||||
headers=headers, params=params)
|
||||
|
||||
def do_request(self, method, url, body=None, headers=None,
|
||||
params=None, connection_type=None):
|
||||
|
||||
status_code = -1
|
||||
replybody_deserialized = ''
|
||||
|
||||
if body:
|
||||
body = self.serialize(body)
|
||||
|
||||
self.headers = headers or {'Content-Type': self.content_type()}
|
||||
if self.cookie:
|
||||
self.headers['cookie'] = self.cookie
|
||||
|
||||
if self.controller_ip != self.controller_ips[0]:
|
||||
controllers = [self.controller_ip]
|
||||
else:
|
||||
controllers = []
|
||||
controllers.extend(self.controller_ips)
|
||||
|
||||
for controller_ip in controllers:
|
||||
serverurl = SDNVE_URL % (controller_ip, self.port, self.base_url)
|
||||
myurl = serverurl + url
|
||||
if params and isinstance(params, dict):
|
||||
myurl += '?' + urllib.urlencode(params, doseq=1)
|
||||
|
||||
try:
|
||||
LOG.debug(_("Sending request to SDN-VE. url: "
|
||||
"%(myurl)s method: %(method)s body: "
|
||||
"%(body)s header: %(header)s "),
|
||||
{'myurl': myurl, 'method': method,
|
||||
'body': body, 'header': self.headers})
|
||||
resp, replybody = self.httpclient.request(
|
||||
myurl, method=method, body=body, headers=self.headers)
|
||||
LOG.debug(("Response recd from SDN-VE. resp: %(resp)s"
|
||||
"body: %(body)s"),
|
||||
{'resp': resp.status, 'body': replybody})
|
||||
status_code = resp.status
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(_("Error: Could not reach server: %(url)s "
|
||||
"Exception: %(excp)s."),
|
||||
{'url': myurl, 'excp': e})
|
||||
self.cookie = None
|
||||
continue
|
||||
|
||||
if status_code not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.debug(_("Error message: %(reply)s -- Status: %(status)s"),
|
||||
{'reply': replybody, 'status': status_code})
|
||||
else:
|
||||
LOG.debug(_("Received response status: %s"), status_code)
|
||||
|
||||
if resp.get('set-cookie'):
|
||||
self.cookie = resp['set-cookie']
|
||||
replybody_deserialized = self.deserialize(
|
||||
replybody,
|
||||
status_code)
|
||||
LOG.debug(_("Deserialized body: %s"), replybody_deserialized)
|
||||
if controller_ip != self.controller_ip:
|
||||
# bcast the change of controller
|
||||
self.new_controller = True
|
||||
self.controller_ip = controller_ip
|
||||
|
||||
return (status_code, replybody_deserialized)
|
||||
|
||||
return (httplib.REQUEST_TIMEOUT, 'Could not reach server(s)')
|
||||
|
||||
|
||||
class Client(RequestHandler):
|
||||
'''Client for SDNVE controller.'''
|
||||
|
||||
def __init__(self):
|
||||
'''Initialize a new SDNVE client.'''
|
||||
super(Client, self).__init__()
|
||||
|
||||
self.keystoneclient = KeystoneClient()
|
||||
|
||||
resource_path = {
|
||||
'network': "ln/networks/",
|
||||
'subnet': "ln/subnets/",
|
||||
'port': "ln/ports/",
|
||||
'tenant': "ln/tenants/",
|
||||
'router': "ln/routers/",
|
||||
'floatingip': "ln/floatingips/",
|
||||
}
|
||||
|
||||
def process_request(self, body):
|
||||
'''Processes requests according to requirements of controller.'''
|
||||
if self.format == 'json':
|
||||
body = dict(
|
||||
(k.replace(':', '_'), v) for k, v in body.items()
|
||||
if attributes.is_attr_set(v))
|
||||
|
||||
def sdnve_list(self, resource, **params):
|
||||
'''Fetches a list of resources.'''
|
||||
|
||||
res = self.resource_path.get(resource, None)
|
||||
if not res:
|
||||
LOG.info(_("Bad resource for forming a list request"))
|
||||
return 0, ''
|
||||
|
||||
return self.get(res, params=params)
|
||||
|
||||
def sdnve_show(self, resource, specific, **params):
|
||||
'''Fetches information of a certain resource.'''
|
||||
|
||||
res = self.resource_path.get(resource, None)
|
||||
if not res:
|
||||
LOG.info(_("Bad resource for forming a show request"))
|
||||
return 0, ''
|
||||
|
||||
return self.get(res + specific, params=params)
|
||||
|
||||
def sdnve_create(self, resource, body):
|
||||
'''Creates a new resource.'''
|
||||
|
||||
res = self.resource_path.get(resource, None)
|
||||
if not res:
|
||||
LOG.info(_("Bad resource for forming a create request"))
|
||||
return 0, ''
|
||||
|
||||
self.process_request(body)
|
||||
status, data = self.post(res, body=body)
|
||||
return (status, data)
|
||||
|
||||
def sdnve_update(self, resource, specific, body=None):
|
||||
'''Updates a resource.'''
|
||||
|
||||
res = self.resource_path.get(resource, None)
|
||||
if not res:
|
||||
LOG.info(_("Bad resource for forming a update request"))
|
||||
return 0, ''
|
||||
|
||||
self.process_request(body)
|
||||
return self.put(res + specific, body=body)
|
||||
|
||||
def sdnve_delete(self, resource, specific):
|
||||
'''Deletes the specified resource.'''
|
||||
|
||||
res = self.resource_path.get(resource, None)
|
||||
if not res:
|
||||
LOG.info(_("Bad resource for forming a delete request"))
|
||||
return 0, ''
|
||||
|
||||
return self.delete(res + specific)
|
||||
|
||||
def _tenant_id_conversion(self, osid):
|
||||
return osid
|
||||
|
||||
def sdnve_get_tenant_byid(self, os_tenant_id):
|
||||
sdnve_tenant_id = self._tenant_id_conversion(os_tenant_id)
|
||||
resp, content = self.sdnve_show('tenant', sdnve_tenant_id)
|
||||
if resp in constants.HTTP_ACCEPTABLE:
|
||||
tenant_id = content.get('id')
|
||||
tenant_type = content.get('network_type')
|
||||
if tenant_type == SDNVE_TENANT_TYPE_OVERLAY:
|
||||
tenant_type = constants.TENANT_TYPE_OVERLAY
|
||||
return tenant_id, tenant_type
|
||||
return None, None
|
||||
|
||||
def sdnve_check_and_create_tenant(self, os_tenant_id, network_type=None):
|
||||
|
||||
if not os_tenant_id:
|
||||
return
|
||||
tenant_id, tenant_type = self.sdnve_get_tenant_byid(os_tenant_id)
|
||||
if tenant_id:
|
||||
if not network_type:
|
||||
return tenant_id
|
||||
if tenant_type != network_type:
|
||||
LOG.info(_("Non matching tenant and network types: "
|
||||
"%(ttype)s %(ntype)s"),
|
||||
{'ttype': tenant_type, 'ntype': network_type})
|
||||
return
|
||||
return tenant_id
|
||||
|
||||
# Have to create a new tenant
|
||||
sdnve_tenant_id = self._tenant_id_conversion(os_tenant_id)
|
||||
if not network_type:
|
||||
network_type = self.keystoneclient.get_tenant_type(os_tenant_id)
|
||||
if network_type == constants.TENANT_TYPE_OVERLAY:
|
||||
network_type = SDNVE_TENANT_TYPE_OVERLAY
|
||||
|
||||
pinn_desc = ("Created by SDN-VE Neutron Plugin, OS project name = " +
|
||||
self.keystoneclient.get_tenant_name(os_tenant_id))
|
||||
|
||||
res, content = self.sdnve_create('tenant',
|
||||
{'id': sdnve_tenant_id,
|
||||
'name': os_tenant_id,
|
||||
'network_type': network_type,
|
||||
'description': pinn_desc})
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
return
|
||||
|
||||
return sdnve_tenant_id
|
||||
|
||||
def sdnve_get_controller(self):
|
||||
if self.new_controller:
|
||||
self.new_controller = False
|
||||
return self.controller_ip
|
||||
|
||||
|
||||
class KeystoneClient(object):
|
||||
|
||||
def __init__(self, username=None, tenant_name=None, password=None,
|
||||
auth_url=None):
|
||||
|
||||
keystone_conf = cfg.CONF.keystone_authtoken
|
||||
keystone_auth_url = ('%s://%s:%s/v2.0/' %
|
||||
(keystone_conf.auth_protocol,
|
||||
keystone_conf.auth_host,
|
||||
keystone_conf.auth_port))
|
||||
|
||||
username = username or keystone_conf.admin_user
|
||||
tenant_name = tenant_name or keystone_conf.admin_tenant_name
|
||||
password = password or keystone_conf.admin_password
|
||||
auth_url = auth_url or keystone_auth_url
|
||||
|
||||
self.overlay_signature = cfg.CONF.SDNVE.overlay_signature
|
||||
self.of_signature = cfg.CONF.SDNVE.of_signature
|
||||
self.default_tenant_type = cfg.CONF.SDNVE.default_tenant_type
|
||||
|
||||
self.client = keyclient.Client(username=username,
|
||||
password=password,
|
||||
tenant_name=tenant_name,
|
||||
auth_url=auth_url)
|
||||
|
||||
def get_tenant_byid(self, id):
|
||||
|
||||
try:
|
||||
return self.client.tenants.get(id)
|
||||
except Exception:
|
||||
LOG.exception(_("Did not find tenant: %r"), id)
|
||||
|
||||
def get_tenant_type(self, id):
|
||||
|
||||
tenant = self.get_tenant_byid(id)
|
||||
if tenant:
|
||||
description = tenant.description
|
||||
if description:
|
||||
if (description.find(self.overlay_signature) >= 0):
|
||||
return constants.TENANT_TYPE_OVERLAY
|
||||
if (description.find(self.of_signature) >= 0):
|
||||
return constants.TENANT_TYPE_OF
|
||||
return self.default_tenant_type
|
||||
|
||||
def get_tenant_name(self, id):
|
||||
|
||||
tenant = self.get_tenant_byid(id)
|
||||
if tenant:
|
||||
return tenant.name
|
||||
return 'not found'
|
64
neutron/plugins/ibm/sdnve_api_fake.py
Normal file
64
neutron/plugins/ibm/sdnve_api_fake.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.ibm.common import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
HTTP_OK = 200
|
||||
|
||||
|
||||
class FakeClient():
|
||||
|
||||
'''Fake Client for SDNVE controller.'''
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
LOG.info(_('Fake SDNVE controller initialized'))
|
||||
|
||||
def sdnve_list(self, resource, **_params):
|
||||
LOG.info(_('Fake SDNVE controller: list'))
|
||||
return (HTTP_OK, None)
|
||||
|
||||
def sdnve_show(self, resource, specific, **_params):
|
||||
LOG.info(_('Fake SDNVE controller: show'))
|
||||
return (HTTP_OK, None)
|
||||
|
||||
def sdnve_create(self, resource, body):
|
||||
LOG.info(_('Fake SDNVE controller: create'))
|
||||
return (HTTP_OK, None)
|
||||
|
||||
def sdnve_update(self, resource, specific, body=None):
|
||||
LOG.info(_('Fake SDNVE controller: update'))
|
||||
return (HTTP_OK, None)
|
||||
|
||||
def sdnve_delete(self, resource, specific):
|
||||
LOG.info(_('Fake SDNVE controller: delete'))
|
||||
return (HTTP_OK, None)
|
||||
|
||||
def sdnve_get_tenant_byid(self, id):
|
||||
LOG.info(_('Fake SDNVE controller: get tenant by id'))
|
||||
return id, constants.TENANT_TYPE_OF
|
||||
|
||||
def sdnve_check_and_create_tenant(self, id, network_type=None):
|
||||
LOG.info(_('Fake SDNVE controller: check and create tenant'))
|
||||
return id
|
||||
|
||||
def sdnve_get_controller(self):
|
||||
LOG.info(_('Fake SDNVE controller: get controller'))
|
||||
return None
|
649
neutron/plugins/ibm/sdnve_neutron_plugin.py
Normal file
649
neutron/plugins/ibm/sdnve_neutron_plugin.py
Normal file
@ -0,0 +1,649 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp.
|
||||
|
||||
|
||||
import functools
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.common import constants as q_const
|
||||
from neutron.common import exceptions as q_exc
|
||||
from neutron.common import rpc as q_rpc
|
||||
from neutron.common import topics
|
||||
from neutron.db import agents_db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import external_net_db
|
||||
from neutron.db import l3_gwmode_db
|
||||
from neutron.db import portbindings_db
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.openstack.common import excutils
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import rpc
|
||||
from neutron.openstack.common.rpc import proxy
|
||||
from neutron.plugins.ibm.common import config # noqa
|
||||
from neutron.plugins.ibm.common import constants
|
||||
from neutron.plugins.ibm.common import exceptions as sdnve_exc
|
||||
from neutron.plugins.ibm import sdnve_api as sdnve
|
||||
from neutron.plugins.ibm import sdnve_api_fake as sdnve_fake
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SdnveRpcCallbacks():
|
||||
|
||||
def __init__(self, notifier):
|
||||
self.notifier = notifier # used to notify the agent
|
||||
|
||||
def create_rpc_dispatcher(self):
|
||||
'''Get the rpc dispatcher for this manager.
|
||||
If a manager would like to set an rpc API version, or support more than
|
||||
one class as the target of rpc messages, override this method.
|
||||
'''
|
||||
return q_rpc.PluginRpcDispatcher([self,
|
||||
agents_db.AgentExtRpcCallback()])
|
||||
|
||||
def sdnve_info(self, rpc_context, **kwargs):
|
||||
'''Update new information.'''
|
||||
info = kwargs.get('info')
|
||||
# Notify all other listening agents
|
||||
self.notifier.info_update(rpc_context, info)
|
||||
return info
|
||||
|
||||
|
||||
class AgentNotifierApi(proxy.RpcProxy):
|
||||
'''Agent side of the SDN-VE rpc API.'''
|
||||
|
||||
BASE_RPC_API_VERSION = '1.0'
|
||||
|
||||
def __init__(self, topic):
|
||||
super(AgentNotifierApi, self).__init__(
|
||||
topic=topic, default_version=self.BASE_RPC_API_VERSION)
|
||||
|
||||
self.topic_info_update = topics.get_topic_name(topic,
|
||||
constants.INFO,
|
||||
topics.UPDATE)
|
||||
|
||||
def info_update(self, context, info):
|
||||
self.fanout_cast(context,
|
||||
self.make_msg('info_update',
|
||||
info=info),
|
||||
topic=self.topic_info_update)
|
||||
|
||||
|
||||
def _ha(func):
|
||||
'''Supports the high availability feature of the controller.'''
|
||||
|
||||
@functools.wraps(func)
|
||||
def hawrapper(self, *args, **kwargs):
|
||||
'''This wrapper sets the new controller if necessary
|
||||
|
||||
When a controller is detected to be not responding, and a
|
||||
new controller is chosen to be used in its place, this decorator
|
||||
makes sure the existing integration bridges are set to point
|
||||
to the new controleer by calling the set_controller method.
|
||||
'''
|
||||
ret_func = func(self, *args, **kwargs)
|
||||
self.set_controller(args[0])
|
||||
return ret_func
|
||||
return hawrapper
|
||||
|
||||
|
||||
class SdnvePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
external_net_db.External_net_db_mixin,
|
||||
portbindings_db.PortBindingMixin,
|
||||
l3_gwmode_db.L3_NAT_db_mixin,
|
||||
):
|
||||
|
||||
'''
|
||||
Implement the Neutron abstractions using SDN-VE SDN Controller.
|
||||
'''
|
||||
|
||||
__native_bulk_support = False
|
||||
__native_pagination_support = False
|
||||
__native_sorting_support = False
|
||||
|
||||
supported_extension_aliases = ["binding", "router", "external-net"]
|
||||
|
||||
def __init__(self, configfile=None):
|
||||
self.base_binding_dict = {
|
||||
portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS,
|
||||
portbindings.VIF_DETAILS: {portbindings.CAP_PORT_FILTER: False}}
|
||||
|
||||
super(SdnvePluginV2, self).__init__()
|
||||
self.setup_rpc()
|
||||
self.sdnve_controller_select()
|
||||
if self.fake_controller:
|
||||
self.sdnve_client = sdnve_fake.FakeClient()
|
||||
else:
|
||||
self.sdnve_client = sdnve.Client()
|
||||
|
||||
def sdnve_controller_select(self):
|
||||
self.fake_controller = cfg.CONF.SDNVE.use_fake_controller
|
||||
|
||||
def setup_rpc(self):
|
||||
# RPC support
|
||||
self.topic = topics.PLUGIN
|
||||
self.conn = rpc.create_connection(new=True)
|
||||
self.notifier = AgentNotifierApi(topics.AGENT)
|
||||
self.callbacks = SdnveRpcCallbacks(self.notifier)
|
||||
self.dispatcher = self.callbacks.create_rpc_dispatcher()
|
||||
self.conn.create_consumer(self.topic, self.dispatcher,
|
||||
fanout=False)
|
||||
# Consume from all consumers in a thread
|
||||
self.conn.consume_in_thread()
|
||||
|
||||
def _update_base_binding_dict(self, tenant_type):
|
||||
if tenant_type == constants.TENANT_TYPE_OVERLAY:
|
||||
self.base_binding_dict[
|
||||
portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE
|
||||
if tenant_type == constants.TENANT_TYPE_OF:
|
||||
self.base_binding_dict[
|
||||
portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS
|
||||
|
||||
def set_controller(self, context):
|
||||
LOG.info(_("Set a new controller if needed."))
|
||||
new_controller = self.sdnve_client.sdnve_get_controller()
|
||||
if new_controller:
|
||||
self.notifier.info_update(
|
||||
context,
|
||||
{'new_controller': new_controller})
|
||||
LOG.info(_("Set the controller to a new controller: %s"),
|
||||
new_controller)
|
||||
|
||||
def _process_request(self, request, current):
|
||||
new_request = dict(
|
||||
(k, v) for k, v in request.items()
|
||||
if v != current.get(k))
|
||||
|
||||
msg = _("Original SDN-VE HTTP request: %(orig)s; New request: %(new)s")
|
||||
LOG.debug(msg, {'orig': request, 'new': new_request})
|
||||
return new_request
|
||||
|
||||
#
|
||||
# Network
|
||||
#
|
||||
|
||||
@_ha
|
||||
def create_network(self, context, network):
|
||||
LOG.debug(_("Create network in progress: %r"), network)
|
||||
session = context.session
|
||||
|
||||
tenant_id = self._get_tenant_id_for_create(context, network['network'])
|
||||
# Create a new SDN-VE tenant if need be
|
||||
sdnve_tenant = self.sdnve_client.sdnve_check_and_create_tenant(
|
||||
tenant_id)
|
||||
if sdnve_tenant is None:
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=_('Create net failed: no SDN-VE tenant.'))
|
||||
|
||||
with session.begin(subtransactions=True):
|
||||
net = super(SdnvePluginV2, self).create_network(context, network)
|
||||
self._process_l3_create(context, net, network['network'])
|
||||
|
||||
# Create SDN-VE network
|
||||
(res, data) = self.sdnve_client.sdnve_create('network', net)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).delete_network(context, net['id'])
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Create net failed in SDN-VE: %s') % res))
|
||||
|
||||
LOG.debug(_("Created network: %s"), net['id'])
|
||||
return net
|
||||
|
||||
@_ha
|
||||
def update_network(self, context, id, network):
|
||||
LOG.debug(_("Update network in progress: %r"), network)
|
||||
session = context.session
|
||||
|
||||
processed_request = {}
|
||||
with session.begin(subtransactions=True):
|
||||
original_network = super(SdnvePluginV2, self).get_network(
|
||||
context, id)
|
||||
processed_request['network'] = self._process_request(
|
||||
network['network'], original_network)
|
||||
net = super(SdnvePluginV2, self).update_network(
|
||||
context, id, network)
|
||||
self._process_l3_update(context, net, network['network'])
|
||||
|
||||
if processed_request['network']:
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'network', id, processed_request['network'])
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
net = super(SdnvePluginV2, self).update_network(
|
||||
context, id, {'network': original_network})
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update net failed in SDN-VE: %s') % res))
|
||||
|
||||
return net
|
||||
|
||||
@_ha
|
||||
def delete_network(self, context, id):
|
||||
LOG.debug(_("Delete network in progress: %s"), id)
|
||||
super(SdnvePluginV2, self).delete_network(context, id)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_delete('network', id)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.error(
|
||||
_("Delete net failed after deleting the network in DB: %s"),
|
||||
res)
|
||||
|
||||
@_ha
|
||||
def get_network(self, context, id, fields=None):
|
||||
LOG.debug(_("Get network in progress: %s"), id)
|
||||
return super(SdnvePluginV2, self).get_network(context, id, fields)
|
||||
|
||||
@_ha
|
||||
def get_networks(self, context, filters=None, fields=None, sorts=None,
|
||||
limit=None, marker=None, page_reverse=False):
|
||||
LOG.debug(_("Get networks in progress"))
|
||||
return super(SdnvePluginV2, self).get_networks(
|
||||
context, filters, fields, sorts, limit, marker, page_reverse)
|
||||
|
||||
#
|
||||
# Port
|
||||
#
|
||||
|
||||
@_ha
|
||||
def create_port(self, context, port):
|
||||
LOG.debug(_("Create port in progress: %r"), port)
|
||||
session = context.session
|
||||
|
||||
# Set port status as 'ACTIVE' to avoid needing the agent
|
||||
port['port']['status'] = q_const.PORT_STATUS_ACTIVE
|
||||
port_data = port['port']
|
||||
|
||||
with session.begin(subtransactions=True):
|
||||
port = super(SdnvePluginV2, self).create_port(context, port)
|
||||
if 'id' not in port:
|
||||
return port
|
||||
# If the tenant_id is set to '' by create_port, add the id to
|
||||
# the request being sent to the controller as the controller
|
||||
# requires a tenant id
|
||||
tenant_id = port.get('tenant_id')
|
||||
if not tenant_id:
|
||||
LOG.debug(_("Create port does not have tenant id info"))
|
||||
original_network = super(SdnvePluginV2, self).get_network(
|
||||
context, port['network_id'])
|
||||
original_tenant_id = original_network['tenant_id']
|
||||
port['tenant_id'] = original_tenant_id
|
||||
LOG.debug(
|
||||
_("Create port does not have tenant id info; "
|
||||
"obtained is: %s"),
|
||||
port['tenant_id'])
|
||||
|
||||
os_tenant_id = tenant_id
|
||||
id_na, tenant_type = self.sdnve_client.sdnve_get_tenant_byid(
|
||||
os_tenant_id)
|
||||
self._update_base_binding_dict(tenant_type)
|
||||
self._process_portbindings_create_and_update(context,
|
||||
port_data, port)
|
||||
|
||||
# NOTE(mb): Remove this block when controller is updated
|
||||
# Remove the information that the controller does not accept
|
||||
sdnve_port = port.copy()
|
||||
sdnve_port.pop('device_id', None)
|
||||
sdnve_port.pop('device_owner', None)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_create('port', sdnve_port)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).delete_port(context, port['id'])
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Create port failed in SDN-VE: %s') % res))
|
||||
|
||||
LOG.debug(_("Created port: %s"), port.get('id', 'id not found'))
|
||||
return port
|
||||
|
||||
@_ha
|
||||
def update_port(self, context, id, port):
|
||||
LOG.debug(_("Update port in progress: %r"), port)
|
||||
session = context.session
|
||||
|
||||
processed_request = {}
|
||||
with session.begin(subtransactions=True):
|
||||
original_port = super(SdnvePluginV2, self).get_port(
|
||||
context, id)
|
||||
processed_request['port'] = self._process_request(
|
||||
port['port'], original_port)
|
||||
updated_port = super(SdnvePluginV2, self).update_port(
|
||||
context, id, port)
|
||||
|
||||
os_tenant_id = updated_port['tenant_id']
|
||||
id_na, tenant_type = self.sdnve_client.sdnve_get_tenant_byid(
|
||||
os_tenant_id)
|
||||
self._update_base_binding_dict(tenant_type)
|
||||
self._process_portbindings_create_and_update(context,
|
||||
port['port'],
|
||||
updated_port)
|
||||
|
||||
if processed_request['port']:
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'port', id, processed_request['port'])
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
updated_port = super(SdnvePluginV2, self).update_port(
|
||||
context, id, {'port': original_port})
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update port failed in SDN-VE: %s') % res))
|
||||
|
||||
return updated_port
|
||||
|
||||
@_ha
|
||||
def delete_port(self, context, id, l3_port_check=True):
|
||||
LOG.debug(_("Delete port in progress: %s"), id)
|
||||
|
||||
# if needed, check to see if this is a port owned by
|
||||
# an l3-router. If so, we should prevent deletion.
|
||||
if l3_port_check:
|
||||
self.prevent_l3_port_deletion(context, id)
|
||||
self.disassociate_floatingips(context, id)
|
||||
|
||||
super(SdnvePluginV2, self).delete_port(context, id)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_delete('port', id)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.error(
|
||||
_("Delete port operation failed in SDN-VE "
|
||||
"after deleting the port from DB: %s"), res)
|
||||
|
||||
#
|
||||
# Subnet
|
||||
#
|
||||
|
||||
@_ha
|
||||
def create_subnet(self, context, subnet):
|
||||
LOG.debug(_("Create subnet in progress: %r"), subnet)
|
||||
new_subnet = super(SdnvePluginV2, self).create_subnet(context, subnet)
|
||||
|
||||
# Note(mb): Use of null string currently required by controller
|
||||
sdnve_subnet = new_subnet.copy()
|
||||
if subnet.get('gateway_ip') is None:
|
||||
sdnve_subnet['gateway_ip'] = 'null'
|
||||
(res, data) = self.sdnve_client.sdnve_create('subnet', sdnve_subnet)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).delete_subnet(context,
|
||||
new_subnet['id'])
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Create subnet failed in SDN-VE: %s') % res))
|
||||
|
||||
LOG.debug(_("Subnet created: %s"), new_subnet['id'])
|
||||
|
||||
return new_subnet
|
||||
|
||||
@_ha
|
||||
def update_subnet(self, context, id, subnet):
|
||||
LOG.debug(_("Update subnet in progress: %r"), subnet)
|
||||
session = context.session
|
||||
|
||||
processed_request = {}
|
||||
with session.begin(subtransactions=True):
|
||||
original_subnet = super(SdnvePluginV2, self).get_subnet(
|
||||
context, id)
|
||||
processed_request['subnet'] = self._process_request(
|
||||
subnet['subnet'], original_subnet)
|
||||
updated_subnet = super(SdnvePluginV2, self).update_subnet(
|
||||
context, id, subnet)
|
||||
|
||||
if processed_request['subnet']:
|
||||
# Note(mb): Use of string containing null required by controller
|
||||
if 'gateway_ip' in processed_request['subnet']:
|
||||
if processed_request['subnet'].get('gateway_ip') is None:
|
||||
processed_request['subnet']['gateway_ip'] = 'null'
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'subnet', id, processed_request['subnet'])
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
for key in subnet['subnet'].keys():
|
||||
subnet['subnet'][key] = original_subnet[key]
|
||||
super(SdnvePluginV2, self).update_subnet(
|
||||
context, id, subnet)
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update subnet failed in SDN-VE: %s') % res))
|
||||
|
||||
return updated_subnet
|
||||
|
||||
@_ha
|
||||
def delete_subnet(self, context, id):
|
||||
LOG.debug(_("Delete subnet in progress: %s"), id)
|
||||
super(SdnvePluginV2, self).delete_subnet(context, id)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_delete('subnet', id)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.error(_("Delete subnet operation failed in SDN-VE after "
|
||||
"deleting the subnet from DB: %s"), res)
|
||||
|
||||
#
|
||||
# Router
|
||||
#
|
||||
|
||||
@_ha
|
||||
def create_router(self, context, router):
|
||||
LOG.debug(_("Create router in progress: %r"), router)
|
||||
|
||||
if router['router']['admin_state_up'] is False:
|
||||
LOG.warning(_('Ignoring admin_state_up=False for router=%r. '
|
||||
'Overriding with True'), router)
|
||||
router['router']['admin_state_up'] = True
|
||||
|
||||
tenant_id = self._get_tenant_id_for_create(context, router['router'])
|
||||
# Create a new Pinnaacles tenant if need be
|
||||
sdnve_tenant = self.sdnve_client.sdnve_check_and_create_tenant(
|
||||
tenant_id)
|
||||
if sdnve_tenant is None:
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=_('Create router failed: no SDN-VE tenant.'))
|
||||
|
||||
new_router = super(SdnvePluginV2, self).create_router(context, router)
|
||||
# Create Sdnve router
|
||||
(res, data) = self.sdnve_client.sdnve_create('router', new_router)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).delete_router(context, new_router['id'])
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Create router failed in SDN-VE: %s') % res))
|
||||
|
||||
LOG.debug(_("Router created: %r"), new_router)
|
||||
return new_router
|
||||
|
||||
@_ha
|
||||
def update_router(self, context, id, router):
|
||||
LOG.debug(_("Update router in progress: id=%(id)s "
|
||||
"router=%(router)r"),
|
||||
{'id': id, 'router': router})
|
||||
session = context.session
|
||||
|
||||
processed_request = {}
|
||||
if not router['router'].get('admin_state_up', True):
|
||||
raise q_exc.NotImplementedError(_('admin_state_up=False '
|
||||
'routers are not '
|
||||
'supported.'))
|
||||
|
||||
with session.begin(subtransactions=True):
|
||||
original_router = super(SdnvePluginV2, self).get_router(
|
||||
context, id)
|
||||
processed_request['router'] = self._process_request(
|
||||
router['router'], original_router)
|
||||
updated_router = super(SdnvePluginV2, self).update_router(
|
||||
context, id, router)
|
||||
|
||||
if processed_request['router']:
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'router', id, processed_request['router'])
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).update_router(
|
||||
context, id, {'router': original_router})
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update router failed in SDN-VE: %s') % res))
|
||||
|
||||
return updated_router
|
||||
|
||||
@_ha
|
||||
def delete_router(self, context, id):
|
||||
LOG.debug(_("Delete router in progress: %s"), id)
|
||||
|
||||
super(SdnvePluginV2, self).delete_router(context, id)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_delete('router', id)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.error(
|
||||
_("Delete router operation failed in SDN-VE after "
|
||||
"deleting the router in DB: %s"), res)
|
||||
|
||||
@_ha
|
||||
def add_router_interface(self, context, router_id, interface_info):
|
||||
LOG.debug(_("Add router interface in progress: "
|
||||
"router_id=%(router_id)s "
|
||||
"interface_info=%(interface_info)r"),
|
||||
{'router_id': router_id, 'interface_info': interface_info})
|
||||
|
||||
new_interface = super(SdnvePluginV2, self).add_router_interface(
|
||||
context, router_id, interface_info)
|
||||
LOG.debug(
|
||||
_("SdnvePluginV2.add_router_interface called. Port info: %s"),
|
||||
new_interface)
|
||||
request_info = interface_info.copy()
|
||||
request_info['port_id'] = new_interface['port_id']
|
||||
# Add the subnet_id to the request sent to the controller
|
||||
if 'subnet_id' not in interface_info:
|
||||
request_info['subnet_id'] = new_interface['subnet_id']
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'router', router_id + '/add_router_interface', request_info)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).remove_router_interface(
|
||||
context, router_id, interface_info)
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update router-add-interface failed in SDN-VE: %s') %
|
||||
res))
|
||||
|
||||
LOG.debug(_("Added router interface: %r"), new_interface)
|
||||
return new_interface
|
||||
|
||||
def _add_router_interface_only(self, context, router_id, interface_info):
|
||||
LOG.debug(_("Add router interface only called: "
|
||||
"router_id=%(router_id)s "
|
||||
"interface_info=%(interface_info)r"),
|
||||
{'router_id': router_id, 'interface_info': interface_info})
|
||||
|
||||
port_id = interface_info.get('port_id')
|
||||
if port_id:
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'router', router_id + '/add_router_interface', interface_info)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.error(_("SdnvePluginV2._add_router_interface_only: "
|
||||
"failed to add the interface in the roll back."
|
||||
" of a remove_router_interface operation"))
|
||||
|
||||
@_ha
|
||||
def remove_router_interface(self, context, router_id, interface_info):
|
||||
LOG.debug(_("Remove router interface in progress: "
|
||||
"router_id=%(router_id)s "
|
||||
"interface_info=%(interface_info)r"),
|
||||
{'router_id': router_id, 'interface_info': interface_info})
|
||||
|
||||
subnet_id = interface_info.get('subnet_id')
|
||||
if not subnet_id:
|
||||
portid = interface_info.get('port_id')
|
||||
if not portid:
|
||||
raise sdnve_exc.BadInputException(msg=_('No port ID'))
|
||||
myport = super(SdnvePluginV2, self).get_port(context, portid)
|
||||
LOG.debug(_("SdnvePluginV2.remove_router_interface port: %s"),
|
||||
myport)
|
||||
myfixed_ips = myport.get('fixed_ips')
|
||||
if not myfixed_ips:
|
||||
raise sdnve_exc.BadInputException(msg=_('No fixed IP'))
|
||||
subnet_id = myfixed_ips[0].get('subnet_id')
|
||||
if subnet_id:
|
||||
interface_info['subnet_id'] = subnet_id
|
||||
LOG.debug(
|
||||
_("SdnvePluginV2.remove_router_interface subnet_id: %s"),
|
||||
subnet_id)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'router', router_id + '/remove_router_interface', interface_info)
|
||||
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update router-remove-interface failed SDN-VE: %s') %
|
||||
res))
|
||||
|
||||
session = context.session
|
||||
with session.begin(subtransactions=True):
|
||||
try:
|
||||
info = super(SdnvePluginV2, self).remove_router_interface(
|
||||
context, router_id, interface_info)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self._add_router_interface_only(context,
|
||||
router_id, interface_info)
|
||||
|
||||
return info
|
||||
|
||||
#
|
||||
# Floating Ip
|
||||
#
|
||||
|
||||
@_ha
|
||||
def create_floatingip(self, context, floatingip):
|
||||
LOG.debug(_("Create floatingip in progress: %r"),
|
||||
floatingip)
|
||||
new_floatingip = super(SdnvePluginV2, self).create_floatingip(
|
||||
context, floatingip)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_create(
|
||||
'floatingip', {'floatingip': new_floatingip})
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).delete_floatingip(
|
||||
context, new_floatingip['id'])
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Creating floating ip operation failed '
|
||||
'in SDN-VE controller: %s') % res))
|
||||
|
||||
LOG.debug(_("Created floatingip : %r"), new_floatingip)
|
||||
return new_floatingip
|
||||
|
||||
@_ha
|
||||
def update_floatingip(self, context, id, floatingip):
|
||||
LOG.debug(_("Update floatingip in progress: %r"), floatingip)
|
||||
session = context.session
|
||||
|
||||
processed_request = {}
|
||||
with session.begin(subtransactions=True):
|
||||
original_floatingip = super(
|
||||
SdnvePluginV2, self).get_floatingip(context, id)
|
||||
processed_request['floatingip'] = self._process_request(
|
||||
floatingip['floatingip'], original_floatingip)
|
||||
updated_floatingip = super(
|
||||
SdnvePluginV2, self).update_floatingip(context, id, floatingip)
|
||||
|
||||
if processed_request['floatingip']:
|
||||
(res, data) = self.sdnve_client.sdnve_update(
|
||||
'floatingip', id,
|
||||
{'floatingip': processed_request['floatingip']})
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
super(SdnvePluginV2, self).update_floatingip(
|
||||
context, id, {'floatingip': original_floatingip})
|
||||
raise sdnve_exc.SdnveException(
|
||||
msg=(_('Update floating ip failed in SDN-VE: %s') % res))
|
||||
|
||||
return updated_floatingip
|
||||
|
||||
@_ha
|
||||
def delete_floatingip(self, context, id):
|
||||
LOG.debug(_("Delete floatingip in progress: %s"), id)
|
||||
super(SdnvePluginV2, self).delete_floatingip(context, id)
|
||||
|
||||
(res, data) = self.sdnve_client.sdnve_delete('floatingip', id)
|
||||
if res not in constants.HTTP_ACCEPTABLE:
|
||||
LOG.error(_("Delete floatingip failed in SDN-VE: %s"), res)
|
0
neutron/tests/unit/ibm/__init__.py
Normal file
0
neutron/tests/unit/ibm/__init__.py
Normal file
121
neutron/tests/unit/ibm/test_sdnve_agent.py
Normal file
121
neutron/tests/unit/ibm/test_sdnve_agent.py
Normal file
@ -0,0 +1,121 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp
|
||||
|
||||
|
||||
import contextlib
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.plugins.ibm.agent import sdnve_neutron_agent
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
NOTIFIER = ('neutron.plugins.ibm.'
|
||||
'sdnve_neutron_plugin.AgentNotifierApi')
|
||||
|
||||
|
||||
class CreateAgentConfigMap(base.BaseTestCase):
|
||||
|
||||
def test_create_agent_config_map_succeeds(self):
|
||||
self.assertTrue(sdnve_neutron_agent.create_agent_config_map(cfg.CONF))
|
||||
|
||||
def test_create_agent_config_using_controller_ips(self):
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
cfg.CONF.set_override('controller_ips',
|
||||
['10.10.10.1', '10.10.10.2'], group='SDNVE')
|
||||
cfgmap = sdnve_neutron_agent.create_agent_config_map(cfg.CONF)
|
||||
self.assertEqual(cfgmap['controller_ip'], '10.10.10.1')
|
||||
|
||||
def test_create_agent_config_using_interface_mappings(self):
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
cfg.CONF.set_override('interface_mappings',
|
||||
['interface1 : eth1', 'interface2 : eth2'],
|
||||
group='SDNVE')
|
||||
cfgmap = sdnve_neutron_agent.create_agent_config_map(cfg.CONF)
|
||||
self.assertEqual(cfgmap['interface_mappings'],
|
||||
{'interface1': 'eth1', 'interface2': 'eth2'})
|
||||
|
||||
|
||||
class TestSdnveNeutronAgent(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSdnveNeutronAgent, self).setUp()
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
notifier_p = mock.patch(NOTIFIER)
|
||||
notifier_cls = notifier_p.start()
|
||||
self.notifier = mock.Mock()
|
||||
notifier_cls.return_value = self.notifier
|
||||
# Avoid rpc initialization for unit tests
|
||||
cfg.CONF.set_override('rpc_backend',
|
||||
'neutron.openstack.common.rpc.impl_fake')
|
||||
cfg.CONF.set_override('integration_bridge',
|
||||
'br_int', group='SDNVE')
|
||||
kwargs = sdnve_neutron_agent.create_agent_config_map(cfg.CONF)
|
||||
|
||||
class MockFixedIntervalLoopingCall(object):
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def start(self, interval=0):
|
||||
self.f()
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.plugins.ibm.agent.sdnve_neutron_agent.'
|
||||
'SdnveNeutronAgent.setup_integration_br',
|
||||
return_value=mock.Mock()),
|
||||
mock.patch('neutron.openstack.common.loopingcall.'
|
||||
'FixedIntervalLoopingCall',
|
||||
new=MockFixedIntervalLoopingCall)):
|
||||
self.agent = sdnve_neutron_agent.SdnveNeutronAgent(**kwargs)
|
||||
|
||||
def test_setup_physical_interfaces(self):
|
||||
with mock.patch.object(self.agent.int_br,
|
||||
'add_port') as add_port_func:
|
||||
with mock.patch.object(ip_lib,
|
||||
'device_exists',
|
||||
return_valxue=True):
|
||||
self.agent.setup_physical_interfaces({"interface1": "eth1"})
|
||||
add_port_func.assert_called_once_with('eth1')
|
||||
|
||||
def test_setup_physical_interfaces_none(self):
|
||||
with mock.patch.object(self.agent.int_br,
|
||||
'add_port') as add_port_func:
|
||||
with mock.patch.object(ip_lib,
|
||||
'device_exists',
|
||||
return_valxue=True):
|
||||
self.agent.setup_physical_interfaces({})
|
||||
self.assertFalse(add_port_func.called)
|
||||
|
||||
def test_get_info_set_controller(self):
|
||||
with mock.patch.object(self.agent.int_br,
|
||||
'run_vsctl') as run_vsctl_func:
|
||||
kwargs = {}
|
||||
kwargs['info'] = {'new_controller': '10.10.10.1'}
|
||||
self.agent.info_update('dummy', **kwargs)
|
||||
run_vsctl_func.assert_called_one_with(['set-controller',
|
||||
'br_int',
|
||||
'tcp:10.10.10.1'])
|
||||
|
||||
def test_get_info(self):
|
||||
with mock.patch.object(self.agent.int_br,
|
||||
'run_vsctl') as run_vsctl_func:
|
||||
kwargs = {}
|
||||
self.agent.info_update('dummy', **kwargs)
|
||||
self.assertFalse(run_vsctl_func.called)
|
139
neutron/tests/unit/ibm/test_sdnve_api.py
Normal file
139
neutron/tests/unit/ibm/test_sdnve_api.py
Normal file
@ -0,0 +1,139 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp
|
||||
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.ibm.common import constants
|
||||
from neutron.plugins.ibm import sdnve_api
|
||||
from neutron.tests import base
|
||||
|
||||
RESOURCE_PATH = {
|
||||
'network': "ln/networks/",
|
||||
}
|
||||
RESOURCE = 'network'
|
||||
HTTP_OK = 200
|
||||
TENANT_ID = uuidutils.generate_uuid()
|
||||
|
||||
|
||||
class TestSdnveApi(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSdnveApi, self).setUp()
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
|
||||
class MockKeystoneClient(object):
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
def get_tenant_name(self, id):
|
||||
return 'test tenant name'
|
||||
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'KeystoneClient',
|
||||
new=MockKeystoneClient):
|
||||
self.api = sdnve_api.Client()
|
||||
|
||||
def mock_do_request(self, method, url, body=None, headers=None,
|
||||
params=None, connection_type=None):
|
||||
return (HTTP_OK, url)
|
||||
|
||||
def mock_do_request_tenant(self, method, url, body=None, headers=None,
|
||||
params=None, connection_type=None):
|
||||
return (HTTP_OK, {'id': TENANT_ID,
|
||||
'network_type': constants.TENANT_TYPE_OF})
|
||||
|
||||
def mock_do_request_no_tenant(self, method, url, body=None, headers=None,
|
||||
params=None, connection_type=None):
|
||||
return (None, None)
|
||||
|
||||
def mock_process_request(self, body):
|
||||
return body
|
||||
|
||||
def test_sdnve_api_list(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request):
|
||||
result = self.api.sdnve_list(RESOURCE)
|
||||
self.assertEqual(result, (HTTP_OK, RESOURCE_PATH[RESOURCE]))
|
||||
|
||||
def test_sdnve_api_show(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request):
|
||||
result = self.api.sdnve_show(RESOURCE, TENANT_ID)
|
||||
self.assertEqual(result,
|
||||
(HTTP_OK, RESOURCE_PATH[RESOURCE] + TENANT_ID))
|
||||
|
||||
def test_sdnve_api_create(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.process_request',
|
||||
new=self.mock_process_request):
|
||||
result = self.api.sdnve_create(RESOURCE, '')
|
||||
self.assertEqual(result, (HTTP_OK, RESOURCE_PATH[RESOURCE]))
|
||||
|
||||
def test_sdnve_api_update(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.process_request',
|
||||
new=self.mock_process_request):
|
||||
result = self.api.sdnve_update(RESOURCE, TENANT_ID, '')
|
||||
self.assertEqual(result,
|
||||
(HTTP_OK,
|
||||
RESOURCE_PATH[RESOURCE] + TENANT_ID))
|
||||
|
||||
def test_sdnve_api_delete(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request):
|
||||
result = self.api.sdnve_delete(RESOURCE, TENANT_ID)
|
||||
self.assertEqual(result,
|
||||
(HTTP_OK, RESOURCE_PATH[RESOURCE] + TENANT_ID))
|
||||
|
||||
def test_sdnve_get_tenant_by_id(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request_tenant):
|
||||
id = TENANT_ID
|
||||
result = self.api.sdnve_get_tenant_byid(id)
|
||||
self.assertEqual(result,
|
||||
(TENANT_ID, constants.TENANT_TYPE_OF))
|
||||
|
||||
def test_sdnve_check_and_create_tenant(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request_tenant):
|
||||
id = TENANT_ID
|
||||
result = self.api.sdnve_check_and_create_tenant(id)
|
||||
self.assertEqual(result, TENANT_ID)
|
||||
|
||||
def test_sdnve_check_and_create_tenant_fail(self):
|
||||
with mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client.do_request',
|
||||
new=self.mock_do_request_no_tenant):
|
||||
id = TENANT_ID
|
||||
result = self.api.sdnve_check_and_create_tenant(
|
||||
id, constants.TENANT_TYPE_OF)
|
||||
self.assertIsNone(result)
|
126
neutron/tests/unit/ibm/test_sdnve_plugin.py
Normal file
126
neutron/tests/unit/ibm/test_sdnve_plugin.py
Normal file
@ -0,0 +1,126 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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: Mohammad Banikazemi, IBM Corp
|
||||
|
||||
|
||||
import contextlib
|
||||
import mock
|
||||
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.tests.unit import _test_extension_portbindings as test_bindings
|
||||
from neutron.tests.unit import test_db_plugin as test_plugin
|
||||
from neutron.tests.unit import test_l3_plugin as test_l3_plugin
|
||||
|
||||
from neutron.plugins.ibm.common import constants
|
||||
|
||||
|
||||
_plugin_name = ('neutron.plugins.ibm.'
|
||||
'sdnve_neutron_plugin.SdnvePluginV2')
|
||||
HTTP_OK = 200
|
||||
|
||||
|
||||
class MockClient(object):
|
||||
def sdnve_list(self, resource, **params):
|
||||
return (HTTP_OK, 'body')
|
||||
|
||||
def sdnve_show(self, resource, specific, **params):
|
||||
return (HTTP_OK, 'body')
|
||||
|
||||
def sdnve_create(self, resource, body):
|
||||
return (HTTP_OK, 'body')
|
||||
|
||||
def sdnve_update(self, resource, specific, body=None):
|
||||
return (HTTP_OK, 'body')
|
||||
|
||||
def sdnve_delete(self, resource, specific):
|
||||
return (HTTP_OK, 'body')
|
||||
|
||||
def sdnve_get_tenant_byid(self, os_tenant_id):
|
||||
return (os_tenant_id, constants.TENANT_TYPE_OF)
|
||||
|
||||
def sdnve_check_and_create_tenant(
|
||||
self, os_tenant_id, network_type=None):
|
||||
return os_tenant_id
|
||||
|
||||
def sdnve_get_controller(self):
|
||||
return
|
||||
|
||||
|
||||
class MockKeystoneClient(object):
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
def get_tenant_type(self, id):
|
||||
return constants.TENANT_TYPE_OF
|
||||
|
||||
def get_tenant_name(self, id):
|
||||
return "tenant name"
|
||||
|
||||
|
||||
class IBMPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
def setUp(self):
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'KeystoneClient',
|
||||
new=MockKeystoneClient),
|
||||
mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client',
|
||||
new=MockClient)):
|
||||
super(IBMPluginV2TestCase, self).setUp(plugin=_plugin_name)
|
||||
|
||||
|
||||
class TestIBMBasicGet(test_plugin.TestBasicGet,
|
||||
IBMPluginV2TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestIBMV2HTTPResponse(test_plugin.TestV2HTTPResponse,
|
||||
IBMPluginV2TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestIBMNetworksV2(test_plugin.TestNetworksV2,
|
||||
IBMPluginV2TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestIBMPortsV2(test_plugin.TestPortsV2,
|
||||
IBMPluginV2TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestIBMSubnetsV2(test_plugin.TestSubnetsV2,
|
||||
IBMPluginV2TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TestIBMPortBinding(IBMPluginV2TestCase,
|
||||
test_bindings.PortBindingsTestCase):
|
||||
VIF_TYPE = portbindings.VIF_TYPE_OVS
|
||||
|
||||
|
||||
class IBMPluginRouterTestCase(test_l3_plugin.L3NatDBIntTestCase):
|
||||
|
||||
def setUp(self):
|
||||
with contextlib.nested(
|
||||
mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'KeystoneClient',
|
||||
new=MockKeystoneClient),
|
||||
mock.patch('neutron.plugins.ibm.sdnve_api.'
|
||||
'Client',
|
||||
new=MockClient)):
|
||||
super(IBMPluginRouterTestCase, self).setUp(plugin=_plugin_name)
|
@ -51,6 +51,7 @@ data_files =
|
||||
etc/neutron/plugins/brocade = etc/neutron/plugins/brocade/brocade.ini
|
||||
etc/neutron/plugins/cisco = etc/neutron/plugins/cisco/cisco_plugins.ini
|
||||
etc/neutron/plugins/hyperv = etc/neutron/plugins/hyperv/hyperv_neutron_plugin.ini
|
||||
etc/neutron/plugins/ibm = etc/neutron/plugins/ibm/sdnve_neutron_plugin.ini
|
||||
etc/neutron/plugins/linuxbridge = etc/neutron/plugins/linuxbridge/linuxbridge_conf.ini
|
||||
etc/neutron/plugins/metaplugin = etc/neutron/plugins/metaplugin/metaplugin.ini
|
||||
etc/neutron/plugins/midonet = etc/neutron/plugins/midonet/midonet.ini
|
||||
@ -85,6 +86,7 @@ console_scripts =
|
||||
neutron-debug = neutron.debug.shell:main
|
||||
neutron-dhcp-agent = neutron.agent.dhcp_agent:main
|
||||
neutron-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
|
||||
neutron-ibm-agent = neutron.plugins.ibm.agent.sdnve_neutron_agent:main
|
||||
neutron-l3-agent = neutron.agent.l3_agent:main
|
||||
neutron-lbaas-agent = neutron.services.loadbalancer.agent.agent:main
|
||||
neutron-linuxbridge-agent = neutron.plugins.linuxbridge.agent.linuxbridge_neutron_agent:main
|
||||
@ -106,6 +108,7 @@ console_scripts =
|
||||
quantum-debug = neutron.debug.shell:main
|
||||
quantum-dhcp-agent = neutron.agent.dhcp_agent:main
|
||||
quantum-hyperv-agent = neutron.plugins.hyperv.agent.hyperv_neutron_agent:main
|
||||
quantum-ibm-agent = neutron.plugins.ibm.agent.sdnve_neutron_agent:main
|
||||
quantum-l3-agent = neutron.agent.l3_agent:main
|
||||
quantum-lbaas-agent = neutron.services.loadbalancer.agent.agent:main
|
||||
quantum-linuxbridge-agent = neutron.plugins.linuxbridge.agent.linuxbridge_neutron_agent:main
|
||||
@ -127,6 +130,7 @@ neutron.core_plugins =
|
||||
cisco = neutron.plugins.cisco.network_plugin:PluginV2
|
||||
embrane = neutron.plugins.embrane.plugins.embrane_ovs_plugin:EmbraneOvsPlugin
|
||||
hyperv = neutron.plugins.hyperv.hyperv_neutron_plugin:HyperVNeutronPlugin
|
||||
ibm = neutron.plugins.ibm.sdnve_neutron_plugin:SdnvePluginV2
|
||||
linuxbridge = neutron.plugins.linuxbridge.lb_neutron_plugin:LinuxBridgePluginV2
|
||||
midonet = neutron.plugins.midonet.plugin:MidonetPluginV2
|
||||
ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
|
||||
|
Loading…
Reference in New Issue
Block a user