neutron/quantum/plugins/midonet/plugin.py

653 lines
28 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Midokura Japan K.K.
# Copyright (C) 2013 Midokura PTE LTD
# 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: Takaaki Suzuki, Midokura Japan KK
# @author: Tomoe Sugihara, Midokura Japan KK
# @author: Ryu Ishimoto, Midokura Japan KK
from midonetclient import api
from oslo.config import cfg
from quantum.common import exceptions as q_exc
from quantum.db import api as db
from quantum.db import db_base_plugin_v2
from quantum.db import l3_db
from quantum.db import models_v2
from quantum.db import securitygroups_db
from quantum.extensions import securitygroup as ext_sg
from quantum.openstack.common import log as logging
from quantum.plugins.midonet import config # noqa
from quantum.plugins.midonet import midonet_lib
LOG = logging.getLogger(__name__)
class MidonetPluginException(q_exc.QuantumException):
message = _("%(msg)s")
class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
l3_db.L3_NAT_db_mixin,
securitygroups_db.SecurityGroupDbMixin):
supported_extension_aliases = ['router', 'security-group']
__native_bulk_support = False
def __init__(self):
# Read config values
midonet_conf = cfg.CONF.MIDONET
midonet_uri = midonet_conf.midonet_uri
admin_user = midonet_conf.username
admin_pass = midonet_conf.password
admin_project_id = midonet_conf.project_id
provider_router_id = midonet_conf.provider_router_id
metadata_router_id = midonet_conf.metadata_router_id
mode = midonet_conf.mode
self.mido_api = api.MidonetApi(midonet_uri, admin_user,
admin_pass,
project_id=admin_project_id)
self.client = midonet_lib.MidoClient(self.mido_api)
if provider_router_id and metadata_router_id:
# get MidoNet provider router and metadata router
self.provider_router = self.client.get_router(provider_router_id)
self.metadata_router = self.client.get_router(metadata_router_id)
elif not provider_router_id or not metadata_router_id:
if mode == 'dev':
msg = _('No provider router and metadata device ids found. '
'But skipping because running in dev env.')
LOG.debug(msg)
else:
msg = _('provider_router_id and metadata_router_id '
'should be configured in the plugin config file')
LOG.exception(msg)
raise MidonetPluginException(msg=msg)
db.configure_db()
def create_subnet(self, context, subnet):
"""Create Quantum subnet.
Creates a Quantum subnet and a DHCP entry in MidoNet bridge.
"""
LOG.debug(_("MidonetPluginV2.create_subnet called: subnet=%r"), subnet)
if subnet['subnet']['ip_version'] == 6:
raise q_exc.NotImplementedError(
_("MidoNet doesn't support IPv6."))
net = super(MidonetPluginV2, self).get_network(
context, subnet['subnet']['network_id'], fields=None)
if net['subnets']:
raise q_exc.NotImplementedError(
_("MidoNet doesn't support multiple subnets "
"on the same network."))
session = context.session
with session.begin(subtransactions=True):
sn_entry = super(MidonetPluginV2, self).create_subnet(context,
subnet)
bridge = self.client.get_bridge(sn_entry['network_id'])
gateway_ip = subnet['subnet']['gateway_ip']
network_address, prefix = subnet['subnet']['cidr'].split('/')
self.client.create_dhcp(bridge, gateway_ip, network_address,
prefix)
# For external network, link the bridge to the provider router.
if net['router:external']:
gateway_ip = sn_entry['gateway_ip']
network_address, length = sn_entry['cidr'].split('/')
self.client.link_bridge_to_provider_router(
bridge, self.provider_router, gateway_ip, network_address,
length)
LOG.debug(_("MidonetPluginV2.create_subnet exiting: sn_entry=%r"),
sn_entry)
return sn_entry
def delete_subnet(self, context, id):
"""Delete Quantum subnet.
Delete quantum network and its corresponding MidoNet bridge.
"""
LOG.debug(_("MidonetPluginV2.delete_subnet called: id=%s"), id)
subnet = super(MidonetPluginV2, self).get_subnet(context, id,
fields=None)
net = super(MidonetPluginV2, self).get_network(context,
subnet['network_id'],
fields=None)
bridge = self.client.get_bridge(subnet['network_id'])
self.client.delete_dhcp(bridge)
# If the network is external, clean up routes, links, ports.
if net['router:external']:
self.client.unlink_bridge_from_provider_router(
bridge, self.provider_router)
super(MidonetPluginV2, self).delete_subnet(context, id)
LOG.debug(_("MidonetPluginV2.delete_subnet exiting"))
def create_network(self, context, network):
"""Create Quantum network.
Create a new Quantum network and its corresponding MidoNet bridge.
"""
LOG.debug(_('MidonetPluginV2.create_network called: network=%r'),
network)
if network['network']['admin_state_up'] is False:
LOG.warning(_('Ignoring admin_state_up=False for network=%r '
'because it is not yet supported'), network)
tenant_id = self._get_tenant_id_for_create(context, network['network'])
self._ensure_default_security_group(context, tenant_id)
session = context.session
with session.begin(subtransactions=True):
bridge = self.client.create_bridge(tenant_id,
network['network']['name'])
# Set MidoNet bridge ID to the quantum DB entry
network['network']['id'] = bridge.get_id()
net = super(MidonetPluginV2, self).create_network(context, network)
# to handle l3 related data in DB
self._process_l3_create(context, net, network['network'])
LOG.debug(_("MidonetPluginV2.create_network exiting: net=%r"), net)
return net
def update_network(self, context, id, network):
"""Update Quantum network.
Update an existing Quantum network and its corresponding MidoNet
bridge.
"""
LOG.debug(_("MidonetPluginV2.update_network called: id=%(id)r, "
"network=%(network)r"), {'id': id, 'network': network})
# Reject admin_state_up=False
if network['network'].get('admin_state_up') and network['network'][
'admin_state_up'] is False:
raise q_exc.NotImplementedError(_('admin_state_up=False '
'networks are not '
'supported.'))
session = context.session
with session.begin(subtransactions=True):
net = super(MidonetPluginV2, self).update_network(
context, id, network)
self.client.update_bridge(id, net['name'])
LOG.debug(_("MidonetPluginV2.update_network exiting: net=%r"), net)
return net
def get_network(self, context, id, fields=None):
"""Get Quantum network.
Retrieves a Quantum network and its corresponding MidoNet bridge.
"""
LOG.debug(_("MidonetPluginV2.get_network called: id=%(id)r, "
"fields=%(fields)r"), {'id': id, 'fields': fields})
qnet = super(MidonetPluginV2, self).get_network(context, id, fields)
self.client.get_bridge(id)
LOG.debug(_("MidonetPluginV2.get_network exiting: qnet=%r"), qnet)
return qnet
def delete_network(self, context, id):
"""Delete a network and its corresponding MidoNet bridge."""
LOG.debug(_("MidonetPluginV2.delete_network called: id=%r"), id)
self.client.delete_bridge(id)
try:
super(MidonetPluginV2, self).delete_network(context, id)
except Exception:
LOG.error(_('Failed to delete quantum db, while Midonet bridge=%r'
'had been deleted'), id)
raise
def create_port(self, context, port):
"""Create a L2 port in Quantum/MidoNet."""
LOG.debug(_("MidonetPluginV2.create_port called: port=%r"), port)
is_compute_interface = False
port_data = port['port']
# get the bridge and create a port on it.
bridge = self.client.get_bridge(port_data['network_id'])
device_owner = port_data['device_owner']
if device_owner.startswith('compute:') or device_owner is '':
is_compute_interface = True
bridge_port = self.client.create_exterior_bridge_port(bridge)
elif device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
bridge_port = self.client.create_interior_bridge_port(bridge)
elif (device_owner == l3_db.DEVICE_OWNER_ROUTER_GW or
device_owner == l3_db.DEVICE_OWNER_FLOATINGIP):
# This is a dummy port to make l3_db happy.
# This will not be used in MidoNet
bridge_port = self.client.create_interior_bridge_port(bridge)
if bridge_port:
# set midonet port id to quantum port id and create a DB record.
port_data['id'] = bridge_port.get_id()
session = context.session
with session.begin(subtransactions=True):
port_db_entry = super(MidonetPluginV2,
self).create_port(context, port)
# Caveat: port_db_entry is not a db model instance
sg_ids = self._get_security_groups_on_port(context, port)
self._process_port_create_security_group(context, port, sg_ids)
if is_compute_interface:
# Create a DHCP entry if needed.
if 'ip_address' in (port_db_entry['fixed_ips'] or [{}])[0]:
# get ip and mac from DB record, assuming one IP address
# at most since we only support one subnet per network now.
fixed_ip = port_db_entry['fixed_ips'][0]['ip_address']
mac = port_db_entry['mac_address']
# create dhcp host entry under the bridge.
self.client.create_dhcp_hosts(bridge, fixed_ip, mac)
LOG.debug(_("MidonetPluginV2.create_port exiting: port_db_entry=%r"),
port_db_entry)
return port_db_entry
def get_port(self, context, id, fields=None):
"""Retrieve port."""
LOG.debug(_("MidonetPluginV2.get_port called: id=%(id)s "
"fields=%(fields)r"), {'id': id, 'fields': fields})
# get the quantum port from DB.
port_db_entry = super(MidonetPluginV2, self).get_port(context,
id, fields)
# verify that corresponding port exists in MidoNet.
self.client.get_port(id)
LOG.debug(_("MidonetPluginV2.get_port exiting: port_db_entry=%r"),
port_db_entry)
return port_db_entry
def get_ports(self, context, filters=None, fields=None):
"""List quantum ports and verify that they exist in MidoNet."""
LOG.debug(_("MidonetPluginV2.get_ports called: filters=%(filters)s "
"fields=%(fields)r"),
{'filters': filters, 'fields': fields})
ports_db_entry = super(MidonetPluginV2, self).get_ports(context,
filters,
fields)
if ports_db_entry:
for port in ports_db_entry:
if 'security_gorups' in port:
self._extend_port_dict_security_group(context, port)
return ports_db_entry
def delete_port(self, context, id, l3_port_check=True):
"""Delete a quantum port and corresponding MidoNet bridge port."""
LOG.debug(_("MidonetPluginV2.delete_port called: id=%(id)s "
"l3_port_check=%(l3_port_check)r"),
{'id': id, 'l3_port_check': l3_port_check})
# if needed, check to see if this is a port owned by
# and l3-router. If so, we should prevent deletion.
if l3_port_check:
self.prevent_l3_port_deletion(context, id)
session = context.session
with session.begin(subtransactions=True):
port_db_entry = super(MidonetPluginV2, self).get_port(context,
id, None)
# Clean up dhcp host entry if needed.
if 'ip_address' in (port_db_entry['fixed_ips'] or [{}])[0]:
# get ip and mac from DB record.
ip = port_db_entry['fixed_ips'][0]['ip_address']
mac = port_db_entry['mac_address']
# create dhcp host entry under the bridge.
self.client.delete_dhcp_hosts(port_db_entry['network_id'], ip,
mac)
self.client.delete_port(id)
return super(MidonetPluginV2, self).delete_port(context, id)
#
# L3 APIs.
#
def create_router(self, context, router):
LOG.debug(_("MidonetPluginV2.create_router called: router=%r"), router)
if router['router']['admin_state_up'] is False:
LOG.warning(_('Ignoring admin_state_up=False for router=%r. '
'Overriding with True'), router)
router['router']['admin_state_up'] = True
tenant_id = self._get_tenant_id_for_create(context, router['router'])
session = context.session
with session.begin(subtransactions=True):
mrouter = self.client.create_tenant_router(
tenant_id, router['router']['name'], self.metadata_router)
qrouter = super(MidonetPluginV2, self).create_router(context,
router)
# get entry from the DB and update 'id' with MidoNet router id.
qrouter_entry = self._get_router(context, qrouter['id'])
qrouter['id'] = mrouter.get_id()
qrouter_entry.update(qrouter)
LOG.debug(_("MidonetPluginV2.create_router exiting: qrouter=%r"),
qrouter)
return qrouter
def update_router(self, context, id, router):
LOG.debug(_("MidonetPluginV2.update_router called: id=%(id)s "
"router=%(router)r"), router)
if router['router'].get('admin_state_up') is False:
raise q_exc.NotImplementedError(_('admin_state_up=False '
'routers are not '
'supported.'))
op_gateway_set = False
op_gateway_clear = False
# figure out which operation it is in
if ('external_gateway_info' in router['router'] and
'network_id' in router['router']['external_gateway_info']):
op_gateway_set = True
elif ('external_gateway_info' in router['router'] and
router['router']['external_gateway_info'] == {}):
op_gateway_clear = True
qports = super(MidonetPluginV2, self).get_ports(
context, {'device_id': [id],
'device_owner': ['network:router_gateway']})
assert len(qports) == 1
qport = qports[0]
snat_ip = qport['fixed_ips'][0]['ip_address']
qport['network_id']
session = context.session
with session.begin(subtransactions=True):
qrouter = super(MidonetPluginV2, self).update_router(context, id,
router)
changed_name = router['router'].get('name')
if changed_name:
self.client.update_router(id, changed_name)
if op_gateway_set:
# find a qport with the network_id for the router
qports = super(MidonetPluginV2, self).get_ports(
context, {'device_id': [id],
'device_owner': ['network:router_gateway']})
assert len(qports) == 1
qport = qports[0]
snat_ip = qport['fixed_ips'][0]['ip_address']
self.client.set_router_external_gateway(id,
self.provider_router,
snat_ip)
if op_gateway_clear:
self.client.clear_router_external_gateway(id)
LOG.debug(_("MidonetPluginV2.update_router exiting: qrouter=%r"),
qrouter)
return qrouter
def delete_router(self, context, id):
LOG.debug(_("MidonetPluginV2.delete_router called: id=%s"), id)
self.client.delete_tenant_router(id)
result = super(MidonetPluginV2, self).delete_router(context, id)
LOG.debug(_("MidonetPluginV2.delete_router exiting: result=%s"),
result)
return result
def add_router_interface(self, context, router_id, interface_info):
LOG.debug(_("MidonetPluginV2.add_router_interface called: "
"router_id=%(router_id)s "
"interface_info=%(interface_info)r"),
{'router_id': router_id, 'interface_info': interface_info})
qport = super(MidonetPluginV2, self).add_router_interface(
context, router_id, interface_info)
# TODO(tomoe): handle a case with 'port' in interface_info
if 'subnet_id' in interface_info:
subnet_id = interface_info['subnet_id']
subnet = self._get_subnet(context, subnet_id)
gateway_ip = subnet['gateway_ip']
network_address, length = subnet['cidr'].split('/')
# Link the router and the bridge port.
self.client.link_bridge_port_to_router(qport['port_id'], router_id,
gateway_ip, network_address,
length,
self.metadata_router)
LOG.debug(_("MidonetPluginV2.add_router_interface exiting: "
"qport=%r"), qport)
return qport
def remove_router_interface(self, context, router_id, interface_info):
"""Remove interior router ports."""
LOG.debug(_("MidonetPluginV2.remove_router_interface called: "
"router_id=%(router_id)s "
"interface_info=%(interface_info)r"),
{'router_id': router_id, 'interface_info': interface_info})
port_id = None
if 'port_id' in interface_info:
port_id = interface_info['port_id']
subnet_id = self.get_port(context,
interface_info['port_id']
)['fixed_ips'][0]['subnet_id']
subnet = self._get_subnet(context, subnet_id)
if 'subnet_id' in interface_info:
subnet_id = interface_info['subnet_id']
subnet = self._get_subnet(context, subnet_id)
network_id = subnet['network_id']
# find a quantum port for the network
rport_qry = context.session.query(models_v2.Port)
ports = rport_qry.filter_by(
device_id=router_id,
device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF,
network_id=network_id)
network_port = None
for p in ports:
if p['fixed_ips'][0]['subnet_id'] == subnet_id:
network_port = p
break
assert network_port
port_id = network_port['id']
assert port_id
# get network information from subnet data
network_addr, network_length = subnet['cidr'].split('/')
network_length = int(network_length)
# Unlink the router and the bridge.
self.client.unlink_bridge_port_from_router(port_id, network_addr,
network_length,
self.metadata_router)
info = super(MidonetPluginV2, self).remove_router_interface(
context, router_id, interface_info)
LOG.debug(_("MidonetPluginV2.remove_router_interface exiting"))
return info
def update_floatingip(self, context, id, floatingip):
LOG.debug(_("MidonetPluginV2.update_floatingip called: id=%(id)s "
"floatingip=%(floatingip)s "),
{'id': id, 'floatingip': floatingip})
session = context.session
with session.begin(subtransactions=True):
if floatingip['floatingip']['port_id']:
fip = super(MidonetPluginV2, self).update_floatingip(
context, id, floatingip)
self.client.setup_floating_ip(fip['router_id'],
self.provider_router,
fip['floating_ip_address'],
fip['fixed_ip_address'], id)
# disassociate floating IP
elif floatingip['floatingip']['port_id'] is None:
fip = super(MidonetPluginV2, self).get_floatingip(context, id)
self.client.clear_floating_ip(fip['router_id'],
self.provider_router,
fip['floating_ip_address'], id)
super(MidonetPluginV2, self).update_floatingip(context, id,
floatingip)
LOG.debug(_("MidonetPluginV2.update_floating_ip exiting: fip=%s"), fip)
return fip
#
# Security groups supporting methods
#
def create_security_group(self, context, security_group, default_sg=False):
"""Create chains for Quantum security group."""
LOG.debug(_("MidonetPluginV2.create_security_group called: "
"security_group=%(security_group)s "
"default_sg=%(default_sg)s "),
{'security_group': security_group, 'default_sg': default_sg})
sg = security_group.get('security_group')
tenant_id = self._get_tenant_id_for_create(context, sg)
with context.session.begin(subtransactions=True):
sg_db_entry = super(MidonetPluginV2, self).create_security_group(
context, security_group, default_sg)
# Create MidoNet chains and portgroup for the SG
self.client.create_for_sg(tenant_id, sg_db_entry['id'],
sg_db_entry['name'])
LOG.debug(_("MidonetPluginV2.create_security_group exiting: "
"sg_db_entry=%r"), sg_db_entry)
return sg_db_entry
def delete_security_group(self, context, id):
"""Delete chains for Quantum security group."""
LOG.debug(_("MidonetPluginV2.delete_security_group called: id=%s"), id)
with context.session.begin(subtransactions=True):
sg_db_entry = super(MidonetPluginV2, self).get_security_group(
context, id)
if not sg_db_entry:
raise ext_sg.SecurityGroupNotFound(id=id)
sg_name = sg_db_entry['name']
sg_id = sg_db_entry['id']
tenant_id = sg_db_entry['tenant_id']
if sg_name == 'default' and not context.is_admin:
raise ext_sg.SecurityGroupCannotRemoveDefault()
filters = {'security_group_id': [sg_id]}
if super(MidonetPluginV2, self)._get_port_security_group_bindings(
context, filters):
raise ext_sg.SecurityGroupInUse(id=sg_id)
# Delete MidoNet Chains and portgroup for the SG
self.client.delete_for_sg(tenant_id, sg_id, sg_name)
return super(MidonetPluginV2, self).delete_security_group(
context, id)
def get_security_groups(self, context, filters=None, fields=None,
default_sg=False):
LOG.debug(_("MidonetPluginV2.get_security_groups called: "
"filters=%(filters)r fields=%(fields)r"),
{'filters': filters, 'fields': fields})
return super(MidonetPluginV2, self).get_security_groups(
context, filters, fields, default_sg=default_sg)
def get_security_group(self, context, id, fields=None, tenant_id=None):
LOG.debug(_("MidonetPluginV2.get_security_group called: id=%(id)s "
"fields=%(fields)r tenant_id=%(tenant_id)s"),
{'id': id, 'fields': fields, 'tenant_id': tenant_id})
return super(MidonetPluginV2, self).get_security_group(context, id,
fields)
def create_security_group_rule(self, context, security_group_rule):
LOG.debug(_("MidonetPluginV2.create_security_group_rule called: "
"security_group_rule=%(security_group_rule)r"),
{'security_group_rule': security_group_rule})
with context.session.begin(subtransactions=True):
rule_db_entry = super(
MidonetPluginV2, self).create_security_group_rule(
context, security_group_rule)
self.client.create_for_sg_rule(rule_db_entry)
LOG.debug(_("MidonetPluginV2.create_security_group_rule exiting: "
"rule_db_entry=%r"), rule_db_entry)
return rule_db_entry
def delete_security_group_rule(self, context, sgrid):
LOG.debug(_("MidonetPluginV2.delete_security_group_rule called: "
"sgrid=%s"), sgrid)
with context.session.begin(subtransactions=True):
rule_db_entry = super(MidonetPluginV2,
self).get_security_group_rule(context, sgrid)
if not rule_db_entry:
raise ext_sg.SecurityGroupRuleNotFound(id=sgrid)
self.client.delete_for_sg_rule(rule_db_entry)
return super(MidonetPluginV2,
self).delete_security_group_rule(context, sgrid)
def get_security_group_rules(self, context, filters=None, fields=None):
LOG.debug(_("MidonetPluginV2.get_security_group_rules called: "
"filters=%(filters)r fields=%(fields)r"),
{'filters': filters, 'fields': fields})
return super(MidonetPluginV2, self).get_security_group_rules(
context, filters, fields)
def get_security_group_rule(self, context, id, fields=None):
LOG.debug(_("MidonetPluginV2.get_security_group_rule called: "
"id=%(id)s fields=%(fields)r"),
{'id': id, 'fields': fields})
return super(MidonetPluginV2, self).get_security_group_rule(
context, id, fields)