Allow nodegroups with node_count equal to 0
This change allows users to create clusters and nodegroups with node_count equal to 0. Also adds support for resizing existing nodegroups to 0. Change-Id: Id63459d0fe9836e678bb7569f23d29eabc225e9e story: 2007851 task: 40145 Signed-off-by: Diogo Guerra <diogo.filipe.tomas.guerra@cern.ch>
This commit is contained in:
parent
fd79dd4fa6
commit
f46923cc5e
@ -105,7 +105,7 @@ class Cluster(base.APIBase):
|
||||
default=None)
|
||||
"""The name of the nova ssh keypair"""
|
||||
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=0), default=1)
|
||||
"""The node count for this cluster. Default to 1 if not set"""
|
||||
|
||||
master_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
||||
|
@ -44,7 +44,7 @@ class ClusterResizeRequest(base.APIBase):
|
||||
This class enforces type checking and value constraints.
|
||||
"""
|
||||
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), mandatory=True)
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=0), mandatory=True)
|
||||
"""The expected node count after resize."""
|
||||
|
||||
nodes_to_remove = wsme.wsattr([wtypes.text], mandatory=False,
|
||||
|
@ -98,18 +98,18 @@ class NodeGroup(base.APIBase):
|
||||
node_addresses = wsme.wsattr([wtypes.text], readonly=True)
|
||||
"""IP addresses of nodegroup nodes"""
|
||||
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=0), default=1)
|
||||
"""The node count for this nodegroup. Default to 1 if not set"""
|
||||
|
||||
role = wsme.wsattr(wtypes.StringType(min_length=1, max_length=255),
|
||||
default='worker')
|
||||
"""The role of the nodes included in this nodegroup"""
|
||||
|
||||
min_node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
||||
"""The minimum allowed nodes for this nodegroup. Default to 1 if not set"""
|
||||
min_node_count = wsme.wsattr(wtypes.IntegerType(minimum=0), default=0)
|
||||
"""The minimum allowed nodes for this nodegroup. Default to 0 if not set"""
|
||||
|
||||
max_node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=None)
|
||||
"""The maximum allowed nodes for this nodegroup. Default to 1 if not set"""
|
||||
max_node_count = wsme.wsattr(wtypes.IntegerType(minimum=0), default=None)
|
||||
"""The maximum allowed nodes for this nodegroup."""
|
||||
|
||||
is_default = types.BooleanType()
|
||||
"""Specifies is a nodegroup was created by default or not"""
|
||||
|
@ -210,11 +210,11 @@ class JsonPatchType(wtypes.Base):
|
||||
raise wsme.exc.ClientSideError(msg % patch.path)
|
||||
|
||||
if patch.op != 'remove':
|
||||
if not patch.value:
|
||||
if patch.value is None or patch.value == wtypes.Unset:
|
||||
msg = _("'add' and 'replace' operations needs value")
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
ret = {'path': patch.path, 'op': patch.op}
|
||||
if patch.value:
|
||||
if patch.value is not None and patch.value != wtypes.Unset:
|
||||
ret['value'] = patch.value
|
||||
return ret
|
||||
|
@ -17,16 +17,6 @@ from webob import exc
|
||||
|
||||
from magnum.i18n import _
|
||||
|
||||
# NOTE(yuntong): v1.0 is reserved to indicate Kilo's API, but is not presently
|
||||
# supported by the API service. All changes between Kilo and the
|
||||
# point where we added microversioning are considered backwards-
|
||||
# compatible, but are not specifically discoverable at this time.
|
||||
#
|
||||
# The v1.1 version indicates this "initial" version as being
|
||||
# different from Kilo (v1.0), and includes the following changes:
|
||||
#
|
||||
# Add details of new api versions here:
|
||||
|
||||
#
|
||||
# For each newly added microversion change, update the API version history
|
||||
# string below with a one or two line description. Also update
|
||||
@ -42,10 +32,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
* 1.7 - Add resize API
|
||||
* 1.8 - Add upgrade API
|
||||
* 1.9 - Add nodegroup API
|
||||
* 1.10 - Allow nodegroups with 0 nodes
|
||||
"""
|
||||
|
||||
BASE_VER = '1.1'
|
||||
CURRENT_MAX_VER = '1.9'
|
||||
CURRENT_MAX_VER = '1.10'
|
||||
|
||||
|
||||
class Version(object):
|
||||
|
@ -75,3 +75,12 @@ user documentation.
|
||||
|
||||
An admin user can set/update/delete/list quotas for the given tenant.
|
||||
A non-admin user can get self quota information.
|
||||
|
||||
|
||||
1.10
|
||||
---
|
||||
|
||||
Allow nodegroups with 0 nodes
|
||||
|
||||
Allow the cluster to be created with node_count = 0 as well as to update
|
||||
existing nodegroups to have 0 nodes.
|
||||
|
@ -18,6 +18,7 @@ from pycadf import cadftaxonomy as taxonomy
|
||||
from pycadf import cadftype
|
||||
from pycadf import eventfactory
|
||||
from pycadf import resource
|
||||
from wsme import types as wtypes
|
||||
|
||||
from magnum.common import clients
|
||||
from magnum.common import rpc
|
||||
@ -176,6 +177,7 @@ def _get_nodegroup_object(context, cluster, node_count, is_master=False):
|
||||
ng.image_id = cluster.cluster_template.image_id
|
||||
ng.docker_volume_size = (cluster.docker_volume_size or
|
||||
cluster.cluster_template.docker_volume_size)
|
||||
|
||||
if is_master:
|
||||
ng.flavor_id = (cluster.master_flavor_id or
|
||||
cluster.cluster_template.master_flavor_id)
|
||||
@ -183,6 +185,11 @@ def _get_nodegroup_object(context, cluster, node_count, is_master=False):
|
||||
else:
|
||||
ng.flavor_id = cluster.flavor_id or cluster.cluster_template.flavor_id
|
||||
ng.role = "worker"
|
||||
if (cluster.labels != wtypes.Unset and cluster.labels is not None
|
||||
and 'min_node_count' in cluster.labels):
|
||||
ng.min_node_count = cluster.labels['min_node_count']
|
||||
else:
|
||||
ng.min_node_count = 0
|
||||
ng.name = "default-%s" % ng.role
|
||||
ng.is_default = True
|
||||
ng.status = fields.ClusterStatus.CREATE_IN_PROGRESS
|
||||
|
@ -842,7 +842,7 @@ parameters:
|
||||
type: number
|
||||
description: >
|
||||
minimum node count of cluster workers when doing scale down
|
||||
default: 1
|
||||
default: 0
|
||||
|
||||
max_node_count:
|
||||
type: number
|
||||
|
@ -858,7 +858,7 @@ parameters:
|
||||
type: number
|
||||
description: >
|
||||
minimum node count of cluster workers when doing scale down
|
||||
default: 1
|
||||
default: 0
|
||||
|
||||
max_node_count:
|
||||
type: number
|
||||
|
@ -26,8 +26,9 @@ from magnum.objects import fields as m_fields
|
||||
class NodeGroup(base.MagnumPersistentObject, base.MagnumObject,
|
||||
base.MagnumObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: min_node_count defaults to 0
|
||||
|
||||
VERSION = '1.0'
|
||||
VERSION = '1.1'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
@ -45,7 +46,7 @@ class NodeGroup(base.MagnumPersistentObject, base.MagnumObject,
|
||||
'node_count': fields.IntegerField(nullable=False, default=1),
|
||||
'role': fields.StringField(),
|
||||
'max_node_count': fields.IntegerField(nullable=True),
|
||||
'min_node_count': fields.IntegerField(nullable=False, default=1),
|
||||
'min_node_count': fields.IntegerField(nullable=False, default=0),
|
||||
'is_default': fields.BooleanField(default=False),
|
||||
'stack_id': fields.StringField(nullable=True),
|
||||
'status': m_fields.ClusterStatusField(nullable=True),
|
||||
|
@ -41,7 +41,7 @@ class TestRootController(api_base.FunctionalTest):
|
||||
[{u'href': u'http://localhost/v1/',
|
||||
u'rel': u'self'}],
|
||||
u'status': u'CURRENT',
|
||||
u'max_version': u'1.9',
|
||||
u'max_version': u'1.10',
|
||||
u'min_version': u'1.1'}]}
|
||||
|
||||
self.v1_expected = {
|
||||
|
@ -683,8 +683,7 @@ class TestPost(api_base.FunctionalTest):
|
||||
bdict['node_count'] = 0
|
||||
response = self.post_json('/clusters', bdict, expect_errors=True)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(400, response.status_int)
|
||||
self.assertTrue(response.json['errors'])
|
||||
self.assertEqual(202, response.status_int)
|
||||
|
||||
def test_create_cluster_with_node_count_negative(self):
|
||||
bdict = apiutils.cluster_post_data()
|
||||
|
@ -40,7 +40,7 @@ class TestNodegroupObject(base.TestCase):
|
||||
del nodegroup_dict['max_node_count']
|
||||
nodegroup = api_nodegroup.NodeGroup(**nodegroup_dict)
|
||||
self.assertEqual(1, nodegroup.node_count)
|
||||
self.assertEqual(1, nodegroup.min_node_count)
|
||||
self.assertEqual(0, nodegroup.min_node_count)
|
||||
self.assertIsNone(nodegroup.max_node_count)
|
||||
|
||||
|
||||
@ -293,6 +293,20 @@ class TestPost(NodeGroupControllerTest):
|
||||
# Verify node_count defaults to 1
|
||||
self.assertEqual(1, response.json['node_count'])
|
||||
|
||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
||||
def test_create_nodegroup_with_zero_nodes(self, mock_utcnow):
|
||||
ng_dict = apiutils.nodegroup_post_data()
|
||||
ng_dict['node_count'] = 0
|
||||
ng_dict['min_node_count'] = 0
|
||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||
mock_utcnow.return_value = test_time
|
||||
|
||||
response = self.post_json(self.url, ng_dict)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(202, response.status_int)
|
||||
# Verify node_count is set to zero
|
||||
self.assertEqual(0, response.json['node_count'])
|
||||
|
||||
@mock.patch('oslo_utils.timeutils.utcnow')
|
||||
def test_create_nodegroup_with_max_node_count(self, mock_utcnow):
|
||||
ng_dict = apiutils.nodegroup_post_data(max_node_count=5)
|
||||
@ -366,7 +380,7 @@ class TestPost(NodeGroupControllerTest):
|
||||
self.assertEqual(self.cluster.project_id, response.json['project_id'])
|
||||
self.assertEqual(self.cluster.labels, response.json['labels'])
|
||||
self.assertEqual('worker', response.json['role'])
|
||||
self.assertEqual(1, response.json['min_node_count'])
|
||||
self.assertEqual(0, response.json['min_node_count'])
|
||||
self.assertEqual(1, response.json['node_count'])
|
||||
self.assertIsNone(response.json['max_node_count'])
|
||||
|
||||
@ -662,7 +676,7 @@ class TestPatch(NodeGroupControllerTest):
|
||||
|
||||
response = self.get_json(self.url + self.nodegroup.uuid)
|
||||
# Removing the min_node_count just restores the default value
|
||||
self.assertEqual(1, response['min_node_count'])
|
||||
self.assertEqual(0, response['min_node_count'])
|
||||
return_updated_at = timeutils.parse_isotime(
|
||||
response['updated_at']).replace(tzinfo=None)
|
||||
self.assertEqual(test_time, return_updated_at)
|
||||
|
@ -364,7 +364,7 @@ object_data = {
|
||||
'Stats': '1.0-73a1cd6e3c0294c932a66547faba216c',
|
||||
'Quota': '1.0-94e100aebfa88f7d8428e007f2049c18',
|
||||
'Federation': '1.0-166da281432b083f0e4b851336e12e20',
|
||||
'NodeGroup': '1.0-8cb4544a28a49860d816158a7c3060b1'
|
||||
'NodeGroup': '1.1-70211d19fcf53903a470607f1f4a784f'
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Clusters can now be created with empty nodegroups. Existing nodegroups
|
||||
can be set to node_count = 0. min_node_count defaults to 0.
|
||||
This is usefull for HA or special hardware clusters with multiple
|
||||
nodegroups managed by the cluster auto-scaller.
|
Loading…
Reference in New Issue
Block a user