Add support for allowable network drivers configuration

Problem:
With the changes that were merged with https://review.openstack.org/230147, the
choices of container network drivers that can be selected for a given COE type
when creating a Magnum baymodel are hardcoded, and are based on network drivers
that Magnum can support for each COE.

These hard-coded selections will probably make sense in most cases. However, in
some cloud instances, a cloud provider or cloud admin may want to restrict the
choices of network drivers that a user can select for a given COE even further,
based on specific restrictions in the cloud.

Magnum should provide support for configuration of what container network
drivers are allowed, and this needs to be provided on a per-COE basis.

Proposed fix:
Add 3 configuration list options to the config in /etc/magnum/magnum.conf for
configuring allowed nework drivers for kubernetes, swarm and mesos
respectively. The keyword 'all' can be used to allow all network drivers
supported by Magnum for that COE. The new config options are:
- kubernetes_allowed_network_drivers
  (default=['all'])
- swarm_allowed_network_drivers
  (default=['all'])
- mesos_allowed_network_drivers
  (default=['all'])
Validation of the --network-driver attributes for a baymodel create will be
performed against this above configuration on a per-COE basis.

Change-Id: Ibaa21d43fd6b5c1a6acc10e56145280eeaea8534
Closes-Bug: #1504635
This commit is contained in:
Dane LeBlanc 2015-10-09 16:42:45 -04:00
parent 6bf2bcad88
commit ce0ed4e44d
6 changed files with 267 additions and 99 deletions

View File

@ -333,6 +333,28 @@
#bay_create_timeout = <None>
[baymodel]
#
# From magnum
#
# Allowed network drivers for kubernetes baymodels. Use 'all' keyword
# to allow all drivers supported for kubernetes baymodels. Supported
# network drivers include flannel.
#kubernetes_allowed_network_drivers = all
# Allowed network drivers for docker swarm baymodels. Use 'all' keyword
# to allow all drivers supported for swarm baymodels. Supported
# network drivers include docker.
#swarm_allowed_network_drivers = all
# Allowed network drivers for mesos baymodels. Use 'all' keyword
# to allow all drivers supported for mesos baymodels. Supported
# network drivers include docker.
#mesos_allowed_network_drivers = all
[certificates]
#

View File

@ -14,6 +14,7 @@
import glanceclient.exc
import novaclient.exceptions as nova_exc
from oslo_config import cfg
from oslo_utils import timeutils
import pecan
from pecan import rest
@ -33,6 +34,29 @@ from magnum.common import policy
from magnum import objects
baymodel_opts = [
cfg.ListOpt('kubernetes_allowed_network_drivers',
default=['all'],
help="Allowed network drivers for kubernetes baymodels. "
"Use 'all' keyword to allow all drivers supported "
"for kubernetes baymodels. Supported network drivers "
"include flannel."),
cfg.ListOpt('swarm_allowed_network_drivers',
default=['all'],
help="Allowed network drivers for docker swarm baymodels. "
"Use 'all' keyword to allow all drivers supported "
"for swarm baymodels. Supported network drivers "
"include docker."),
cfg.ListOpt('mesos_allowed_network_drivers',
default=['all'],
help="Allowed network drivers for mesos baymodels. "
"Use 'all' keyword to allow all drivers supported "
"for mesos baymodels. Supported network drivers "
"include docker.")
]
cfg.CONF.register_opts(baymodel_opts, group='baymodel')
class BayModelPatchType(types.JsonPatchType):
pass
@ -215,16 +239,6 @@ class BayModelsController(rest.RestController):
'detail': ['GET'],
}
# Allowed network driver types per COE. An entry of None in this
# dictionary allows the user to leave out the selection of
# network-driver, in which case the default network driver for
# the chosen COE will be used.
_allowed_network_driver_types = {
'kubernetes': ['flannel', None],
'swarm': [None],
'mesos': [None],
}
def _get_baymodels_collection(self, marker, limit,
sort_key, sort_dir, expand=False,
resource_url=None):
@ -319,7 +333,7 @@ class BayModelsController(rest.RestController):
@policy.enforce_wsgi("baymodel", "create")
@expose.expose(BayModel, body=BayModel, status_code=201)
@validation.enforce_network_driver_types(_allowed_network_driver_types)
@validation.enforce_network_driver_types_create()
def post(self, baymodel):
"""Create a new baymodel.
@ -352,7 +366,7 @@ class BayModelsController(rest.RestController):
@policy.enforce_wsgi("baymodel", "update")
@wsme.validate(types.uuid, [BayModelPatchType])
@expose.expose(BayModel, types.uuid, body=[BayModelPatchType])
@validation.enforce_network_driver_types(_allowed_network_driver_types)
@validation.enforce_network_driver_types_update()
def patch(self, baymodel_uuid, patch):
"""Update an existing baymodel.

