File: //snap/google-cloud-cli/current/lib/surface/compute/instance_groups/managed/create.py
# -*- coding: utf-8 -*- #
# Copyright 2015 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.
"""Command for creating managed instance group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import sys
from apitools.base.py import encoding
from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import managed_instance_groups_utils
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.api_lib.compute import zone_utils
from googlecloudsdk.api_lib.compute.instance_groups.managed import stateful_policy_utils as policy_utils
from googlecloudsdk.api_lib.compute.managed_instance_groups_utils import ValueOrNone
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import flags
from googlecloudsdk.command_lib.compute import resource_manager_tags_utils
from googlecloudsdk.command_lib.compute import scope as compute_scope
from googlecloudsdk.command_lib.compute.instance_groups import flags as instance_groups_flags
from googlecloudsdk.command_lib.compute.instance_groups.managed import flags as managed_flags
from googlecloudsdk.command_lib.compute.managed_instance_groups import auto_healing_utils
from googlecloudsdk.command_lib.util.apis import arg_utils
from googlecloudsdk.core import properties
import six
# API allows up to 58 characters but asked us to send only 54 (unless user
# explicitly asks us for more).
_MAX_LEN_FOR_DEDUCED_BASE_INSTANCE_NAME = 54
# Flags valid only for regional MIGs.
REGIONAL_FLAGS = [
'instance_redistribution_type',
'target_distribution_shape',
'on_repair_allow_changing_zone',
]
def _AddInstanceGroupManagerArgs(parser):
"""Adds args."""
parser.add_argument(
'--base-instance-name',
help=('Base name to use for the Compute Engine instances that will '
'be created with the managed instance group. If not provided '
'base instance name will be the prefix of instance group name.'))
parser.add_argument(
'--size',
required=True,
type=arg_parsers.BoundedInt(0, sys.maxsize, unlimited=True),
help='Initial number of instances you want in this group.')
instance_groups_flags.AddDescriptionFlag(parser)
parser.add_argument(
'--target-pool',
type=arg_parsers.ArgList(),
metavar='TARGET_POOL',
help=('Specifies any target pools you want the instances of this '
'managed instance group to be part of.'))
managed_flags.INSTANCE_TEMPLATE_ARG.AddArgument(parser)
def _IsZonalGroup(ref):
"""Checks if reference to instance group is zonal."""
return ref.Collection() == 'compute.instanceGroupManagers'
def ValidateUpdatePolicyAgainstStateful(update_policy, group_ref,
stateful_policy, client):
"""Validates and fixed update policy for stateful MIG.
Sets default values in update_policy for stateful IGMs or throws exception
if the wrong value is set explicitly.
Args:
update_policy: Update policy to be validated
group_ref: Reference of IGM being validated
stateful_policy: Stateful policy to check if the group is stateful
client: The compute API client
"""
if stateful_policy is None or _IsZonalGroup(group_ref):
return
redistribution_type_none = (
client.messages.InstanceGroupManagerUpdatePolicy
.InstanceRedistributionTypeValueValuesEnum.NONE)
if (not update_policy or
update_policy.instanceRedistributionType != redistribution_type_none):
raise exceptions.RequiredArgumentException(
'--instance-redistribution-type',
'Stateful regional IGMs need to have instance redistribution type '
'set to \'NONE\'. Use \'--instance-redistribution-type=NONE\'.')
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
class CreateGA(base.CreateCommand):
"""Create Compute Engine managed instance groups."""
support_update_policy_min_ready_flag = False
support_resource_manager_tags = False
@classmethod
def Args(cls, parser):
parser.display_info.AddFormat(managed_flags.DEFAULT_CREATE_OR_LIST_FORMAT)
_AddInstanceGroupManagerArgs(parser)
auto_healing_utils.AddAutohealingArgs(parser)
igm_arg = instance_groups_flags.GetInstanceGroupManagerArg(zones_flag=True)
igm_arg.AddArgument(parser, operation_type='create')
instance_groups_flags.AddZonesFlag(parser)
instance_groups_flags.AddMigCreateStatefulFlags(parser)
instance_groups_flags.AddMigCreateStatefulIPsFlags(parser)
managed_flags.AddMigInstanceRedistributionTypeFlag(parser)
managed_flags.AddMigDistributionPolicyTargetShapeFlag(parser)
managed_flags.AddMigListManagedInstancesResultsFlag(parser)
managed_flags.AddMigUpdatePolicyFlags(
parser, support_min_ready_flag=cls.support_update_policy_min_ready_flag)
managed_flags.AddMigForceUpdateOnRepairFlags(parser)
if cls.support_resource_manager_tags:
managed_flags.AddMigResourceManagerTagsFlags(parser)
managed_flags.AddMigDefaultActionOnVmFailure(parser, cls.ReleaseTrack())
managed_flags.AddInstanceFlexibilityPolicyArgs(parser)
managed_flags.AddStandbyPolicyFlags(parser)
managed_flags.AddWorkloadPolicyFlag(parser)
# When adding RMIG-specific flag, update REGIONAL_FLAGS constant.
def _HandleStatefulArgs(self, instance_group_manager, args, client):
instance_groups_flags.ValidateManagedInstanceGroupStatefulDisksProperties(
args)
instance_groups_flags.ValidateManagedInstanceGroupStatefulIPsProperties(
args
)
if (
args.stateful_disk
or args.stateful_internal_ip
or args.stateful_external_ip
):
instance_group_manager.statefulPolicy = (
self._CreateStatefulPolicy(args, client))
def _CreateStatefulPolicy(self, args, client):
"""Create stateful policy from disks of args --stateful-disk, and ips of args --stateful-external-ips and --stateful-internal-ips."""
stateful_disks = []
for stateful_disk_dict in (args.stateful_disk or []):
stateful_disks.append(
policy_utils.MakeStatefulPolicyPreservedStateDiskEntry(
client.messages, stateful_disk_dict))
stateful_disks.sort(key=lambda x: x.key)
stateful_policy = policy_utils.MakeStatefulPolicy(
client.messages, stateful_disks
)
stateful_internal_ips = []
for stateful_ip_dict in args.stateful_internal_ip or []:
stateful_internal_ips.append(
policy_utils.MakeStatefulPolicyPreservedStateInternalIPEntry(
client.messages, stateful_ip_dict
)
)
stateful_internal_ips.sort(key=lambda x: x.key)
stateful_policy.preservedState.internalIPs = (
client.messages.StatefulPolicyPreservedState.InternalIPsValue(
additionalProperties=stateful_internal_ips
)
)
stateful_external_ips = []
for stateful_ip_dict in args.stateful_external_ip or []:
stateful_external_ips.append(
policy_utils.MakeStatefulPolicyPreservedStateExternalIPEntry(
client.messages, stateful_ip_dict
)
)
stateful_external_ips.sort(key=lambda x: x.key)
stateful_policy.preservedState.externalIPs = (
client.messages.StatefulPolicyPreservedState.ExternalIPsValue(
additionalProperties=stateful_external_ips
)
)
return stateful_policy
def _CreateGroupReference(self, args, client, resources):
if args.zones:
zone_ref = resources.Parse(
args.zones[0],
collection='compute.zones',
params={'project': properties.VALUES.core.project.GetOrFail})
region = utils.ZoneNameToRegionName(zone_ref.Name())
return resources.Parse(
args.name,
params={
'region': region,
'project': properties.VALUES.core.project.GetOrFail
},
collection='compute.regionInstanceGroupManagers')
group_ref = (
instance_groups_flags.GetInstanceGroupManagerArg().ResolveAsResource)(
args,
resources,
default_scope=compute_scope.ScopeEnum.ZONE,
scope_lister=flags.GetDefaultScopeLister(client))
if _IsZonalGroup(group_ref):
zonal_resource_fetcher = zone_utils.ZoneResourceFetcher(client)
zonal_resource_fetcher.WarnForZonalCreation([group_ref])
return group_ref
def _CreateDistributionPolicy(self, args, resources, messages):
distribution_policy = messages.DistributionPolicy()
if args.zones:
policy_zones = []
for zone in args.zones:
zone_ref = resources.Parse(
zone,
collection='compute.zones',
params={'project': properties.VALUES.core.project.GetOrFail})
policy_zones.append(
messages.DistributionPolicyZoneConfiguration(
zone=zone_ref.SelfLink()))
distribution_policy.zones = policy_zones
if args.target_distribution_shape:
distribution_policy.targetShape = arg_utils.ChoiceToEnum(
args.target_distribution_shape,
messages.DistributionPolicy.TargetShapeValueValuesEnum)
return ValueOrNone(distribution_policy)
def _GetRegionForGroup(self, group_ref):
if _IsZonalGroup(group_ref):
return utils.ZoneNameToRegionName(group_ref.zone)
else:
return group_ref.region
def _GetServiceForGroup(self, group_ref, compute):
if _IsZonalGroup(group_ref):
return compute.instanceGroupManagers
else:
return compute.regionInstanceGroupManagers
def _CreateResourceRequest(self, group_ref, instance_group_manager, client,
resources):
if _IsZonalGroup(group_ref):
instance_group_manager.zone = group_ref.zone
return client.messages.ComputeInstanceGroupManagersInsertRequest(
instanceGroupManager=instance_group_manager,
project=group_ref.project,
zone=group_ref.zone)
else:
region_link = resources.Parse(
group_ref.region,
params={'project': properties.VALUES.core.project.GetOrFail},
collection='compute.regions')
instance_group_manager.region = region_link.SelfLink()
return client.messages.ComputeRegionInstanceGroupManagersInsertRequest(
instanceGroupManager=instance_group_manager,
project=group_ref.project,
region=group_ref.region)
def _GetInstanceGroupManagerTargetPools(self, target_pools, group_ref,
holder):
pool_refs = []
if target_pools:
region = self._GetRegionForGroup(group_ref)
for pool in target_pools:
pool_refs.append(
holder.resources.Parse(
pool,
params={
'project': properties.VALUES.core.project.GetOrFail,
'region': region
},
collection='compute.targetPools'))
return [pool_ref.SelfLink() for pool_ref in pool_refs]
def _CreateParams(self, client, resource_manager_tags):
resource_manager_tags_map = (
resource_manager_tags_utils.GetResourceManagerTags(
resource_manager_tags
)
)
params = client.messages.InstanceGroupManagerParams
additional_properties = [
params.ResourceManagerTagsValue.AdditionalProperty(key=key, value=value)
for key, value in sorted(six.iteritems(resource_manager_tags_map))
]
return params(
resourceManagerTags=params.ResourceManagerTagsValue(
additionalProperties=additional_properties
)
)
def _CreateInstanceGroupManager(self, args, group_ref, template_ref, client,
holder):
"""Create parts of Instance Group Manager shared for the track."""
managed_flags.ValidateRegionalMigFlagsUsage(args, REGIONAL_FLAGS, group_ref)
instance_groups_flags.ValidateManagedInstanceGroupScopeArgs(
args, holder.resources)
health_check = managed_instance_groups_utils.GetHealthCheckUri(
holder.resources, args)
auto_healing_policies = (
managed_instance_groups_utils.CreateAutohealingPolicies(
client.messages, health_check, args.initial_delay))
managed_instance_groups_utils.ValidateAutohealingPolicies(
auto_healing_policies)
update_policy = managed_instance_groups_utils.PatchUpdatePolicy(
client, args, None)
instance_lifecycle_policy = (
managed_instance_groups_utils.CreateInstanceLifecyclePolicy(
client.messages, args
)
)
instance_flexibility_policy = (
managed_instance_groups_utils.CreateInstanceFlexibilityPolicy(
args, client.messages
)
)
resource_policies = managed_instance_groups_utils.CreateResourcePolicies(
client.messages, args
)
instance_group_manager = client.messages.InstanceGroupManager(
name=group_ref.Name(),
description=args.description,
instanceTemplate=template_ref.SelfLink(),
baseInstanceName=args.base_instance_name,
targetPools=self._GetInstanceGroupManagerTargetPools(
args.target_pool, group_ref, holder
),
targetSize=int(args.size),
autoHealingPolicies=auto_healing_policies,
distributionPolicy=self._CreateDistributionPolicy(
args, holder.resources, client.messages
),
updatePolicy=update_policy,
instanceLifecyclePolicy=instance_lifecycle_policy,
instanceFlexibilityPolicy=instance_flexibility_policy,
resourcePolicies=resource_policies,
)
if args.IsSpecified('list_managed_instances_results'):
instance_group_manager.listManagedInstancesResults = (
client.messages.InstanceGroupManager
.ListManagedInstancesResultsValueValuesEnum)(
args.list_managed_instances_results.upper())
if self.support_resource_manager_tags and args.resource_manager_tags:
instance_group_manager.params = self._CreateParams(
client, args.resource_manager_tags
)
self._HandleStatefulArgs(instance_group_manager, args, client)
# Validate updatePolicy + statefulPolicy combination
ValidateUpdatePolicyAgainstStateful(instance_group_manager.updatePolicy,
group_ref,
instance_group_manager.statefulPolicy,
client)
standby_policy = managed_instance_groups_utils.CreateStandbyPolicy(
client.messages,
args.standby_policy_initial_delay,
args.standby_policy_mode,
)
if standby_policy:
instance_group_manager.standbyPolicy = standby_policy
if args.suspended_size:
instance_group_manager.targetSuspendedSize = args.suspended_size
if args.stopped_size:
instance_group_manager.targetStoppedSize = args.stopped_size
if args.IsKnownAndSpecified('target_size_policy_mode'):
instance_group_manager.targetSizePolicy = (
managed_instance_groups_utils.CreateTargetSizePolicy(
client.messages, args.target_size_policy_mode
)
)
return instance_group_manager
def _PostProcessOutput(self, holder, migs):
# 0 to 1 MIGs.
for mig in [encoding.MessageToDict(m) for m in migs]:
# At this point we're missing information about autoscaler and current
# size. To avoid making additional calls to API, we assume current size to
# be 0, since MIG has just been created. We also assume that there's no
# autoscaler, since API doesn't allow to insert MIG simultaneously with
# autoscaler.
mig['size'] = 0
# Same as "mig['autoscaled'] = 'no'", but making sure that property value
# is consistent with the one used to list groups.
managed_instance_groups_utils.ResolveAutoscalingStatusForMig(
holder.client, mig)
yield mig
def Run(self, args):
"""Creates and issues an instanceGroupManagers.Insert request.
Args:
args: the argparse arguments that this command was invoked with.
Returns:
List containing one dictionary: resource augmented with 'autoscaled'
property
"""
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
group_ref = self._CreateGroupReference(args, client, holder.resources)
template_ref = managed_flags.INSTANCE_TEMPLATE_ARG.ResolveAsResource(
args,
holder.resources,
default_scope=flags.compute_scope.ScopeEnum.GLOBAL,
)
instance_group_manager = self._CreateInstanceGroupManager(
args, group_ref, template_ref, client, holder)
request = self._CreateResourceRequest(group_ref, instance_group_manager,
client, holder.resources)
service = self._GetServiceForGroup(group_ref, client.apitools_client)
migs = client.MakeRequests([(service, 'Insert', request)])
return self._PostProcessOutput(holder, migs)
CreateGA.detailed_help = {
'brief': 'Create a Compute Engine managed instance group',
'DESCRIPTION': """\
*{command}* creates a Compute Engine managed instance group.
""",
'EXAMPLES': """\
Running:
$ {command} example-managed-instance-group --zone=us-central1-a --template=example-global-instance-template --size=1
will create a managed instance group called 'example-managed-instance-group'
in the ``us-central1-a'' zone with a global instance template resource
'example-global-instance-template'.
To use a regional instance template, specify the full or partial URL of the template.
Running:
$ {command} example-managed-instance-group --zone=us-central1-a \\
--template=projects/example-project/regions/us-central1/instanceTemplates/example-regional-instance-template \\
--size=1
will create a managed instance group called
'example-managed-instance-group' in the ``us-central1-a'' zone with a
regional instance template resource 'example-regional-instance-template'.
""",
}
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class CreateBeta(CreateGA):
"""Create Compute Engine managed instance groups."""
support_update_policy_min_ready_flag = True
support_resource_manager_tags = True
@classmethod
def Args(cls, parser):
managed_flags.AddMigActionOnVmFailedHealthCheck(parser)
managed_flags.AddTargetSizePolicyModeFlag(parser)
managed_flags.AddOnRepairFlags(parser)
super(CreateBeta, cls).Args(parser)
def _CreateInstanceGroupManager(self, args, group_ref, template_ref, client,
holder):
instance_group_manager = super(CreateBeta,
self)._CreateInstanceGroupManager(
args, group_ref, template_ref, client,
holder)
return instance_group_manager
CreateBeta.detailed_help = CreateGA.detailed_help
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class CreateAlpha(CreateBeta):
"""Create Compute Engine managed instance groups."""
support_resource_manager_tags = True
@classmethod
def Args(cls, parser):
super(CreateAlpha, cls).Args(parser)
def _CreateInstanceGroupManager(
self, args, group_ref, template_ref, client, holder
):
instance_group_manager = super(
CreateAlpha, self
)._CreateInstanceGroupManager(args, group_ref, template_ref, client, holder)
return instance_group_manager
CreateAlpha.detailed_help = CreateGA.detailed_help