Add cluster scale-in/out actions

This patch adds scale-in and scale-out actions for cluster
into Angularized clusters panel.

Change-Id: I1964a26f94e9afc358fbf85e3b98a534fed2dc28
This commit is contained in:
Shu Muto 2017-07-28 16:52:09 +09:00
parent b8c45e2039
commit 6a370f178b
7 changed files with 401 additions and 60 deletions

View File

@ -0,0 +1,7 @@
---
features:
- >
Scale-in and Scale-out actions for cluster added.
These actions are added as row action for each cluster
in Clusters table view. Although, this action is only
for Angularized clusters panel.

View File

@ -397,6 +397,78 @@ class Cluster(generic.View):
updated_cluster.to_dict())
@urls.register
class ClusterActions(generic.View):
"""API for Senlin cluster."""
url_regex = r'senlin/clusters/(?P<cluster_id>[^/]+)/(?P<action>[^/]+)$'
@rest_utils.ajax()
def get(self, request, cluster_id, action):
if action == "policy":
"""Get policies of a single cluster with the cluster id.
The following get parameters may be passed in the GET
:param cluster_id: the id of the cluster
The result is a cluster object.
"""
policies = senlin.cluster_policy_list(request, cluster_id, {})
return {
'items': [p.to_dict() for p in policies],
}
elif action == "":
return None
@rest_utils.ajax(data_required=True)
def put(self, request, cluster_id, action):
if action == "policy":
"""Update policies for the cluster."""
params = request.DATA
new_attach_ids = params["ids"]
old_attached = senlin.cluster_policy_list(request, cluster_id, {})
# Extract policies should be detached and execute
for policy in old_attached:
should_detach = True
for new_id in new_attach_ids:
if new_id == policy.policy_id:
# This policy is already attached.
should_detach = False
break
if should_detach:
# If policy is not exist in new policies,
# it should be removed
senlin.cluster_detach_policy(
request, cluster_id, policy.policy_id)
# Extract policies should be attached and execute
for new_id in new_attach_ids:
should_attach = True
for policy in old_attached:
if new_id == policy.policy_id:
# This policy is already attached.
should_attach = False
break
if should_attach:
# If policy is not exist in old policies,
# it should be added
senlin.cluster_attach_policy(request, cluster_id,
new_id, {})
return rest_utils.CreatedResponse(
'/api/senlin/clusters/%s/policy' % cluster_id)
elif action == "scale-in":
count = request.DATA.get("count") or None
return senlin.cluster_scale_in(request, cluster_id, count)
elif action == "scale-out":
count = request.DATA.get("count") or None
return senlin.cluster_scale_out(request, cluster_id, count)
@urls.register
class Policies(generic.View):
"""API for Senlin policies."""
@ -482,62 +554,3 @@ class Policy(generic.View):
return rest_utils.CreatedResponse(
'/api/senlin/policies/%s' % updated_policy.id,
updated_policy.to_dict())
@urls.register
class ClusterPolicies(generic.View):
"""API for Senlin cluster."""
url_regex = r'senlin/clusters/(?P<cluster_id>[^/]+)/policy$'
@rest_utils.ajax()
def get(self, request, cluster_id):
"""Get policies of a single cluster with the cluster id.
The following get parameters may be passed in the GET
:param cluster_id: the id of the cluster
The result is a cluster object.
"""
policies = senlin.cluster_policy_list(request, cluster_id, {})
return {
'items': [p.to_dict() for p in policies],
}
@rest_utils.ajax(data_required=True)
def put(self, request, cluster_id):
"""Update policies for the cluster."""
params = request.DATA
new_attach_ids = params["ids"]
old_attached = senlin.cluster_policy_list(request, cluster_id, {})
# Extract policies should be detached and execute
for policy in old_attached:
should_detach = True
for new_id in new_attach_ids:
if new_id == policy.policy_id:
# This policy is already attached.
should_detach = False
break
if should_detach:
# If policy is not exist in new policies, it should be removed
senlin.cluster_detach_policy(
request, cluster_id, policy.policy_id)
# Extract policies should be attached and execute
for new_id in new_attach_ids:
should_attach = True
for policy in old_attached:
if new_id == policy.policy_id:
# This policy is already attached.
should_attach = False
break
if should_attach:
# If policy is not exist in old policies, it should be added
senlin.cluster_attach_policy(request, cluster_id, new_id, {})
return rest_utils.CreatedResponse(
'/api/senlin/clusters/%s/policy' % cluster_id)

