Skip to content

Commit ab02c97

Browse files
committed
Implement control plane resizing with driver.
This implements spec proposed in [1] which in the base driver defines a list of valid sizes for control plane(master) nodes. In other drivers, this provides an interface to enable resizing of the master nodegroup, and define any list of supported control plane sizes. [1] https://review.opendev.org/c/openstack/magnum-specs/+/905281 Change-Id: Iafb2e1e5c983e09ee58a3a0180051ed096268ed1
1 parent 17a081b commit ab02c97

File tree

9 files changed

+104
-22
lines changed

9 files changed

+104
-22
lines changed

magnum/api/attr_validator.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,11 @@ def validate_os_resources(context, cluster_template, cluster=None):
168168
validate_keypair(cli, cluster['keypair'])
169169

170170

171-
def validate_master_count(cluster, cluster_template):
172-
if cluster['master_count'] > 1 and \
173-
not cluster['master_lb_enabled']:
171+
def validate_master_count(context, cluster):
172+
if (
173+
cluster['master_count'] > 1 and
174+
not cluster['master_lb_enabled']
175+
):
174176
raise exception.InvalidParameterValue(_(
175177
"master_count must be 1 when master_lb_enabled is False"))
176178

magnum/api/controllers/v1/cluster.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ def _check_cluster_quota_limit(self, context):
475475
@validation.ct_not_found_to_bad_request()
476476
@validation.enforce_cluster_type_supported()
477477
@validation.enforce_cluster_volume_storage_size()
478+
@validation.enforce_cluster_master_size_supported()
478479
def post(self, cluster):
479480
if cluster.node_count == 0:
480481
raise exception.ZeroNodeCountNotSupported()
@@ -484,6 +485,7 @@ def post(self, cluster):
484485
@expose.expose(ClusterID, body=Cluster, status_code=202)
485486
@validation.enforce_cluster_type_supported()
486487
@validation.enforce_cluster_volume_storage_size()
488+
@validation.enforce_cluster_master_size_supported()
487489
def post(self, cluster): # noqa
488490
return self._post(cluster)
489491

@@ -545,8 +547,8 @@ def _post(self, cluster):
545547
attr_validator.validate_os_resources(context,
546548
cluster_template.as_dict(),
547549
cluster_dict)
548-
attr_validator.validate_master_count(cluster_dict,
549-
cluster_template.as_dict())
550+
attr_validator.validate_master_count(context,
551+
cluster_dict)
550552

551553
cluster_dict['project_id'] = context.project_id
552554
cluster_dict['user_id'] = context.user_id

magnum/api/controllers/v1/cluster_actions.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from magnum.api import utils as api_utils
2121
from magnum.common import exception
2222
from magnum.common import policy
23+
from magnum.drivers.common.driver import Driver
2324
from magnum import objects
2425

2526

@@ -114,22 +115,24 @@ def _resize(self, cluster_ident, cluster_resize_req):
114115
nodegroup = objects.NodeGroup.get(
115116
context, cluster.uuid, cluster_resize_req.nodegroup)
116117

