--- /dev/null
+[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
'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
'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
'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
'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 = [
PLUGINS['ryu'],
PLUGINS['brocade'],
PLUGINS['plumgrid'],
+ PLUGINS['ibm'],
]
FOLSOM_QUOTA = [
--- /dev/null
+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
--- /dev/null
+# 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()
--- /dev/null
+# 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")
--- /dev/null
+# 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
+ ]
--- /dev/null
+# 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")
--- /dev/null
+# 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'
--- /dev/null
+# 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
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
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
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
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
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