View File

@ -165,6 +165,16 @@ def cluster_recover(request, cluster, params=None):
senlinclient(request).recover_cluster(cluster, **params)
def cluster_scale_in(request, cluster, count=None):
"""Scale in a Cluster"""
senlinclient(request).cluster_scale_in(cluster, count)
def cluster_scale_out(request, cluster, count=None):
"""Scale out a Cluster"""
senlinclient(request).cluster_scale_out(cluster, count)
def cluster_delete(request, cluster):
"""Delete cluster."""
senlinclient(request).delete_cluster(cluster)

View File

@ -34,6 +34,8 @@
'horizon.cluster.clusters.actions.manage-policy.service',
'horizon.cluster.clusters.actions.delete.service',
'horizon.cluster.clusters.actions.update.service',
'horizon.cluster.clusters.actions.scale-in.service',
'horizon.cluster.clusters.actions.scale-out.service',
'horizon.app.core.clusters.resourceType'
];
@ -43,6 +45,8 @@
managePolicyService,
deleteClusterService,
updateClusterService,
scaleInClusterService,
scaleOutClusterService,
clusterResourceType
) {
var clusterResource = registry.getResourceType(clusterResourceType);
@ -84,6 +88,22 @@
type: 'row'
}
})
.append({
id: 'scaleInClusterAction',
service: scaleInClusterService,
template: {
text: gettext('Scale-in Cluster'),
type: 'row'
}
})
.append({
id: 'scaleOutClusterAction',
service: scaleOutClusterService,
template: {
text: gettext('Scale-out Cluster'),
type: 'row'
}
})
.append({
id: 'deleteClusterAction',
service: deleteClusterService,

View File

@ -38,6 +38,7 @@
createCluster: createCluster,
updateCluster: updateCluster,
deleteCluster: deleteCluster,
scaleCluster: scaleCluster,
createProfile: createProfile,
updateProfile: updateProfile,
deleteProfile: deleteProfile,
@ -476,7 +477,39 @@
});
}
// Policies
/**
* @name scaleCluster
* @description
* Scale a Cluster.
*
* @param {Object} id
* Cluster ID to scale.
* @param {Object} name
* Cluster name to scale.
* @param {Object} scale
* Direction of scale. 'in' or 'out'.
* @param {Object} count
* Count to scale-in a cluster.
* @param {boolean} suppressError
* If passed in, this will not show the default error handling
* @returns {Object} The result of the API call
*/
function scaleCluster(id, name, scale, count, suppressError) {
var promise = apiService.put(
'/api/senlin/clusters/' + id + '/scale-' + scale,
{count: count});
return suppressError ? promise : promise.error(function() {
var msg = gettext('Unable to scale-%(scale)s the cluster with name: %(name)s');
var scaleMsg;
if (scale === 'in') {
scaleMsg = gettext('in');
} else {
scaleMsg = gettext('out');
}
toastService.add('error', interpolate(msg, { scale: scaleMsg, name: name }, true));
});
}
/*
* @name getClusterPolicies
@ -511,6 +544,8 @@
});
}
// Policies
/*
* @name getPolicies
* @description

View File

@ -0,0 +1,128 @@
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use self 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.
*/
(function() {
'use strict';
/**
* @ngDoc factory
* @name horizon.cluster.clusters.actions.scale-in.service
* @Description
* restart container.
*/
angular
.module('horizon.cluster.clusters.actions')
.factory('horizon.cluster.clusters.actions.scale-in.service', scaleService);
scaleService.$inject = [
'horizon.app.core.openstack-service-api.senlin',
'horizon.app.core.clusters.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions',
'horizon.framework.widgets.form.ModalFormService',
'horizon.framework.widgets.toast.service'
];
function scaleService(
senlin, resourceType, actionResult, gettext, $qExtensions, modal, toast
) {
// schema
var schema = {
type: "object",
properties: {
count: {
title: gettext("Node Count"),
type: "number",
minimum: 1
}
}
};
// form
var form = [
{
type: 'section',
htmlClass: 'row',
items: [
{
type: 'section',
htmlClass: 'col-sm-12',
items: [
{
key: "count",
placeholder: gettext("Specify node count for scale-in.")
}
]
}
]
}
];
// model
var model;
var message = {
success: gettext('Cluster scale-in %s was successfully accepted.')
};
var service = {
initAction: initAction,
allowed: allowed,
perform: perform
};
return service;
//////////////
// include this function in your service
// if you plan to emit events to the parent controller
function initAction() {
}
function allowed() {
return $qExtensions.booleanAsPromise(true);
}
function perform(selected) {
model = {
id: selected.id,
name: selected.name,
count: null
};
// modal config
var config = {
title: gettext('Scale-In'),
submitText: gettext('Scale-In'),
schema: schema,
form: form,
model: model
};
return modal.open(config).then(submit);
function submit(context) {
var id = context.model.id;
var name = context.model.name;
delete context.model.id;
delete context.model.name;
return senlin.scaleCluster(id, name, 'in', context.model.count).then(function() {
toast.add('success', interpolate(message.success, [name]));
var result = actionResult.getActionResult().updated(resourceType, id);
return result.result;
});
}
}
}
})();

