3c95d18311
Implements blueprint embrane-neutron-plugin This commit implements the main Embrane plugin, which covers route and extratoute extensions. The plugin will rely on existing plugins for leveraging L2 networks, for now it only supports OpenVSwitch and vlan networks, but more supports are to come if the model is approved (or a different one is suggested). Change-Id: I90aff8ec4324bd3a7c59c411374db6a118d1a72b
365 lines
17 KiB
Python
365 lines
17 KiB
Python
# 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
|