From a5ff9ecb4fb84e95c266657b5aabdefe33010983 Mon Sep 17 00:00:00 2001 From: Jake Yip Date: Wed, 16 Oct 2024 12:28:09 +1100 Subject: [PATCH] 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 --- magnum_ui/api/magnum.py | 5 ++++ magnum_ui/api/rest/magnum.py | 28 +++++++++++++------ .../clusters/resize/resize.service.js | 19 ++++++++----- .../clusters/resize/resize.service.spec.js | 21 +++++++++++--- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/magnum_ui/api/magnum.py b/magnum_ui/api/magnum.py index ba5c62fc..befaf45d 100644 --- a/magnum_ui/api/magnum.py +++ b/magnum_ui/api/magnum.py @@ -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) diff --git a/magnum_ui/api/rest/magnum.py b/magnum_ui/api/rest/magnum.py index ba66e0e5..74ea73b3 100644 --- a/magnum_ui/api/rest/magnum.py +++ b/magnum_ui/api/rest/magnum.py @@ -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): diff --git a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js index ebc6a961..39ecb3f1 100644 --- a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js +++ b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js @@ -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 ' + diff --git a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js index 842df87d..0f64b938 100644 --- a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js +++ b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js @@ -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();