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:
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user