480 lines
15 KiB
Python
480 lines
15 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2023 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Utils for VMware Engine private-clouds clusters commands.
|
|
|
|
Provides helpers for parsing the autoscaling settings and node type configs and
|
|
for combining settings from many sources together.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import annotations
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import collections
|
|
import dataclasses
|
|
from typing import Any, Dict, List, Union
|
|
|
|
from googlecloudsdk.core import exceptions
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class ScalingThresholds:
|
|
"""Scaling thresholds for a single condition. Uses None for empty values.
|
|
|
|
Attributes:
|
|
scale_in: The threshold for scaling in.
|
|
scale_out: The threshold for scaling out.
|
|
"""
|
|
|
|
scale_in: int
|
|
scale_out: int
|
|
|
|
|
|
def _MergeFields(left, right):
|
|
"""Merges two fields, favoring right one.
|
|
|
|
Args:
|
|
left: First field.
|
|
right: Second field.
|
|
|
|
Returns:
|
|
Merged field.
|
|
"""
|
|
return right if right is not None else left
|
|
|
|
|
|
def _MergeScalingThresholds(
|
|
left: ScalingThresholds | None, right: ScalingThresholds | None
|
|
) -> ScalingThresholds | None:
|
|
"""Merges two ScalingThresholds objects, favoring right one.
|
|
|
|
Args:
|
|
left: First ScalingThresholds object.
|
|
right: Second ScalingThresholds object.
|
|
|
|
Returns:
|
|
Merged ScalingThresholds - It contains the updated scale_in and scale_out
|
|
values, favoring the right one.
|
|
None - It indicates removal of threshold from autoscaling policy, favoring
|
|
right one. Therefore, if right is None, return None.
|
|
|
|
"""
|
|
if left is None:
|
|
return right
|
|
if right is None:
|
|
return None
|
|
|
|
return ScalingThresholds(
|
|
scale_in=_MergeFields(left.scale_in, right.scale_in),
|
|
scale_out=_MergeFields(left.scale_out, right.scale_out),
|
|
)
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class AutoscalingPolicy:
|
|
"""Represents the autoscaling policy for a single node type.
|
|
|
|
Uses None for empty settings.
|
|
|
|
Attributes:
|
|
node_type_id: The node type id.
|
|
scale_out_size: The size of a single scale out operation.
|
|
min_node_count: The minimum number of nodes of this type in the cluster.
|
|
max_node_count: The maximum number of nodes of this type in the cluster.
|
|
cpu_thresholds: The CPU thresholds.
|
|
granted_memory_thresholds: The granted memory thresholds.
|
|
consumed_memory_thresholds: The consumed memory thresholds.
|
|
storage_thresholds: The storage thresholds.
|
|
"""
|
|
|
|
node_type_id: str
|
|
scale_out_size: int
|
|
min_node_count: int
|
|
max_node_count: int
|
|
cpu_thresholds: ScalingThresholds
|
|
granted_memory_thresholds: ScalingThresholds
|
|
consumed_memory_thresholds: ScalingThresholds
|
|
storage_thresholds: ScalingThresholds
|
|
|
|
|
|
def _MergeAutoscalingPolicies(
|
|
left: AutoscalingPolicy,
|
|
right: AutoscalingPolicy,
|
|
) -> AutoscalingPolicy:
|
|
"""Merges two AutoscalingPolicy objects, favoring right one.
|
|
|
|
Args:
|
|
left: First AutoscalingPolicy object.
|
|
right: Second AutoscalingPolicy object.
|
|
|
|
Returns:
|
|
Merged AutoscalingPolicy.
|
|
"""
|
|
if left is None:
|
|
return right
|
|
if right is None:
|
|
return left
|
|
|
|
return AutoscalingPolicy(
|
|
node_type_id=_MergeFields(left.node_type_id, right.node_type_id),
|
|
scale_out_size=_MergeFields(left.scale_out_size, right.scale_out_size),
|
|
min_node_count=_MergeFields(left.min_node_count, right.min_node_count),
|
|
max_node_count=_MergeFields(left.max_node_count, right.max_node_count),
|
|
cpu_thresholds=_MergeScalingThresholds(
|
|
left.cpu_thresholds, right.cpu_thresholds
|
|
),
|
|
granted_memory_thresholds=_MergeScalingThresholds(
|
|
left.granted_memory_thresholds, right.granted_memory_thresholds
|
|
),
|
|
consumed_memory_thresholds=_MergeScalingThresholds(
|
|
left.consumed_memory_thresholds, right.consumed_memory_thresholds
|
|
),
|
|
storage_thresholds=_MergeScalingThresholds(
|
|
left.storage_thresholds, right.storage_thresholds
|
|
),
|
|
)
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class AutoscalingSettings:
|
|
"""Represents the autoscaling settings for a private-cloud cluster.
|
|
|
|
Uses None for empty settings.
|
|
|
|
Attributes:
|
|
min_cluster_node_count: The minimum number of nodes in the cluster.
|
|
max_cluster_node_count: The maximum number of nodes in the cluster.
|
|
cool_down_period: The cool down period for autoscaling.
|
|
autoscaling_policies: The autoscaling policies for each node type.
|
|
"""
|
|
|
|
min_cluster_node_count: int
|
|
max_cluster_node_count: int
|
|
cool_down_period: str
|
|
autoscaling_policies: Dict[str, AutoscalingPolicy]
|
|
|
|
|
|
def MergeAutoscalingSettings(
|
|
left: AutoscalingSettings, right: AutoscalingSettings
|
|
) -> AutoscalingSettings:
|
|
"""Merges two AutoscalingSettings objects, favoring right one.
|
|
|
|
Args:
|
|
left: First AutoscalingSettings object.
|
|
right: Second AutoscalingSettings object.
|
|
|
|
Returns:
|
|
Merged AutoscalingSettings.
|
|
"""
|
|
if left is None:
|
|
return right
|
|
if right is None:
|
|
return left
|
|
|
|
policies = {}
|
|
for policy_name, policy in left.autoscaling_policies.items():
|
|
if policy_name in right.autoscaling_policies:
|
|
policies[policy_name] = _MergeAutoscalingPolicies(
|
|
policy, right.autoscaling_policies[policy_name]
|
|
)
|
|
else:
|
|
policies[policy_name] = policy
|
|
|
|
for policy_name, policy in right.autoscaling_policies.items():
|
|
if policy_name not in left.autoscaling_policies:
|
|
policies[policy_name] = policy
|
|
|
|
return AutoscalingSettings(
|
|
min_cluster_node_count=_MergeFields(
|
|
left.min_cluster_node_count, right.min_cluster_node_count
|
|
),
|
|
max_cluster_node_count=_MergeFields(
|
|
left.max_cluster_node_count, right.max_cluster_node_count
|
|
),
|
|
cool_down_period=_MergeFields(
|
|
left.cool_down_period, right.cool_down_period
|
|
),
|
|
autoscaling_policies=policies,
|
|
)
|
|
|
|
|
|
class InvalidNodeConfigsProvidedError(exceptions.Error):
|
|
|
|
def __init__(self, details):
|
|
super(InvalidNodeConfigsProvidedError, self).__init__(
|
|
f'INVALID_ARGUMENT: {details}'
|
|
)
|
|
|
|
|
|
class InvalidAutoscalingSettingsProvidedError(exceptions.Error):
|
|
|
|
def __init__(self, details):
|
|
super(InvalidAutoscalingSettingsProvidedError, self).__init__(
|
|
f'INVALID_ARGUMENT: {details}'
|
|
)
|
|
|
|
|
|
NodeTypeConfig = collections.namedtuple(
|
|
typename='NodeTypeConfig',
|
|
field_names=['type', 'count', 'custom_core_count'],
|
|
)
|
|
|
|
|
|
def FindDuplicatedTypes(types):
|
|
type_counts = collections.Counter(types)
|
|
return [node_type for node_type, count in type_counts.items() if count > 1]
|
|
|
|
|
|
def ParseNodesConfigsParameters(nodes_configs):
|
|
requested_node_types = [config['type'] for config in nodes_configs]
|
|
|
|
duplicated_types = FindDuplicatedTypes(requested_node_types)
|
|
if duplicated_types:
|
|
raise InvalidNodeConfigsProvidedError(
|
|
'types: {} provided more than once.'.format(duplicated_types)
|
|
)
|
|
|
|
return [
|
|
NodeTypeConfig(
|
|
config['type'], config['count'], config.get('custom-core-count', 0)
|
|
)
|
|
for config in nodes_configs
|
|
]
|
|
|
|
|
|
def ParseAutoscalingSettingsFromInlinedFormat(
|
|
min_cluster_node_count: int,
|
|
max_cluster_node_count: int,
|
|
cool_down_period: str,
|
|
autoscaling_policies: List[Dict[str, Union[str, int]]],
|
|
) -> AutoscalingSettings:
|
|
"""Parses inlined autoscaling settings (passed as CLI arguments).
|
|
|
|
The resulting object can later be passed to
|
|
googlecloudsdk.api_lib.vmware.util.ConstructAutoscalingSettingsMessage.
|
|
|
|
Args:
|
|
min_cluster_node_count: autoscaling-min-cluster-node-count CLI argument.
|
|
max_cluster_node_count: autoscaling-max-cluster-node-count CLI argument.
|
|
cool_down_period: autoscaling-cool-down-period CLI argument.
|
|
autoscaling_policies: list of update-autoscaling-policy CLI arguments.
|
|
|
|
Returns:
|
|
Equivalent AutoscalingSettings instance.
|
|
"""
|
|
parsed_settings = AutoscalingSettings(
|
|
min_cluster_node_count=min_cluster_node_count,
|
|
max_cluster_node_count=max_cluster_node_count,
|
|
cool_down_period=cool_down_period,
|
|
autoscaling_policies={},
|
|
)
|
|
|
|
for policy in autoscaling_policies:
|
|
parsed_policy = AutoscalingPolicy(
|
|
node_type_id=policy.get('node-type-id'),
|
|
scale_out_size=policy.get('scale-out-size'),
|
|
min_node_count=policy.get('min-node-count'),
|
|
max_node_count=policy.get('max-node-count'),
|
|
cpu_thresholds=_AutoscalingThresholdsFromPolicy(
|
|
policy, 'cpu-thresholds'
|
|
),
|
|
granted_memory_thresholds=_AutoscalingThresholdsFromPolicy(
|
|
policy, 'granted-memory-thresholds'
|
|
),
|
|
consumed_memory_thresholds=_AutoscalingThresholdsFromPolicy(
|
|
policy, 'consumed-memory-thresholds'
|
|
),
|
|
storage_thresholds=_AutoscalingThresholdsFromPolicy(
|
|
policy, 'storage-thresholds'
|
|
),
|
|
)
|
|
|
|
parsed_settings.autoscaling_policies[policy['name']] = parsed_policy
|
|
|
|
return parsed_settings
|
|
|
|
|
|
def _AutoscalingThresholdsFromPolicy(
|
|
policy: Dict[str, Union[str, int]], threshold: str
|
|
) -> ScalingThresholds:
|
|
scale_in = policy.get(f'{threshold}-scale-in')
|
|
scale_out = policy.get(f'{threshold}-scale-out')
|
|
if scale_in is None and scale_out is None:
|
|
return None
|
|
return ScalingThresholds(scale_in=scale_in, scale_out=scale_out)
|
|
|
|
|
|
def _ValidateIfOnlySupportedKeysArePassed(
|
|
keys: List[str], supported_keys: List[str]
|
|
):
|
|
for key in keys:
|
|
if key not in supported_keys:
|
|
raise InvalidAutoscalingSettingsProvidedError(
|
|
'unsupported key: {key}, supported keys are: {supported_keys}'.format(
|
|
key=key, supported_keys=supported_keys
|
|
)
|
|
)
|
|
|
|
|
|
def ParseAutoscalingSettingsFromFileFormat(
|
|
cluster: Dict[str, Any]
|
|
) -> AutoscalingSettings:
|
|
"""Parses the autoscaling settings from the format returned by the describe command.
|
|
|
|
The resulting object can later be passed to
|
|
googlecloudsdk.api_lib.vmware.util.ConstructAutoscalingSettingsMessage.
|
|
|
|
Args:
|
|
cluster: dictionary with the settings. Parsed from a file provided by user.
|
|
|
|
Returns:
|
|
Equivalent AutoscalingSettings instance.
|
|
|
|
Raises:
|
|
InvalidAutoscalingSettingsProvidedError: if the file format was wrong.
|
|
"""
|
|
|
|
def _ParseThresholds(thresholds_dict):
|
|
if thresholds_dict is None:
|
|
return None
|
|
|
|
_ValidateIfOnlySupportedKeysArePassed(
|
|
thresholds_dict.keys(), ['scaleIn', 'scaleOut']
|
|
)
|
|
|
|
return ScalingThresholds(
|
|
scale_in=thresholds_dict.get('scaleIn'),
|
|
scale_out=thresholds_dict.get('scaleOut'),
|
|
)
|
|
|
|
_ValidateIfOnlySupportedKeysArePassed(cluster.keys(), ['autoscalingSettings'])
|
|
if 'autoscalingSettings' not in cluster:
|
|
raise InvalidAutoscalingSettingsProvidedError(
|
|
'autoscalingSettings not provided in the file'
|
|
)
|
|
autoscaling_settings = cluster['autoscalingSettings']
|
|
|
|
_ValidateIfOnlySupportedKeysArePassed(
|
|
autoscaling_settings.keys(),
|
|
[
|
|
'minClusterNodeCount',
|
|
'maxClusterNodeCount',
|
|
'coolDownPeriod',
|
|
'autoscalingPolicies',
|
|
],
|
|
)
|
|
parsed_settings = AutoscalingSettings(
|
|
min_cluster_node_count=autoscaling_settings.get('minClusterNodeCount'),
|
|
max_cluster_node_count=autoscaling_settings.get('maxClusterNodeCount'),
|
|
cool_down_period=autoscaling_settings.get('coolDownPeriod'),
|
|
autoscaling_policies={},
|
|
)
|
|
|
|
if 'autoscalingPolicies' not in autoscaling_settings:
|
|
return parsed_settings
|
|
|
|
for policy_name, policy_settings in autoscaling_settings[
|
|
'autoscalingPolicies'
|
|
].items():
|
|
_ValidateIfOnlySupportedKeysArePassed(
|
|
policy_settings.keys(),
|
|
[
|
|
'nodeTypeId',
|
|
'scaleOutSize',
|
|
'minNodeCount',
|
|
'maxNodeCount',
|
|
'cpuThresholds',
|
|
'grantedMemoryThresholds',
|
|
'consumedMemoryThresholds',
|
|
'storageThresholds',
|
|
],
|
|
)
|
|
parsed_policy = AutoscalingPolicy(
|
|
node_type_id=policy_settings.get('nodeTypeId'),
|
|
scale_out_size=policy_settings.get('scaleOutSize'),
|
|
min_node_count=policy_settings.get('minNodeCount'),
|
|
max_node_count=policy_settings.get('maxNodeCount'),
|
|
cpu_thresholds=_ParseThresholds(policy_settings.get('cpuThresholds')),
|
|
granted_memory_thresholds=_ParseThresholds(
|
|
policy_settings.get('grantedMemoryThresholds')
|
|
),
|
|
consumed_memory_thresholds=_ParseThresholds(
|
|
policy_settings.get('consumedMemoryThresholds')
|
|
),
|
|
storage_thresholds=_ParseThresholds(
|
|
policy_settings.get('storageThresholds')
|
|
),
|
|
)
|
|
parsed_settings.autoscaling_policies[policy_name] = parsed_policy
|
|
|
|
return parsed_settings
|
|
|
|
|
|
def ParseAutoscalingSettingsFromApiFormat(
|
|
cluster_message,
|
|
) -> AutoscalingSettings:
|
|
"""Parses the autoscaling settings from the format returned by the describe command.
|
|
|
|
The resulting object can later be passed to
|
|
googlecloudsdk.api_lib.vmware.util.ConstructAutoscalingSettingsMessage.
|
|
|
|
Args:
|
|
cluster_message: cluster object with the autoscaling settings.
|
|
|
|
Returns:
|
|
Equivalent AutoscalingSettings istance.
|
|
"""
|
|
if cluster_message.autoscalingSettings is None:
|
|
return None
|
|
|
|
autoscaling_settings = cluster_message.autoscalingSettings
|
|
|
|
parsed_settings = AutoscalingSettings(
|
|
min_cluster_node_count=autoscaling_settings.minClusterNodeCount,
|
|
max_cluster_node_count=autoscaling_settings.maxClusterNodeCount,
|
|
cool_down_period=autoscaling_settings.coolDownPeriod,
|
|
autoscaling_policies={},
|
|
)
|
|
|
|
for item in autoscaling_settings.autoscalingPolicies.additionalProperties:
|
|
policy_name, policy_settings = item.key, item.value
|
|
|
|
def _ParseThresholds(thresholds):
|
|
if thresholds is None:
|
|
return None
|
|
return ScalingThresholds(
|
|
scale_in=thresholds.scaleIn,
|
|
scale_out=thresholds.scaleOut,
|
|
)
|
|
|
|
parsed_policy = AutoscalingPolicy(
|
|
node_type_id=policy_settings.nodeTypeId,
|
|
scale_out_size=policy_settings.scaleOutSize,
|
|
min_node_count=policy_settings.minNodeCount,
|
|
max_node_count=policy_settings.maxNodeCount,
|
|
cpu_thresholds=_ParseThresholds(policy_settings.cpuThresholds),
|
|
granted_memory_thresholds=_ParseThresholds(
|
|
policy_settings.grantedMemoryThresholds
|
|
),
|
|
consumed_memory_thresholds=_ParseThresholds(
|
|
policy_settings.consumedMemoryThresholds
|
|
),
|
|
storage_thresholds=_ParseThresholds(policy_settings.storageThresholds),
|
|
)
|
|
parsed_settings.autoscaling_policies[policy_name] = parsed_policy
|
|
|
|
return parsed_settings
|