diff --git a/etc/neutron/plugins/embrane/heleos_conf.ini b/etc/neutron/plugins/embrane/heleos_conf.ini new file mode 100644 index 0000000000..0ca9b46f80 --- /dev/null +++ b/etc/neutron/plugins/embrane/heleos_conf.ini @@ -0,0 +1,41 @@ +[heleos] +#configure the ESM management address +#in the first version of this plugin, only one ESM can be specified +#Example: +#esm_mgmt= + +#configure admin username and password +#admin_username= +#admin_password= + +#router image id +#Example: +#router_image=932ce713-e210-3d54-a0a5-518b0b5ee1b0 + +#mgmt shared security zone id +#defines the shared management security zone. Each tenant can have a private one configured through the ESM +#Example: +#mgmt_id=c0bc9b6c-f110-46cf-bb01-733bfe4b5a1a + +#in-band shared security zone id +#defines the shared in-band security zone. Each tenant can have a private one configured through the ESM +#Example: +#inband_id=a6b7999d-3806-4b04-81f6-e0c5c8271afc + +#oob-band shared security zone id +#defines the shared out-of-band security zone. Each tenant can have a private one configured through the ESM +#Example: +#oob_id=e7eda5cc-b977-46cb-9c14-cab43c1b7871 + +#dummy security zone id +#defines the dummy security zone ID. this security zone will be used by the DVAs with no neutron interfaces +#Example: +#dummy_utif_id=d9911310-25fc-4733-a2e0-c0eda024ef08 + +#resource pool id +#define the shared resource pool. Each tenant can have a private one configured through the ESM +#Example +#resource_pool_id= + +#define if the requests have to be executed asynchronously by the plugin or not +#async_requests= diff --git a/neutron/plugins/embrane/README b/neutron/plugins/embrane/README new file mode 100644 index 0000000000..15ad1abbd9 --- /dev/null +++ b/neutron/plugins/embrane/README @@ -0,0 +1,9 @@ +Embrane Neutron Plugin + +This plugin interfaces OpenStack Neutron with Embrane's heleos platform, which +provides layer 3-7 network services for cloud environments. + +L2 connectivity is leveraged by one of the supported existing plugins. + +For more details on use, configuration and implementation please refer to: +http://wiki.openstack.org/wiki/Neutron/EmbraneNeutronPlugin \ No newline at end of file diff --git a/neutron/plugins/embrane/__init__.py b/neutron/plugins/embrane/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/agent/__init__.py b/neutron/plugins/embrane/agent/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/agent/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/agent/dispatcher.py b/neutron/plugins/embrane/agent/dispatcher.py new file mode 100644 index 0000000000..8038b93e02 --- /dev/null +++ b/neutron/plugins/embrane/agent/dispatcher.py @@ -0,0 +1,145 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from eventlet import greenthread +from eventlet import queue +from heleosapi import constants as h_con +from heleosapi import exceptions as h_exc + +from neutron.openstack.common import log as logging +from neutron.plugins.embrane.agent.operations import router_operations +from neutron.plugins.embrane.common import constants as p_con +from neutron.plugins.embrane.common import contexts as ctx +from neutron.plugins.embrane.common import exceptions as plugin_exc + + +LOG = logging.getLogger(__name__) + + +def _validate_operation(event, status, item_id): + if status and event not in p_con.operation_filter[status]: + raise plugin_exc.StateConstraintException(operation=event, + dva_id=item_id, state=status) + + +class Dispatcher(object): + + def __init__(self, plugin, async=True): + self._async = async + self._plugin = plugin + self.sync_items = dict() + + def dispatch_l3(self, d_context, args=(), kwargs={}): + item = d_context.item + event = d_context.event + q_context = d_context.q_context + chain = d_context.chain + + item_id = item["id"] + # First round validation (Controller level) + _validate_operation(event, item["status"], item_id) + + handlers = router_operations.handlers + if event in handlers: + for f in handlers[event]: + first_run = False + if item_id not in self.sync_items: + self.sync_items[item_id] = queue.Queue() + first_run = True + self.sync_items[item_id].put( + ctx.OperationContext(event, q_context, item, chain, f, + args, kwargs)) + if first_run: + t = greenthread.spawn(self._consume_l3, + item_id, + self.sync_items[item_id], + self._plugin) + if not self._async: + t.wait() + + def _consume_l3(self, sync_item, sync_queue, plugin): + current_state = None + while True: + try: + # If the DVA is deleted, the thread (and the associated queue) + # can die as well + if current_state == p_con.Status.DELETED: + del self.sync_items[sync_item] + return + try: + operation_context = sync_queue.get( + timeout=p_con.QUEUE_TIMEOUT) + except queue.Empty: + del self.sync_items[sync_item] + return + # Second round validation (enqueued level) + _validate_operation(operation_context.event, + current_state, + operation_context.item["id"]) + # Execute the preliminary operations + (operation_context.chain and + operation_context.chain.execute_all()) + # Execute the main operation, a transient state is maintained + # so that the consumer can decide if it has + # to be burned to the DB + transient_state = None + try: + dva_state = operation_context.function( + plugin._esm_api, + operation_context.q_context.tenant_id, + operation_context.item, + *operation_context.args, + **operation_context.kwargs) + if dva_state == p_con.Status.DELETED: + transient_state = dva_state + else: + if not dva_state: + transient_state = p_con.Status.ERROR + elif dva_state == h_con.DvaState.POWER_ON: + transient_state = p_con.Status.ACTIVE + else: + transient_state = p_con.Status.READY + + except (h_exc.PendingDva, h_exc.DvaNotFound, + h_exc.BrokenInterface, h_exc.DvaCreationFailed, + h_exc.DvaCreationPending, h_exc.BrokenDva, + h_exc.ConfigurationFailed) as ex: + LOG.warning(p_con.error_map[type(ex)] % ex.message) + transient_state = p_con.Status.ERROR + except h_exc.DvaDeleteFailed as ex: + LOG.warning(p_con.error_map[type(ex)] % ex.message) + transient_state = p_con.Status.DELETED + finally: + # if the returned transient state is None, no operations + # are required on the DVA status + if transient_state: + if transient_state == p_con.Status.DELETED: + current_state = plugin._delete_router( + operation_context.q_context, + operation_context.item["id"]) + # Error state cannot be reverted + elif current_state != p_con.Status.ERROR: + current_state = plugin._update_neutron_state( + operation_context.q_context, + operation_context.item, + transient_state) + except plugin_exc.StateConstraintException as e: + LOG.error(_("%s"), e.message) + except Exception: + LOG.exception(_("Unhandled exception occurred")) diff --git a/neutron/plugins/embrane/agent/operations/__init__.py b/neutron/plugins/embrane/agent/operations/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/agent/operations/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/agent/operations/router_operations.py b/neutron/plugins/embrane/agent/operations/router_operations.py new file mode 100644 index 0000000000..d460bc47ef --- /dev/null +++ b/neutron/plugins/embrane/agent/operations/router_operations.py @@ -0,0 +1,154 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from functools import wraps + +from heleosapi import exceptions as h_exc + +from neutron.openstack.common import log as logging +from neutron.plugins.embrane.common import constants as p_con + +LOG = logging.getLogger(__name__) +handlers = dict() + + +def handler(event, handler): + def wrap(f): + if event not in handler.keys(): + new_func_list = [f] + handler[event] = new_func_list + else: + handler[event].append(f) + + @wraps(f) + def wrapped_f(*args, **kwargs): + return f(*args, **kwargs) + return wrapped_f + return wrap + + +@handler(p_con.Events.CREATE_ROUTER, handlers) +def _create_dva_and_assign_address(api, tenant_id, neutron_router, + flavor, utif_info=None, + ip_allocation_info=None): + """Creates a new router, and assign the gateway interface if any.""" + + dva = api.create_router(tenant_id=tenant_id, + router_id=neutron_router["id"], + name=neutron_router["name"], + flavor=flavor, + up=neutron_router["admin_state_up"]) + try: + if utif_info: + api.grow_interface(utif_info, neutron_router["admin_state_up"], + tenant_id, neutron_router["id"]) + if ip_allocation_info: + dva = api.allocate_address(neutron_router["id"], + neutron_router["admin_state_up"], + ip_allocation_info) + except h_exc.PreliminaryOperationsFailed as ex: + raise h_exc.BrokenInterface(err_msg=ex.message) + + state = api.extract_dva_state(dva) + return state + + +@handler(p_con.Events.UPDATE_ROUTER, handlers) +def _update_dva_and_assign_address(api, tenant_id, neutron_router, + utif_info=None, ip_allocation_info=None, + routes_info=[]): + name = neutron_router["name"] + up = neutron_router["admin_state_up"] + r_id = neutron_router["id"] + if ip_allocation_info or routes_info: + up = True + dva = api.update_dva(tenant_id=tenant_id, router_id=r_id, name=name, + up=up, utif_info=utif_info) + if ip_allocation_info: + api.allocate_address(r_id, up, ip_allocation_info) + + if routes_info: + api.delete_extra_routes(r_id, up) + api.set_extra_routes(r_id, neutron_router["admin_state_up"], + routes_info) + + return api.extract_dva_state(dva) + + +@handler(p_con.Events.DELETE_ROUTER, handlers) +def _delete_dva(api, tenant_id, neutron_router): + try: + api.delete_dva(tenant_id, neutron_router["id"]) + except h_exc.DvaNotFound: + LOG.warning(_("The router %s had no physical representation," + "likely already deleted"), neutron_router["id"]) + return p_con.Status.DELETED + + +@handler(p_con.Events.GROW_ROUTER_IF, handlers) +def _grow_dva_iface_and_assign_address(api, tenant_id, neutron_router, + utif_info=None, + ip_allocation_info=None): + try: + dva = api.grow_interface(utif_info, neutron_router["admin_state_up"], + tenant_id, neutron_router["id"]) + if ip_allocation_info: + dva = api.allocate_address(neutron_router["id"], + neutron_router["admin_state_up"], + ip_allocation_info) + except h_exc.PreliminaryOperationsFailed as ex: + raise h_exc.BrokenInterface(err_msg=ex.message) + + state = api.extract_dva_state(dva) + return state + + +@handler(p_con.Events.SHRINK_ROUTER_IF, handlers) +def _shrink_dva_iface(api, tenant_id, neutron_router, port_id): + try: + dva = api.shrink_interface(tenant_id, neutron_router["id"], + neutron_router["admin_state_up"], port_id) + except h_exc.InterfaceNotFound: + LOG.warning(_("Interface %s not found in the heleos back-end," + "likely already deleted"), port_id) + except h_exc.PreliminaryOperationsFailed as ex: + raise h_exc.BrokenInterface(err_msg=ex.message) + state = api.extract_dva_state(dva) + return state + + +@handler(p_con.Events.SET_NAT_RULE, handlers) +def _create_nat_rule(api, tenant_id, neutron_router, nat_info=None): + + dva = api.create_nat_entry(neutron_router["id"], + neutron_router["admin_state_up"], nat_info) + + state = api.extract_dva_state(dva) + return state + + +@handler(p_con.Events.RESET_NAT_RULE, handlers) +def _delete_nat_rule(api, tenant_id, neutron_router, floating_ip_id): + + dva = api.remove_nat_entry(neutron_router["id"], + neutron_router["admin_state_up"], + floating_ip_id) + + state = api.extract_dva_state(dva) + return state diff --git a/neutron/plugins/embrane/base_plugin.py b/neutron/plugins/embrane/base_plugin.py new file mode 100644 index 0000000000..2aa981f01b --- /dev/null +++ b/neutron/plugins/embrane/base_plugin.py @@ -0,0 +1,364 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from heleosapi import backend_operations as h_op +from heleosapi import constants as h_con +from heleosapi import exceptions as h_exc +from oslo.config import cfg + +from neutron.common import constants as l3_constants +from neutron.common import exceptions as neutron_exc +from neutron.db import extraroute_db +from neutron.db import l3_db +from neutron.db import models_v2 +from neutron.extensions import l3 +from neutron.openstack.common import log as logging +from neutron.plugins.embrane.agent import dispatcher +from neutron.plugins.embrane.common import config # noqa +from neutron.plugins.embrane.common import constants as p_con +from neutron.plugins.embrane.common import contexts as embrane_ctx +from neutron.plugins.embrane.common import exceptions as c_exc +from neutron.plugins.embrane.common import operation +from neutron.plugins.embrane.common import utils + +LOG = logging.getLogger(__name__) +conf = cfg.CONF.heleos + + +class EmbranePlugin(object): + """Embrane Neutron plugin. + + uses the heleos(c) platform and a support L2 plugin to leverage networking + in cloud environments. + + """ + _l3super = extraroute_db.ExtraRoute_db_mixin + + def __init__(self): + pass + + def _run_embrane_config(self): + # read configurations + config_esm_mgmt = conf.esm_mgmt + config_admin_username = conf.admin_username + config_admin_password = conf.admin_password + config_router_image_id = conf.router_image + config_security_zones = {h_con.SzType.IB: conf.inband_id, + h_con.SzType.OOB: conf.oob_id, + h_con.SzType.MGMT: conf.mgmt_id, + h_con.SzType.DUMMY: conf.dummy_utif_id} + config_resource_pool = conf.resource_pool_id + self._embrane_async = conf.async_requests + self._esm_api = h_op.BackendOperations( + esm_mgmt=config_esm_mgmt, + admin_username=config_admin_username, + admin_password=config_admin_password, + router_image_id=config_router_image_id, + security_zones=config_security_zones, + resource_pool=config_resource_pool) + self._dispatcher = dispatcher.Dispatcher(self, self._embrane_async) + + def _make_router_dict(self, *args, **kwargs): + return self._l3super._make_router_dict(self, *args, **kwargs) + + def _delete_router(self, context, router_id): + self._l3super.delete_router(self, context, router_id) + + def _update_db_router_state(self, context, neutron_router, dva_state): + if not dva_state: + new_state = p_con.Status.ERROR + elif dva_state == h_con.DvaState.POWER_ON: + new_state = p_con.Status.ACTIVE + else: + new_state = p_con.Status.READY + self._set_db_router_state(context, neutron_router, new_state) + return new_state + + def _set_db_router_state(self, context, neutron_router, new_state): + return utils.set_db_item_state(context, neutron_router, new_state) + + def _update_db_interfaces_state(self, context, neutron_router): + router_ports = self.get_ports(context, + {"device_id": [neutron_router["id"]]}) + self._esm_api.update_ports_status(neutron_router["id"], router_ports) + for port in router_ports: + db_port = self._get_port(context, port["id"]) + db_port["status"] = port["status"] + context.session.merge(db_port) + + def _update_neutron_state(self, context, neutron_router, state): + try: + self._update_db_interfaces_state(context, neutron_router) + except Exception: + LOG.exception(_("Unhandled exception occurred")) + return self._set_db_router_state(context, neutron_router, state) + + def _retrieve_prefix_from_port(self, context, neutron_port): + subnet_id = neutron_port["fixed_ips"][0]["subnet_id"] + subnet = self._get_subnet(context, subnet_id) + prefix = subnet["cidr"].split("/")[1] + return prefix + + # L3 extension + def create_router(self, context, router): + r = router["router"] + self._get_tenant_id_for_create(context, r) + with context.session.begin(subtransactions=True): + neutron_router = self._l3super.create_router(self, context, router) + network_id = None + gw_port = None + ext_gw_info = neutron_router.get(l3_db.EXTERNAL_GW_INFO) + if ext_gw_info: + network_id = ext_gw_info.get("network_id") + if network_id: + gw_ports = self.get_ports( + context, + {"device_id": [id], + "device_owner": ["network:router_gateway"]}) + if len(gw_ports) != 1: + raise c_exc.EmbranePluginException( + err_msg=_("there must be only one gateway port " + "per router at once")) + gw_port = gw_ports[0] + + # For now, only small flavor is used + utif_info = (self._plugin_support.retrieve_utif_info(context, + gw_port) + if network_id else None) + ip_allocation_info = (utils.retrieve_ip_allocation_info(self, + context, + gw_port) + if network_id else None) + neutron_router = self._l3super._get_router(self, context, + neutron_router["id"]) + neutron_router["status"] = p_con.Status.CREATING + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.CREATE_ROUTER, neutron_router, context, None), + args=(h_con.Flavor.SMALL, utif_info, ip_allocation_info)) + return self._make_router_dict(neutron_router) + + def update_router(self, context, id, router): + with context.session.begin(subtransactions=True): + db_router = self._l3super.update_router(self, context, id, router) + gw_port = None + ext_gw_info = db_router.get(l3_db.EXTERNAL_GW_INFO) + if ext_gw_info: + ext_gw_info = db_router[l3_db.EXTERNAL_GW_INFO] + network_id = (ext_gw_info.get("network_id") + if ext_gw_info else None) + if network_id: + gw_ports = self.get_ports( + context, + {"device_id": [id], + "device_owner": ["network:router_gateway"]}) + if len(gw_ports) != 1: + raise c_exc.EmbranePluginException( + err_msg=_("there must be only one gateway port" + " per router at once")) + gw_port = gw_ports[0] + + utif_info = (self._plugin_support.retrieve_utif_info(context, + gw_port) + if gw_port else None) + ip_allocation_info = (utils.retrieve_ip_allocation_info(self, + context, + gw_port) + if gw_port else None) + + routes_info = router["router"].get("routes") + + neutron_router = self._l3super._get_router(self, context, id) + state_change = operation.Operation( + self._set_db_router_state, + args=(context, neutron_router, p_con.Status.UPDATING)) + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.UPDATE_ROUTER, neutron_router, context, + state_change), + args=(utif_info, ip_allocation_info, routes_info)) + return self._make_router_dict(neutron_router) + + def get_router(self, context, id, fields=None): + """Ensures that id does exist in the ESM.""" + neutron_router = self._get_router(context, id) + + try: + if neutron_router["status"] != p_con.Status.CREATING: + self._esm_api.get_dva(id) + except h_exc.DvaNotFound: + + LOG.error(_("The following routers have not physical match: %s"), + id) + self._set_db_router_state(context, neutron_router, + p_con.Status.ERROR) + + LOG.debug(_("Requested router: %s"), neutron_router) + return self._make_router_dict(neutron_router, fields) + + def get_routers(self, context, filters=None, fields=None, sorts=None, + limit=None, marker=None, page_reverse=False): + """Retrieves the router list defined by the incoming filters.""" + router_query = self._apply_filters_to_query( + self._model_query(context, l3_db.Router), + l3_db.Router, filters) + id_list = [x["id"] for x in router_query + if x["status"] != p_con.Status.CREATING] + try: + self._esm_api.get_dvas(id_list) + except h_exc.DvaNotFound: + LOG.error(_("The following routers have not physical match: %s"), + repr(id_list)) + error_routers = [] + for id in id_list: + try: + error_routers.append(self._get_router(context, id)) + except l3.RouterNotFound: + pass + for error_router in error_routers: + self._set_db_router_state(context, error_router, + p_con.Status.ERROR) + return [self._make_router_dict(router, fields) + for router in router_query] + + def delete_router(self, context, id): + """Deletes the DVA with the specific router id.""" + # Copy of the parent validation code, shouldn't the base modules + # provide functions for validating operations? + with context.session.begin(subtransactions=True): + DEVICE_OWNER_ROUTER_INTF = l3_constants.DEVICE_OWNER_ROUTER_INTF + fips = self.get_floatingips_count(context.elevated(), + filters={"router_id": [id]}) + if fips: + raise l3.RouterInUse(router_id=id) + + device_filter = {"device_id": [id], + "device_owner": [DEVICE_OWNER_ROUTER_INTF]} + ports = self.get_ports_count(context.elevated(), + filters=device_filter) + if ports: + raise l3.RouterInUse(router_id=id) + neutron_router = self._get_router(context, id) + state_change = operation.Operation(self._set_db_router_state, + args=(context, neutron_router, + p_con.Status.DELETING)) + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.DELETE_ROUTER, neutron_router, context, + state_change), args=()) + LOG.debug(_("Deleting router=%s"), neutron_router) + return neutron_router + + def add_router_interface(self, context, router_id, interface_info): + """Grows DVA interface in the specified subnet.""" + with context.session.begin(subtransactions=True): + neutron_router = self._get_router(context, router_id) + rport_qry = context.session.query(models_v2.Port) + ports = rport_qry.filter_by( + device_id=router_id).all() + if len(ports) >= p_con.UTIF_LIMIT: + raise neutron_exc.BadRequest( + resource=router_id, + msg=("this router doesn't support more than " + + str(p_con.UTIF_LIMIT) + " interfaces")) + neutron_router_iface = self._l3super.add_router_interface( + self, context, router_id, interface_info) + port = self._get_port(context, neutron_router_iface["port_id"]) + utif_info = self._plugin_support.retrieve_utif_info(context, port) + ip_allocation_info = utils.retrieve_ip_allocation_info(self, + context, + port) + state_change = operation.Operation(self._set_db_router_state, + args=(context, neutron_router, + p_con.Status.UPDATING)) + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.GROW_ROUTER_IF, neutron_router, context, + state_change), + args=(utif_info, ip_allocation_info)) + return neutron_router_iface + + def remove_router_interface(self, context, router_id, interface_info): + port_id = None + if "port_id" in interface_info: + port_id = interface_info["port_id"] + elif "subnet_id" in interface_info: + subnet_id = interface_info["subnet_id"] + subnet = self._get_subnet(context, subnet_id) + rport_qry = context.session.query(models_v2.Port) + ports = rport_qry.filter_by( + device_id=router_id, + device_owner=l3_constants.DEVICE_OWNER_ROUTER_INTF, + network_id=subnet["network_id"]) + for p in ports: + if p["fixed_ips"][0]["subnet_id"] == subnet_id: + port_id = p["id"] + break + neutron_router = self._get_router(context, router_id) + self._l3super.remove_router_interface(self, context, router_id, + interface_info) + state_change = operation.Operation(self._set_db_router_state, + args=(context, neutron_router, + p_con.Status.UPDATING)) + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.SHRINK_ROUTER_IF, neutron_router, context, + state_change), + args=(port_id,)) + + def update_floatingip(self, context, id, floatingip): + with context.session.begin(subtransactions=True): + db_fip = self._l3super.get_floatingip(self, context, id) + result = self._l3super.update_floatingip(self, context, id, + floatingip) + + if db_fip["port_id"]: + neutron_router = self._get_router(context, db_fip["router_id"]) + fip_id = db_fip["id"] + state_change = operation.Operation( + self._set_db_router_state, + args=(context, neutron_router, p_con.Status.UPDATING)) + + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.RESET_NAT_RULE, neutron_router, context, + state_change), + args=(fip_id,)) + if floatingip["floatingip"]["port_id"]: + neutron_router = self._get_router(context, result["router_id"]) + db_fixed_port = self._get_port(context, result["port_id"]) + fixed_prefix = self._retrieve_prefix_from_port(context, + db_fixed_port) + db_floating_port = neutron_router["gw_port"] + floating_prefix = self._retrieve_prefix_from_port( + context, db_floating_port) + nat_info = utils.retrieve_nat_info(context, result, + fixed_prefix, + floating_prefix, + neutron_router) + state_change = operation.Operation( + self._set_db_router_state, + args=(context, neutron_router, p_con.Status.UPDATING)) + + self._dispatcher.dispatch_l3( + d_context=embrane_ctx.DispatcherContext( + p_con.Events.SET_NAT_RULE, neutron_router, context, + state_change), + args=(nat_info,)) + return result diff --git a/neutron/plugins/embrane/common/__init__.py b/neutron/plugins/embrane/common/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/common/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/common/config.py b/neutron/plugins/embrane/common/config.py new file mode 100644 index 0000000000..bbf825b9b7 --- /dev/null +++ b/neutron/plugins/embrane/common/config.py @@ -0,0 +1,50 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from oslo.config import cfg + + +heleos_opts = [ + cfg.StrOpt('esm_mgmt', + default=None, + help=_('ESM management root address')), + cfg.StrOpt('admin_username', default='admin', + help=_('ESM admin username.')), + cfg.StrOpt('admin_password', default=None, + secret=True, + help=_('ESM admin password.')), + cfg.StrOpt('router_image', default=None, + help=_('Router image id (Embrane FW/VPN)')), + cfg.StrOpt('inband_id', default=None, + help=_('In band Security Zone id')), + cfg.StrOpt('oob_id', default=None, + help=_('Out of band Security Zone id')), + cfg.StrOpt('mgmt_id', default=None, + help=_('Management Security Zone id')), + cfg.StrOpt('dummy_utif_id', default=None, + help=_('Dummy user traffic Security Zone id')), + cfg.StrOpt('resource_pool_id', default='default', + help=_('Shared resource pool id')), + cfg.BoolOpt('async_requests', default=True, + help=_('define if the requests have ' + 'run asynchronously or not')), +] + + +cfg.CONF.register_opts(heleos_opts, "heleos") diff --git a/neutron/plugins/embrane/common/constants.py b/neutron/plugins/embrane/common/constants.py new file mode 100644 index 0000000000..ae75e89d2c --- /dev/null +++ b/neutron/plugins/embrane/common/constants.py @@ -0,0 +1,84 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from heleosapi import exceptions as h_exc + +from neutron.plugins.common import constants + + +# Router specific constants +UTIF_LIMIT = 7 +QUEUE_TIMEOUT = 300 + + +class Status: + # Transient + CREATING = constants.PENDING_CREATE + UPDATING = constants.PENDING_UPDATE + DELETING = constants.PENDING_DELETE + # Final + ACTIVE = constants.ACTIVE + ERROR = constants.ERROR + READY = constants.INACTIVE + DELETED = "DELETED" # not visible + + +class Events: + CREATE_ROUTER = "create_router" + UPDATE_ROUTER = "update_router" + DELETE_ROUTER = "delete_router" + GROW_ROUTER_IF = "grow_router_if" + SHRINK_ROUTER_IF = "shrink_router_if" + SET_NAT_RULE = "set_nat_rule" + RESET_NAT_RULE = "reset_nat_rule" + +operation_filter = { + Status.ACTIVE: [Events.DELETE_ROUTER, Events.GROW_ROUTER_IF, + Events.SHRINK_ROUTER_IF, Events.UPDATE_ROUTER, + Events.SET_NAT_RULE, Events.RESET_NAT_RULE], + Status.READY: [Events.DELETE_ROUTER, Events.GROW_ROUTER_IF, + Events.SHRINK_ROUTER_IF, Events.UPDATE_ROUTER], + Status.ERROR: [Events.DELETE_ROUTER, Events.SHRINK_ROUTER_IF], + Status.UPDATING: [Events.DELETE_ROUTER, Events.SHRINK_ROUTER_IF, + Events.RESET_NAT_RULE], + Status.CREATING: [Events.DELETE_ROUTER, Events.CREATE_ROUTER], + Status.DELETING: [Events.DELETE_ROUTER]} + +_DVA_PENDING_ERROR_MSG = _("Dva is pending for the following reason: %s") +_DVA_NOT_FOUNT_ERROR_MSG = _("Dva can't be found to execute the operation, " + "probably was cancelled through the heleos UI") +_DVA_BROKEN_ERROR_MSG = _("Dva seems to be broken for reason %s") +_DVA_BROKEN_INTERFACE_ERROR_MSG = _("Dva interface seems to be broken " + "for reason %s") +_DVA_CREATION_FAILED_ERROR_MSG = _("Dva creation failed reason %s") +_DVA_CREATION_PENDING_ERROR_MSG = _("Dva creation is in pending state " + "for reason %s") +_CFG_FAILED_ERROR_MSG = _("Dva configuration failed for reason %s") +_DVA_DEL_FAILED_ERROR_MSG = _("Failed to delete the backend " + "router for reason %s. Please remove " + "it manually through the heleos UI") + +error_map = {h_exc.PendingDva: _DVA_PENDING_ERROR_MSG, + h_exc.DvaNotFound: _DVA_NOT_FOUNT_ERROR_MSG, + h_exc.BrokenDva: _DVA_BROKEN_ERROR_MSG, + h_exc.BrokenInterface: _DVA_BROKEN_INTERFACE_ERROR_MSG, + h_exc.DvaCreationFailed: _DVA_CREATION_FAILED_ERROR_MSG, + h_exc.DvaCreationPending: _DVA_CREATION_PENDING_ERROR_MSG, + h_exc.ConfigurationFailed: _CFG_FAILED_ERROR_MSG, + h_exc.DvaDeleteFailed: _DVA_DEL_FAILED_ERROR_MSG} diff --git a/neutron/plugins/embrane/common/contexts.py b/neutron/plugins/embrane/common/contexts.py new file mode 100644 index 0000000000..fbbf0aff51 --- /dev/null +++ b/neutron/plugins/embrane/common/contexts.py @@ -0,0 +1,40 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + + +class DispatcherContext(object): + + def __init__(self, event, item, neutron_context, chain=None): + self.event = event + self.item = item + self.q_context = neutron_context + self.chain = chain + + +class OperationContext(DispatcherContext): + """Operational context. + + contains all the parameters needed to execute a status aware operation + + """ + def __init__(self, event, context, item, chain, function, args, kwargs): + super(OperationContext, self).__init__(event, item, context, chain) + self.function = function + self.args = args + self.kwargs = kwargs diff --git a/neutron/plugins/embrane/common/exceptions.py b/neutron/plugins/embrane/common/exceptions.py new file mode 100644 index 0000000000..763dabdad2 --- /dev/null +++ b/neutron/plugins/embrane/common/exceptions.py @@ -0,0 +1,35 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from neutron.common import exceptions as neutron_exec + + +class EmbranePluginException(neutron_exec.NeutronException): + message = _("An unexpected error occurred:%(err_msg)s") + + +# Not permitted operation +class NonPermitted(neutron_exec.BadRequest): + pass + + +class StateConstraintException(NonPermitted): + message = _("Operation not permitted due to state constraint violation:" + "%(operation)s not allowed for DVA %(dva_id)s in state " + " %(state)s") diff --git a/neutron/plugins/embrane/common/operation.py b/neutron/plugins/embrane/common/operation.py new file mode 100644 index 0000000000..39fa413e2c --- /dev/null +++ b/neutron/plugins/embrane/common/operation.py @@ -0,0 +1,51 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + + +class Operation(object): + """Defines a series of operations which shall be executed in order. + + the operations expected are procedures, return values are discarded + + """ + + def __init__(self, procedure, args=(), kwargs={}, nextop=None): + self._procedure = procedure + self.args = args[:] + self.kwargs = dict(kwargs) + self.nextop = nextop + + def execute(self): + args = self.args + self._procedure(*args, **self.kwargs) + return self.nextop + + def execute_all(self): + nextop = self.execute() + while nextop: + nextop = self.execute_all() + + def has_next(self): + return self.nextop is not None + + def add_bottom_operation(self, operation): + op = self + while op.has_next(): + op = op.nextop + op.nextop = operation diff --git a/neutron/plugins/embrane/common/utils.py b/neutron/plugins/embrane/common/utils.py new file mode 100644 index 0000000000..78c3f8f730 --- /dev/null +++ b/neutron/plugins/embrane/common/utils.py @@ -0,0 +1,65 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from heleosapi import info as h_info + +from neutron.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +def set_db_item_state(context, neutron_item, new_state): + with context.session.begin(subtransactions=True): + if neutron_item["status"] != new_state: + neutron_item["status"] = new_state + context.session.merge(neutron_item) + + +def retrieve_ip_allocation_info(l2_plugin, context, neutron_port): + """Retrieves ip allocation info for a specific port if any.""" + + try: + subnet_id = neutron_port["fixed_ips"][0]["subnet_id"] + except (KeyError, IndexError): + LOG.info(_("No ip allocation set")) + return + subnet = l2_plugin._get_subnet(context, subnet_id) + allocated_ip = neutron_port["fixed_ips"][0]["ip_address"] + is_gw_port = neutron_port["device_owner"] == "network:router_gateway" + gateway_ip = subnet["gateway_ip"] + + ip_allocation_info = h_info.IpAllocationInfo( + is_gw=is_gw_port, + ip_version=subnet["ip_version"], + prefix=subnet["cidr"].split("/")[1], + ip_address=allocated_ip, + port_id=neutron_port["id"], + gateway_ip=gateway_ip) + + return ip_allocation_info + + +def retrieve_nat_info(context, fip, fixed_prefix, floating_prefix, router): + nat_info = h_info.NatInfo(source_address=fip["floating_ip_address"], + source_prefix=floating_prefix, + destination_address=fip["fixed_ip_address"], + destination_prefix=fixed_prefix, + floating_ip_id=fip["id"], + fixed_port_id=fip["port_id"]) + return nat_info diff --git a/neutron/plugins/embrane/l2base/__init__.py b/neutron/plugins/embrane/l2base/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/l2base/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/l2base/fake/__init__.py b/neutron/plugins/embrane/l2base/fake/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/l2base/fake/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/l2base/fake/fake_l2_plugin.py b/neutron/plugins/embrane/l2base/fake/fake_l2_plugin.py new file mode 100644 index 0000000000..5cf68df289 --- /dev/null +++ b/neutron/plugins/embrane/l2base/fake/fake_l2_plugin.py @@ -0,0 +1,24 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from neutron.db import db_base_plugin_v2 + + +class FakeL2Plugin(db_base_plugin_v2.NeutronDbPluginV2): + supported_extension_aliases = [] diff --git a/neutron/plugins/embrane/l2base/fake/fakeplugin_support.py b/neutron/plugins/embrane/l2base/fake/fakeplugin_support.py new file mode 100644 index 0000000000..51a4d0b733 --- /dev/null +++ b/neutron/plugins/embrane/l2base/fake/fakeplugin_support.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from heleosapi import info as h_info + +from neutron import manager +from neutron.plugins.embrane.l2base import support_base as base + + +class FakePluginSupport(base.SupportBase): + + def __init__(self): + super(FakePluginSupport, self).__init__() + + def retrieve_utif_info(self, context, neutron_port): + plugin = manager.NeutronManager.get_plugin() + network_id = neutron_port["network_id"] + network = plugin._get_network(context, network_id) + is_gw = neutron_port["device_owner"] == "network:router_gateway" + result = h_info.UtifInfo(vlan=0, + network_name=network["name"], + network_id=network["id"], + is_gw=is_gw, + owner_tenant=network["tenant_id"], + port_id=neutron_port["id"], + mac_address=neutron_port["mac_address"]) + return result diff --git a/neutron/plugins/embrane/l2base/openvswitch/__init__.py b/neutron/plugins/embrane/l2base/openvswitch/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/l2base/openvswitch/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/l2base/openvswitch/openvswitch_support.py b/neutron/plugins/embrane/l2base/openvswitch/openvswitch_support.py new file mode 100644 index 0000000000..d342abaef3 --- /dev/null +++ b/neutron/plugins/embrane/l2base/openvswitch/openvswitch_support.py @@ -0,0 +1,56 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from heleosapi import info as h_info + +from neutron import manager +from neutron.plugins.embrane.l2base import support_base as base +from neutron.plugins.embrane.l2base import support_exceptions as exc +from neutron.plugins.openvswitch import ovs_db_v2 + + +class OpenvswitchSupport(base.SupportBase): + """OpenVSwitch plugin support. + + Obtains the informations needed to build the user security zones + + """ + + def __init__(self): + super(OpenvswitchSupport, self).__init__() + + def retrieve_utif_info(self, context, neutron_port): + plugin = manager.NeutronManager.get_plugin() + session = context.session + network_id = neutron_port["network_id"] + network_binding = ovs_db_v2.get_network_binding(session, network_id) + if not network_binding["segmentation_id"]: + raise exc.UtifInfoError( + err_msg=_("No segmentation_id found for the network, " + "please be sure that tenant_network_type is vlan")) + network = plugin._get_network(context, network_id) + is_gw = neutron_port["device_owner"] == "network:router_gateway" + result = h_info.UtifInfo(vlan=network_binding["segmentation_id"], + network_name=network["name"], + network_id=network["id"], + is_gw=is_gw, + owner_tenant=network["tenant_id"], + port_id=neutron_port["id"], + mac_address=neutron_port["mac_address"]) + return result diff --git a/neutron/plugins/embrane/l2base/support_base.py b/neutron/plugins/embrane/l2base/support_base.py new file mode 100644 index 0000000000..ee4ca65cc5 --- /dev/null +++ b/neutron/plugins/embrane/l2base/support_base.py @@ -0,0 +1,48 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +import abc + + +class SupportBase(object): + """abstract support class. + + Defines the methods a plugin support should implement to be used as + the L2 base for Embrane plugin. + + """ + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def __init__(self): + pass + + @abc.abstractmethod + def retrieve_utif_info(self, context, neutron_port=None, network=None): + """Retrieve specific network info. + + each plugin support, querying its own DB, can collect all the + information needed by the ESM in order to create the + user traffic security zone. + + :param interface_info: the foo parameter + :param context: neutron request context + :returns: heleosapi.info.UtifInfo -- specific network info + :raises: UtifInfoError + """ diff --git a/neutron/plugins/embrane/l2base/support_exceptions.py b/neutron/plugins/embrane/l2base/support_exceptions.py new file mode 100644 index 0000000000..3af17e3118 --- /dev/null +++ b/neutron/plugins/embrane/l2base/support_exceptions.py @@ -0,0 +1,25 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from neutron.plugins.embrane.common import exceptions as embrane_exc + + +class UtifInfoError(embrane_exc.EmbranePluginException): + message = _("cannot retrieve utif info for the following reason: " + "%(err_msg)s") diff --git a/neutron/plugins/embrane/plugins/__init__.py b/neutron/plugins/embrane/plugins/__init__.py new file mode 100644 index 0000000000..1fac4725b7 --- /dev/null +++ b/neutron/plugins/embrane/plugins/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/plugins/embrane/plugins/embrane_fake_plugin.py b/neutron/plugins/embrane/plugins/embrane_fake_plugin.py new file mode 100644 index 0000000000..a7ba6782dc --- /dev/null +++ b/neutron/plugins/embrane/plugins/embrane_fake_plugin.py @@ -0,0 +1,34 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from neutron.db import extraroute_db +from neutron.plugins.embrane import base_plugin as base +from neutron.plugins.embrane.l2base.fake import fake_l2_plugin as l2 +from neutron.plugins.embrane.l2base.fake import fakeplugin_support as sup + + +class EmbraneFakePlugin(base.EmbranePlugin, extraroute_db.ExtraRoute_db_mixin, + l2.FakeL2Plugin): + _plugin_support = sup.FakePluginSupport + + def __init__(self): + '''First run plugin specific initialization, then Embrane's.''' + self.supported_extension_aliases += ["extraroute", "router"] + l2.FakeL2Plugin.__init__(self) + self._run_embrane_config() diff --git a/neutron/plugins/embrane/plugins/embrane_ovs_plugin.py b/neutron/plugins/embrane/plugins/embrane_ovs_plugin.py new file mode 100644 index 0000000000..ded3b9916b --- /dev/null +++ b/neutron/plugins/embrane/plugins/embrane_ovs_plugin.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +from neutron.plugins.embrane import base_plugin as base +from neutron.plugins.embrane.l2base.openvswitch import openvswitch_support +from neutron.plugins.openvswitch import ovs_neutron_plugin as l2 + + +class EmbraneOvsPlugin(base.EmbranePlugin, l2.OVSNeutronPluginV2): + '''EmbraneOvsPlugin. + + This plugin uses OpenVSwitch specific L2 plugin for providing L2 networks + and the base EmbranePlugin for L3. + + ''' + _plugin_support = openvswitch_support.OpenvswitchSupport + + def __init__(self): + '''First run plugin specific initialization, then Embrane's.''' + l2.OVSNeutronPluginV2.__init__(self) + self._run_embrane_config() diff --git a/neutron/tests/unit/embrane/__init__.py b/neutron/tests/unit/embrane/__init__.py new file mode 100644 index 0000000000..bb81770cdb --- /dev/null +++ b/neutron/tests/unit/embrane/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. diff --git a/neutron/tests/unit/embrane/test_embrane_defaults.py b/neutron/tests/unit/embrane/test_embrane_defaults.py new file mode 100644 index 0000000000..1bb6a565ec --- /dev/null +++ b/neutron/tests/unit/embrane/test_embrane_defaults.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +import sys + +import mock +from oslo.config import cfg + +from neutron.plugins.embrane.common import config # noqa +from neutron.tests import base + +# Need to mock heleosapi. +sys.modules["heleosapi"] = mock.Mock() + + +class ConfigurationTest(base.BaseTestCase): + + def test_defaults(self): + self.assertEqual('admin', cfg.CONF.heleos.admin_username) + self.assertEqual('default', cfg.CONF.heleos.resource_pool_id) + self.assertTrue(cfg.CONF.heleos.async_requests) diff --git a/neutron/tests/unit/embrane/test_embrane_l3_plugin.py b/neutron/tests/unit/embrane/test_embrane_l3_plugin.py new file mode 100644 index 0000000000..f7353bd415 --- /dev/null +++ b/neutron/tests/unit/embrane/test_embrane_l3_plugin.py @@ -0,0 +1,46 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +import sys + +import mock +from oslo.config import cfg + +from neutron.db import api as db +from neutron.plugins.embrane.common import config # noqa +from neutron.tests.unit import test_extension_extraroute as extraroute_test +from neutron.tests.unit import test_l3_plugin as router_test + +PLUGIN_NAME = ('neutron.plugins.embrane.plugins.embrane_fake_plugin.' + 'EmbraneFakePlugin') +sys.modules["heleosapi"] = mock.Mock() + + +class TestEmbraneL3NatDBTestCase(router_test.L3NatDBTestCase): + _plugin_name = PLUGIN_NAME + + def setUp(self): + cfg.CONF.set_override('admin_password', "admin123", 'heleos') + self.addCleanup(cfg.CONF.reset) + self.addCleanup(db.clear_db) + super(TestEmbraneL3NatDBTestCase, self).setUp() + + +class ExtraRouteDBTestCase(extraroute_test.ExtraRouteDBTestCase): + _plugin_name = PLUGIN_NAME diff --git a/neutron/tests/unit/embrane/test_embrane_neutron_plugin.py b/neutron/tests/unit/embrane/test_embrane_neutron_plugin.py new file mode 100644 index 0000000000..4594de9710 --- /dev/null +++ b/neutron/tests/unit/embrane/test_embrane_neutron_plugin.py @@ -0,0 +1,81 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Embrane, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ivar Lazzaro, Embrane, Inc. + +import sys + +import mock +from oslo.config import cfg + +from neutron.db import api as db +from neutron.plugins.embrane.common import config # noqa +from neutron.tests.unit import test_db_plugin as test_plugin + +PLUGIN_NAME = ('neutron.plugins.embrane.plugins.embrane_fake_plugin.' + 'EmbraneFakePlugin') +sys.modules["heleosapi"] = mock.Mock() + + +class EmbranePluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): + _plugin_name = PLUGIN_NAME + + def setUp(self): + cfg.CONF.set_override('admin_password', "admin123", 'heleos') + self.addCleanup(cfg.CONF.reset) + self.addCleanup(db.clear_db) + super(EmbranePluginV2TestCase, self).setUp(self._plugin_name) + + +class TestEmbraneBasicGet(test_plugin.TestBasicGet, EmbranePluginV2TestCase): + pass + + +class TestEmbraneV2HTTPResponse(test_plugin.TestV2HTTPResponse, + EmbranePluginV2TestCase): + pass + + +class TestEmbranePortsV2(test_plugin.TestPortsV2, EmbranePluginV2TestCase): + + def test_create_ports_bulk_emulated_plugin_failure(self): + self.skip("Temporary skipping due to incompatibility with the" + " plugin dynamic class type") + + def test_recycle_expired_previously_run_within_context(self): + self.skip("Temporary skipping due to incompatibility with the" + " plugin dynamic class type") + + def test_recycle_held_ip_address(self): + self.skip("Temporary skipping due to incompatibility with the" + " plugin dynamic class type") + + +class TestEmbraneNetworksV2(test_plugin.TestNetworksV2, + EmbranePluginV2TestCase): + + def test_create_networks_bulk_emulated_plugin_failure(self): + self.skip("Temporary skipping due to incompatibility with the" + " plugin dynamic class type") + + +class TestEmbraneSubnetsV2(test_plugin.TestSubnetsV2, + EmbranePluginV2TestCase): + + def test_create_subnets_bulk_emulated_plugin_failure(self): + self.skip("Temporary skipping due to incompatibility with the" + " plugin dynamic class type")