View File

@ -14,6 +14,7 @@
# limitations under the License.
import decorator
from oslo_config import cfg
import pecan
from magnum.common import exception
@ -39,32 +40,92 @@ def enforce_bay_types(*bay_types):
return wrapper
def enforce_network_driver_types(network_driver_types_dict):
def enforce_network_driver_types_create():
@decorator.decorator
def wrapper(func, *args, **kwargs):
obj = args[1]
if hasattr(obj, 'network_driver'):
# Post operation: baymodel API instance has been passed
driver = obj.network_driver
coe = obj.coe
else:
# Patch operation: baymodel UUID has been passed
baymodel = objects.BayModel.get_by_uuid(pecan.request.context,
obj)
driver = baymodel.network_driver
coe = baymodel.coe
if (coe in network_driver_types_dict and
driver not in network_driver_types_dict[coe]):
raise exception.InvalidParameterValue(
'Cannot fulfill request with a '
'%(network_driver_type)s network_driver, '
'expecting a %(supported_network_driver_types)s '
'network_driver.' %
{'network_driver_type': driver,
'supported_network_driver_types':
'/'.join([x if x else 'unspecified' for x in
network_driver_types_dict[coe]])})
baymodel = args[1]
_enforce_network_driver_types(baymodel)
return func(*args, **kwargs)
return wrapper
def enforce_network_driver_types_update():
@decorator.decorator
def wrapper(func, *args, **kwargs):
uuid = args[1]
baymodel = objects.BayModel.get_by_uuid(pecan.request.context, uuid)
_enforce_network_driver_types(baymodel)
return func(*args, **kwargs)
return wrapper
def _enforce_network_driver_types(baymodel):
driver = baymodel.network_driver
if driver:
validator = Validator.get_coe_validator(baymodel.coe)
validator.validate_network_driver(driver)
class Validator(object):
@staticmethod
def get_coe_validator(coe):
if coe == 'kubernetes':
return K8sValidator()
if coe == 'swarm':
return SwarmValidator()
if coe == 'mesos':
return MesosValidator()
raise exception.InvalidParameterValue(
'Requested COE type %s is not supported.' % coe)
@classmethod
def validate_network_driver(cls, driver):
cls._validate_network_driver_supported(driver)
cls._validate_network_driver_allowed(driver)
@classmethod
def _validate_network_driver_supported(cls, driver):
"""Confirm that driver is supported by Magnum for this COE."""
if driver not in cls.supported_drivers:
raise exception.InvalidParameterValue(
'Network driver type %(driver)s is not supported, '
'expecting a %(supported_drivers)s network driver.' % {
'driver': driver,
'supported_drivers': '/'.join(
cls.supported_drivers + ['unspecified'])})
@classmethod
def _validate_network_driver_allowed(cls, driver):
"""Confirm that driver is allowed via configuration for this COE."""
allowed_drivers = cfg.CONF.baymodel[cls.allowed_driver_config]
if ('all' not in allowed_drivers and
driver not in allowed_drivers):
raise exception.InvalidParameterValue(
'Network driver type %(driver)s is not allowed, '
'expecting a %(allowed_drivers)s network driver. '
'Check %(config)s configuration.' % {
'driver': driver,
'allowed_drivers': '/'.join(
allowed_drivers + ['unspecified']),
'config': cls.allowed_driver_config})
class K8sValidator(Validator):
supported_drivers = ['flannel']
allowed_driver_config = 'kubernetes_allowed_network_drivers'
class SwarmValidator(Validator):
supported_drivers = ['docker']
allowed_driver_config = 'swarm_allowed_network_drivers'
class MesosValidator(Validator):
supported_drivers = ['docker']
allowed_driver_config = 'mesos_allowed_network_drivers'

View File

@ -172,3 +172,12 @@ class BayModelTest(base.BaseMagnumTest):
self.assertRaises(
exceptions.BadRequest,
bay_model_client.patch_baymodel, datagen.random_uuid(), gen_model)
@testtools.testcase.attr('negative')
def test_create_baymodel_invalid_network_driver(self):
bay_model_client = cli.BayModelClient.as_user('default')
gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id()
gen_model.network_driver = 'invalid_network_driver'
self.assertRaises(
exceptions.BadRequest,
bay_model_client.post_baymodel, gen_model)

View File

