astara-neutron/akanda/quantum/plugins/nvp_quantum_plugin.py

225 lines
8.7 KiB
Python

import logging
import random
import netaddr
from quantum.api.v2 import attributes
from quantum.common import exceptions as q_exc
from quantum.db import models_v2 as qmodels
from quantum.db import l3_db
from quantum.openstack.common import cfg
from quantum.plugins.nicira.nicira_nvp_plugin import QuantumPlugin as nvp
from sqlalchemy.orm import exc
# this import is here to ensure that models are loaded by SQLAlchemy
from akanda.quantum.db import models_v2 as akmodels
IPV6_ASSIGNMENT_ATTEMPTS = 1000
LOG = logging.getLogger(__name__)
akanda_opts = [
cfg.StrOpt('akanda_ipv6_tenant_range',
default='fdd6:a1fa:cfa8::/48',
help='IPv6 address prefix'),
cfg.IntOpt('akanda_ipv6_prefix_length',
default=64,
help='Default length of prefix to pre-assign'),
cfg.ListOpt('akanda_allowed_cidr_ranges',
default=['10.0.0.8/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'],
help='List of allowed subnet cidrs for non-admin users')
]
cfg.CONF.register_opts(akanda_opts)
class NVPQuantumPlugin(nvp.NvpPluginV2, l3_db.L3_NAT_db_mixin):
supported_extension_aliases = (
nvp.NvpPluginV2.supported_extension_aliases +
['router', 'dhportforward', 'dhaddressgroup', 'dhaddressentry',
'dhfilterrule', 'dhportalias'])
def create_network(self, context, network):
with context.session.begin(subtransactions=True):
net = super(NVPQuantumPlugin, self).create_network(context,
network)
self._process_l3_create(context, network['network'], net['id'])
self._extend_network_dict_l3(context, net)
# auto create IPv6 network
self._akanda_add_ipv6_subnet(context, net)
return net
def update_network(self, context, id, network):
with context.session.begin(subtransactions=True):
net = super(NVPQuantumPlugin, self).update_network(context,
id,
network)
self._process_l3_update(context, network['network'], id)
self._extend_network_dict_l3(context, net)
# TODO: need to remove ports from router when state is down?
return net
def get_network(self, context, id, fields=None):
net = super(NVPQuantumPlugin, self).get_network(context, id, None)
self._extend_network_dict_l3(context, net)
return self._fields(net, fields)
def get_networks(self, context, filters=None, fields=None):
nets = super(NVPQuantumPlugin, self).get_networks(context,
filters,
None)
for net in nets:
self._extend_network_dict_l3(context, net)
nets = self._filter_nets_l3(context, nets, filters)
return [self._fields(net, fields) for net in nets]
def create_subnet(self, context, subnet):
# ensure cidr is allowed v4:RFC1918 v6:ULA for non-admins
if not context.is_admin:
net = netaddr.IPNetwork(subnet['subnet']['cidr'])
for allowed_cidr in cfg.CONF.akanda_allowed_cidr_ranges:
if net in netaddr.IPNetwork(allowed_cidr):
break
else:
reason = _('Cannot create a subnet that is not within the '
'allowed address ranges [%s].' %
cfg.CONF.akanda_allowed_cidr_ranges)
raise q_exc.AdminRequired(reason=reason)
retval = super(NVPQuantumPlugin, self).create_subnet(context, subnet)
self._akanda_auto_add_subnet_to_router(context, retval)
return retval
def update_subnet(self, context, id, subnet):
old_gateway = self._get_subnet(context, id)['gateway_ip']
retval = super(NVPQuantumPlugin, self).update_subnet(context,
id,
subnet)
# update router ports to make sure gateway matches
if old_gateway != retval['gateway_ip']:
self._akanda_update_internal_gateway_port_ip(context, retval)
return retval
def delete_subnet(self, context, id):
# remove port from router first
return super(NVPQuantumPlugin, self).delete_subnet(context, id)
def delete_port(self, context, id, l3_port_check=True):
if l3_port_check:
self.prent_l3_port_deletion(context, id)
self.disassociate_floatingips(context, id)
return super(NVPQuantumPluginV2, self).delete_port(context, id)
def _akanda_auto_add_subnet_to_router(self, context, subnet):
if context.is_admin:
# admins can manually add their own interfaces
return
if not subnet.get('gateway_ip'):
return
router_q = context.session.query(l3_db.Router)
router_q = router_q.filter_by(tenant_id=context.tenant_id)
try:
router = router_q.one()
except exc.NoResultFound:
router_args = {'tenant_id': context.tenant_id,
'name': 'ak-%s' % context.tenant_id,
'admin_state_up': True}
router = self.create_router(context, {'router': router_args})
if not self._akanda_update_internal_gateway_port_ip(context, subnet):
self.add_router_interface(context.elevated(),
router['id'],
{'subnet_id': subnet['id']})
def _akanda_update_internal_gateway_port_ip(self, context, subnet):
if not subnet.get('gateway_ip'):
return
filters = {
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
'network_id': [subnet['network_id']]
}
ports = self.get_ports(context, filters=filters)
for port in ports:
for fixed_ip in port['fixed_ips']:
if fixed_ip['subnet_id'] == subnet['id']:
fixed_ip['ip_address'] = subnet['gateway_ip']
break
else:
port['fixed_ips'].append({'subnet_id': subnet['id'],
'ip_address': subnet['gateway_ip']})
self.update_port(context.elevated(),
port['id'],
{'port': port})
return True
def _akanda_add_ipv6_subnet(self, context, network):
try:
subnet_generator = _ipv6_subnet_generator(
cfg.CONF.akanda_ipv6_tenant_range,
cfg.CONF.akanda_ipv6_prefix_length)
except:
LOG.exception('Unable able to add tenant IPv6 subnet.')
return
remaining = IPV6_ASSIGNMENT_ATTEMPTS
while remaining:
remaining -=1
candidate_cidr = subnet_generator.next()
sub_q = context.session.query(qmodels.Subnet)
sub_q = sub_q.filter_by(cidr=str(candidate_cidr))
existing = sub_q.all()
if not existing:
create_args = {
'network_id': network['id'],
'name': '',
'cidr': str(candidate_cidr),
'ip_version': candidate_cidr.version,
'enable_dhcp': False,
'gateway_ip': attributes.ATTR_NOT_SPECIFIED,
'dns_nameservers': attributes.ATTR_NOT_SPECIFIED,
'host_routes': attributes.ATTR_NOT_SPECIFIED,
'allocation_pools': attributes.ATTR_NOT_SPECIFIED}
self.create_subnet(context, {'subnet': create_args})
break
else:
LOG.error('Unable to generate a unique tenant subnet cidr')
def _ipv6_subnet_generator(network_range, prefixlen):
# coerce prefixlen to stay within bounds
prefixlen = min(128, prefixlen)
net = netaddr.IPNetwork(network_range)
if net.version != 6:
raise ValueError('Tenant range %s is not a valid IPv6 cidr' %
network_range)
if prefixlen < net.prefixlen:
raise ValueError('Prefixlen (/%d) must be larger than the network '
'range prefixlen (/%s)' % (prefixlen, net.prefixlen))
rand = random.SystemRandom()
max_range = 2**(prefixlen - net.prefixlen)
while True:
rand_bits = rand.randint(0, max_range)
candidate_cidr = netaddr.IPNetwork(
netaddr.IPAddress(net.value + (rand_bits << prefixlen)))
candidate_cidr.prefixlen = prefixlen
yield candidate_cidr