feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""The command group for the vmware clusters CLI."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Clusters(base.Group):
"""Manage clusters in Google Cloud VMware Engine."""
category = base.COMPUTE_CATEGORY

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""'vmware clusters create' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware.clusters import ClustersClient
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
from googlecloudsdk.command_lib.vmware.clusters import util
from googlecloudsdk.core import log
DETAILED_HELP = {
'DESCRIPTION':
"""
Create a cluster in a VMware Engine private cloud. Successful creation of a cluster results in a cluster in READY state. Check the progress of a cluster using `{parent_command} list`.
""",
'EXAMPLES':
"""
To create a cluster called `my-cluster` in private cloud `my-private-cloud`, with 3 initial `standard-72` nodes in zone `us-west2-a`, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --node-type-config=type=standard-72,count=3
Or:
$ {command} my-cluster --private-cloud=my-private-cloud --node-type-config=type=standard-72,count=3
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
"""Create a Google Cloud VMware Engine cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser, positional=True)
base.ASYNC_FLAG.AddToParser(parser)
base.ASYNC_FLAG.SetDefault(parser, True)
parser.display_info.AddFormat('yaml')
parser.add_argument(
'--node-type-config',
required=True,
type=arg_parsers.ArgDict(
spec={
'type': str,
'count': int,
'custom-core-count': int
},
required_keys=('type', 'count')),
action='append',
help="""\
Information about the type and number of nodes associated with the cluster.
type (required): canonical identifier of the node type.
count (required): number of nodes of this type in the cluster.
custom-core-count (optional): customized number of cores available to each node of the type.
To get a list of valid values for your node type,
run the gcloud vmware node-types describe command and reference the
availableCustomCoreCounts field in the output.
""")
flags.AddAutoscalingSettingsFlagsToParser(parser)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
client = ClustersClient()
is_async = args.async_
nodes_configs = util.ParseNodesConfigsParameters(args.node_type_config)
autoscaling_settings = None
if args.autoscaling_settings_from_file:
autoscaling_settings = util.ParseAutoscalingSettingsFromFileFormat(
args.autoscaling_settings_from_file
)
if (
args.autoscaling_min_cluster_node_count
or args.autoscaling_max_cluster_node_count
or args.autoscaling_cool_down_period
or args.autoscaling_policy
):
autoscaling_settings = util.ParseAutoscalingSettingsFromInlinedFormat(
args.autoscaling_min_cluster_node_count,
args.autoscaling_max_cluster_node_count,
args.autoscaling_cool_down_period,
args.autoscaling_policy,
)
operation = client.Create(cluster, nodes_configs, autoscaling_settings)
if is_async:
log.CreatedResource(operation.name, kind='cluster', is_async=True)
return
resource = client.WaitForOperation(
operation_ref=client.GetOperationRef(operation),
message='waiting for cluster [{}] to be created'.format(
cluster.RelativeName()))
log.CreatedResource(cluster.RelativeName(), kind='cluster')
return resource

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""'vmware clusters delete' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware.clusters import ClustersClient
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
from googlecloudsdk.core import log
DETAILED_HELP = {
'DESCRIPTION':
"""
Delete a cluster in a VMware Engine private cloud.
""",
'EXAMPLES':
"""
To delete a cluster called `my-cluster` in private cloud `my-private-cloud` and zone `us-west2-a`, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud
Or:
$ {command} my-cluster --private-cloud=my-private-cloud
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Delete(base.DeleteCommand):
"""Delete a Google Cloud VMware Engine cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser, positional=True)
base.ASYNC_FLAG.AddToParser(parser)
base.ASYNC_FLAG.SetDefault(parser, True)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
client = ClustersClient()
is_async = args.async_
operation = client.Delete(cluster)
if is_async:
log.DeletedResource(operation.name, kind='cluster', is_async=True)
return operation
return client.WaitForOperation(
operation_ref=client.GetOperationRef(operation),
message='waiting for cluster [{}] to be deleted'.format(
cluster.RelativeName()),
has_result=False)

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""'vmware clusters describe' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware.clusters import ClustersClient
from googlecloudsdk.api_lib.vmware.nodetypes import NodeTypesClient
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
from googlecloudsdk.core.resource import resource_projector
DETAILED_HELP = {
'DESCRIPTION':
"""
Display data associated with a VMware Engine cluster, such as its node count, node type, and status.
""",
'EXAMPLES':
"""
To describe a cluster called `my-cluster` in private cloud `my-private-cloud` and zone `us-west2-a`, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud
Or:
$ {command} my-cluster --private-cloud=my-private-cloud
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Describe(base.DescribeCommand):
"""Describe a Google Cloud VMware Engine cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser, positional=True)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
location = cluster.Parent().Parent()
clusters_client = ClustersClient()
node_types_client = NodeTypesClient()
existing_cluster = resource_projector.MakeSerializable(
clusters_client.Get(cluster)
)
node_types = node_types_client.List(location)
id_to_node_type = {
node_type.nodeTypeId: node_type for node_type in node_types
}
cluster_memory, cluster_storage, cluster_vcpu, cluster_cores = 0, 0, 0, 0
for node_type_id, node_type_config in existing_cluster[
'nodeTypeConfigs'
].items():
if node_type_id not in id_to_node_type:
continue
node_type = id_to_node_type[node_type_id]
node_count = node_type_config['nodeCount']
custom_core_count = node_type_config.get('customCoreCount') or 0
cores_count = custom_core_count or node_type.totalCoreCount or 0
vcpu_ratio = (
node_type.virtualCpuCount // node_type.totalCoreCount
if node_type.totalCoreCount
else 0
)
cluster_memory += (node_type.memoryGb or 0) * node_count
cluster_storage += (node_type.diskSizeGb or 0) * node_count
cluster_vcpu += cores_count * vcpu_ratio * node_count
cluster_cores += cores_count * node_count
existing_cluster['clusterMemoryGb'] = cluster_memory
existing_cluster['clusterStorageGb'] = cluster_storage
existing_cluster['clusterVirtualCpuCount'] = cluster_vcpu
existing_cluster['clusterCoreCount'] = cluster_cores
return existing_cluster