@ -544,47 +544,66 @@ class TestPost(api_base.FunctionalTest):
@mock.patch.object(api_baymodel.BayModelsController, '_get_image_data')
@mock.patch.object(api_baymodel.BayModelsController,
'check_keypair_exists')
def test_create_baymodel_with_network_driver(self, mock_keypair_exists,
mock_image_data):
def _test_create_baymodel_network_driver_attr(self,
baymodel_dict,
baymodel_config_dict,
expect_errors,
mock_keypair_exists,
mock_image_data):
mock_keypair_exists.return_value = None
with mock.patch.object(self.dbapi, 'create_baymodel',
wraps=self.dbapi.create_baymodel) as cc_mock:
mock_keypair_exists.return_value = None
mock_image_data.return_value = {'name': 'mock_name',
'os_distro': 'fedora-atomic'}
bdict = apiutils.baymodel_post_data(coe='kubernetes',
network_driver='flannel')
response = self.post_json('/baymodels', bdict)
self.assertEqual(bdict['network_driver'],
response.json['network_driver'])
cc_mock.assert_called_once_with(mock.ANY)
self.assertNotIn('id', cc_mock.call_args[0][0])
@mock.patch.object(api_baymodel.BayModelsController, '_get_image_data')
@mock.patch.object(api_baymodel.BayModelsController,
'check_keypair_exists')
def test_create_baymodel_with_no_network_driver(self, mock_keypair_exists,
mock_image_data):
mock_keypair_exists.return_value = None
with mock.patch.object(self.dbapi, 'create_baymodel',
wraps=self.dbapi.create_baymodel) as cc_mock:
mock_image_data.return_value = {'name': 'mock_name',
'os_distro': 'fedora-atomic'}
bdict = apiutils.baymodel_post_data()
response = self.post_json('/baymodels', bdict)
self.assertEqual(bdict['network_driver'],
response.json['network_driver'])
cc_mock.assert_called_once_with(mock.ANY)
self.assertNotIn('id', cc_mock.call_args[0][0])
mock_image_data.return_value = {'name': 'mock_name',
'os_distro': 'fedora-atomic'}
bdict = apiutils.baymodel_post_data()
del bdict['uuid']
response = self.post_json('/baymodels', bdict)
self.assertEqual(bdict['image_id'],
response.json['image_id'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
for k, v in baymodel_config_dict.items():
cfg.CONF.set_override(k, v, 'baymodel')
with mock.patch.object(self.dbapi, 'create_baymodel',
wraps=self.dbapi.create_baymodel) as cc_mock:
bdict = apiutils.baymodel_post_data(**baymodel_dict)
response = self.post_json('/baymodels', bdict,
expect_errors=expect_errors)
if expect_errors:
self.assertEqual(400, response.status_int)
else:
self.assertEqual(bdict['network_driver'],
response.json['network_driver'])
self.assertEqual(bdict['image_id'],
response.json['image_id'])
cc_mock.assert_called_once_with(mock.ANY)
self.assertNotIn('id', cc_mock.call_args[0][0])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
def test_create_baymodel_with_network_driver(self):
baymodel_dict = {'coe': 'kubernetes', 'network_driver': 'flannel'}
config_dict = {} # Default config
expect_errors_flag = False
self._test_create_baymodel_network_driver_attr(baymodel_dict,
config_dict,
expect_errors_flag)
def test_create_baymodel_with_no_network_driver(self):
baymodel_dict = {}
config_dict = {}
expect_errors_flag = False
self._test_create_baymodel_network_driver_attr(baymodel_dict,
config_dict,
expect_errors_flag)
def test_create_baymodel_with_network_driver_non_def_config(self):
baymodel_dict = {'coe': 'kubernetes', 'network_driver': 'flannel'}
config_dict = {
'kubernetes_allowed_network_drivers': ['flannel', 'foo']}
expect_errors_flag = False
self._test_create_baymodel_network_driver_attr(baymodel_dict,
config_dict,
expect_errors_flag)
def test_create_baymodel_with_invalid_network_driver(self):
baymodel_dict = {'coe': 'kubernetes', 'network_driver': 'bad_driver'}
config_dict = {
'kubernetes_allowed_network_drivers': ['flannel', 'good_driver']}
expect_errors_flag = True
self._test_create_baymodel_network_driver_attr(baymodel_dict,
config_dict,
expect_errors_flag)
@mock.patch.object(api_baymodel.BayModelsController, '_get_image_data')
@mock.patch.object(api_baymodel.BayModelsController,

View File

@ -14,7 +14,9 @@
# limitations under the License.
import mock
from oslo_config import cfg
from magnum.api.controllers.v1 import baymodel as api_baymodel # noqa
from magnum.api import validation as v
from magnum.common import exception
from magnum.tests import base
@ -102,56 +104,83 @@ class TestValidation(base.BaseTestCase):
mock_pecan_request, bay_type, allowed_bay_types,
assert_raised=True)
def _test_enforce_network_driver_types_post(
def _test_enforce_network_driver_types_create(
self,
network_driver_type,
allowed_network_driver_types,
network_driver_config_dict,
coe='kubernetes',
assert_raised=False):
@v.enforce_network_driver_types(allowed_network_driver_types)
@v.enforce_network_driver_types_create()
def test(self, baymodel):
pass
for key, val in network_driver_config_dict.items():
cfg.CONF.set_override(key, val, 'baymodel')
baymodel = mock.MagicMock()
baymodel.name = 'test_baymodel'
baymodel.network_driver = network_driver_type
baymodel.coe = 'kubernetes'
baymodel.coe = coe
validator = v.K8sValidator
validator.supported_drivers = ['flannel', 'type1', 'type2']
validator.allowed_driver_config = 'kubernetes_allowed_network_drivers'
if assert_raised:
self.assertRaises(exception.InvalidParameterValue,
test, self, baymodel)
else:
test(self, baymodel)
def test_enforce_network_driver_types_one_allowed_post(self):
self._test_enforce_network_driver_types_post(
def test_enforce_network_driver_types_one_allowed_create(self):
self._test_enforce_network_driver_types_create(
network_driver_type='type1',
allowed_network_driver_types={'kubernetes': ['type1']})
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['type1']})
def test_enforce_network_driver_types_two_allowed_post(self):
self._test_enforce_network_driver_types_post(
def test_enforce_network_driver_types_two_allowed_create(self):
self._test_enforce_network_driver_types_create(
network_driver_type='type1',
allowed_network_driver_types={'kubernetes': ['type1', 'type2']})
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['type1', 'type2']})
def test_enforce_network_driver_types_not_allowed_post(self):
self._test_enforce_network_driver_types_post(
def test_enforce_network_driver_types_not_allowed_create(self):
self._test_enforce_network_driver_types_create(
network_driver_type='type1',
allowed_network_driver_types={'kubernetes': ['type2']},
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['type2']},
assert_raised=True)
def test_enforce_network_driver_types_all_allowed_create(self):
for driver in ['flannel', 'type1', 'type2']:
self._test_enforce_network_driver_types_create(
network_driver_type=driver,
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['all']})
def test_enforce_network_driver_types_invalid_coe_create(self):
self._test_enforce_network_driver_types_create(
network_driver_type='flannel',
network_driver_config_dict={},
coe='invalid_coe_type',
assert_raised=True)
@mock.patch('pecan.request')
@mock.patch('magnum.objects.BayModel.get_by_uuid')
def _test_enforce_network_driver_types_patch(
def _test_enforce_network_driver_types_update(
self,
mock_baymodel_get_by_uuid,
mock_pecan_request,
network_driver_type,
allowed_network_driver_types,
network_driver_config_dict,
assert_raised=False):
@v.enforce_network_driver_types(allowed_network_driver_types)
@v.enforce_network_driver_types_update()
def test(self, baymodel_uuid):
pass
for key, val in network_driver_config_dict.items():
cfg.CONF.set_override(key, val, 'baymodel')
context = mock_pecan_request.context
baymodel_uuid = 'test_uuid'
baymodel = mock.MagicMock()
@ -159,6 +188,10 @@ class TestValidation(base.BaseTestCase):
baymodel.coe = 'kubernetes'
mock_baymodel_get_by_uuid.return_value = baymodel
validator = v.K8sValidator
validator.supported_drivers = ['flannel', 'type1', 'type2']
validator.allowed_driver_config = 'kubernetes_allowed_network_drivers'
if assert_raised:
self.assertRaises(exception.InvalidParameterValue,
test, self, baymodel_uuid)
@ -167,18 +200,28 @@ class TestValidation(base.BaseTestCase):
mock_baymodel_get_by_uuid.assert_called_once_with(
context, baymodel_uuid)
def test_enforce_network_driver_types_one_allowed_patch(self):
self._test_enforce_network_driver_types_patch(
def test_enforce_network_driver_types_one_allowed_update(self):
self._test_enforce_network_driver_types_update(
network_driver_type='type1',
allowed_network_driver_types={'kubernetes': ['type1']})
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['type1']})
def test_enforce_network_driver_types_two_allowed_patch(self):
self._test_enforce_network_driver_types_patch(
def test_enforce_network_driver_types_two_allowed_update(self):
self._test_enforce_network_driver_types_update(
network_driver_type='type1',
allowed_network_driver_types={'kubernetes': ['type1', 'type2']})
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['type1', 'type2']})
def test_enforce_network_driver_types_not_allowed_patch(self):
self._test_enforce_network_driver_types_patch(
def test_enforce_network_driver_types_not_allowed_update(self):
self._test_enforce_network_driver_types_update(
network_driver_type='type1',
allowed_network_driver_types={'kubernetes': ['type2']},
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['type2']},
assert_raised=True)
def test_enforce_network_driver_types_all_allowed_update(self):
for driver in ['flannel', 'type1', 'type2']:
self._test_enforce_network_driver_types_update(
network_driver_type=driver,
network_driver_config_dict={
'kubernetes_allowed_network_drivers': ['all']})