File: //snap/google-cloud-cli/current/lib/surface/bigtable/instances/create.py
# -*- coding: utf-8 -*- #
# Copyright 2016 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.
"""bigtable instances create command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import textwrap
from googlecloudsdk.api_lib.bigtable import clusters
from googlecloudsdk.api_lib.bigtable import util
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.bigtable import arguments
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class CreateInstance(base.CreateCommand):
"""Create a new Bigtable instance."""
_support_tags = False
detailed_help = {
'EXAMPLES': textwrap.dedent("""\
To create an instance with id `my-instance-id` with a cluster located
in `us-east1-c`, run:
$ {command} my-instance-id --display-name="My Instance" --cluster-config=id=my-cluster-id,zone=us-east1-c
To create an instance with multiple clusters, run:
$ {command} my-instance-id --display-name="My Instance" --cluster-config=id=my-cluster-id-1,zone=us-east1-c --cluster-config=id=my-cluster-id-2,zone=us-west1-c,nodes=3
To create an instance with `HDD` storage and `10` nodes, run:
$ {command} my-hdd-instance --display-name="HDD Instance" --cluster-storage-type=HDD --cluster-config=id=my-cluster-id,zone=us-east1-c,nodes=10
"""),
}
@classmethod
def Args(cls, parser):
"""Register flags for this command."""
(
arguments.ArgAdder(parser)
.AddInstanceDisplayName(required=True)
.AddClusterConfig()
.AddDeprecatedCluster()
.AddDeprecatedClusterZone()
.AddDeprecatedClusterNodes()
.AddClusterStorage()
.AddAsync()
.AddDeprecatedInstanceType()
)
if cls._support_tags:
arguments.ArgAdder(parser).AddTags()
arguments.AddInstanceResourceArg(parser, 'to create', positional=True)
parser.display_info.AddCacheUpdater(arguments.InstanceCompleter)
def Run(self, args):
"""Executes the instances create command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
Some value that we want to have printed later.
"""
return self._Run(args)
def _Run(self, args):
"""Implements Run() with different possible features flags."""
cli = util.GetAdminClient()
ref = args.CONCEPTS.instance.Parse()
# TODO(b/153576330): This is a workaround for inconsistent collection names.
parent_ref = resources.REGISTRY.Create(
'bigtableadmin.projects', projectId=ref.projectsId)
msgs = util.GetAdminMessages()
instance_type = msgs.Instance.TypeValueValuesEnum(args.instance_type)
new_clusters = self._Clusters(args)
clusters_properties = []
for cluster_id, cluster in sorted(new_clusters.items()):
clusters_properties.append(
msgs.CreateInstanceRequest.ClustersValue.AdditionalProperty(
key=cluster_id, value=cluster))
msg = msgs.CreateInstanceRequest(
instanceId=ref.Name(),
parent=parent_ref.RelativeName(),
instance=msgs.Instance(
displayName=args.display_name,
type=instance_type,
tags=self._Tags(args),
),
clusters=msgs.CreateInstanceRequest.ClustersValue(
additionalProperties=clusters_properties
),
)
result = cli.projects_instances.Create(msg)
operation_ref = util.GetOperationRef(result)
if args.async_:
log.CreatedResource(
operation_ref.RelativeName(),
kind='bigtable instance {0}'.format(ref.Name()),
is_async=True)
return result
return util.AwaitInstance(
operation_ref, 'Creating bigtable instance {0}'.format(ref.Name()))
def _Clusters(self, args):
"""Get the clusters configs from command arguments.
Args:
args: the argparse namespace from Run().
Returns:
A dict mapping from cluster id to msg.Cluster.
"""
msgs = util.GetAdminMessages()
storage_type = msgs.Cluster.DefaultStorageTypeValueValuesEnum(
args.cluster_storage_type.upper())
if args.cluster_config is not None:
if (args.cluster is not None
or args.cluster_zone is not None
or args.cluster_num_nodes is not None):
raise exceptions.InvalidArgumentException(
'--cluster-config --cluster --cluster-zone --cluster-num-nodes',
'Use --cluster-config or the combination of --cluster, '
'--cluster-zone and --cluster-num-nodes to specify cluster(s), not '
'both.')
self._ValidateClusterConfigArgs(args.cluster_config)
new_clusters = {}
for cluster_dict in args.cluster_config:
nodes = cluster_dict.get('nodes', 1)
node_scaling_factor = cluster_dict.get(
'node-scaling-factor',
msgs.Cluster.NodeScalingFactorValueValuesEnum(
msgs.Cluster.NodeScalingFactorValueValuesEnum.NODE_SCALING_FACTOR_1X
),
)
cluster = msgs.Cluster(
serveNodes=nodes,
nodeScalingFactor=node_scaling_factor,
defaultStorageType=storage_type,
# TODO(b/36049938): switch location to resource
# when b/29566669 is fixed on API
location=util.LocationUrl(cluster_dict['zone']))
if 'kms-key' in cluster_dict:
cluster.encryptionConfig = msgs.EncryptionConfig(
kmsKeyName=cluster_dict['kms-key'])
if ('autoscaling-min-nodes' in cluster_dict or
'autoscaling-max-nodes' in cluster_dict or
'autoscaling-cpu-target' in cluster_dict):
# autoscaling-storage-target is optional.
if 'autoscaling-storage-target' in cluster_dict:
storage_target = cluster_dict['autoscaling-storage-target']
else:
storage_target = None
cluster.clusterConfig = clusters.BuildClusterConfig(
autoscaling_min=cluster_dict['autoscaling-min-nodes'],
autoscaling_max=cluster_dict['autoscaling-max-nodes'],
autoscaling_cpu_target=cluster_dict['autoscaling-cpu-target'],
autoscaling_storage_target=storage_target)
# serveNodes must be set to None or 0 to enable Autoscaling.
# go/cbt-autoscaler-api
cluster.serveNodes = None
new_clusters[cluster_dict['id']] = cluster
return new_clusters
elif args.cluster is not None:
if args.cluster_zone is None:
raise exceptions.InvalidArgumentException(
'--cluster-zone', '--cluster-zone must be specified.')
cluster = msgs.Cluster(
serveNodes=arguments.ProcessInstanceTypeAndNodes(args),
# nodeScalingFactor is not supported in deprecated workflow
defaultStorageType=storage_type,
nodeScalingFactor=msgs.Cluster.NodeScalingFactorValueValuesEnum(
msgs.Cluster.NodeScalingFactorValueValuesEnum.NODE_SCALING_FACTOR_1X
),
# TODO(b/36049938): switch location to resource
# when b/29566669 is fixed on API
location=util.LocationUrl(args.cluster_zone))
return {args.cluster: cluster}
else:
raise exceptions.InvalidArgumentException(
'--cluster --cluster-config',
'Use --cluster-config to specify cluster(s).',
)
def _ValidateClusterConfigArgs(self, cluster_config):
"""Validates arguments in cluster-config as a repeated dict."""
# Validate cluster-config of each cluster.
for cluster_dict in cluster_config:
if ('autoscaling-min-nodes' in cluster_dict or
'autoscaling-max-nodes' in cluster_dict or
'autoscaling-cpu-target' in cluster_dict or
'autoscaling-storage-target' in cluster_dict):
# nodes and autoscaling args are mutual exclusive.
if 'nodes' in cluster_dict:
raise exceptions.InvalidArgumentException(
'--autoscaling-min-nodes --autoscaling-max-nodes '
'--autoscaling-cpu-target --autoscaling-storage-target',
'At most one of nodes | autoscaling-min-nodes '
'autoscaling-max-nodes autoscaling-cpu-target '
'autoscaling-storage-target may be specified in --cluster-config')
# To enable autoscaling, all related args must be set.
if ('autoscaling-min-nodes' not in cluster_dict or
'autoscaling-max-nodes' not in cluster_dict or
'autoscaling-cpu-target' not in cluster_dict):
raise exceptions.InvalidArgumentException(
'--autoscaling-min-nodes --autoscaling-max-nodes '
'--autoscaling-cpu-target', 'All of --autoscaling-min-nodes '
'--autoscaling-max-nodes --autoscaling-cpu-target must be set to '
'enable Autoscaling.')
@classmethod
def _Tags(cls, args, tags_arg_name='tags'):
"""Get the tags from command arguments.
Args:
args: the argparse namespace from Run().
tags_arg_name: the name of the tags argument.
Returns:
A dict mapping from tag key to tag value.
"""
if not cls._support_tags:
return None
tags = getattr(args, tags_arg_name)
if not tags:
return None
tags_message = util.GetAdminMessages().Instance.TagsValue
# Sorted for test stability.
return tags_message(
additionalProperties=[
tags_message.AdditionalProperty(key=key, value=value)
for key, value in sorted(tags.items())
]
)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class CreateInstanceAlpha(CreateInstance):
"""Create a new Bigtable instance."""
_support_tags = True