View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""'vmware clusters list' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware.clusters import ClustersClient
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
DETAILED_HELP = {
'DESCRIPTION':
"""
List clusters in a VMware Engine private cloud.
""",
'EXAMPLES':
"""
To list clusters in the `my-private-cloud` private cloud run:
$ {command} --location=us-west2-a --project=my-project --private-cloud=my-private-cloud
Or:
$ {command} --private-cloud=my-private-cloud
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List clusters in a Google Cloud VMware Engine private cloud."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddPrivatecloudArgToParser(parser)
parser.display_info.AddFormat('table(name.segment(-1):label=NAME,'
'name.segment(-5):label=LOCATION,'
'name.segment(-3):label=PRIVATE_CLOUD,'
'createTime,state)')
def Run(self, args):
privatecloud = args.CONCEPTS.private_cloud.Parse()
client = ClustersClient()
return client.List(privatecloud)

View File

@@ -0,0 +1,164 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""'vmware clusters mount-datastore' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.api_lib.vmware import clusters
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
from googlecloudsdk.core import log
ClustersClient = clusters.ClustersClient
DETAILED_HELP = {
'DESCRIPTION': """
Mount a datastore to a cluster in a VMware Engine private cloud.
""",
'EXAMPLES': """
To mount a datastore `my-datastore` to cluster `my-cluster` in private cloud `my-private-cloud` in zone `us-west2-a`, providing subnet, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --datastore=projects/my-project/locations/us-west2-a/datastores/my-datastore --subnet=my-subnet
Or:
$ {command} my-cluster --private-cloud=my-private-cloud --datastore=projects/my-project/locations/us-west2-a/datastores/my-datastore --subnet=my-subnet
To mount a datastore `my-datastore` to cluster `my-cluster` in private cloud `my-private-cloud` in zone `us-west2-a`, providing a json file for datastore network, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --datastore=projects/my-project/locations/us-west2-a/datastores/my-datastore --datastore-network=network-config.json
Where `network-config.json` contains:
{
"subnet": "my-subnet",
"mtu": 1500,
"connection-count": 4
}
In the examples without location and project, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.Hidden
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class MountDatastore(base.UpdateCommand):
"""Mount a datastore to a Google Cloud VMware Engine cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser, positional=True)
base.ASYNC_FLAG.AddToParser(parser)
base.ASYNC_FLAG.SetDefault(parser, True)
parser.display_info.AddFormat('yaml')
parser.add_argument(
'--datastore',
required=True,
help='The datastore resource name to mount.',
)
network_group = parser.add_mutually_exclusive_group(required=True)
inlined_network_group = network_group.add_argument_group(
help='Datastore network configuration if not providing via file.'
)
inlined_network_group.add_argument(
'--subnet',
required=True,
help="""Subnet to use for inlined datastore network configuration.""",
)
inlined_network_group.add_argument(
'--mtu',
type=int,
help="""MTU for inlined datastore network configuration.""",
)
inlined_network_group.add_argument(
'--connection-count',
type=int,
help="""Connection count for inlined datastore network configuration.""",
)
network_group.add_argument(
'--datastore-network',
type=arg_parsers.FileContents(),
help="""Path to a JSON file containing the datastore network configuration.""",
)
parser.add_argument(
'--access-mode',
choices=['READ_WRITE', 'READ_ONLY'],
help="""Access mode for the datastore.""",
)
parser.add_argument(
'--nfs-version',
choices=['NFS_V3', 'NFS_V4'],
help="""NFS version for the datastore.""",
)
parser.add_argument(
'--ignore-colocation',
action='store_true',
help="""If set, ignore colocation checks.""",
)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
client = ClustersClient()
is_async = args.async_
subnet = args.subnet
mtu = args.mtu
connection_count = args.connection_count
if args.datastore_network:
try:
datastore_network_config = json.loads(args.datastore_network)
subnet = datastore_network_config.get('subnet')
mtu = datastore_network_config.get('mtu')
connection_count = datastore_network_config.get('connection-count')
except ValueError as e:
raise ValueError(
'Invalid JSON format for datastore-network file: ' + str(e)
)
operation = client.MountDatastore(
cluster_ref=cluster,
datastore=args.datastore,
subnet=subnet,
mtu=mtu,
connection_count=connection_count,
access_mode=args.access_mode,
nfs_version=args.nfs_version,
ignore_colocation=args.ignore_colocation,
)
if is_async:
log.UpdatedResource(
operation.name,
kind=f'cluster {cluster.RelativeName()}',
is_async=True,
)
return
resource = client.WaitForOperation(
operation_ref=client.GetOperationRef(operation),
message=f'waiting for cluster [{cluster.RelativeName()}] to be updated',
)
log.UpdatedResource(
cluster.RelativeName(),
kind='cluster',
details=f'datastore [{args.datastore}] mounted',
)
return resource

View File

@@ -0,0 +1,28 @@
# -*- 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.
"""The command group for the VMware Engine nodes CLI."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Nodes(base.Group):
"""Manage nodes in Google Cloud VMware Engine."""
category = base.COMPUTE_CATEGORY

View File

@@ -0,0 +1,57 @@
# -*- 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.
"""'vmware nodes describe command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware.nodes import NodesClient
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
DETAILED_HELP = {
'DESCRIPTION': """
Display data associated with a VMware Engine node, such as its name, state, node type, ip, fqdn.
""",
'EXAMPLES': """
To describe a node called `my-node` in private cloud `my-private-cloud` cluster `my-cluster` and zone `us-west2-a`, run:
$ {command} my-node --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --cluster=my-cluster
Or:
$ {command} my-node --private-cloud=my-private-cloud --cluster=my-cluster
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Describe(base.DescribeCommand):
"""Describe a Google Cloud VMware Engine node."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddNodeArgToParser(parser)
def Run(self, args):
node = args.CONCEPTS.node.Parse()
client = NodesClient()
return client.Get(node)

View File

@@ -0,0 +1,65 @@
# -*- 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.
"""'vmware nodes list command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware.nodes import NodesClient
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
DETAILED_HELP = {
'DESCRIPTION': """
List nodes in a VMware Engine private cloud's cluster.
""",
'EXAMPLES': """
To list nodes in the `my-private-cloud` private cloud and `my-cluster` cluster:
$ {command} --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --cluster=my-cluster
Or:
$ {command} --private-cloud=my-private-cloud --cluster=my-cluster
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List nodes in a Google Cloud VMware Engine private cloud's cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser)
parser.display_info.AddFormat(
'table(name.segment(-1):label=NAME,'
'name.segment(-7):label=LOCATION,'
'name.segment(-5):label=PRIVATE_CLOUD,'
'name.segment(-3):label=CLUSTER,'
'state,nodeTypeId,fqdn,'
'internalIp)'
)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
client = NodesClient()
return client.List(cluster)

View File

@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""'vmware clusters unmount-datastore' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.vmware import clusters
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
from googlecloudsdk.core import log
ClustersClient = clusters.ClustersClient
DETAILED_HELP = {
'DESCRIPTION': """
Unmount a datastore from a cluster in a VMware Engine private cloud.
""",
'EXAMPLES': """
To unmount a datastore `my-datastore` from cluster `my-cluster` in private cloud `my-private-cloud` in zone `us-west2-a`, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --datastore=projects/my-project/locations/us-west2-a/datastores/my-datastore
Or:
$ {command} my-cluster --private-cloud=my-private-cloud --datastore=projects/my-project/locations/us-west2-a/datastores/my-datastore
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
""",
}
@base.Hidden
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class UnmountDatastore(base.UpdateCommand):
"""Unmount a datastore from a Google Cloud VMware Engine cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser, positional=True)
base.ASYNC_FLAG.AddToParser(parser)
base.ASYNC_FLAG.SetDefault(parser, True)
parser.display_info.AddFormat('yaml')
parser.add_argument(
'--datastore',
required=True,
help='The datastore resource name to unmount.',
)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
client = ClustersClient()
is_async = args.async_
operation = client.UnmountDatastore(
cluster_ref=cluster,
datastore=args.datastore,
)
if is_async:
log.UpdatedResource(
operation.name,
kind=f'cluster {cluster.RelativeName()}',
is_async=True,
)
return
resource = client.WaitForOperation(
operation_ref=client.GetOperationRef(operation),
message=f'waiting for cluster [{cluster.RelativeName()}] to be updated',
)
log.UpdatedResource(
cluster.RelativeName(),
kind='cluster',
details=f'datastore [{args.datastore}] unmounted',
)
return resource

View File

@@ -0,0 +1,408 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""'vmware clusters update' command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from typing import List
from googlecloudsdk.api_lib.vmware import clusters
from googlecloudsdk.calliope import actions
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.vmware import flags
from googlecloudsdk.command_lib.vmware.clusters import util
from googlecloudsdk.core import log
DETAILED_HELP = {
'DESCRIPTION': """
Adjust the number of nodes in the VMware Engine cluster. Successful addition or removal of a node results in a cluster in READY state. Check the progress of a cluster using `{parent_command} list`.
""",
'EXAMPLES': """
To resize a cluster called `my-cluster` in private cloud `my-private-cloud` and zone `us-west2-a` to have `3` nodes of type `standard-72`, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --update-nodes-config=type=standard-72,count=3
Or:
$ {command} my-cluster --private-cloud=my-private-cloud --update-nodes-config=type=standard-72,count=3
In the second example, the project and location are taken from gcloud properties core/project and compute/zone.
To enable autoscale in a cluster called `my-cluster` in private cloud `my-private-cloud` and zone `us-west2-a`, run:
$ {command} my-cluster --location=us-west2-a --project=my-project --private-cloud=my-private-cloud --autoscaling-min-cluster-node-count=3 --autoscaling-max-cluster-node-count=5 --update-autoscaling-policy=name=custom-policy,node-type-id=standard-72,scale-out-size=1,storage-thresholds-scale-in=10,storage-thresholds-scale-out=80
""",
}
_NODE_TYPE_CONFIG_HELP = """
Information about the type and number of nodes associated with the cluster.
type (required): canonical identifier of the node type.
count (required): number of nodes of this type in the cluster.
"""
_OLD_NODE_TYPE_CONFIG_HELP = _NODE_TYPE_CONFIG_HELP + """
custom_core_count: can be passed, but the value will be ignored. Updating custom core count is not supported.
"""
def _ParseOldNodesConfigsParameters(existing_cluster, nodes_configs):
"""Parses the node configs parameters passed in the old format.
In the old format, the nodes configs are passed in a way that specifies what
exact node configs should be attached to the cluster after the operation. It's
not possible to remove existing node types. Even unchanged nodes configs have
to be specified in the parameters.
Args:
existing_cluster: cluster whose nodes configs should be updated
nodes_configs: nodes configs to be attached to the cluster
Returns:
list of NodeTypeConfig objects prepared for further processing
Raises:
InvalidNodeConfigsProvidedError:
if duplicate node types were specified or if a config for an existing node
type is not specified
"""
current_node_types = [
prop.key for prop in existing_cluster.nodeTypeConfigs.additionalProperties
]
requested_node_types = [config['type'] for config in nodes_configs]
duplicated_types = util.FindDuplicatedTypes(requested_node_types)
if duplicated_types:
raise util.InvalidNodeConfigsProvidedError(
f'types: {duplicated_types} provided more than once.'
)
unspecified_types = set(current_node_types) - set(requested_node_types)
if unspecified_types:
raise util.InvalidNodeConfigsProvidedError(
'when using `--node-type-config` parameters you need to specify node'
' counts for all node types present in the cluster. Missing node'
f' types: {list(unspecified_types)}.'
)
return [
util.NodeTypeConfig(
type=config['type'], count=config['count'], custom_core_count=0
)
for config in nodes_configs
]
def _ParseNewNodesConfigsParameters(
existing_cluster, updated_nodes_configs, removed_types
):
"""Parses the node configs parameters passed in the new format.
In the new format, the nodes configs are passed using two parameters. One of
them specifies which configs should be updated or created (unchanged configs
don't have to be specified at all). The other lists the configs to be removed.
This format is more flexible than the old one because it allows for config
removal and doesn't require re-specifying unchanged configs.
Args:
existing_cluster: cluster whose nodes configs should be updated
updated_nodes_configs: list of nodes configs to update or create
removed_types: list of node types for which nodes configs should be removed
Returns:
list of NodeTypeConfig objects prepared for further processing
Raises:
InvalidNodeConfigsProvidedError:
if duplicate node types were specified
"""
requested_node_types = [
config['type'] for config in updated_nodes_configs
] + removed_types
duplicated_types = util.FindDuplicatedTypes(requested_node_types)
if duplicated_types:
raise util.InvalidNodeConfigsProvidedError(
f'types: {duplicated_types} provided more than once.'
)
node_count = {}
for prop in existing_cluster.nodeTypeConfigs.additionalProperties:
node_count[prop.key] = prop.value.nodeCount
for config in updated_nodes_configs:
node_count[config['type']] = config['count']
for node_type in removed_types:
node_count[node_type] = 0
return [
util.NodeTypeConfig(type=node_type, count=count, custom_core_count=0)
for node_type, count in node_count.items()
]
def _ValidatePoliciesToRemove(
existing_cluster, updated_settings, policies_to_remove
):
"""Checks if the policies specified for removal actually exist and that they are not updated in the same call.
Args:
existing_cluster: cluster before the update
updated_settings: updated autoscale settings
policies_to_remove: list of policy names to remove
Raises:
InvalidAutoscalingSettingsProvidedError: if the validation fails.
"""
if not policies_to_remove:
return
if updated_settings and updated_settings.autoscaling_policies:
for name in updated_settings.autoscaling_policies:
if name in policies_to_remove:
raise util.InvalidAutoscalingSettingsProvidedError(
f"policy '{name}' specified both for update and removal"
)
if not existing_cluster.autoscalingSettings:
raise util.InvalidAutoscalingSettingsProvidedError(
f"nonexistent policies '{policies_to_remove}' specified for removal"
)
existing_policies = {
p.key
for p in existing_cluster.autoscalingSettings.autoscalingPolicies.additionalProperties
}
for name in policies_to_remove:
if name not in existing_policies:
raise util.InvalidAutoscalingSettingsProvidedError(
f"nonexistent policies '{policies_to_remove}' specified for removal"
)
def _RemoveAutoscalingPolicies(
autoscaling_settings: util.AutoscalingSettings,
policies_to_remove: List[str],
) -> util.AutoscalingSettings:
if not policies_to_remove:
return autoscaling_settings
for policy in policies_to_remove:
del autoscaling_settings.autoscaling_policies[policy]
return autoscaling_settings
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Update(base.UpdateCommand):
"""Update a Google Cloud VMware Engine cluster."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
"""Register flags for this command."""
flags.AddClusterArgToParser(parser, positional=True)
base.ASYNC_FLAG.AddToParser(parser)
base.ASYNC_FLAG.SetDefault(parser, True)
parser.display_info.AddFormat('yaml')
parser.add_argument(
'--node-type-config',
required=False,
type=arg_parsers.ArgDict(
spec={'type': str, 'count': int, 'custom-core-count': int},
required_keys=('type', 'count'),
),
action=actions.DeprecationAction(
'--node-type-config',
warn=(
'The {flag_name} option is deprecated; please use'
' --update-nodes-config and --remove-nodes-config instead.'
),
removed=False,
action='append',
),
metavar='[count=COUNT],[type=TYPE]',
help=_OLD_NODE_TYPE_CONFIG_HELP,
)
parser.add_argument(
'--update-nodes-config',
required=False,
default=list(),
type=arg_parsers.ArgDict(
spec={'type': str, 'count': int},
required_keys=('type', 'count'),
),
action='append',
help=_NODE_TYPE_CONFIG_HELP,
)
parser.add_argument(
'--remove-nodes-config',
required=False,
metavar='TYPE',
default=list(),
type=str,
action='append',
help='Type of node that should be removed from the cluster',
)
autoscaling_settings_group = parser.add_mutually_exclusive_group(
required=False
)
inlined_autoscaling_settings_group = autoscaling_settings_group.add_group()
inlined_autoscaling_settings_group.add_argument(
'--autoscaling-min-cluster-node-count',
type=int,
help='Minimum number of nodes in the cluster',
)
inlined_autoscaling_settings_group.add_argument(
'--autoscaling-max-cluster-node-count',
type=int,
help='Maximum number of nodes in the cluster',
)
inlined_autoscaling_settings_group.add_argument(
'--autoscaling-cool-down-period',
type=str,
help=(
'Cool down period (in minutes) between consecutive cluster'
' expansions/contractions'
),
)
inlined_autoscaling_settings_group.add_argument(
'--update-autoscaling-policy',
type=arg_parsers.ArgDict(
spec={
'name': str,
'node-type-id': str,
'scale-out-size': int,
'min-node-count': int,
'max-node-count': int,
'cpu-thresholds-scale-in': int,
'cpu-thresholds-scale-out': int,
'granted-memory-thresholds-scale-in': int,
'granted-memory-thresholds-scale-out': int,
'consumed-memory-thresholds-scale-in': int,
'consumed-memory-thresholds-scale-out': int,
'storage-thresholds-scale-in': int,
'storage-thresholds-scale-out': int,
},
required_keys=['name'],
),
action='append',
default=list(),
help='Autoscaling policy to be applied to the cluster',
)
autoscaling_settings_group.add_argument(
'--autoscaling-settings-from-file',
type=arg_parsers.YAMLFileContents(),
help=(
'A YAML file containing the autoscaling settings to be applied to'
' the cluster'
),
)
parser.add_argument(
'--remove-autoscaling-policy',
required=False,
metavar='NAME',
default=list(),
type=str,
action='append',
help=(
'Names of autoscaling policies that should be removed from the'
' cluster'
),
)
def Run(self, args):
cluster = args.CONCEPTS.cluster.Parse()
client = clusters.ClustersClient()
if args.node_type_config and (
args.update_nodes_config or args.remove_nodes_config
):
raise util.InvalidNodeConfigsProvidedError(
'flag `--node-type-config` is mutually exclusive with'
' `--update-nodes-config` and `--remove-nodes-config` flags.'
)
existing_cluster = client.Get(cluster)
if args.node_type_config:
configs = _ParseOldNodesConfigsParameters(
existing_cluster, args.node_type_config
)
elif args.update_nodes_config or args.remove_nodes_config:
configs = _ParseNewNodesConfigsParameters(
existing_cluster, args.update_nodes_config, args.remove_nodes_config
)
else:
configs = None
if args.autoscaling_settings_from_file:
updated_settings = util.ParseAutoscalingSettingsFromFileFormat(
args.autoscaling_settings_from_file
)
elif (
args.autoscaling_min_cluster_node_count
or args.autoscaling_max_cluster_node_count
or args.autoscaling_cool_down_period
or args.update_autoscaling_policy
):
updated_settings = util.ParseAutoscalingSettingsFromInlinedFormat(
args.autoscaling_min_cluster_node_count,
args.autoscaling_max_cluster_node_count,
args.autoscaling_cool_down_period,
args.update_autoscaling_policy,
)
else:
updated_settings = None
_ValidatePoliciesToRemove(
existing_cluster, updated_settings, args.remove_autoscaling_policy
)
autoscaling_settings = None
if updated_settings is not None or args.remove_autoscaling_policy:
old_settings = util.ParseAutoscalingSettingsFromApiFormat(
existing_cluster
)
autoscaling_settings = util.MergeAutoscalingSettings(
old_settings, updated_settings
)
autoscaling_settings = _RemoveAutoscalingPolicies(
autoscaling_settings, args.remove_autoscaling_policy
)
operation = client.Update(cluster, configs, autoscaling_settings)
is_async = args.async_
if is_async:
log.UpdatedResource(operation.name, kind='cluster', is_async=True)
return
resource = client.WaitForOperation(
operation_ref=client.GetOperationRef(operation),
message='waiting for cluster [{}] to be updated'.format(
cluster.RelativeName()
),
)
log.UpdatedResource(cluster.RelativeName(), kind='cluster')
return resource