Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions magnum/api/attr_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from glanceclient import exc as glance_exception
from novaclient import exceptions as nova_exception
from oslo_utils import strutils

from magnum.api import utils as api_utils
from magnum.common import clients
Expand Down Expand Up @@ -177,6 +178,29 @@ def validate_master_count(context, cluster):
"master_count must be 1 when master_lb_enabled is False"))


def validate_flavor_root_volume_size(cli, flavor, boot_volume_size):
"""Validate flavor root volume size."""

boot_volume_size = strutils.validate_integer(boot_volume_size,
"boot_volume_size")

if boot_volume_size > 0:
return

flavor_obj = None
flavor_list = cli.nova().flavors.list()
for f in flavor_list:
if f.name == flavor or f.id == flavor:
flavor_obj = f
break

if flavor_obj is None:
raise exception.FlavorNotFound(flavor=flavor)

if flavor_obj.disk == 0 and boot_volume_size == 0:
raise exception.FlavorZeroRootVolumeNotSupported()


def validate_federation_hostcluster(cluster_uuid):
"""Validate Federation `hostcluster_id` parameter.

Expand Down
19 changes: 19 additions & 0 deletions magnum/api/controllers/v1/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.api import validation
from magnum.common import clients
from magnum.common import exception
from magnum.common import name_generator
from magnum.common import policy
Expand Down Expand Up @@ -542,6 +543,15 @@ def _post(self, cluster):
not getattr(cluster, attr)):
setattr(cluster, attr, getattr(cluster_template, attr))

# check root volume size
boot_volume_size = cluster.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
cli = clients.OpenStackClients(context)
attr_validator.validate_flavor_root_volume_size(
cli, cluster.flavor_id, boot_volume_size)
attr_validator.validate_flavor_root_volume_size(
cli, cluster.master_flavor_id, boot_volume_size)

cluster_dict = cluster.as_dict()

attr_validator.validate_os_resources(context,
Expand Down Expand Up @@ -660,6 +670,15 @@ def _patch(self, cluster_ident, patch):
if getattr(cluster, field) != getattr(new_cluster, field):
delta.add(field)

# check root volume size
boot_volume_size = new_cluster.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
cli = clients.OpenStackClients(context)
attr_validator.validate_flavor_root_volume_size(
cli, new_cluster.flavor_id, boot_volume_size)
attr_validator.validate_flavor_root_volume_size(
cli, new_cluster.master_flavor_id, boot_volume_size)

validation.validate_cluster_properties(delta)

# NOTE(brtknr): cluster.node_count is the size of the whole cluster
Expand Down
21 changes: 21 additions & 0 deletions magnum/api/controllers/v1/cluster_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
from magnum import objects
from magnum.objects import fields

from oslo_config import cfg

CONF = cfg.CONF

LOG = logging.getLogger(__name__)


Expand Down Expand Up @@ -425,6 +429,14 @@ def post(self, cluster_template):
do_raise=False):
raise exception.ClusterTemplatePublishDenied()

# check root volume size
boot_volume_size = cluster_template.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
attr_validator.validate_flavor_root_volume_size(
cli, cluster_template.flavor_id, boot_volume_size)
attr_validator.validate_flavor_root_volume_size(
cli, cluster_template.master_flavor_id, boot_volume_size)

if (cluster_template.docker_storage_driver in ('devicemapper',
'overlay')):
warnings.warn(self._devicemapper_overlay_deprecation_note,
Expand Down Expand Up @@ -498,6 +510,15 @@ def patch(self, cluster_template_ident, patch): # noqa
do_raise=False):
raise exception.ClusterTemplatePublishDenied()

# check root volume size
boot_volume_size = cluster_template.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
cli = clients.OpenStackClients(context)
attr_validator.validate_flavor_root_volume_size(
cli, new_cluster_template.flavor_id, boot_volume_size)
attr_validator.validate_flavor_root_volume_size(
cli, new_cluster_template.master_flavor_id, boot_volume_size)

# Update only the fields that have changed
for field in objects.ClusterTemplate.fields:
try:
Expand Down
21 changes: 21 additions & 0 deletions magnum/api/controllers/v1/nodegroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@
import wsme
from wsme import types as wtypes

from magnum.api import attr_validator
from magnum.api.controllers import base
from magnum.api.controllers import link
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.common import clients
from magnum.common import exception
from magnum.common import policy
import magnum.conf
from magnum import objects
from magnum.objects import fields

CONF = magnum.conf.CONF


def _validate_node_count(ng):
if ng.max_node_count:
Expand Down Expand Up @@ -350,6 +355,13 @@ def post(self, cluster_id, nodegroup):
labels.update(nodegroup.labels)
nodegroup.labels = labels

# check root volume size
boot_volume_size = cluster.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
cli = clients.OpenStackClients(context)
attr_validator.validate_flavor_root_volume_size(
cli, nodegroup.flavor_id, boot_volume_size)

nodegroup_dict = nodegroup.as_dict()
nodegroup_dict['cluster_id'] = cluster.uuid
nodegroup_dict['project_id'] = context.project_id
Expand Down Expand Up @@ -414,6 +426,15 @@ def _patch(self, cluster_uuid, nodegroup_id, patch):
patch_val = None
if nodegroup[field] != patch_val:
nodegroup[field] = patch_val

# check root volume size
cluster = api_utils.get_resource('Cluster', cluster_uuid)
boot_volume_size = cluster.labels.get(
'boot_volume_size', CONF.cinder.default_boot_volume_size)
cli = clients.OpenStackClients(context)
attr_validator.validate_flavor_root_volume_size(
cli, nodegroup.flavor_id, boot_volume_size)

_validate_node_count(nodegroup)

return nodegroup
5 changes: 5 additions & 0 deletions magnum/common/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ class ZeroNodeCountNotSupported(NotSupported):
"provided microversion.")


class FlavorZeroRootVolumeNotSupported(NotSupported):
message = _("Flavor with zero root volume size is not supported "
"when boot_volume_size is zero.")


class ClusterUpgradeNotSupported(NotSupported):
message = _("Cluster upgrade is not supported in the "
"provided microversion.")
Expand Down
17 changes: 17 additions & 0 deletions magnum/tests/unit/api/controllers/v1/test_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ def setUp(self):
self.mock_cluster_update = p.start()
self.mock_cluster_update.side_effect = self._sim_rpc_cluster_update
self.addCleanup(p.stop)
p = mock.patch.object(
attr_validator, 'validate_flavor_root_volume_size')
self.mock_valid_flavor_disk = p.start()
self.addCleanup(p.stop)

def _sim_rpc_cluster_update(self, cluster, node_count, health_status,
health_status_reason, rollback=False):
Expand Down Expand Up @@ -612,6 +616,10 @@ def setUp(self):
p = mock.patch.object(attr_validator, 'validate_os_resources')
self.mock_valid_os_res = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(
attr_validator, 'validate_flavor_root_volume_size')
self.mock_valid_flavor_disk = p.start()
self.addCleanup(p.stop)

def _simulate_cluster_create(self, cluster, master_count, node_count,
create_timeout):
Expand Down Expand Up @@ -873,6 +881,15 @@ def test_create_cluster_with_invalid_flavor(self):
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(400, response.status_int)

def test_create_cluster_with_invalid_flavor_disk_size(self):
bdict = apiutils.cluster_post_data()
self.mock_valid_flavor_disk.side_effect = \
exception.FlavorZeroRootVolumeNotSupported()
response = self.post_json('/clusters', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_flavor_disk.called)
self.assertEqual(400, response.status_int)

def test_create_cluster_with_invalid_ext_network(self):
bdict = apiutils.cluster_post_data()
self.mock_valid_os_res.side_effect = \
Expand Down
36 changes: 36 additions & 0 deletions magnum/tests/unit/api/controllers/v1/test_cluster_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ def setUp(self):
labels={'key1': 'val1', 'key2': 'val2'},
hidden=False
)
p = mock.patch.object(
attr_validator, 'validate_flavor_root_volume_size')
self.mock_valid_flavor_disk = p.start()
self.addCleanup(p.stop)

def test_update_not_found(self):
uuid = uuidutils.generate_uuid()
Expand Down Expand Up @@ -482,6 +486,19 @@ def test_replace_cluster_template_with_no_exist_flavor_id(self):
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])

def test_replace_cluster_template_with_invalid_flavor(self):
self.mock_valid_flavor_disk.side_effect = \
exception.FlavorZeroRootVolumeNotSupported()
response = self.patch_json('/clustertemplates/%s' %
self.cluster_template.uuid,
[{'path': '/flavor_id', 'value': 'aaa',
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(self.mock_valid_flavor_disk.called)
self.assertTrue(response.json['errors'])

def test_replace_cluster_template_with_no_exist_keypair_id(self):
self.mock_valid_os_res.side_effect = exception.KeyPairNotFound("aaa")
response = self.patch_json('/clustertemplates/%s' %
Expand Down Expand Up @@ -632,6 +649,10 @@ def setUp(self):
p = mock.patch.object(attr_validator, 'validate_os_resources')
self.mock_valid_os_res = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(
attr_validator, 'validate_flavor_root_volume_size')
self.mock_valid_flavor_disk = p.start()
self.addCleanup(p.stop)

@mock.patch('magnum.api.attr_validator.validate_image')
@mock.patch('oslo_utils.timeutils.utcnow')
Expand Down Expand Up @@ -1110,6 +1131,21 @@ def test_create_cluster_template_with_no_exist_flavor(self,
expect_errors=True)
self.assertEqual(400, response.status_int)

@mock.patch('magnum.api.attr_validator.validate_image')
def test_create_cluster_template_with_invalid_flavor(
self,
mock_image_data
):
self.mock_valid_flavor_disk.side_effect = \
exception.FlavorZeroRootVolumeNotSupported()
mock_image_data.return_value = {'name': 'mock_name',
'os_distro': 'fedora-coreos'}
bdict = apiutils.cluster_template_post_data()
response = self.post_json('/clustertemplates', bdict,
expect_errors=True)
self.assertTrue(self.mock_valid_flavor_disk.called)
self.assertEqual(400, response.status_int)

@mock.patch('magnum.api.attr_validator.validate_image')
def test_create_cluster_template_with_external_network(self,
mock_image_data):
Expand Down
9 changes: 9 additions & 0 deletions magnum/tests/unit/api/controllers/v1/test_nodegroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from oslo_utils import timeutils
from oslo_utils import uuidutils

from magnum.api import attr_validator
from magnum.api.controllers.v1 import nodegroup as api_nodegroup
from magnum.conductor import api as rpcapi
import magnum.conf
Expand Down Expand Up @@ -265,6 +266,10 @@ def setUp(self):
self.mock_ng_create.side_effect = self._simulate_nodegroup_create
self.addCleanup(p.stop)
self.url = "/clusters/%s/nodegroups" % self.cluster.uuid
p = mock.patch.object(
attr_validator, 'validate_flavor_root_volume_size')
self.mock_valid_flavor_disk = p.start()
self.addCleanup(p.stop)

def _simulate_nodegroup_create(self, cluster, nodegroup):
nodegroup.create()
Expand Down Expand Up @@ -571,6 +576,10 @@ def setUp(self):
self.mock_ng_update.side_effect = self._simulate_nodegroup_update
self.addCleanup(p.stop)
self.url = "/clusters/%s/nodegroups/" % self.cluster.uuid
p = mock.patch.object(
attr_validator, 'validate_flavor_root_volume_size')
self.mock_valid_flavor_disk = p.start()
self.addCleanup(p.stop)

def _simulate_nodegroup_update(self, cluster, nodegroup):
nodegroup.save()
Expand Down
47 changes: 47 additions & 0 deletions magnum/tests/unit/api/test_attr_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,50 @@ def test_validate_os_resources_with_cluster(self, mock_os_cli):
attr_validator.validate_os_resources(mock_context,
mock_cluster_template,
mock_cluster)

def test_validate_flavor_root_volume_size_with_valid_boot_volume_size(
self
):
boot_volume_size = 100
mock_flavor = mock.MagicMock()
mock_flavor.name = 'test_flavor'
mock_flavor.id = 'test_flavor_id'
mock_flavor.disk = 0
mock_flavors = [mock_flavor]
mock_nova = mock.MagicMock()
mock_nova.flavors.list.return_value = mock_flavors
mock_os_cli = mock.MagicMock()
mock_os_cli.nova.return_value = mock_nova
attr_validator.validate_flavor_root_volume_size(
mock_os_cli, 'test_flavor', boot_volume_size)
self.assertFalse(mock_nova.flavors.list.called)

def test_validate_flavor_root_volume_size_with_valid_flavor(self):
boot_volume_size = 0
mock_flavor = mock.MagicMock()
mock_flavor.name = 'test_flavor'
mock_flavor.id = 'test_flavor_id'
mock_flavor.disk = 100
mock_flavors = [mock_flavor]
mock_nova = mock.MagicMock()
mock_nova.flavors.list.return_value = mock_flavors
mock_os_cli = mock.MagicMock()
mock_os_cli.nova.return_value = mock_nova
attr_validator.validate_flavor_root_volume_size(
mock_os_cli, 'test_flavor', boot_volume_size)
self.assertTrue(mock_nova.flavors.list.called)

def test_validate_flavor_root_volume_size_with_invalid_resources(self):
boot_volume_size = 0
mock_flavor = mock.MagicMock()
mock_flavor.name = 'test_flavor'
mock_flavor.id = 'test_flavor_id'
mock_flavor.disk = 0
mock_flavors = [mock_flavor]
mock_nova = mock.MagicMock()
mock_nova.flavors.list.return_value = mock_flavors
mock_os_cli = mock.MagicMock()
mock_os_cli.nova.return_value = mock_nova
self.assertRaises(exception.FlavorZeroRootVolumeNotSupported,
attr_validator.validate_flavor_root_volume_size,
mock_os_cli, 'test_flavor', boot_volume_size)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- |
Add a validation for the case when boot_volume_size
label and flavor's disk are both zero.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
The boot_volume_size label value is passed as a string from the API.
The validator now converts it to an integer before comparison.
Loading