Support autoscaling for the default node group

Similar to the heat driver, this patch means we now respect the cluster labels that specifiy a min and max node count for the default worker group.

Note that (unlike the heat driver) the "current" node count is being ignored here, and the cluster always starts with min_node_count in the default group, if that label has been specified.

Change-Id: Ic0e22efdf2ccb06b19ea713413eb3b6cfc405de4
This commit is contained in:
Travis Holton
2024-04-03 19:36:19 +13:00
committed by sd109
parent 551230cb22
commit f5881c9388
3 changed files with 180 additions and 9 deletions
+46 -9
View File
@@ -578,6 +578,31 @@ class Driver(driver.Driver):
def _get_autoheal_enabled(self, cluster):
return self._get_label_bool(cluster, "auto_healing_enabled", True)
def _get_autoscale(self, cluster, nodegroup):
auto_scale = self._get_label_bool(
cluster, "auto_scaling_enabled", False
)
if auto_scale:
auto_scale_args = dict(autoscale="true")
min_nodes = max(1, nodegroup.min_node_count)
max_nodes = self._get_label_int(
cluster, "max_node_count", min_nodes
)
if min_nodes > nodegroup.node_count:
raise exception.MagnumException(
message="min_node_count must be less than or equal to "
"default-worker nodegroup node_count."
)
auto_scale_args["machineCountMin"] = min_nodes
if max_nodes < min_nodes:
raise exception.MagnumException(
message="max_node_count must be greater than or "
"equal to min_node_count"
)
auto_scale_args["machineCountMax"] = max_nodes
return auto_scale_args
return auto_scale
def _get_k8s_keystone_auth_enabled(self, cluster):
return self._get_label_bool(cluster, "keystone_auth_enabled", False)
@@ -722,6 +747,26 @@ class Driver(driver.Driver):
additionalStorageClasses=additional_storage_classes,
)
def _process_node_groups(self, cluster):
nodegroups = cluster.nodegroups
nodegroup_set = []
for ng in nodegroups:
if ng.role != NODE_GROUP_ROLE_CONTROLLER:
nodegroup_item = dict(
name=driver_utils.sanitized_name(ng.name),
machineFlavor=ng.flavor_id,
machineCount=ng.node_count,
)
# Assume first nodegroup is default-worker.
if not nodegroup_set:
auto_scale = self._get_autoscale(cluster, ng)
if auto_scale:
nodegroup_item = helm.mergeconcat(
nodegroup_item, auto_scale
)
nodegroup_set.append(nodegroup_item)
return nodegroup_set
def _update_helm_release(self, context, cluster, nodegroups=None):
if nodegroups is None:
nodegroups = cluster.nodegroups
@@ -772,15 +817,7 @@ class Driver(driver.Driver):
"enabled": self._get_autoheal_enabled(cluster),
},
},
"nodeGroups": [
{
"name": driver_utils.sanitized_name(ng.name),
"machineFlavor": ng.flavor_id,
"machineCount": ng.node_count,
}
for ng in nodegroups
if ng.role != NODE_GROUP_ROLE_CONTROLLER
],
"nodeGroups": self._process_node_groups(cluster),
"addons": {
"openstack": {
"csiCinder": self._storageclass_definitions(
+126
View File
@@ -2695,3 +2695,129 @@ class ClusterAPIDriverTest(base.DbTestCase):
.get("LoadBalancer", {})
.get("create-monitor")
)
def test_validate_auto_scale_max_lt_min(self):
self.cluster_obj.labels = dict(
auto_scaling_enabled="true", min_node_count=3, max_node_count=0
)
self.assertRaises(
exception.MagnumException,
self.driver._get_autoscale,
self.cluster_obj,
self.cluster_obj.nodegroups[0],
)
@mock.patch.object(
driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False
)
@mock.patch.object(
driver.Driver,
"_storageclass_definitions",
return_value=mock.ANY,
)
@mock.patch.object(driver.Driver, "_validate_allowed_flavor")
@mock.patch.object(neutron, "get_network", autospec=True)
@mock.patch.object(
driver.Driver, "_ensure_certificate_secrets", autospec=True
)
@mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True)
@mock.patch.object(kubernetes.Client, "load", autospec=True)
@mock.patch.object(driver.Driver, "_get_image_details", autospec=True)
@mock.patch.object(helm.Client, "install_or_upgrade", autospec=True)
def test_create_cluster_auto_scale_enabled(
self,
mock_install,
mock_image,
mock_load,
mock_appcred,
mock_certs,
mock_get_net,
mock_validate_allowed_flavor,
mock_storageclasses,
mock_get_keystone_auth_enabled,
):
auto_scale_labels = dict(
auto_scaling_enabled="true", min_node_count=2, max_node_count=6
)
self.cluster_obj.labels = auto_scale_labels
mock_image.return_value = (
"imageid1",
"1.27.4",
"ubuntu",
)
mock_client = mock.MagicMock(spec=kubernetes.Client)
mock_load.return_value = mock_client
self.driver.create_cluster(self.context, self.cluster_obj, 10)
helm_install_values = mock_install.call_args[0][3]
self.assertEqual(
helm_install_values["nodeGroups"][0]["autoscale"],
auto_scale_labels["auto_scaling_enabled"],
)
# min_node_count is hardcode to max(1, ng.min_node_count)
self.assertEqual(
helm_install_values["nodeGroups"][0]["machineCountMin"],
self.cluster_obj.nodegroups[0].min_node_count,
)
self.assertEqual(
helm_install_values["nodeGroups"][0]["machineCountMax"],
auto_scale_labels["max_node_count"],
)
@mock.patch.object(
driver.Driver, "_get_k8s_keystone_auth_enabled", return_value=False
)
@mock.patch.object(
driver.Driver,
"_storageclass_definitions",
return_value=mock.ANY,
)
@mock.patch.object(driver.Driver, "_validate_allowed_flavor")
@mock.patch.object(neutron, "get_network", autospec=True)
@mock.patch.object(
driver.Driver, "_ensure_certificate_secrets", autospec=True
)
@mock.patch.object(driver.Driver, "_create_appcred_secret", autospec=True)
@mock.patch.object(kubernetes.Client, "load", autospec=True)
@mock.patch.object(driver.Driver, "_get_image_details", autospec=True)
@mock.patch.object(helm.Client, "install_or_upgrade", autospec=True)
def test_create_cluster_auto_scale_disabled(
self,
mock_install,
mock_image,
mock_load,
mock_appcred,
mock_certs,
mock_get_net,
mock_validate_allowed_flavor,
mock_storageclasses,
mock_get_keystone_auth_enabled,
):
auto_scale_labels = dict(
auto_scaling_enabled="false", min_node_count=2, max_node_count=6
)
self.cluster_obj.labels = auto_scale_labels
mock_image.return_value = (
"imageid1",
"1.27.4",
"ubuntu",
)
mock_client = mock.MagicMock(spec=kubernetes.Client)
mock_load.return_value = mock_client
self.driver.create_cluster(self.context, self.cluster_obj, 10)
helm_install_values = mock_install.call_args[0][3]
self.assertNotIn(
"autoscale",
helm_install_values["nodeGroups"][0],
)
self.assertNotIn(
"machineCountMin",
helm_install_values["nodeGroups"][0],
)
self.assertNotIn(
"machineCountMax",
helm_install_values["nodeGroups"][0],
)
@@ -0,0 +1,8 @@
---
features:
- |
Adds support for autoscaling of the default worker node group by respecting the
`min_node_count` and `max_node_count` cluster labels. Unlike the Heat driver,
when these labels are provided, the cluster will always start with node count
equal to the minimum node count value and the alternative `node_count` flag will
be ignored.