1# -*- coding: utf-8 -*- # 2# Copyright 2014 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Convenience functions for dealing with instances and instance templates.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21import collections 22import re 23 24from googlecloudsdk.api_lib.compute import constants 25from googlecloudsdk.api_lib.compute import containers_utils 26from googlecloudsdk.api_lib.compute import csek_utils 27from googlecloudsdk.api_lib.compute import metadata_utils 28from googlecloudsdk.api_lib.compute import utils 29from googlecloudsdk.api_lib.compute import zone_utils 30from googlecloudsdk.calliope import exceptions as calliope_exceptions 31from googlecloudsdk.command_lib.compute import flags as compute_flags 32from googlecloudsdk.command_lib.compute import scope as compute_scopes 33from googlecloudsdk.command_lib.compute.instances import flags 34from googlecloudsdk.command_lib.compute.sole_tenancy import util as sole_tenancy_util 35from googlecloudsdk.core import log 36from googlecloudsdk.core import resources as cloud_resources 37import six 38 39EMAIL_REGEX = re.compile(r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)') 40 41_DEFAULT_DEVICE_NAME_CONTAINER_WARNING = ( 42 'Default device-name for disk name [{0}] will be [{0}] because it is being ' 43 'mounted to a container with [`--container-mount-disk`]') 44 45 46def GetCpuRamVmFamilyFromCustomName(name): 47 """Gets the CPU and memory specs from the custom machine type name. 48 49 Args: 50 name: the custom machine type name for the 'instance create' call 51 52 Returns: 53 A three-tuple with the vm family, number of cpu and amount of memory for the 54 custom machine type. 55 custom_family, the name of the VM family 56 custom_cpu, the number of cpu desired for the custom machine type instance 57 custom_memory_mib, the amount of ram desired in MiB for the custom machine 58 type instance 59 None for both variables otherwise 60 """ 61 check_custom = re.search('([a-zA-Z0-9]+)-custom-([0-9]+)-([0-9]+)', name) 62 if check_custom: 63 custom_family = check_custom.group(1) 64 custom_cpu = check_custom.group(2) 65 custom_memory_mib = check_custom.group(3) 66 return custom_family, custom_cpu, custom_memory_mib 67 return None, None, None 68 69 70def GetNameForCustom(custom_cpu, custom_memory_mib, ext=False, vm_type=False): 71 """Creates a custom machine type name from the desired CPU and memory specs. 72 73 Args: 74 custom_cpu: the number of cpu desired for the custom machine type 75 custom_memory_mib: the amount of ram desired in MiB for the custom machine 76 type instance 77 ext: extended custom machine type should be used if true 78 vm_type: VM instance generation 79 80 Returns: 81 The custom machine type name for the 'instance create' call 82 """ 83 if vm_type: 84 machine_type = '{0}-custom-{1}-{2}'.format(vm_type, custom_cpu, 85 custom_memory_mib) 86 else: 87 machine_type = 'custom-{0}-{1}'.format(custom_cpu, custom_memory_mib) 88 if ext: 89 machine_type += '-ext' 90 return machine_type 91 92 93def InterpretMachineType(machine_type, 94 custom_cpu, 95 custom_memory, 96 ext=True, 97 vm_type=False, 98 confidential_vm=False): 99 """Interprets the machine type for the instance. 100 101 Args: 102 machine_type: name of existing machine type, eg. n1-standard 103 custom_cpu: number of CPU cores for custom machine type, 104 custom_memory: amount of RAM memory in bytes for custom machine type, 105 ext: extended custom machine type should be used if true, 106 vm_type: VM instance generation 107 confidential_vm: If True, default machine type is different for confidential 108 VMs. 109 110 Returns: 111 A string representing the URL naming a machine-type. 112 113 Raises: 114 calliope_exceptions.RequiredArgumentException when only one of the two 115 custom machine type flags are used. 116 calliope_exceptions.InvalidArgumentException when both the machine type and 117 custom machine type flags are used to generate a new instance. 118 """ 119 # Setting the machine type 120 if machine_type: 121 machine_type_name = machine_type 122 elif confidential_vm: 123 machine_type_name = constants.DEFAULT_MACHINE_TYPE_FOR_CONFIDENTIAL_VMS 124 else: 125 machine_type_name = constants.DEFAULT_MACHINE_TYPE 126 127 # Setting the specs for the custom machine. 128 if custom_cpu or custom_memory or ext: 129 if not custom_cpu: 130 raise calliope_exceptions.RequiredArgumentException( 131 '--custom-cpu', 'Both [--custom-cpu] and [--custom-memory] must be ' 132 'set to create a custom machine type instance.') 133 if not custom_memory: 134 raise calliope_exceptions.RequiredArgumentException( 135 '--custom-memory', 'Both [--custom-cpu] and [--custom-memory] must ' 136 'be set to create a custom machine type instance.') 137 if machine_type: 138 raise calliope_exceptions.InvalidArgumentException( 139 '--machine-type', 'Cannot set both [--machine-type] and ' 140 '[--custom-cpu]/[--custom-memory] for the same instance.') 141 custom_type_string = GetNameForCustom( 142 custom_cpu, 143 # converting from B to MiB. 144 custom_memory // (2**20), 145 ext, 146 vm_type) 147 148 # Updating the machine type that is set for the URIs 149 machine_type_name = custom_type_string 150 return machine_type_name 151 152 153def CheckCustomCpuRamRatio(compute_client, project, zone, machine_type_name): 154 """Checks that the CPU and memory ratio is a supported custom instance type. 155 156 Args: 157 compute_client: GCE API client, 158 project: a project, 159 zone: the zone of the instance(s) being created, 160 machine_type_name: The machine type of the instance being created. 161 162 Returns: 163 Nothing. Function acts as a bound checker, and will raise an exception from 164 within the function if needed. 165 166 Raises: 167 utils.RaiseToolException if a custom machine type ratio is out of bounds. 168 """ 169 messages = compute_client.messages 170 compute = compute_client.apitools_client 171 if 'custom' in machine_type_name: 172 mt_get_pb = messages.ComputeMachineTypesGetRequest( 173 machineType=machine_type_name, project=project, zone=zone) 174 mt_get_reqs = [(compute.machineTypes, 'Get', mt_get_pb)] 175 errors = [] 176 177 # Makes a 'machine-types describe' request to check the bounds 178 _ = list( 179 compute_client.MakeRequests( 180 requests=mt_get_reqs, errors_to_collect=errors)) 181 182 if errors: 183 utils.RaiseToolException( 184 errors, error_message='Could not fetch machine type:') 185 186 187def CreateServiceAccountMessages(messages, scopes, service_account): 188 """Returns a list of ServiceAccount messages corresponding to scopes.""" 189 if scopes is None: 190 scopes = constants.DEFAULT_SCOPES 191 if service_account is None: 192 service_account = 'default' 193 194 accounts_to_scopes = collections.defaultdict(list) 195 for scope in scopes: 196 parts = scope.split('=') 197 if len(parts) == 1: 198 account = service_account 199 scope_uri = scope 200 elif len(parts) == 2: 201 # TODO(b/33688878) Remove exception for this deprecated format 202 raise calliope_exceptions.InvalidArgumentException( 203 '--scopes', 204 'Flag format --scopes [ACCOUNT=]SCOPE,[[ACCOUNT=]SCOPE, ...] is ' 205 'removed. Use --scopes [SCOPE,...] --service-account ACCOUNT ' 206 'instead.') 207 else: 208 raise calliope_exceptions.ToolException( 209 '[{0}] is an illegal value for [--scopes]. Values must be of the ' 210 'form [SCOPE].'.format(scope)) 211 212 if service_account != 'default' and not EMAIL_REGEX.match(service_account): 213 raise calliope_exceptions.InvalidArgumentException( 214 '--service-account', 215 'Invalid format: expected default or user@domain.com, received ' + 216 service_account) 217 218 # Expands the scope if the user provided an alias like 219 # "compute-rw". 220 scope_uri = constants.SCOPES.get(scope_uri, [scope_uri]) 221 accounts_to_scopes[account].extend(scope_uri) 222 223 if not scopes and service_account != 'default': 224 return [messages.ServiceAccount(email=service_account, scopes=[])] 225 res = [] 226 for account, scopes in sorted(six.iteritems(accounts_to_scopes)): 227 res.append(messages.ServiceAccount(email=account, scopes=sorted(scopes))) 228 return res 229 230 231def CreateOnHostMaintenanceMessage(messages, maintenance_policy): 232 """Create on-host-maintenance message for VM.""" 233 if maintenance_policy: 234 on_host_maintenance = messages.Scheduling.OnHostMaintenanceValueValuesEnum( 235 maintenance_policy) 236 else: 237 on_host_maintenance = None 238 return on_host_maintenance 239 240 241def CreateSchedulingMessage(messages, 242 maintenance_policy, 243 preemptible, 244 restart_on_failure, 245 node_affinities=None, 246 min_node_cpu=None, 247 location_hint=None, 248 maintenance_freeze_duration=None, 249 maintenance_interval=None): 250 """Create scheduling message for VM.""" 251 # Note: We always specify automaticRestart=False for preemptible VMs. This 252 # makes sense, since no-restart-on-failure is defined as "store-true", and 253 # thus can't be given an explicit value. Hence it either has its default 254 # value (in which case we override it for convenience's sake to the only 255 # setting that makes sense for preemptible VMs), or the user actually 256 # specified no-restart-on-failure, the only usable setting. 257 on_host_maintenance = CreateOnHostMaintenanceMessage(messages, 258 maintenance_policy) 259 if preemptible: 260 scheduling = messages.Scheduling( 261 automaticRestart=False, 262 onHostMaintenance=on_host_maintenance, 263 preemptible=True) 264 else: 265 scheduling = messages.Scheduling( 266 automaticRestart=restart_on_failure, 267 onHostMaintenance=on_host_maintenance) 268 if node_affinities: 269 scheduling.nodeAffinities = node_affinities 270 271 if min_node_cpu is not None: 272 scheduling.minNodeCpus = int(min_node_cpu) 273 274 if location_hint: 275 scheduling.locationHint = location_hint 276 277 if maintenance_freeze_duration: 278 scheduling.maintenanceFreezeDurationHours = \ 279 maintenance_freeze_duration // 3600 # sec to hour 280 281 if maintenance_interval: 282 scheduling.maintenanceInterval = messages.\ 283 Scheduling.MaintenanceIntervalValueValuesEnum(maintenance_interval) 284 return scheduling 285 286 287def CreateShieldedInstanceConfigMessage(messages, enable_secure_boot, 288 enable_vtpm, 289 enable_integrity_monitoring): 290 """Create shieldedInstanceConfig message for VM.""" 291 292 shielded_instance_config = messages.ShieldedInstanceConfig( 293 enableSecureBoot=enable_secure_boot, 294 enableVtpm=enable_vtpm, 295 enableIntegrityMonitoring=enable_integrity_monitoring) 296 297 return shielded_instance_config 298 299 300def CreateShieldedInstanceIntegrityPolicyMessage(messages, 301 update_auto_learn_policy=True): 302 """Creates shieldedInstanceIntegrityPolicy message for VM.""" 303 304 shielded_instance_integrity_policy = messages.ShieldedInstanceIntegrityPolicy( 305 updateAutoLearnPolicy=update_auto_learn_policy) 306 307 return shielded_instance_integrity_policy 308 309 310def CreateConfidentialInstanceMessage(messages, enable_confidential_compute): 311 """Create confidentialInstanceConfig message for VM.""" 312 confidential_instance_config = messages.ConfidentialInstanceConfig( 313 enableConfidentialCompute=enable_confidential_compute) 314 315 return confidential_instance_config 316 317 318def CreateAdvancedMachineFeaturesMessage(messages, 319 enable_nested_virtualization=None, 320 threads_per_core=None): 321 """Create AdvancedMachineFeatures message for an Instance.""" 322 # Start with an empty AdvancedMachineFeatures and optionally add on 323 # the features we have like CreateSchedulingMessage does. This lets us 324 # treat None as also "not supported in this version of the API (yet)". 325 features = messages.AdvancedMachineFeatures() 326 327 if enable_nested_virtualization is not None: 328 features.enableNestedVirtualization = enable_nested_virtualization 329 330 if threads_per_core is not None: 331 features.threadsPerCore = threads_per_core 332 333 return features 334 335 336def ParseDiskResource(resources, name, project, zone, type_): 337 """Parses disk resources. 338 339 Project and zone are ignored if a fully-qualified resource name is given, i.e. 340 - https://compute.googleapis.com/compute/v1/projects/my-project 341 /zones/us-central1-a/disks/disk-1 342 - projects/my-project/zones/us-central1-a/disks/disk-1 343 344 If project and zone cannot be parsed, we will use the given project and zone 345 as fallbacks. 346 347 Args: 348 resources: resources.Registry, The resource registry 349 name: str, name of the disk. 350 project: str, project of the disk. 351 zone: str, zone of the disk. 352 type_: ScopeEnum, type of the disk. 353 354 Returns: 355 A disk resource. 356 """ 357 if type_ == compute_scopes.ScopeEnum.REGION: 358 return resources.Parse( 359 name, 360 collection='compute.regionDisks', 361 params={ 362 'project': project, 363 'region': utils.ZoneNameToRegionName(zone) 364 }) 365 else: 366 return resources.Parse( 367 name, 368 collection='compute.disks', 369 params={ 370 'project': project, 371 'zone': zone 372 }) 373 374 375def ParseDiskResourceFromAttachedDisk(resources, attached_disk): 376 """Parses the source disk resource of an AttachedDisk. 377 378 The source of an AttachedDisk is either a partial or fully specified URL 379 referencing either a regional or zonal disk. 380 381 Args: 382 resources: resources.Registry, The resource registry 383 attached_disk: AttachedDisk 384 385 Returns: 386 A disk resource. 387 388 Raises: 389 InvalidResourceException: If the attached disk source cannot be parsed as a 390 regional or zonal disk. 391 """ 392 try: 393 disk = resources.Parse(attached_disk.source, 394 collection='compute.regionDisks') 395 if disk: 396 return disk 397 except (cloud_resources.WrongResourceCollectionException, 398 cloud_resources.RequiredFieldOmittedException): 399 pass 400 401 try: 402 disk = resources.Parse(attached_disk.source, 403 collection='compute.disks') 404 if disk: 405 return disk 406 except (cloud_resources.WrongResourceCollectionException, 407 cloud_resources.RequiredFieldOmittedException): 408 pass 409 410 raise cloud_resources.InvalidResourceException('Unable to parse [{}]'.format( 411 attached_disk.source)) 412 413 414def GetDiskDeviceName(disk, name, container_mount_disk): 415 """Helper method to get device-name for a disk message.""" 416 if (container_mount_disk and filter( 417 bool, [d.get('name', name) == name for d in container_mount_disk])): 418 # device-name must be the same as name if it is being mounted to a 419 # container. 420 if not disk.get('device-name'): 421 log.warning(_DEFAULT_DEVICE_NAME_CONTAINER_WARNING.format(name)) 422 return name 423 # This is defensive only; should be validated before this method is called. 424 elif disk.get('device-name') != name: 425 raise calliope_exceptions.InvalidArgumentException( 426 '--container-mount-disk', 427 'Attempting to mount disk named [{}] with device-name [{}]. If ' 428 'being mounted to container, disk name must match device-name.' 429 .format(name, disk.get('device-name'))) 430 return disk.get('device-name') 431 432 433def ParseDiskType(resources, disk_type, project, location, scope): 434 """Parses disk type reference based on location scope.""" 435 if scope == compute_scopes.ScopeEnum.ZONE: 436 collection = 'compute.diskTypes' 437 params = {'project': project, 'zone': location} 438 elif scope == compute_scopes.ScopeEnum.REGION: 439 collection = 'compute.regionDiskTypes' 440 params = {'project': project, 'region': location} 441 disk_type_ref = resources.Parse( 442 disk_type, 443 collection=collection, 444 params=params) 445 return disk_type_ref 446 447 448def UseExistingBootDisk(disks): 449 """Returns True if the user has specified an existing boot disk.""" 450 return any(disk.get('boot') == 'yes' for disk in disks) 451 452 453def IsAnySpecified(args, *dests): 454 return any([args.IsSpecified(dest) for dest in dests]) 455 456 457def GetSourceInstanceTemplate(args, resources, source_instance_template_arg): 458 if not args.IsSpecified('source_instance_template'): 459 return None 460 ref = source_instance_template_arg.ResolveAsResource(args, resources) 461 return ref.SelfLink() 462 463 464def GetSkipDefaults(source_instance_template): 465 # gcloud creates default values for some fields in Instance resource 466 # when no value was specified on command line. 467 # When --source-instance-template was specified, defaults are taken from 468 # Instance Template and gcloud flags are used to override them - by default 469 # fields should not be initialized. 470 return source_instance_template is not None 471 472 473def GetScheduling(args, 474 client, 475 skip_defaults, 476 support_node_affinity=False, 477 support_min_node_cpu=True, 478 support_location_hint=False): 479 """Generate a Scheduling Message or None based on specified args.""" 480 node_affinities = None 481 if support_node_affinity: 482 node_affinities = sole_tenancy_util.GetSchedulingNodeAffinityListFromArgs( 483 args, client.messages) 484 min_node_cpu = None 485 if support_min_node_cpu: 486 min_node_cpu = args.min_node_cpu 487 location_hint = None 488 if support_location_hint: 489 location_hint = args.location_hint 490 if (skip_defaults and not IsAnySpecified( 491 args, 'maintenance_policy', 'preemptible', 'restart_on_failure') and 492 not node_affinities): 493 return None 494 freeze_duration = None 495 if hasattr(args, 'maintenance_freeze_duration') and args.IsSpecified( 496 'maintenance_freeze_duration'): 497 freeze_duration = args.maintenance_freeze_duration 498 maintenance_interval = None 499 if hasattr(args, 'maintenance_interval') and args.IsSpecified( 500 'maintenance_interval'): 501 maintenance_interval = args.maintenance_interval 502 return CreateSchedulingMessage( 503 messages=client.messages, 504 maintenance_policy=args.maintenance_policy, 505 preemptible=args.preemptible, 506 restart_on_failure=args.restart_on_failure, 507 node_affinities=node_affinities, 508 min_node_cpu=min_node_cpu, 509 location_hint=location_hint, 510 maintenance_freeze_duration=freeze_duration, 511 maintenance_interval=maintenance_interval) 512 513 514def GetServiceAccounts(args, client, skip_defaults): 515 if args.no_service_account: 516 service_account = None 517 else: 518 service_account = args.service_account 519 if (skip_defaults and not IsAnySpecified( 520 args, 'scopes', 'no_scopes', 'service_account', 'no_service_account')): 521 return [] 522 return CreateServiceAccountMessages( 523 messages=client.messages, 524 scopes=[] if args.no_scopes else args.scopes, 525 service_account=service_account) 526 527 528def GetValidatedMetadata(args, client): 529 user_metadata = metadata_utils.ConstructMetadataMessage( 530 client.messages, 531 metadata=args.metadata, 532 metadata_from_file=args.metadata_from_file) 533 containers_utils.ValidateUserMetadata(user_metadata) 534 return user_metadata 535 536 537def GetMetadata(args, client, skip_defaults): 538 if (skip_defaults and 539 not IsAnySpecified(args, 'metadata', 'metadata_from_file')): 540 return None 541 else: 542 return metadata_utils.ConstructMetadataMessage( 543 client.messages, 544 metadata=args.metadata, 545 metadata_from_file=args.metadata_from_file) 546 547 548def GetBootDiskSizeGb(args): 549 boot_disk_size_gb = utils.BytesToGb(args.boot_disk_size) 550 utils.WarnIfDiskSizeIsTooSmall(boot_disk_size_gb, args.boot_disk_type) 551 return boot_disk_size_gb 552 553 554def GetInstanceRefs(args, client, holder): 555 instance_refs = flags.INSTANCES_ARG.ResolveAsResource( 556 args, 557 holder.resources, 558 scope_lister=compute_flags.GetDefaultScopeLister(client)) 559 # Check if the zone is deprecated or has maintenance coming. 560 zone_resource_fetcher = zone_utils.ZoneResourceFetcher(client) 561 zone_resource_fetcher.WarnForZonalCreation(instance_refs) 562 return instance_refs 563 564 565def GetSourceMachineImageKey(args, source_image, compute_client, holder): 566 machine_image_ref = source_image.ResolveAsResource(args, holder.resources) 567 csek_keys = csek_utils.CsekKeyStore.FromFile( 568 args.source_machine_image_csek_key_file, allow_rsa_encrypted=False) 569 disk_key_or_none = csek_utils.MaybeLookupKeyMessage( 570 csek_keys, machine_image_ref, compute_client.apitools_client) 571 return disk_key_or_none 572 573 574def CheckSpecifiedMachineTypeArgs(args, skip_defaults): 575 return (not skip_defaults or 576 IsAnySpecified(args, 'machine_type', 'custom_cpu', 'custom_memory')) 577 578 579def CreateMachineTypeUri(args, compute_client, resource_parser, project, 580 location, scope, confidential_vm=False): 581 """Create a machine type URI for given args and instance reference.""" 582 583 machine_type = args.machine_type 584 custom_cpu = args.custom_cpu 585 custom_memory = args.custom_memory 586 vm_type = getattr(args, 'custom_vm_type', None) 587 ext = getattr(args, 'custom_extensions', None) 588 589 # Setting the machine type 590 machine_type_name = InterpretMachineType( 591 machine_type=machine_type, 592 custom_cpu=custom_cpu, 593 custom_memory=custom_memory, 594 ext=ext, 595 vm_type=vm_type, 596 confidential_vm=confidential_vm) 597 598 # Check to see if the custom machine type ratio is supported 599 CheckCustomCpuRamRatio(compute_client, project, location, machine_type_name) 600 601 machine_type_uri = ParseMachineType(resource_parser, machine_type_name, 602 project, location, scope) 603 return machine_type_uri 604 605 606def ParseMachineType(resource_parser, machine_type_name, project, location, 607 scope): 608 """Returns the location-specific machine type uri.""" 609 if scope == compute_scopes.ScopeEnum.ZONE: 610 collection = 'compute.machineTypes' 611 params = {'project': project, 'zone': location} 612 elif scope == compute_scopes.ScopeEnum.REGION: 613 collection = 'compute.regionMachineTypes' 614 params = {'project': project, 'region': location} 615 machine_type_uri = resource_parser.Parse( 616 machine_type_name, collection=collection, params=params).SelfLink() 617 return machine_type_uri 618 619 620def GetCanIpForward(args, skip_defaults): 621 if skip_defaults and not args.IsSpecified('can_ip_forward'): 622 return None 623 return args.can_ip_forward 624 625 626def GetTags(args, client): 627 if args.tags: 628 return client.messages.Tags(items=args.tags) 629 return None 630 631 632def GetLabels(args, client, instance_properties=False): 633 """Gets labels for the instance message.""" 634 labels_value = client.messages.Instance.LabelsValue 635 if instance_properties: 636 labels_value = client.messages.InstanceProperties.LabelsValue 637 if args.labels: 638 return labels_value(additionalProperties=[ 639 labels_value.AdditionalProperty( 640 key=key, value=value) 641 for key, value in sorted(six.iteritems(args.labels)) 642 ]) 643 return None 644 645 646def ParseAcceleratorType(accelerator_type_name, resource_parser, project, 647 location, scope): 648 """Returns accelerator type ref based on location scope.""" 649 if scope == compute_scopes.ScopeEnum.ZONE: 650 collection = 'compute.acceleratorTypes' 651 params = {'project': project, 'zone': location} 652 elif scope == compute_scopes.ScopeEnum.REGION: 653 collection = 'compute.regionAcceleratorTypes' 654 params = {'project': project, 'region': location} 655 accelerator_type = resource_parser.Parse( 656 accelerator_type_name, collection=collection, params=params).SelfLink() 657 return accelerator_type 658 659 660def ResolveSnapshotURI(user_project, snapshot, resource_parser): 661 if user_project and snapshot and resource_parser: 662 snapshot_ref = resource_parser.Parse( 663 snapshot, 664 collection='compute.snapshots', 665 params={'project': user_project}) 666 return snapshot_ref.SelfLink() 667 return None 668 669 670def GetReservationAffinity(args, client): 671 """Returns the message of reservation affinity for the instance.""" 672 if args.IsSpecified('reservation_affinity'): 673 type_msgs = ( 674 client.messages.ReservationAffinity 675 .ConsumeReservationTypeValueValuesEnum) 676 677 reservation_key = None 678 reservation_values = [] 679 680 if args.reservation_affinity == 'none': 681 reservation_type = type_msgs.NO_RESERVATION 682 elif args.reservation_affinity == 'specific': 683 reservation_type = type_msgs.SPECIFIC_RESERVATION 684 # Currently, the key is fixed and the value is the name of the 685 # reservation. 686 # The value being a repeated field is reserved for future use when user 687 # can specify more than one reservation names from which the VM can take 688 # capacity from. 689 reservation_key = _RESERVATION_AFFINITY_KEY 690 reservation_values = [args.reservation] 691 else: 692 reservation_type = type_msgs.ANY_RESERVATION 693 694 return client.messages.ReservationAffinity( 695 consumeReservationType=reservation_type, 696 key=reservation_key or None, 697 values=reservation_values) 698 699 return None 700 701 702def GetNetworkPerformanceConfig(args, client): 703 """Get NetworkPerformanceConfig message for the instance.""" 704 705 network_perf_args = getattr(args, 'network_performance_configs', []) 706 network_perf_configs = client.messages.NetworkPerformanceConfig() 707 708 for config in network_perf_args: 709 total_tier = config.get('total-egress-bandwidth-tier', '').upper() 710 if total_tier: 711 network_perf_configs.totalEgressBandwidthTier = \ 712 client.messages.NetworkPerformanceConfig.\ 713 TotalEgressBandwidthTierValueValuesEnum(total_tier) 714 715 return network_perf_configs 716 717 718_RESERVATION_AFFINITY_KEY = 'compute.googleapis.com/reservation-name' 719