Merge "Support autoscaling for the default node group"

This commit is contained in:
Zuul 2024-05-28 10:27:50 +00:00 committed by Gerrit Code Review
commit c5e8e14ba8
3 changed files with 180 additions and 9 deletions

View File

@ -584,6 +584,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)
@ -728,6 +753,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
@ -778,15 +823,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(

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],
)

View File

@ -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.