neutron/neutron/plugins/ml2/drivers/type_vlan.py

251 lines
11 KiB
Python

# Copyright (c) 2013 OpenStack Foundation
# 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 collections
import sys
from neutron_lib import constants as p_const
from neutron_lib import context
from neutron_lib.db import api as lib_db_api
from neutron_lib import exceptions as exc
from neutron_lib.plugins.ml2 import api
from neutron_lib.plugins import utils as plugin_utils
from oslo_config import cfg
from oslo_log import log
from neutron._i18n import _
from neutron.conf.plugins.ml2.drivers import driver_type
from neutron.db import api as db_api
from neutron.objects.plugins.ml2 import vlanallocation as vlanalloc
from neutron.plugins.ml2.drivers import helpers
LOG = log.getLogger(__name__)
driver_type.register_ml2_drivers_vlan_opts()
class VlanTypeDriver(helpers.SegmentTypeDriver):
"""Manage state for VLAN networks with ML2.
The VlanTypeDriver implements the 'vlan' network_type. VLAN
network segments provide connectivity between VMs and other
devices using any connected IEEE 802.1Q conformant
physical_network segmented into virtual networks via IEEE 802.1Q
headers. Up to 4094 VLAN network segments can exist on each
available physical_network.
"""
def __init__(self):
super(VlanTypeDriver, self).__init__(vlanalloc.VlanAllocation)
self._parse_network_vlan_ranges()
def _parse_network_vlan_ranges(self):
try:
self.network_vlan_ranges = plugin_utils.parse_network_vlan_ranges(
cfg.CONF.ml2_type_vlan.network_vlan_ranges)
except Exception:
LOG.exception("Failed to parse network_vlan_ranges. "
"Service terminated!")
sys.exit(1)
LOG.info("Network VLAN ranges: %s", self.network_vlan_ranges)
@lib_db_api.retry_db_errors
def _sync_vlan_allocations(self):
ctx = context.get_admin_context()
with db_api.context_manager.writer.using(ctx):
# VLAN ranges per physical network:
# {phy1: [(1, 10), (30, 50)], ...}
ranges = self.network_vlan_ranges
# Delete those VLAN registers from unconfigured physical networks
physnets = vlanalloc.VlanAllocation.get_physical_networks(ctx)
physnets_unconfigured = physnets - set(ranges)
if physnets_unconfigured:
LOG.debug('Removing any VLAN register on physical networks %s',
physnets_unconfigured)
vlanalloc.VlanAllocation.delete_physical_networks(
ctx, physnets_unconfigured)
# Get existing allocations for all configured physical networks
allocations = collections.defaultdict(list)
for alloc in vlanalloc.VlanAllocation.get_objects(ctx):
allocations[alloc.physical_network].append(alloc)
for physical_network, vlan_ranges in ranges.items():
# determine current configured allocatable vlans for
# this physical network
vlan_ids = set()
for vlan_min, vlan_max in vlan_ranges:
vlan_ids |= set(range(vlan_min, vlan_max + 1))
# remove from table unallocated vlans not currently
# allocatable
if physical_network in allocations:
for alloc in allocations[physical_network]:
try:
# see if vlan is allocatable
vlan_ids.remove(alloc.vlan_id)
except KeyError:
# it's not allocatable, so check if its allocated
if not alloc.allocated:
# it's not, so remove it from table
LOG.debug("Removing vlan %(vlan_id)s on "
"physical network "
"%(physical_network)s from pool",
{'vlan_id': alloc.vlan_id,
'physical_network':
physical_network})
# This UPDATE WHERE statement blocks anyone
# from concurrently changing the allocation
# values to True while our transaction is
# open so we don't accidentally delete
# allocated segments. If someone has already
# allocated, update_objects will return 0 so we
# don't delete.
if vlanalloc.VlanAllocation.update_objects(
ctx, values={'allocated': False},
allocated=False, vlan_id=alloc.vlan_id,
physical_network=physical_network):
alloc.delete()
del allocations[physical_network]
# Add missing allocatable VLAN registers for "physical_network"
vlanalloc.VlanAllocation.bulk_create(ctx, physical_network,
vlan_ids)
def get_type(self):
return p_const.TYPE_VLAN
def initialize(self):
self._sync_vlan_allocations()
LOG.info("VlanTypeDriver initialization complete")
def is_partial_segment(self, segment):
return segment.get(api.SEGMENTATION_ID) is None
def validate_provider_segment(self, segment):
physical_network = segment.get(api.PHYSICAL_NETWORK)
segmentation_id = segment.get(api.SEGMENTATION_ID)
if physical_network:
if physical_network not in self.network_vlan_ranges:
msg = (_("physical_network '%s' unknown "
"for VLAN provider network") % physical_network)
raise exc.InvalidInput(error_message=msg)
if segmentation_id is not None:
if not plugin_utils.is_valid_vlan_tag(segmentation_id):
msg = (_("segmentation_id out of range (%(min)s through "
"%(max)s)") %
{'min': p_const.MIN_VLAN_TAG,
'max': p_const.MAX_VLAN_TAG})
raise exc.InvalidInput(error_message=msg)
else:
if not self.network_vlan_ranges.get(physical_network):
msg = (_("Physical network %s requires segmentation_id "
"to be specified when creating a provider "
"network") % physical_network)
raise exc.InvalidInput(error_message=msg)
elif segmentation_id is not None:
msg = _("segmentation_id requires physical_network for VLAN "
"provider network")
raise exc.InvalidInput(error_message=msg)
for key, value in segment.items():
if value and key not in [api.NETWORK_TYPE,
api.PHYSICAL_NETWORK,
api.SEGMENTATION_ID]:
msg = _("%s prohibited for VLAN provider network") % key
raise exc.InvalidInput(error_message=msg)
def reserve_provider_segment(self, context, segment):
filters = {}
physical_network = segment.get(api.PHYSICAL_NETWORK)
if physical_network is not None:
filters['physical_network'] = physical_network
vlan_id = segment.get(api.SEGMENTATION_ID)
if vlan_id is not None:
filters['vlan_id'] = vlan_id
if self.is_partial_segment(segment):
alloc = self.allocate_partially_specified_segment(
context, **filters)
if not alloc:
raise exc.NoNetworkAvailable()
else:
alloc = self.allocate_fully_specified_segment(
context, **filters)
if not alloc:
raise exc.VlanIdInUse(**filters)
return {api.NETWORK_TYPE: p_const.TYPE_VLAN,
api.PHYSICAL_NETWORK: alloc.physical_network,
api.SEGMENTATION_ID: alloc.vlan_id,
api.MTU: self.get_mtu(alloc.physical_network)}
def allocate_tenant_segment(self, context):
for physnet in self.network_vlan_ranges:
alloc = self.allocate_partially_specified_segment(
context, physical_network=physnet)
if alloc:
break
else:
return
return {api.NETWORK_TYPE: p_const.TYPE_VLAN,
api.PHYSICAL_NETWORK: alloc.physical_network,
api.SEGMENTATION_ID: alloc.vlan_id,
api.MTU: self.get_mtu(alloc.physical_network)}
def release_segment(self, context, segment):
physical_network = segment[api.PHYSICAL_NETWORK]
vlan_id = segment[api.SEGMENTATION_ID]
ranges = self.network_vlan_ranges.get(physical_network, [])
inside = any(lo <= vlan_id <= hi for lo, hi in ranges)
count = False
with db_api.context_manager.writer.using(context):
alloc = vlanalloc.VlanAllocation.get_object(
context, physical_network=physical_network, vlan_id=vlan_id)
if alloc:
if inside and alloc.allocated:
count = True
alloc.allocated = False
alloc.update()
LOG.debug("Releasing vlan %(vlan_id)s on physical "
"network %(physical_network)s to pool",
{'vlan_id': vlan_id,
'physical_network': physical_network})
else:
count = True
alloc.delete()
LOG.debug("Releasing vlan %(vlan_id)s on physical "
"network %(physical_network)s outside pool",
{'vlan_id': vlan_id,
'physical_network': physical_network})
if not count:
LOG.warning("No vlan_id %(vlan_id)s found on physical "
"network %(physical_network)s",
{'vlan_id': vlan_id,
'physical_network': physical_network})
def get_mtu(self, physical_network):
seg_mtu = super(VlanTypeDriver, self).get_mtu()
mtu = []
if seg_mtu > 0:
mtu.append(seg_mtu)
if physical_network in self.physnet_mtus:
mtu.append(int(self.physnet_mtus[physical_network]))
return min(mtu) if mtu else 0