Add randomness to the returned vlan_ids

This patch modifies allocate_segmentation_id so that the vlan_id
returned is randomly selected between the available ids.
The main rationale behind is to avoid vlan_id collisions, for
instance in active-active deployments, among different calls.
Note this will minimize the collision if nested containers are
created by using both kubernetes and docker in the same VM.

Partially Implements blueprint containers-in-instances

Change-Id: Iba2c94707c4932d59483c793452da88c43d4215c
This commit is contained in:
Luis Tomas Bolivar 2017-01-19 13:30:25 +00:00
parent 6a12fc5547
commit af9d0eb0d6
3 changed files with 117 additions and 6 deletions

View File

@ -9,11 +9,15 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import random
from six import moves from six import moves
from kuryr.lib import constants as const from kuryr.lib import constants as const
from kuryr.lib import exceptions from kuryr.lib import exceptions
DEFAULT_MAX_RETRY_COUNT = 3
class SegmentationDriver(object): class SegmentationDriver(object):
def __init__(self): def __init__(self):
@ -22,12 +26,20 @@ class SegmentationDriver(object):
def allocate_segmentation_id(self, allocated_ids=set()): def allocate_segmentation_id(self, allocated_ids=set()):
self.available_local_vlans.difference_update(allocated_ids) self.available_local_vlans.difference_update(allocated_ids)
try: for i in range(DEFAULT_MAX_RETRY_COUNT):
allocated = self.available_local_vlans.pop() try:
except KeyError: allocated = random.choice(list(self.available_local_vlans))
raise exceptions.segmentationIdAllocationFailure self.available_local_vlans.remove(allocated)
return allocated
return allocated except IndexError:
raise exceptions.SegmentationIdAllocationFailure('There are '
'no vlan ids available.')
except KeyError:
# Other thread obtained the same vlan_id, so a new try is
# needed
continue
raise exceptions.SegmentationIdAllocationFailure('Max number of '
'retries reached without finding an available vlan id.')
def release_segmentation_id(self, id): def release_segmentation_id(self, id):
self.available_local_vlans.add(id) self.available_local_vlans.add(id)

View File

@ -0,0 +1,99 @@
# Copyright 2017 Red Hat, Inc.
#
# 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 mock
from oslo_config import cfg
from six import moves
from kuryr.lib import constants as const
from kuryr.lib import exceptions
from kuryr.tests.unit import base
from kuryr.lib.segmentation_type_drivers import vlan
class VlanSegmentationDriverTest(base.TestCase):
"""Unit tests for VLAN segmentation driver."""
def setUp(self):
super(VlanSegmentationDriverTest, self).setUp()
cfg.CONF.binding.driver = 'kuryr.lib.binding.drivers.vlan'
def test_allocate_segmentation_id(self):
vlan_seg_driver = vlan.SegmentationDriver()
allocated_ids = set([1, 2, 3])
vlan_id = vlan_seg_driver.allocate_segmentation_id(allocated_ids)
self.assertNotIn(vlan_id, vlan_seg_driver.available_local_vlans)
self.assertNotIn(allocated_ids, vlan_seg_driver.available_local_vlans)
def test_allocate_segmentation_id_only_1_available(self):
vlan_seg_driver = vlan.SegmentationDriver()
allocated_ids = set(moves.range(const.MIN_VLAN_TAG,
const.MAX_VLAN_TAG + 1))
allocated_ids.remove(const.MAX_VLAN_TAG)
vlan_id = vlan_seg_driver.allocate_segmentation_id(allocated_ids)
self.assertNotIn(vlan_id, vlan_seg_driver.available_local_vlans)
self.assertNotIn(allocated_ids, vlan_seg_driver.available_local_vlans)
self.assertEqual(vlan_id, const.MAX_VLAN_TAG)
def test_allocate_segmentation_id_no_allocated_ids(self):
vlan_seg_driver = vlan.SegmentationDriver()
vlan_id = vlan_seg_driver.allocate_segmentation_id()
self.assertNotIn(vlan_id, vlan_seg_driver.available_local_vlans)
def test_allocate_segmentation_id_no_available_vlans(self):
vlan_seg_driver = vlan.SegmentationDriver()
allocated_ids = set(moves.range(const.MIN_VLAN_TAG,
const.MAX_VLAN_TAG + 1))
self.assertRaises(exceptions.SegmentationIdAllocationFailure,
vlan_seg_driver.allocate_segmentation_id,
allocated_ids)
@mock.patch('random.choice')
def test_allocate_segmentation_id_max_retries(self, mock_choice):
mock_choice.side_effect = [1, 1, 1]
vlan_seg_driver = vlan.SegmentationDriver()
allocated_ids = set([1, 2, 3])
self.assertRaises(exceptions.SegmentationIdAllocationFailure,
vlan_seg_driver.allocate_segmentation_id,
allocated_ids)
self.assertEqual(len(mock_choice.mock_calls), 3)
@mock.patch('random.choice')
def test_allocate_segmentation_id_2_retries(self, mock_choice):
vlan_seg_driver = vlan.SegmentationDriver()
vlan_seg_driver.available_local_vlans = set(moves.range(1, 10))
allocated_ids = set([1, 2, 3])
mock_choice.side_effect = [1, 1, 5]
vlan_id = vlan_seg_driver.allocate_segmentation_id(allocated_ids)
self.assertEqual(len(mock_choice.mock_calls), 3)
self.assertEqual(vlan_id, 5)
def test_release_segmentation_id(self):
vlan_seg_driver = vlan.SegmentationDriver()
vlan_seg_driver.available_local_vlans = set(moves.range(1, 10))
vlan_id = 20
vlan_seg_driver.release_segmentation_id(vlan_id)
self.assertIn(vlan_id, vlan_seg_driver.available_local_vlans)