View File

@ -0,0 +1,128 @@
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use self 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.
*/
(function() {
'use strict';
/**
* @ngDoc factory
* @name horizon.cluster.clusters.actions.scale-in.service
* @Description
* restart container.
*/
angular
.module('horizon.cluster.clusters.actions')
.factory('horizon.cluster.clusters.actions.scale-out.service', scaleService);
scaleService.$inject = [
'horizon.app.core.openstack-service-api.senlin',
'horizon.app.core.clusters.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions',
'horizon.framework.widgets.form.ModalFormService',
'horizon.framework.widgets.toast.service'
];
function scaleService(
senlin, resourceType, actionResult, gettext, $qExtensions, modal, toast
) {
// schema
var schema = {
type: "object",
properties: {
count: {
title: gettext("Node Count"),
type: "number",
minimum: 1
}
}
};
// form
var form = [
{
type: 'section',
htmlClass: 'row',
items: [
{
type: 'section',
htmlClass: 'col-sm-12',
items: [
{
key: "count",
placeholder: gettext("Specify node count for scale-out.")
}
]
}
]
}
];
// model
var model;
var message = {
success: gettext('Cluster scale-out %s was successfully accepted.')
};
var service = {
initAction: initAction,
allowed: allowed,
perform: perform
};
return service;
//////////////
// include this function in your service
// if you plan to emit events to the parent controller
function initAction() {
}
function allowed() {
return $qExtensions.booleanAsPromise(true);
}
function perform(selected) {
model = {
id: selected.id,
name: selected.name,
count: null
};
// modal config
var config = {
title: gettext('Scale-Out'),
submitText: gettext('Scale-Out'),
schema: schema,
form: form,
model: model
};
return modal.open(config).then(submit);
function submit(context) {
var id = context.model.id;
var name = context.model.name;
delete context.model.id;
delete context.model.name;
return senlin.scaleCluster(id, name, 'out', context.model.count).then(function() {
toast.add('success', interpolate(message.success, [name]));
var result = actionResult.getActionResult().updated(resourceType, id);
return result.result;
});
}
}
}
})();