117-
if nodegroup.role == 'master':
118-
# NOTE(ttsiouts): Restrict the resize to worker nodegroups
119-
raise exception.MasterNGResizeNotSupported()
120-
121118
# NOTE(ttsiouts): Make sure that the new node count is within
122119
# the configured boundaries of the selected nodegroup.
123-
if nodegroup.min_node_count > cluster_resize_req.node_count:
120+
if (nodegroup.role != "master" and
121+
nodegroup.min_node_count > cluster_resize_req.node_count):
124122
raise exception.NGResizeOutBounds(
125123
nodegroup=nodegroup.name, min_nc=nodegroup.min_node_count,
126124
max_nc=nodegroup.max_node_count)
127-
if (nodegroup.max_node_count and
125+
if (nodegroup.role != "master" and nodegroup.max_node_count and
128126
nodegroup.max_node_count < cluster_resize_req.node_count):
129127
raise exception.NGResizeOutBounds(
130128
nodegroup=nodegroup.name, min_nc=nodegroup.min_node_count,
131129
max_nc=nodegroup.max_node_count)
132130

131+
if nodegroup.role == "master":
132+
cluster_driver = Driver.get_driver_for_cluster(context, cluster)
133+
cluster_driver.validate_master_resize(
134+
cluster_resize_req.node_count)
135+
133136
pecan.request.rpcapi.cluster_resize_async(
134137
cluster,
135138
cluster_resize_req.node_count,

magnum/api/validation.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ def wrapper(func, *args, **kwargs):
9090
return wrapper
9191

9292

93+
def enforce_cluster_master_size_supported():
94+
@decorator.decorator
95+
def wrapper(func, *args, **kwargs):
96+
cluster = args[1]
97+
cluster_driver = driver.Driver.get_driver_for_cluster(
98+
pecan.request.context, cluster)
99+
# Call into the driver to validate initial master size
100+
cluster_driver.validate_master_size(cluster.master_count)
101+
return func(*args, **kwargs)
102+
103+
return wrapper
104+
105+
93106
def enforce_cluster_volume_storage_size():
94107
@decorator.decorator
95108
def wrapper(func, *args, **kwargs):

magnum/common/exception.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,14 @@ class NodeGroupNotFound(ResourceNotFound):
434434
message = _("Nodegroup %(nodegroup)s could not be found.")
435435

436436

437+
class MasterNGSizeInvalid(InvalidParameterValue):
438+
message = _("master nodegroup size of %(requested_size)s is invalid, "
439+
"size cannot be an even number.")
440+
441+
437442
class MasterNGResizeNotSupported(NotSupported):
438-
message = _("Resizing a master nodegroup is not supported.")
443+
message = _("Resizing the master nodegroup is not supported "
444+
"by this driver.")
439445

440446

441447
class ZeroNodeCountNotSupported(NotSupported):

magnum/conductor/utils.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,9 @@ def _get_nodegroup_object(context, cluster, node_count, is_master=False):
185185
else:
186186
ng.flavor_id = cluster.flavor_id or cluster.cluster_template.flavor_id
187187
ng.role = "worker"
188-
if (cluster.labels != wtypes.Unset and cluster.labels is not None
189-
and 'min_node_count' in cluster.labels):
190-
ng.min_node_count = cluster.labels['min_node_count']
191-
else:
192-
ng.min_node_count = 0
188+
if (cluster.labels != wtypes.Unset and cluster.labels is not None
189+
and 'min_node_count' in cluster.labels):
190+
ng.min_node_count = cluster.labels['min_node_count']
193191
ng.name = "default-%s" % ng.role
194192
ng.is_default = True
195193
ng.status = fields.ClusterStatus.CREATE_IN_PROGRESS

magnum/drivers/common/driver.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from magnum.common import exception
2424
from magnum.objects import cluster_template
2525

26-
2726
CONF = cfg.CONF
2827
LOG = logging.getLogger(__name__)
2928

@@ -219,6 +218,15 @@ def resize_cluster(self, context, cluster, resize_manager,
219218
raise NotImplementedError("Subclasses must implement "
220219
"'resize_cluster'.")
221220

221+
def validate_master_size(self, node_count):
222+
if node_count % 2 == 0 or node_count < 1:
223+
raise exception.MasterNGSizeInvalid(
224+
requested_size=node_count)
225+
226+
def validate_master_resize(self, node_count):
227+
# Base driver does not support resizing masters.
228+
raise exception.MasterNGResizeNotSupported()
229+
222230
@abc.abstractmethod
223231
def create_federation(self, context, federation):
224232
raise NotImplementedError("Subclasses must implement "

magnum/tests/unit/api/controllers/v1/test_cluster.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ def test_create_cluster_with_non_existent_cluster_template_name(self):
708708

709709
def test_create_cluster_with_cluster_template_name(self):
710710
modelname = self.cluster_template.name
711-
bdict = apiutils.cluster_post_data(cluster_template_id=modelname)
711+
bdict = apiutils.cluster_post_data(name=modelname)
712712
response = self.post_json('/clusters', bdict, expect_errors=True)
713713
self.assertEqual('application/json', response.content_type)
714714
self.assertEqual(202, response.status_int)
@@ -767,6 +767,40 @@ def test_create_cluster_with_no_master_count(self):
767767
self.assertEqual('application/json', response.content_type)
768768
self.assertEqual(202, response.status_int)
769769

770+
def test_create_cluster_with_even_master_count_oldmicroversion(self):
771+
bdict = apiutils.cluster_post_data()
772+
bdict['master_count'] = 2
773+
response = self.post_json(
774+
'/clusters',
775+
bdict,
776+
expect_errors=True,
777+
headers={"Openstack-Api-Version": "container-infra 1.9"}
778+
)
779+
self.assertEqual('application/json', response.content_type)
780+
self.assertEqual(400, response.status_int)
781+
self.assertTrue(response.json['errors'])
782+
783+
def test_create_cluster_with_even_master_count(self):
784+
bdict = apiutils.cluster_post_data()
785+
bdict['master_count'] = 2
786+
response = self.post_json(
787+
'/clusters',
788+
bdict,
789+
expect_errors=True,
790+
headers={"Openstack-Api-Version": "container-infra 1.10"}
791+
)
792+
self.assertEqual('application/json', response.content_type)
793+
self.assertEqual(400, response.status_int)
794+
self.assertTrue(response.json['errors'])
795+
796+
def test_create_cluster_with_negative_master_count(self):
797+
bdict = apiutils.cluster_post_data()
798+
bdict['master_count'] = -1
799+
response = self.post_json('/clusters', bdict, expect_errors=True)
800+
self.assertEqual('application/json', response.content_type)
801+
self.assertEqual(400, response.status_int)
802+
self.assertTrue(response.json['errors'])
803+
770804
def test_create_cluster_with_invalid_name(self):
771805
invalid_names = ['x' * 243, '123456', '123456test_cluster',
772806
'-test_cluster', '.test_cluster', '_test_cluster', '']
@@ -874,7 +908,7 @@ def test_create_cluster_with_multi_images_same_name(self):
874908
self.assertTrue(self.mock_valid_os_res.called)
875909
self.assertEqual(409, response.status_int)
876910

877-
def test_create_cluster_with_on_os_distro_image(self):
911+
def test_create_cluster_with_no_os_distro_image(self):
878912
bdict = apiutils.cluster_post_data()
879913
self.mock_valid_os_res.side_effect = \
880914
exception.OSDistroFieldNotFound('img')

magnum/tests/unit/api/controllers/v1/test_cluster_actions.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,24 @@ def test_resize_with_nodegroup(self):
8080
self.assertEqual(self.cluster_obj.cluster_template_id,
8181
response['cluster_template_id'])
8282

83-
def test_resize_with_master_nodegroup(self):
84-
new_node_count = 6
83+
def test_resize_with_master_nodegroup_even_unsupported(self):
84+
new_node_count = 4
85+
nodegroup = self.cluster_obj.default_ng_master
86+
cluster_resize_req = {
87+
"node_count": new_node_count,
88+
"nodegroup": nodegroup.uuid
89+
}
90+
response = self.post_json('/clusters/%s/actions/resize' %
91+
self.cluster_obj.uuid,
92+
cluster_resize_req,
93+
headers={"Openstack-Api-Version":
94+
"container-infra 1.9",
95+
"X-Roles": "member"},
96+
expect_errors=True)
97+
self.assertEqual(400, response.status_code)
98+
99+
def test_resize_with_master_nodegroup_odd_unsupported(self):
100+
new_node_count = 3
85101
nodegroup = self.cluster_obj.default_ng_master
86102
cluster_resize_req = {
87103
"node_count": new_node_count,

0 commit comments

Comments
 (0)