Brocade plugins/drivers for ML2 and Service Plugins
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
networking-brocade/networking_brocade/vyatta/vrouter/neutron_plugin.py

715 lines
30 KiB

# Copyright 2015 Brocade Communications System, 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.
import netaddr
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import importutils
from sqlalchemy.orm import exc
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.api.rpc.handlers import l3_rpc
from neutron.api.v2 import attributes
from neutron.common import constants as l3_constants
from neutron.common import exceptions as q_exc
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron.db import common_db_mixin
from neutron.db import extraroute_db
from neutron.db import l3_agentschedulers_db
from neutron.db import l3_db
from neutron.db import l3_dvr_db
from neutron.db import l3_gwmode_db
from neutron.db import models_v2
from neutron.extensions import l3
from neutron.extensions import portsecurity as psec
from neutron.i18n import _LE
from neutron.plugins.common import constants
from networking_brocade.vyatta.common import config
from networking_brocade.vyatta.common import exceptions as v_exc
from networking_brocade.vyatta.common import utils as vyatta_utils
from networking_brocade.vyatta.vrouter import driver as vrouter_driver
LOG = logging.getLogger(__name__)
class VyattaVRouterMixin(common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin,
l3_dvr_db.L3_NAT_with_dvr_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
l3_agentschedulers_db.L3AgentSchedulerDbMixin):
"""Brocade Neutron L3 Plugin for Vyatta vRouter.
Supports CRUD operations on vRouter, add/remove interfaces from vRouter
and floating IPs for VMs.It performs vRouter VM lifecyle management by
calling Nova APIs during the Create and Delete Router calls.
Once the vRouter VM is up, L3 plugin uses REST API to perform the
configurations. L3 plugin supports add/remove router interfaces by
attaching the neutron ports to vRouter VM using Nova API.
RPC notifications will be used by the firewall agent that is coupled
with l3-agent. This is needed for our firewall plugin.
"""
ATTACH_PORT_RETRY_LIMIT = 5
ATTACH_PORT_RETRY_DELAY = 5
def __init__(self):
self.setup_rpc()
self.driver = vrouter_driver.VyattaVRouterDriver()
self.router_scheduler = importutils.import_object(
config.CONF.router_scheduler_driver)
self.start_periodic_l3_agent_status_check()
def setup_rpc(self):
# RPC support
self.topic = topics.L3PLUGIN
self.conn = n_rpc.create_connection(new=True)
self.agent_notifiers.update(
{l3_constants.AGENT_TYPE_L3: l3_rpc_agent_api.L3AgentNotifyAPI()})
self.endpoints = [_VyattaL3RPCEndpoint()]
self.conn.create_consumer(self.topic, self.endpoints,
fanout=False)
self.conn.consume_in_threads()
def get_plugin_type(self):
return constants.L3_ROUTER_NAT
def get_plugin_description(self):
"""Returns string description of the plugin."""
return ("Brocade Vyatta Router Service Plugin for basic L3 forwarding "
"between (L2) Neutron networks and access to external "
"networks via a NAT gateway.")
def create_router(self, context, router):
"""Creates the vRouter VM using vrouter_driver.
If we encounter vRouter VM creation failure or connectivity failure
vrouter_driver will handle the appropriate exceptions and delete
the vRouter VM.
"""
LOG.debug("Vyatta vRouter Plugin::Create router: %s", router)
r = router['router']
router_id = self.driver.create_router(context)
if router_id is None:
raise q_exc.BadRequest(
resource='router',
msg=_('Vyatta vRouter creation failed'))
gw_info = r.pop(l3.EXTERNAL_GW_INFO, attributes.ATTR_NOT_SPECIFIED)
tenant_id = self._get_tenant_id_for_create(context, r)
with context.session.begin(subtransactions=True):
# noinspection PyArgumentList
router_db = l3_db.Router(id=router_id,
tenant_id=tenant_id,
name=r['name'],
admin_state_up=r['admin_state_up'],
status="ACTIVE")
context.session.add(router_db)
self._process_extra_attr_router_create(context, router_db, router)
router_dict = self._make_router_dict(router_db)
try:
self.driver.init_router(context, router_dict)
except (v_exc.InvalidVRouterInstance,
v_exc.InvalidInstanceConfiguration,
v_exc.VRouterConnectFailure,
v_exc.VRouterOperationError,
Exception):
with excutils.save_and_reraise_exception():
with context.session.begin(subtransactions=True):
context.session.delete(router_db)
if gw_info != attributes.ATTR_NOT_SPECIFIED:
try:
self._update_router_gw_info(context, router_db['id'], gw_info)
router_dict[l3.EXTERNAL_GW_INFO] = gw_info
except q_exc.NeutronException:
self.delete_router(context, router_id)
with excutils.save_and_reraise_exception():
with context.session.begin(subtransactions=True):
context.session.delete(router_db)
return self._make_router_dict(router_db)
def update_router(self, context, router_id, router):
LOG.debug("Vyatta vRouter Plugin::Update router: %s", router)
r = router['router']
gw_info = r.pop(l3.EXTERNAL_GW_INFO, attributes.ATTR_NOT_SPECIFIED)
if gw_info != attributes.ATTR_NOT_SPECIFIED:
self._update_router_gw_info(context, router_id, gw_info)
return super(VyattaVRouterMixin, self).update_router(
context, router_id, router)
def delete_router(self, context, router_id):
LOG.debug("Vyatta vRouter Plugin::Delete router: %s", router_id)
gw_port = None
with context.session.begin(subtransactions=True):
router = self._get_router(context, router_id)
self._ensure_router_not_in_use(context, router_id)
# delete any gw port
device_filter = {
'device_id': [router_id],
'device_owner': [l3_constants.DEVICE_OWNER_ROUTER_GW]
}
ports = self._core_plugin.get_ports(context.elevated(),
filters=device_filter)
if ports:
gw_port = ports[0]
router.gw_port = None
context.session.add(router)
if gw_port:
self._delete_router_port(context, router_id, gw_port,
external_gw=True)
with context.session.begin(subtransactions=True):
context.session.delete(router)
self.driver.delete_router(context, router_id)
self.l3_rpc_notifier.router_deleted(context, router_id)
def add_router_interface(self, context, router_id, interface_info):
LOG.debug("Vyatta vRouter Plugin::Add Router Interface. "
"router: %s; interface: %s", router_id, interface_info)
add_by_port, add_by_sub = self._validate_interface_info(interface_info)
router = self._get_router(context, router_id)
new_port = True
if add_by_port:
port, subnets = self._add_interface_by_port(
context, router, interface_info['port_id'], '')
elif add_by_sub:
port, subnets, new_port = self._add_interface_by_subnet(
context, router, interface_info['subnet_id'], '')
if new_port:
port_tenant_id = port['tenant_id']
self._core_plugin._delete_port_security_group_bindings(
context.elevated(), port['id'])
port = self._core_plugin.update_port(
context.elevated(), port['id'], {'port': {
'tenant_id': config.VROUTER.tenant_id,
'device_id': '',
psec.PORTSECURITY: False,
}})
try:
self._attach_port(context, router_id, port)
except Exception:
with excutils.save_and_reraise_exception():
if add_by_sub:
try:
self._core_plugin.delete_port(context.elevated(),
port['id'])
except Exception:
LOG.exception(_LE(
'Failed to delete previously created '
'port for Vyatta vRouter.'))
port = self._core_plugin.update_port(
context.elevated(), port['id'], {'port': {
'tenant_id': port_tenant_id,
}})
with context.session.begin(subtransactions=True):
router_port = l3_db.RouterPort(
port_id=port['id'],
router_id=router.id,
port_type=port['device_owner']
)
context.session.add(router_port)
else:
self._update_port(context, router_id, port)
router_interface_info = self._make_router_interface_info(
router_id, port['tenant_id'], port['id'], subnets[-1]['id'],
[subnet['id'] for subnet in subnets])
self.notify_router_interface_action(
context, router_interface_info, 'add')
return router_interface_info
def remove_router_interface(self, context, router_id, interface_info):
LOG.debug("Vyatta vRouter Plugin::Remove Router Interface. "
"router: %s; interface_info: %s", router_id, interface_info)
remove_by_port, remove_by_subnet = (
self._validate_interface_info(interface_info, for_removal=True)
)
port_id = interface_info.get('port_id')
subnet_id = interface_info.get('subnet_id')
router = self._get_router(context, router_id)
device_owner = self._get_device_owner(context, router)
if remove_by_port:
port, subnets, delete_port = self._remove_interface_by_port(
context, router_id, port_id, subnet_id, device_owner)
# remove_by_subnet is not used here, because the validation logic of
# _validate_interface_info ensures that at least one of remote_by_*
# is True.
else:
port, subnets, delete_port = self._remove_interface_by_subnet(
context, router_id, subnet_id, device_owner)
port_tenant_id = port['tenant_id']
if delete_port:
port = self._core_plugin.update_port(
context.elevated(), port['id'], {'port': {
'tenant_id': config.VROUTER.tenant_id,
}})
self._delete_router_port(context, router_id, port)
else:
self._update_port(context, router_id, port)
router_interface_info = self._make_router_interface_info(
router_id, port_tenant_id, port['id'], subnets[0]['id'],
[subnet['id'] for subnet in subnets])
self.notify_router_interface_action(
context, router_interface_info, 'remove')
return router_interface_info
def _remove_interface_by_port(self, context, router_id,
port_id, subnet_id, owner):
qry = context.session.query(l3_db.RouterPort)
qry = qry.filter_by(
port_id=port_id,
router_id=router_id,
port_type=owner
)
try:
port_db = qry.one().port
except exc.NoResultFound:
raise l3.RouterInterfaceNotFound(router_id=router_id,
port_id=port_id)
port_subnet_ids = [fixed_ip['subnet_id']
for fixed_ip in port_db['fixed_ips']]
if subnet_id and subnet_id not in port_subnet_ids:
raise q_exc.SubnetMismatchForPort(
port_id=port_id, subnet_id=subnet_id)
subnets = [self._core_plugin._get_subnet(context, port_subnet_id)
for port_subnet_id in port_subnet_ids]
for port_subnet_id in port_subnet_ids:
self._confirm_router_interface_not_in_use(
context, router_id, port_subnet_id)
return port_db, subnets, True
def _remove_interface_by_subnet(self, context,
router_id, subnet_id, owner):
self._confirm_router_interface_not_in_use(
context, router_id, subnet_id)
subnet = self._core_plugin._get_subnet(context, subnet_id)
try:
rport_qry = context.session.query(models_v2.Port).join(
l3_db.RouterPort)
ports = rport_qry.filter(
l3_db.RouterPort.router_id == router_id,
l3_db.RouterPort.port_type == owner,
models_v2.Port.network_id == subnet['network_id']
)
for p in ports:
port_subnets = [fip['subnet_id'] for fip in p['fixed_ips']]
if subnet_id in port_subnets and len(port_subnets) > 1:
# multiple prefix port - delete prefix from port
fixed_ips = [fip for fip in p['fixed_ips'] if
fip['subnet_id'] != subnet_id]
p = self._core_plugin.update_port(context, p['id'],
{'port':
{'fixed_ips': fixed_ips}})
return p, [subnet], False
elif subnet_id in port_subnets:
return p, [subnet], True
except exc.NoResultFound:
pass
raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
subnet_id=subnet_id)
def _get_interface_infos(self, context, port):
LOG.debug("Vyatta vRouter Plugin::Get interface infos")
mac_address = port['mac_address']
interface_infos = []
for fip in port['fixed_ips']:
try:
subnet = self._core_plugin._get_subnet(context.elevated(),
fip['subnet_id'])
ipnet = netaddr.IPNetwork(subnet.cidr)
interface_infos.append({
'mac_address': mac_address,
'ip_address': '{0}/{1}'.format(fip['ip_address'],
ipnet.prefixlen),
'gateway_ip': subnet.gateway_ip
})
except q_exc.SubnetNotFound:
pass
return interface_infos
def _get_interface_infos_new(self, context, port):
mac_address = port['mac_address']
ip_list = []
for fixed_ip in port['fixed_ips']:
subnet = self._core_plugin._get_subnet(
context.elevated(), fixed_ip['subnet_id'])
network = netaddr.IPNetwork(subnet.cidr)
ip = netaddr.IPNetwork('{0}/{1}'.format(
fixed_ip['ip_address'], network.prefixlen))
ip_list.append(ip)
return vyatta_utils.InterfaceInfo(
ip_addresses=ip_list, mac_address=mac_address)
def _delete_router_port(self, context, router_id, port, external_gw=False):
# Get instance, deconfigure interface and detach port from it. To do
# this need to change port owner back to that instance.
LOG.debug("Vyatta vRouter Plugin::Delete router port. "
"router: %s; port: %s", router_id, port)
if external_gw:
self.driver.clear_gateway(context, router_id)
else:
self.driver.deconfigure_interface(context, router_id,
self._get_interface_infos(context.elevated(), port))
self._core_plugin.update_port(context.elevated(), port['id'],
{'port': {'device_owner': '',
'device_id': router_id}})
self.driver.detach_interface(context, router_id, port['id'])
self._core_plugin.delete_port(context.elevated(), port['id'])
def _attach_port(self, context, router_id, port, external_gw=False):
LOG.debug("Vyatta vRouter Plugin::Attach port. "
"router: %s; port: %s", router_id, port)
# Attach interface
self.driver.attach_interface(context, router_id, port['id'])
def configure_gateway_wrapper():
if external_gw:
self.driver.configure_gateway(
context, router_id,
self._get_interface_infos(context, port))
else:
self.driver.configure_interface(
context, router_id,
self._get_interface_infos(context, port))
vyatta_utils.retry(
configure_gateway_wrapper,
exceptions=(v_exc.VRouterOperationError,),
limit=self.ATTACH_PORT_RETRY_LIMIT,
delay=self.ATTACH_PORT_RETRY_DELAY)
if external_gw:
device_owner = l3_constants.DEVICE_OWNER_ROUTER_GW
else:
device_owner = l3_constants.DEVICE_OWNER_ROUTER_INTF
self._core_plugin.update_port(context.elevated(), port['id'],
{'port': {'device_owner': device_owner,
'device_id': router_id}})
def _update_port(self, context, router_id, port):
interface_info = self._get_interface_infos_new(context, port)
self.driver.update_interface(context, router_id, interface_info)
def _update_router_gw_info(self, context, router_id, info, router=None):
LOG.debug("Vyatta vRouter Plugin::Update router gateway info")
router = router or self._get_router(context, router_id)
gw_port = router.gw_port
ext_ips = info.get('external_fixed_ips') if info else []
network_id = self._validate_gw_info(context, gw_port, info, ext_ips)
ext_ip_change = self._check_for_external_ip_change(
context, gw_port, ext_ips)
if gw_port and ext_ip_change and gw_port['network_id'] == network_id:
self._update_current_gw_port(context, router_id, router,
ext_ips)
else:
self._delete_current_gw_port(context, router_id, router,
network_id)
self._create_gw_port(context, router_id, router, network_id,
ext_ips)
def _update_current_gw_port(self, context, router_id, router, ext_ips):
super(VyattaVRouterMixin, self)._update_current_gw_port(
context, router_id, router, ext_ips)
port = router.gw_port
self.driver.clear_gateway(context, router_id)
self.driver.configure_gateway(
context, router_id,
self._get_interface_infos(context.elevated(), port))
def _delete_current_gw_port(self, context, router_id, router, new_network):
"""Delete gw port if attached to an old network or IPs changed."""
port_requires_deletion = (
router.gw_port and router.gw_port['network_id'] != new_network)
if not port_requires_deletion:
return
admin_ctx = context.elevated()
if self.get_floatingips_count(
admin_ctx, {'router_id': [router_id]}):
raise l3.RouterExternalGatewayInUseByFloatingIp(
router_id=router_id, net_id=router.gw_port['network_id'])
gw_port = router.gw_port
with context.session.begin(subtransactions=True):
router.gw_port = None
context.session.add(router)
context.session.expire(gw_port)
self._check_router_gw_port_in_use(context, router_id)
self._delete_router_port(
context, router_id, gw_port, external_gw=True)
def _create_router_gw_port(self, context, router, network_id, ext_ips):
if ext_ips and len(ext_ips) > 1:
msg = _("Routers support only 1 external IP")
raise q_exc.BadRequest(resource='router', msg=msg)
gw_port = self._core_plugin.create_port(context.elevated(), {
'port': {
'tenant_id': config.VROUTER.tenant_id,
'network_id': network_id,
'mac_address': attributes.ATTR_NOT_SPECIFIED,
'fixed_ips': ext_ips or attributes.ATTR_NOT_SPECIFIED,
'device_owner': '',
'device_id': '',
'admin_state_up': True,
'name': '',
psec.PORTSECURITY: False,
}})
if not gw_port['fixed_ips']:
self._core_plugin.delete_port(context.elevated(), gw_port['id'],
l3_port_check=False)
msg = (_('No IPs available for external network %s') %
network_id)
raise q_exc.BadRequest(resource='router', msg=msg)
with context.session.begin(subtransactions=True):
router.gw_port = self._core_plugin._get_port(context.elevated(),
gw_port['id'])
router_port = l3_db.RouterPort(
router_id=router.id,
port_id=gw_port['id'],
port_type=l3_constants.DEVICE_OWNER_ROUTER_GW
)
context.session.add(router)
context.session.add(router_port)
try:
self._attach_port(context, router['id'], gw_port,
external_gw=True)
except Exception as ex:
LOG.exception(_LE("Exception while attaching port : %s"), ex)
with excutils.save_and_reraise_exception():
try:
with context.session.begin(subtransactions=True):
router.gw_port = None
context.session.add(router)
self._core_plugin.delete_port(context.elevated(),
gw_port['id'])
except Exception:
LOG.exception(_LE('Failed to roll back changes to '
'Vyatta vRouter after external '
'gateway assignment.'))
def _update_extra_routes(self, context, router, routes):
LOG.debug(
'Vyatta vRouter Plugin::update static routes. '
'router_id={0}'.format(router['id']))
routes_old = self._get_extra_routes_by_router_id(
context, router['id'])
super(VyattaVRouterMixin, self)._update_extra_routes(
context, router, routes)
routes_new = self._get_extra_routes_by_router_id(
context, router['id'])
routes_old = self._route_rules_to_set(routes_old)
routes_new = self._route_rules_to_set(routes_new)
self.driver.update_static_routes(
context, router['id'],
tuple(routes_new - routes_old),
tuple(routes_old - routes_new))
@staticmethod
def _route_rules_to_set(rules):
result = set()
for r in rules:
result.add(vyatta_utils.RouteRule(
dest_cidr=r['destination'], next_hop=r['nexthop']))
return result
def create_floatingip(
self, context, floatingip,
initial_status=l3_constants.FLOATINGIP_STATUS_ACTIVE):
LOG.debug("Vyatta vRouter Plugin::Create floating ip")
floatingip_dict = super(VyattaVRouterMixin, self).create_floatingip(
context, floatingip,
initial_status=initial_status)
router_id = floatingip_dict['router_id']
if router_id:
self.associate_floatingip(context, router_id, floatingip_dict)
return floatingip_dict
def associate_floatingip(self, context, router_id, floatingip):
LOG.debug("Vyatta vRouter Plugin::Associate floating ip")
fixed_ip = floatingip['fixed_ip_address']
floating_ip = floatingip['floating_ip_address']
if router_id:
self.driver.assign_floating_ip(
context, router_id, floating_ip, fixed_ip)
with context.session.begin(subtransactions=True):
floatingip_db = self._get_floatingip(context, floatingip['id'])
floatingip_db['status'] = l3_constants.FLOATINGIP_STATUS_ACTIVE
def update_floatingip(self, context, floatingip_id, floatingip):
LOG.debug("Vyatta vRouter Plugin::Update floating ip")
fip = floatingip['floatingip']
with context.session.begin(subtransactions=True):
floatingip_db = self._get_floatingip(context, floatingip_id)
old_floatingip = self._make_floatingip_dict(floatingip_db)
fip['tenant_id'] = floatingip_db['tenant_id']
fip['id'] = floatingip_id
fip_port_id = floatingip_db['floating_port_id']
before_router_id = floatingip_db['router_id']
self._update_fip_assoc(context, fip, floatingip_db,
self._core_plugin.get_port(
context.elevated(), fip_port_id))
if before_router_id:
self.disassociate_floatingip(
context, before_router_id, old_floatingip)
router_id = floatingip_db['router_id']
if router_id:
self.associate_floatingip(context, router_id, floatingip_db)
return self._make_floatingip_dict(floatingip_db)
def delete_floatingip(self, context, floatingip_id):
LOG.debug("Vyatta vRouter Plugin::Delete floating ip: %s",
floatingip_id)
floatingip_dict = self._get_floatingip(context, floatingip_id)
router_id = floatingip_dict['router_id']
if router_id:
self.disassociate_floatingip(context, router_id, floatingip_dict)
super(VyattaVRouterMixin, self).delete_floatingip(
context, floatingip_id)
def disassociate_floatingip(self, context, router_id, floatingip):
LOG.debug("Vyatta vRouter Plugin::Disassociate floating ip."
"router: %s; floating_ip: %s", router_id, floatingip)
fixed_ip = floatingip['fixed_ip_address']
floating_ip = floatingip['floating_ip_address']
if router_id:
self.driver.unassign_floating_ip(
context, router_id, floating_ip, fixed_ip)
with context.session.begin(subtransactions=True):
floatingip_db = self._get_floatingip(context, floatingip['id'])
floatingip_db['status'] = l3_constants.FLOATINGIP_STATUS_DOWN
def disassociate_floatingips(self, context, port_id, do_notify=True):
LOG.debug("Vyatta vRouter Plugin::Disassociate floating ips."
"port_id: %s", port_id)
with context.session.begin(subtransactions=True):
fip_qry = context.session.query(l3_db.FloatingIP)
floating_ips = fip_qry.filter_by(fixed_port_id=port_id)
for floating_ip in floating_ips:
self.disassociate_floatingip(
context, floating_ip['router_id'], floating_ip)
return super(VyattaVRouterMixin, self).disassociate_floatingips(
context, port_id, do_notify)
class _VyattaL3RPCEndpoint(l3_rpc.L3RpcCallback):
def sync_routers(self, context, **kwargs):
routers_list = super(_VyattaL3RPCEndpoint, self).sync_routers(
context, **kwargs)
if not routers_list:
return routers_list
routers_by_id = dict((x['id'], x) for x in routers_list)
query = context.session.query(models_v2.Port)
query = query.filter(models_v2.Port.network_id
== config.VROUTER.management_network_id)
query = query.filter(models_v2.Port.device_id.in_(routers_by_id))
need_processed = set(routers_by_id)
for port in query:
router_id = port['device_id']
try:
need_processed.remove(router_id)
except KeyError:
raise v_exc.CorruptedSystemError(
description=(
'router {0} contain multiple interface joined to '
'management network').format(router_id))
# this statement can't raise KeyError because query condition
router = routers_by_id[router_id]
try:
ip = port['fixed_ips']
ip = ip[0]
ip = ip['ip_address']
except (IndexError, KeyError):
raise v_exc.CorruptedSystemError(
description=(
'vyatta vrouter id={0} management interface have no '
'ip address').format(router_id))
router['_vyatta'] = {
'management_ip_address': ip}
if need_processed:
raise v_exc.CorruptedSystemError(
description=(
'vyatta vrouters not linked to management network: '
'{0}').format(', '.join(sorted(need_processed))))
return routers_list