commit
0cd35dbcca
@ -0,0 +1,90 @@
|
||||
# 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 pecan
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from magnum.api.controllers import base
|
||||
from magnum.api.controllers.v1 import types
|
||||
from magnum.api import expose
|
||||
from magnum.api import utils as api_utils
|
||||
from magnum.common import policy
|
||||
|
||||
|
||||
class ClusterID(wtypes.Base):
|
||||
"""API representation of a cluster ID
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of a cluster
|
||||
ID.
|
||||
"""
|
||||
|
||||
uuid = types.uuid
|
||||
"""Unique UUID for this cluster"""
|
||||
|
||||
def __init__(self, uuid):
|
||||
self.uuid = uuid
|
||||
|
||||
|
||||
class ClusterResizeRequest(base.APIBase):
|
||||
"""API object for handling resize requests.
|
||||
|
||||
This class enforces type checking and value constraints.
|
||||
"""
|
||||
|
||||
node_count = wtypes.IntegerType(minimum=1)
|
||||
"""The expected node count after resize."""
|
||||
|
||||
nodes_to_remove = wsme.wsattr([wsme.types.text], mandatory=False,
|
||||
default=[])
|
||||
"""Instance ID list for nodes to be removed."""
|
||||
|
||||
nodegroup = wtypes.StringType(min_length=1, max_length=255)
|
||||
"""Group of nodes to be uprgaded (master or node)"""
|
||||
|
||||
|
||||
class ActionsController(base.Controller):
|
||||
"""REST controller for cluster actions."""
|
||||
def __init__(self):
|
||||
super(ActionsController, self).__init__()
|
||||
|
||||
_custom_actions = {
|
||||
'resize': ['POST'],
|
||||
}
|
||||
|
||||
@base.Controller.api_version("1.7")
|
||||
@expose.expose(None, types.uuid_or_name,
|
||||
body=ClusterResizeRequest, status_code=202)
|
||||
def resize(self, cluster_ident, cluster_resize_req):
|
||||
"""Resize a cluster.
|
||||
|
||||
:param cluster_ident: UUID of a cluster or logical name of the cluster.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
cluster = api_utils.get_resource('Cluster', cluster_ident)
|
||||
policy.enforce(context, 'cluster:resize', cluster,
|
||||
action='cluster:resize')
|
||||
|
||||
if (cluster_resize_req.nodegroup == wtypes.Unset or
|
||||
not cluster_resize_req.nodegroup):
|
||||
# TODO(flwang): The default node group of current cluster could be
|
||||
# extracted by objects.NodeGroups.get_by_uuid or something like
|
||||
# that as long as we have node group support.
|
||||
cluster_resize_req.nodegroup = None
|
||||
|
||||
pecan.request.rpcapi.cluster_resize_async(
|
||||
cluster,
|
||||
cluster_resize_req.node_count,
|
||||
cluster_resize_req.nodes_to_remove,
|
||||
cluster_resize_req.nodegroup)
|
||||
return ClusterID(cluster.uuid)
|
@ -0,0 +1,53 @@
|
||||
# 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 magnum.conductor import api as rpcapi
|
||||
import magnum.conf
|
||||
from magnum.tests.unit.api import base as api_base
|
||||
from magnum.tests.unit.objects import utils as obj_utils
|
||||
|
||||
CONF = magnum.conf.CONF
|
||||
|
||||
|
||||
class TestClusterActions(api_base.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestClusterActions, self).setUp()
|
||||
self.cluster_obj = obj_utils.create_test_cluster(
|
||||
self.context, name='cluster_example_A', node_count=3)
|
||||
p = mock.patch.object(rpcapi.API, 'cluster_resize_async')
|
||||
self.mock_cluster_resize = p.start()
|
||||
self.mock_cluster_resize.side_effect = self._sim_rpc_cluster_resize
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def _sim_rpc_cluster_resize(self, cluster, node_count, nodes_to_remove,
|
||||
nodegroup=None, rollback=False):
|
||||
cluster.node_count = node_count
|
||||
cluster.save()
|
||||
return cluster
|
||||
|
||||
def test_resize(self):
|
||||
new_node_count = 6
|
||||
response = self.post_json('/clusters/%s/actions/resize' %
|
||||
self.cluster_obj.uuid,
|
||||
{"node_count": new_node_count},
|
||||
headers={"Openstack-Api-Version":
|
||||
"container-infra 1.7"})
|
||||
self.assertEqual(202, response.status_code)
|
||||
|
||||
response = self.get_json('/clusters/%s' % self.cluster_obj.uuid)
|
||||
self.assertEqual(new_node_count, response['node_count'])
|
||||
self.assertEqual(self.cluster_obj.uuid, response['uuid'])
|
||||
self.assertEqual(self.cluster_obj.cluster_template_id,
|
||||
response['cluster_template_id'])
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Now an OpenStack driver for Kubernetes Cluster Autoscaler is being
|
||||
proposed to support autoscaling when running k8s cluster on top of
|
||||
OpenStack. However, currently there is no way in Magnum to let
|
||||
the external consumer to control which node will be removed. The
|
||||
alternative option is calling Heat API directly but obviously it
|
||||
is not the best solution and it's confusing k8s community. So this
|
||||
new API is being added into Magnum: POST <ClusterID>/actions/resize
|
||||
|
Loading…
Reference in new issue