Fix resize for CAPI clusters

Clusters created by the magnum-capi-helm driver do not have a heat stack, so
resize currently fails as it depends on the heat stack for a list of worker
nodes. To fix this, we change to querying the nodegroup information and
use that instead.

We keep the old heat stack way for now, as that is the way to get a list
of worker nodes to select candidate(s) for scaling down. This is not
available in capi driver yet, so capi will return an empty list of
worker nodes.

Change-Id: I0d66fc02a7f2c608a4a0b09b98c343017e04ed41
This commit is contained in:
Jake Yip 2024-10-16 12:28:09 +11:00 committed by Jake Yip
parent c9fdb537ea
commit a5ff9ecb4f
4 changed files with 54 additions and 19 deletions

View File

@ -289,3 +289,8 @@ def quotas_update(request, project_id, resource, **kwargs):
def quotas_delete(request, project_id, resource):
return magnumclient(request).quotas.delete(project_id, resource)
def nodegroup_list(request, cluster_id=None, limit=None, marker=None):
return magnumclient(request).nodegroups.list(cluster_id, limit=limit,
marker=marker)

View File

@ -25,6 +25,7 @@ from django.views import generic
from magnum_ui.api import heat
from magnum_ui.api import magnum
from heatclient import exc as heatexc
from openstack_dashboard import api
from openstack_dashboard.api import neutron
from openstack_dashboard.api.rest import urls
@ -237,18 +238,29 @@ class ClusterResize(generic.View):
print(e)
return HttpResponseNotFound()
stack = heat.stack_get(request, cluster["stack_id"])
search_opts = {"name": "%s-" % stack.stack_name}
servers = api.nova.server_list(request, search_opts=search_opts)[0]
try:
ngs = magnum.nodegroup_list(request, cluster_id)
nodegroups = [n.to_dict() for n in ngs]
except AttributeError:
return HttpResponseNotFound()
try:
stack = heat.stack_get(request, cluster["stack_id"])
except heatexc.HTTPNotFound:
stack = None
worker_nodes = []
for server in servers:
if (server.name.startswith("%s-minion" % stack.stack_name) or
server.name.startswith("%s-node" % stack.stack_name)):
worker_nodes.append({"name": server.name, "id": server.id})
if stack:
search_opts = {"name": "%s-" % stack.stack_name}
servers = api.nova.server_list(request, search_opts=search_opts)[0]
for server in servers:
if (server.name.startswith("%s-minion" % stack.stack_name) or
server.name.startswith("%s-node" % stack.stack_name)):
worker_nodes.append({"name": server.name, "id": server.id})
return {"cluster": change_to_id(cluster),
"worker_nodes": worker_nodes}
"worker_nodes": worker_nodes,
"nodegroups": nodegroups}
@rest_utils.ajax(data_required=True)
def post(self, request, cluster_id):

View File

@ -70,7 +70,7 @@
formModel = getFormModelDefaults();
formModel.id = selected.id;
modalConfig = constructModalConfig(response.data.worker_nodes);
modalConfig = constructModalConfig(response.data.nodegroups, response.data.worker_nodes);
deferred.resolve(modal.open(modalConfig).then(onModalSubmit));
$scope.model = formModel;
@ -91,9 +91,13 @@
return $qExtensions.booleanAsPromise(true);
}
function constructModalConfig(workerNodesList) {
formModel.original_node_count = workerNodesList.length;
formModel.node_count = workerNodesList.length;
function constructModalConfig(nodegroups, workerNodesList) {
var defaultWorker = nodegroups.filter(function(ng) {
return ng.name === 'default-worker';
})[0];
formModel.original_node_count = defaultWorker.node_count;
formModel.node_count = defaultWorker.node_count;
formModel.worker_nodes = workerNodesList;
return {
title: gettext('Resize Cluster'),
@ -116,8 +120,8 @@
form: [
{
key: 'node_count',
title: gettext('Node Count'),
placeholder: gettext('The cluster node count.'),
title: gettext('Node Count (default-worker)'),
placeholder: gettext('The default-worker nodegroup node_count.'),
required: true,
validationMessage: {
101: gettext('You cannot resize to fewer than zero worker nodes.')
@ -129,7 +133,8 @@
type: 'checkboxes',
title: gettext('Choose nodes to remove (Optional)'),
titleMap: generateNodesTitleMap(workerNodesList),
condition: 'model.node_count < model.original_node_count',
condition: 'model.node_count < model.original_node_count && ' +
'model.worker_nodes.length > 0',
onChange: validateNodeRemovalCount,
validationMessage: {
nodeRemovalCountExceeded: gettext('You may only select as many nodes ' +

View File

@ -67,10 +67,23 @@
it('should open the modal, hide the loading spinner and check the form model',
inject(function($timeout) {
var mockWorkerNodes = [{id: "456", name: "Worker Node 1"}];
// 2 nodegroups, default-worker and another-nodegroup, with 2 and 3
// nodes respectively. cluster.node_count will be total nodes in all
// nodegroups
var mockDefaultWorker = {name: 'default-worker', node_count: 2};
var mockNodegroups = [mockDefaultWorker,
{name: 'default-master', node_count: 1},
{name: 'another-nodegroup', node_count: 3}];
var mockCluster = {node_count: 5};
// only populated with heat, [] for capi
var mockWorkerNodes = [{id: "456", name: "Worker Node 1"},
{id: "457", name: "Worker Node 2"}];
deferred = $q.defer();
deferred.resolve({data: {cluster: {}, worker_nodes: mockWorkerNodes}});
deferred.resolve({data: {cluster: mockCluster,
worker_nodes: mockWorkerNodes,
nodegroups: mockNodegroups}});
spyOn(magnum, 'getClusterNodes').and.returnValue(deferred.promise);
service.perform(selected, $scope);
@ -82,8 +95,8 @@
// Check if the form's model skeleton is correct
expect(modalConfig.model.id).toBe(selected.id);
expect(modalConfig.model.original_node_count).toBe(mockWorkerNodes.length);
expect(modalConfig.model.node_count).toBe(mockWorkerNodes.length);
expect(modalConfig.model.original_node_count).toBe(mockDefaultWorker.node_count);
expect(modalConfig.model.node_count).toBe(mockDefaultWorker.node_count);
expect(modalConfig.title).toBeDefined();
expect(modalConfig.schema).toBeDefined();
expect(modalConfig.form).toBeDefined();