How to use instance_context method in Testify

Best Python code snippet using Testify_python

instance_cmds.py

Source:instance_cmds.py Github

copy

Full Screen

1# Copyright 2012 Google Inc. All Rights Reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14"""Commands for interacting with Google Compute Engine VM instances."""15import logging16import subprocess17import sys18import time19from apiclient import errors20from google.apputils import app21from google.apputils import appcommands22import gflags as flags23from gcutil_lib import command_base24from gcutil_lib import gcutil_errors25from gcutil_lib import gcutil_logging26from gcutil_lib import image_cmds27from gcutil_lib import metadata28from gcutil_lib import scopes29from gcutil_lib import ssh_keys30from gcutil_lib import windows_password31from gcutil_lib import windows_user_name32FLAGS = flags.FLAGS33LOGGER = gcutil_logging.LOGGER34EPHEMERAL_ROOT_DISK_WARNING_MESSAGE = (35 'You appear to be running on a SCRATCH root disk '36 'so your data will not persist beyond the life '37 'of the instance. Consider using a persistent disk '38 'instead.\n'39 'For more information, see:\n'40 'https://developers.google.com/compute/docs/disks#persistentdisks'41)42def _DefineSchedulingFlags(flag_values):43 flags.DEFINE_enum('on_host_maintenance',44 None,45 ['terminate', 'migrate'],46 'How the instance should behave when the host machine '47 'undergoes maintenance that may temporarily impact '48 'instance performance. Must be unspecified or '49 '\'terminate\' for instances with scratch disk. If '50 'unspecified, a default of \'terminate\' will be used for '51 'instances with scratch disk, or \'migrate\' for instances '52 'without scratch disk.',53 flag_values=flag_values)54 flags.DEFINE_boolean('automatic_restart',55 None,56 'Whether the instance should be automatically '57 'restarted whenever it is terminated by Compute '58 'Engine (not terminated by user).',59 flag_values=flag_values)60def _GetSchedulingFromFlags(flag_values):61 scheduling = {}62 if flag_values['on_host_maintenance'].present:63 scheduling['onHostMaintenance'] = flag_values.on_host_maintenance64 if flag_values['automatic_restart'].present:65 scheduling['automaticRestart'] = flag_values.automatic_restart66 return scheduling67class InstanceCommand(command_base.GoogleComputeCommand):68 """Base command for working with the instances collection."""69 print_spec = command_base.ResourcePrintSpec(70 summary=['name', 'zone', 'status', 'network-ip', 'external-ip'],71 field_mappings=(72 ('name', 'name'),73 ('machine-type', 'machineType'),74 ('image', 'image'),75 ('network', 'networkInterfaces.network'),76 ('network-ip', 'networkInterfaces.networkIP'),77 ('external-ip', 'networkInterfaces.accessConfigs.natIP'),78 ('disks', 'disks.source'),79 ('zone', 'zone'),80 ('status', 'status'),81 ('status-message', 'statusMessage')),82 detail=(83 ('name', 'name'),84 ('description', 'description'),85 ('creation-time', 'creationTimestamp'),86 ('machine', 'machineType'),87 ('image', 'image'),88 ('zone', 'zone'),89 ('tags-fingerprint', 'tags.fingerprint'),90 ('on-host-maintenance', 'scheduling.onHostMaintenance'),91 ('automatic-restart', 'scheduling.automaticRestart'),92 ('metadata-fingerprint', 'metadata.fingerprint'),93 ('status', 'status'),94 ('status-message', 'statusMessage')),95 sort_by='name')96 # A map from legal values for the disk "mode" option to the97 # corresponding API value. Keys in this map should be lowercase, as98 # we convert user provided values to lowercase prior to performing a99 # look-up.100 disk_modes = {101 'read_only': 'READ_ONLY',102 'ro': 'READ_ONLY',103 'read_write': 'READ_WRITE',104 'rw': 'READ_WRITE'}105 resource_collection_name = 'instances'106 # The default network interface name assigned by the service.107 DEFAULT_NETWORK_INTERFACE_NAME = 'nic0'108 # The default access config name109 DEFAULT_ACCESS_CONFIG_NAME = 'External NAT'110 # Currently, only access config type 'ONE_TO_ONE_NAT' is supported.111 ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE = 'ONE_TO_ONE_NAT'112 # Let the server select an ephemeral IP address.113 EPHEMERAL_ACCESS_CONFIG_NAT_IP = 'ephemeral'114 def __init__(self, name, flag_values):115 super(InstanceCommand, self).__init__(name, flag_values)116 flags.DEFINE_string('zone',117 None,118 '[Required] The zone for this request.',119 flag_values=flag_values)120 def GetDetailRow(self, result):121 """Returns an associative list of items for display in a detail table.122 Args:123 result: A dict returned by the server.124 Returns:125 A list.126 """127 data = []128 # Add the disks129 for disk in result.get('disks', []):130 disk_info = [('type', disk['type'])]131 if 'mode' in disk:132 disk_info.append(('mode', disk['mode']))133 if 'deviceName' in disk:134 disk_info.append(('device-name', disk['deviceName']))135 if 'source' in disk:136 disk_info.append(('source', disk['source']))137 if 'boot' in disk:138 disk_info.append(('boot', disk['boot']))139 if 'autoDelete' in disk:140 disk_info.append(('autoDelete', disk['autoDelete']))141 if 'deleteOnTerminate' in disk:142 disk_info.append(('delete-on-terminate', disk['deleteOnTerminate']))143 data.append(('disk', disk_info))144 # Add the networks145 for network in result.get('networkInterfaces', []):146 network_info = [('network', network.get('network')),147 ('ip', network.get('networkIP'))]148 for config in network.get('accessConfigs', []):149 network_info.append(('access-configuration', config.get('name')))150 network_info.append(('type', config.get('type')))151 network_info.append(('external-ip', config.get('natIP')))152 data.append(('network-interface', network_info))153 # Add the service accounts154 for service_account in result.get('serviceAccounts', []):155 account_info = [('service-account', service_account.get('email')),156 ('scopes', service_account.get('scopes'))]157 data.append(('service-account', account_info))158 # Add metadata159 if result.get('metadata', []):160 metadata_container = result.get('metadata', {}).get('items', [])161 metadata_info = []162 for i in metadata_container:163 metadata_info.append((i.get('key'), i.get('value')))164 if metadata_info:165 data.append(('metadata', metadata_info))166 data.append(('metadata-fingerprint',167 result.get('metadata', {}).get('fingerprint')))168 # Add tags169 if result.get('tags', []):170 tags_container = result.get('tags', {}).get('items', [])171 tags_info = []172 for tag in tags_container:173 tags_info.append(tag)174 if tags_info:175 data.append(('tags', tags_info))176 data.append(('tags-fingerprint',177 result.get('tags', {}).get('fingerprint')))178 return data179 def _ExtractExternalIpFromInstanceRecord(self, instance_record):180 """Extract the external IP(s) from an instance record.181 Args:182 instance_record: An instance as returned by the Google Compute Engine API.183 Returns:184 A list of internet IP addresses associated with this VM.185 """186 external_ips = set()187 for network_interface in instance_record.get('networkInterfaces', []):188 for access_config in network_interface.get('accessConfigs', []):189 # At the moment, we only know how to translate 1-to-1 NAT190 if (access_config.get('type') == self.ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE191 and 'natIP' in access_config):192 external_ips.add(access_config['natIP'])193 return list(external_ips)194 def _AddAuthorizedUserKeyToProject(self, authorized_user_key):195 """Update the project to include the specified user/key pair.196 Args:197 authorized_user_key: A dictionary of a user/key pair for the user.198 Returns:199 True iff the ssh key was added to the project.200 Raises:201 gcutil_errors.CommandError: If the metadata update fails.202 """203 project = self.api.projects.get(project=self._project).execute()204 common_instance_metadata = project.get('commonInstanceMetadata', {})205 project_metadata = common_instance_metadata.get(206 'items', [])207 project_ssh_keys = ssh_keys.SshKeys.GetAuthorizedUserKeysFromMetadata(208 project_metadata)209 if authorized_user_key in project_ssh_keys:210 return False211 else:212 project_ssh_keys.append(authorized_user_key)213 ssh_keys.SshKeys.SetAuthorizedUserKeysInMetadata(214 project_metadata, project_ssh_keys)215 try:216 request = self.api.projects.setCommonInstanceMetadata(217 project=self._project,218 body={'kind': self._GetResourceApiKind('metadata'),219 'items': project_metadata})220 request.execute()221 except errors.HttpError:222 # A failure to add the ssh key probably means that the project metadata223 # has exceeded the max size. The user needs to either manually224 # clean up their project metadata, or set the ssh keys manually for this225 # instance. Either way, trigger a usage error to let them know.226 raise gcutil_errors.CommandError(227 'Unable to add the local ssh key to the project. Either manually '228 'remove some entries from the commonInstanceMetadata field of the '229 'project, or explicitly set the authorized keys for this instance.')230 return True231 def _PrepareRequestArgs(self, instance_context,232 **other_args):233 """Gets the dictionary of API method keyword arguments.234 Args:235 instance_context: A context dict for this instance.236 **other_args: Keyword arguments that should be included in the request.237 Returns:238 Dictionary of keyword arguments that should be passed in the API call,239 includes all keyword arguments passed in 'other_args' plus240 common keys such as the name of the resource and the project.241 """242 kwargs = {243 'project': instance_context['project'],244 'instance': instance_context['instance'],245 'zone': instance_context['zone']246 }247 for key, value in other_args.items():248 kwargs[key] = value249 return kwargs250 def _AddComputeKeyToProject(self):251 """Update the current project to include the user's public ssh key.252 Returns:253 True iff the ssh key was added to the project.254 """255 compute_key = ssh_keys.SshKeys.GetPublicKey()256 return self._AddAuthorizedUserKeyToProject(compute_key)257 def _AutoDetectZone(self):258 """Instruct this command to auto detect zone instead of prompting."""259 def _GetZoneContext(object_type, context):260 if self._flags.zone:261 return self.DenormalizeResourceName(self._flags.zone)262 if object_type == 'instances':263 return self.GetZoneForResource(self.api.instances,264 context['instance'],265 project=context['project'])266 elif object_type == 'disks':267 return self.GetZoneForResource(self.api.disks,268 context['disk'],269 project=context['project'])270 self._context_parser.context_prompt_fxns['zone'] = _GetZoneContext271 def _AutoDetectZoneForDisksOnly(self):272 """Instruct this command to auto detect zone for disks only."""273 def _GetZoneContext(object_type, context):274 if self._flags.zone:275 return self.DenormalizeResourceName(self._flags.zone)276 elif object_type == 'disks':277 return self.GetZoneForResource(self.api.disks,278 context['disk'],279 project=context['project'])280 elif object_type == 'instances':281 self._flags.zone = self._GetZone(None)282 return self._flags.zone283 self._context_parser.context_prompt_fxns['zone'] = _GetZoneContext284 def _BuildAttachedBootDisk(self, disk_name, source_image, instance_name,285 disk_size_gb=None, auto_delete=False):286 disk = {287 'type': 'PERSISTENT',288 'mode': 'READ_WRITE',289 'boot': True,290 'deviceName': instance_name,291 'initializeParams': {292 'diskName': disk_name,293 'sourceImage': source_image,294 },295 'autoDelete': auto_delete,296 }297 if disk_size_gb is not None:298 disk['initializeParams']['diskSizeGb'] = disk_size_gb299 return disk300 def _BuildAttachedDisk(self, disk_arg):301 """Converts a disk argument into an AttachedDisk object."""302 # Start with the assumption that the argument only specifies the303 # name of the disk resource.304 disk_name = disk_arg305 device_name = disk_arg306 mode = 'READ_WRITE'307 boot = False308 disk_parts = disk_arg.split(',')309 if len(disk_parts) > 1:310 # The argument includes new-style decorators. The first part is311 # the disk resource name. The other parts are optional key/value312 # pairs.313 disk_name = disk_parts[0]314 device_name = disk_parts[0]315 for option in disk_parts[1:]:316 if option == 'boot':317 boot = True318 continue319 if '=' not in option:320 raise ValueError('Invalid disk option: %s' % option)321 key, value = option.split('=', 2)322 if key == 'deviceName':323 device_name = value324 elif key == 'mode':325 mode = self.disk_modes.get(value.lower())326 if not mode:327 raise ValueError('Invalid disk mode: %s' % value)328 else:329 raise ValueError('Invalid disk option: %s' % key)330 else:331 # The user didn't provide any options using the newer key/value332 # syntax, so check to see if they have used the old syntax where333 # the device name is delimited by a colon.334 disk_parts = disk_arg.split(':')335 if len(disk_parts) > 1:336 disk_name = disk_parts[0]337 device_name = disk_parts[1]338 LOGGER.info(339 'Please use new disk device naming syntax: --disk=%s,deviceName=%s',340 disk_name,341 device_name)342 disk_url = self._context_parser.NormalizeOrPrompt('disks', disk_name)343 disk = {344 'type': 'PERSISTENT',345 'source': disk_url,346 'mode': mode,347 'deviceName': device_name,348 'boot': boot,349 }350 if boot and self._flags.auto_delete_boot_disk:351 disk['autoDelete'] = True352 return disk353class AddInstance(InstanceCommand):354 """Create new virtual machine instances.355 Specify multiple instances as multiple arguments. Each instance will be356 created in parallel.357 """358 positional_args = '<instance-name-1> ... <instance-name-n>'359 status_field = 'status'360 _TERMINAL_STATUS = ['RUNNING', 'TERMINATED']361 _MAX_BOOT_DISK_NAME_LENGTH = 64362 def __init__(self, name, flag_values):363 super(AddInstance, self).__init__(name, flag_values)364 image_cmds.RegisterCommonImageFlags(flag_values)365 flags.DEFINE_string('description',366 '',367 'An optional description for this instance.',368 flag_values=flag_values)369 flags.DEFINE_string('image',370 None,371 'Specifies the image to use for this '372 'instance. For example, \'--image=debian-7\' or '373 '\'--image=debian-7-wheezy-v20130723\'. '374 'To get a list of images built by Google, run '375 '\'gcutil listimages --project=projects/google\'. '376 'To get a list of images you have built, run '377 '\'gcutil --project=<project-id> listimages\'.',378 flag_values=flag_values)379 flags.DEFINE_boolean('persistent_boot_disk',380 None,381 '[Deprecated] Creates a persistent boot disk for this '382 'instance. If this is set, gcutil creates a new '383 'disk named \'<instance-name>\' and copies the '384 'contents of the image onto the new disk and uses '385 'it for booting.',386 flag_values=flag_values)387 flags.DEFINE_string('boot_disk_type',388 'pd-standard',389 'Specifies the disk type used to create the boot disk. '390 'For example, \'--boot_disk_type=pd-standard\'. '391 'To get a list of avaiable disk types, run '392 '\'gcutil listdisktypes\'.',393 flag_values=flag_values)394 flags.DEFINE_integer('boot_disk_size_gb',395 None,396 'Size of the persistent boot disk in GiB.',397 flag_values=flag_values)398 flags.DEFINE_boolean('auto_delete_boot_disk',399 False,400 'Whether to auto-delete the boot disk when the '401 'instance is deleted.',402 flag_values=flag_values)403 flags.DEFINE_string('machine_type',404 None,405 '[Required] Specifies the machine type used for the '406 'instance. For example, '407 '\'--machine_type=machinetype-name\'. '408 'To get a list of available machine types, run '409 '\'gcutil listmachinetypes\'.',410 flag_values=flag_values)411 flags.DEFINE_string('network',412 'default',413 'Specifies the network to use for this instance. If '414 'not specified, the \'default\' network is used.',415 flag_values=flag_values)416 flags.DEFINE_string('external_ip_address',417 self.EPHEMERAL_ACCESS_CONFIG_NAT_IP,418 'Specifies the external NAT IP of this new instance. '419 'The default value is \'ephemeral\' and indicates '420 'that the service should use any available ephemeral '421 'IP. You can also specify "none" '422 '(or an empty string) to indicate that no external '423 'IP should be assigned to the instance. If you want '424 'to explicitly use a certain IP, the IP must be '425 'reserved by the project and not in use by '426 'another resource.',427 flag_values=flag_values)428 flags.DEFINE_multistring('disk',429 [],430 'Attaches a persistent disk to this instance. '431 'You can also provide a list of comma-separated '432 'name=value pairs options. Legal option names '433 'are \'deviceName\', to specify the disk\'s '434 'device name, and \'mode\', to indicate whether '435 'the disk should be attached READ_WRITE '436 '(the default) or READ_ONLY. You may also use '437 'the \'boot\' flag to designate the disk as a '438 'boot device. For example: '439 '\'--disk=mydisk,deviceName=primarydisk,'440 'mode=rw,boot\'',441 flag_values=flag_values)442 flags.DEFINE_boolean('use_compute_key',443 False,444 'Specifies the Google Compute Engine ssh key as one '445 'of the authorized ssh keys for the created '446 'instance. This has the side effect of disabling '447 'project-wide ssh key management for the instance.',448 flag_values=flag_values)449 flags.DEFINE_boolean('add_compute_key_to_project',450 None,451 'Adds the default Google Compute Engine ssh key as '452 'one of the authorized ssh keys for the project.'453 'If the default key has already been added to the '454 'project, then this will have no effect. '455 'The default behavior is to add the key to the '456 'project if no instance-specific keys are defined.',457 flag_values=flag_values)458 flags.DEFINE_list('authorized_ssh_keys',459 [],460 'Updates the list of user/key-file pairs to the '461 'specified entries, disabling project-wide key '462 'management for this instance. These are specified as '463 'a comma separated list of colon separated entries: '464 'user1:keyfile1,user2:keyfile2,...',465 flag_values=flag_values)466 flags.DEFINE_string('service_account',467 'default',468 'Specifies the service accounts for this instance. '469 'Once set, the service account\'s credentials can '470 'be used by the instance to make requests to other '471 'services. The default account is \'default\'. You '472 'can also set specific service account names. For '473 'example, '474 '\'123845678986@project.gserviceaccount.com\'',475 flag_values=flag_values)476 flags.DEFINE_list('service_account_scopes',477 [],478 'Specifies the scope of credentials for the service '479 'account(s) available to this instance. Specify '480 'multiple scopes as comma-separated entries. You can '481 'also use a set of supported scope aliases: %s'482 % ', '.join(sorted(scopes.SCOPE_ALIASES.keys())),483 flag_values=flag_values)484 flags.DEFINE_boolean('wait_until_running',485 False,486 'Specifies that gcutil should wait until the new '487 'instance is running before returning.',488 flag_values=flag_values)489 flags.DEFINE_list('tags',490 [],491 'Applies a set of tags to this instance. Tags can be '492 'used for filtering and configuring network firewall '493 'rules.',494 flag_values=flag_values)495 flags.DEFINE_boolean('can_ip_forward',496 False,497 'Specifies that the newly-created instance is '498 'allowed to send packets with a source IP address '499 'that does not match its own and receive packets '500 'whose destination IP address does not match its '501 'own.',502 flag_values=flag_values)503 _DefineSchedulingFlags(flag_values)504 self._metadata_flags_processor = metadata.MetadataFlagsProcessor(505 flag_values)506 def _GetErrorOperations(self, result_list):507 """Returns the list of operations that experienced a problem."""508 error_operations = []509 for result in result_list:510 error_list = result.get('error', {}).get('errors', [])511 if error_list:512 error_operations.append(result)513 return error_operations514 def _GetDefaultBootDiskName(self, instance_name):515 # The default boot disk name is the same as the instance name.516 return instance_name517 def _IsCreatingWindowsInstance(self, image_url, image_resource, disk_used):518 # If the boot disk is given, we get the disk and check its519 # license.520 if disk_used:521 disk_context = self._context_parser.ParseContextOrPrompt(522 'disks', disk_used['source'])523 disk_resource = self.api.disks.get(524 project=disk_context['project'],525 zone=disk_context['zone'],526 disk=disk_context['disk']).execute()527 return self._HasWindowsLicense(disk_resource.get('licenses', []))528 # If there is no boot disk, check the licenses in the image.529 if not image_resource:530 # If the image_resource is not given, we get it from server.531 image_context = self._context_parser.ParseContextOrPrompt(532 'images', image_url)533 image_resource = self.api.images.get(534 project=image_context['project'],535 image=image_context['image']).execute()536 return self._HasWindowsLicense(image_resource.get('licenses', []))537 def _HasWindowsLicense(self, license_urls):538 """Checks if a list of license urls contains a license for Windows.539 Currently, we consider a license is for Windows, if it540 comes from any of the Windows image provider projects.541 Args:542 license_urls: List of URLs for license resource.543 Returns:544 True if at least one license in the list is considered Windows license.545 """546 if not license_urls:547 return False548 for license_url in license_urls:549 license_context = self._context_parser.ParseContextOrPrompt(550 'licenses', license_url)551 if license_context['project'] in command_base.WINDOWS_IMAGE_PROJECTS:552 return True553 return False554 def Handle(self, *instance_names):555 """Add the specified instance.556 Args:557 *instance_names: A list of instance names to add.558 Returns:559 A tuple of (result, exceptions)560 Raises:561 UsageError: Improper arguments were passed to this command.562 UnsupportedCommand: Unsupported operation.563 """564 if not instance_names:565 raise app.UsageError('You must specify at least one instance name')566 if self._flags.persistent_boot_disk is False:567 raise gcutil_errors.UnsupportedCommand(568 'Persistent boot disk is required.')569 else:570 self._flags.persistent_boot_disk = True571 if len(instance_names) > 1 and self._flags.disk:572 raise gcutil_errors.CommandError(573 'Specifying a disk when starting multiple instances is not '574 'currently supported')575 if max([len(i) for i in instance_names]) > 32:576 LOGGER.warn('Hostnames longer than 32 characters trigger known issues '577 'with some Linux distributions. If possible, you should '578 'select a hostname that is shorter than 32 characters.')579 self._AutoDetectZoneForDisksOnly()580 instance_contexts = [self._context_parser.ParseContextOrPrompt(581 'instances', instance_name) for instance_name in instance_names]582 if not self._flags.machine_type:583 self._flags.machine_type = self._presenter.PromptForMachineType(584 self.api.machine_types)['name']585 # Processes the disks, so we can check for the presence of a boot586 # disk before prompting for image.587 disks = [self._BuildAttachedDisk(disk) for disk in self._flags.disk]588 boot_disk_used = self._GetBootDisk(disks)589 image_resource = command_base.ResolveImageTrackOrImageToResource(590 self.api.images, self._flags.project, self._flags.image,591 lambda image: self._presenter.PresentElement(image['selfLink']))592 self._flags.image = self._context_parser.NormalizeOrPrompt(593 'images',594 image_resource['selfLink'] if image_resource else self._flags.image)595 if not self._flags.image and not self._HasBootDisk(disks):596 image_resource = self._presenter.PromptForImage(597 self.api.images)598 self._flags.image = image_resource['selfLink']599 instance_metadata = self._metadata_flags_processor.GatherMetadata()600 is_windows_instance = self._IsCreatingWindowsInstance(601 self._flags.image, image_resource, boot_disk_used)602 if is_windows_instance:603 if (self._flags.authorized_ssh_keys or604 self._flags.add_compute_key_to_project is not None or605 self._flags.use_compute_key):606 LOGGER.warn('Creating Windows instance. '607 'SSH key options such as authorized_ssh_keys, '608 'add_compute_key_to_project and use_compute_key '609 'are ignored.')610 # For Windows image, we need to set the user name and password611 # in metadata so the daemon on the machine can set up the initial612 # user account.613 windows_user = self._EnsureWindowsUserNameInMetadata(instance_metadata)614 self._EnsureWindowsPasswordInMetadata(instance_metadata, windows_user)615 else:616 # Do ssh related processing only for non-Windows image.617 if self._flags.authorized_ssh_keys or self._flags.use_compute_key:618 instance_metadata = self._AddSshKeysToMetadata(instance_metadata)619 # Instance names that we still want to create.620 instances_to_create = instance_contexts621 if not self._HasBootDisk(disks):622 # Determine the name of the boot disk that the create-instance call will623 # make on our behalf.624 for instance_context in instance_contexts:625 boot_disk_name = self._GetDefaultBootDiskName(626 instance_context['instance'])627 instance_context['boot_disk_name'] = boot_disk_name628 if not is_windows_instance and (629 self._flags.add_compute_key_to_project or (630 self._flags.add_compute_key_to_project is None and631 'sshKeys' not in [entry.get('key', '') for entry632 in instance_metadata])):633 try:634 self._AddComputeKeyToProject()635 except ssh_keys.UserSetupError as e:636 LOGGER.warn('Could not generate compute ssh key: %s', e)637 self._ValidateFlags()638 wait_for_operations = (639 self._flags.wait_until_running or self._flags.synchronous_mode)640 requests = []641 for instance_context in instances_to_create:642 instance_disks = disks643 if 'boot_disk_name' in instance_context:644 boot_disk = self._BuildAttachedBootDisk(645 disk_name=instance_context['boot_disk_name'],646 source_image=self._flags.image,647 instance_name=instance_context['instance'],648 disk_size_gb=self._flags.boot_disk_size_gb,649 auto_delete=self._flags.auto_delete_boot_disk)650 boot_disk['initializeParams']['diskType'] = (651 self._context_parser.NormalizeOrPrompt(652 'diskTypes', self._flags.boot_disk_type))653 instance_disks = [boot_disk] + disks654 requests.append(self._BuildRequestWithMetadata(655 instance_context, instance_metadata, instance_disks))656 (results, exceptions) = self.ExecuteRequests(657 requests, wait_for_operations=wait_for_operations)658 if self._flags.wait_until_running:659 instances_to_wait = results660 results = []661 for result in instances_to_wait:662 if self.IsResultAnOperation(result):663 results.append(result)664 else:665 result_context = self._context_parser.ParseContextOrPrompt(666 'instances', result['selfLink'])667 kwargs = self._PrepareRequestArgs(result_context)668 get_request = self.api.instances.get(**kwargs)669 instance_result = get_request.execute()670 instance_result = self._WaitUntilInstanceIsRunning(671 instance_result, kwargs)672 results.append(instance_result)673 return (self.MakeListResult(results, 'instanceList'), exceptions)674 else:675 return (self.MakeListResult(results, 'operationList'), exceptions)676 def _WaitUntilInstanceIsRunning(self, result, kwargs):677 """Waits for the instance to start.678 Periodically polls the server for current instance status. Exits if the679 status of the instance is RUNNING or TERMINATED or the maximum waiting680 timeout has been reached. In both cases returns the last known instance681 details.682 Args:683 result: the current state of the instance.684 kwargs: keyword arguments to _instances_api.get()685 Returns:686 Json containing full instance information.687 """688 current_status = result[self.status_field]689 start_time = self._timer.time()690 instance_name = kwargs['instance']691 LOGGER.info('Ensuring %s is running. Will wait to start for: %d seconds.',692 instance_name, self._flags.max_wait_time)693 while (self._timer.time() - start_time < self._flags.max_wait_time and694 current_status not in self._TERMINAL_STATUS):695 LOGGER.info(696 'Waiting for instance \'%s\' to start. '697 'Current status: %s. Sleeping for %ss.',698 instance_name,699 current_status, self._flags.sleep_between_polls)700 self._timer.sleep(self._flags.sleep_between_polls)701 result = self.api.instances.get(**kwargs).execute()702 current_status = result[self.status_field]703 if current_status not in self._TERMINAL_STATUS:704 LOGGER.warn('Timeout reached. Instance %s has not yet started.',705 instance_name)706 return result707 def _AddSshKeysToMetadata(self, instance_metadata):708 instance_ssh_keys = ssh_keys.SshKeys.GetAuthorizedUserKeys(709 use_compute_key=self._flags.use_compute_key,710 authorized_ssh_keys=self._flags.authorized_ssh_keys)711 if instance_ssh_keys:712 new_value = ['%(user)s:%(key)s' % user_key713 for user_key in instance_ssh_keys]714 # Have the new value extend the old value715 old_values = [entry['value'] for entry in instance_metadata716 if entry['key'] == 'sshKeys']717 all_values = '\n'.join(old_values + new_value)718 instance_metadata = [entry for entry in instance_metadata719 if entry['key'] != 'sshKeys']720 instance_metadata.append({'key': 'sshKeys', 'value': all_values})721 return instance_metadata722 def _HasBootDisk(self, disks):723 """Determines if any of the disks in a list is a boot disk."""724 return self._GetBootDisk(disks) is not None725 def _GetBootDisk(self, disks):726 """Gets the boot disk from a list of disks."""727 for disk in disks:728 if disk.get('boot', False):729 return disk730 return None731 def _ValidateFlags(self):732 """Validate flags coming in before we start building resources.733 Raises:734 app.UsageError: If service account explicitly given without scopes.735 gcutil_errors.CommandError: If scopes contains ' '.736 """737 if self._flags.service_account and self._flags.service_account_scopes:738 # Ensures that the user did not space-delimit his or her scopes739 # list.740 for scope in self._flags.service_account_scopes:741 if ' ' in scope:742 raise gcutil_errors.CommandError(743 'Scopes list must be comma-delimited, not space-delimited.')744 elif self._flags['service_account'].present:745 raise app.UsageError(746 '--service_account given without --service_account_scopes.')747 def _CreateDiskFromImageRequest(self, disk_name, disk_zone, disk_project):748 """Build a request that creates disk from source image.749 Args:750 disk_name: Name of the disk.751 disk_zone: Zone for the disk.752 disk_project: Project for the disk753 Returns:754 The prepared disk insert request.755 """756 disk_resource = {757 'kind': self._GetResourceApiKind('instance'),758 'name': disk_name,759 'description': 'Persistent boot disk created from %s.' % (760 self._flags.image),761 }762 source_image_url = self._context_parser.NormalizeOrPrompt(763 'images', self._flags.image)764 kwargs = {765 'project': disk_project,766 'body': disk_resource,767 'sourceImage': source_image_url,768 'zone': disk_zone,769 }770 return self.api.disks.insert(**kwargs)771 def _BuildRequestWithMetadata(772 self, instance_context, instance_metadata, disks):773 """Build a request to add the specified instance, given the ssh keys for it.774 Args:775 instance_context: Context dict for the instance that we are building the776 request for.777 instance_metadata: The metadata to be passed to the VM. This is in the778 form of [{'key': <key>, 'value': <value>}] form, ready to be779 sent to the server.780 disks: Disks to attach to the instance.781 Returns:782 The prepared instance request.783 Raises:784 UsageError: Flags were not allowed in this service version.785 """786 instance_resource = {787 'kind': self._GetResourceApiKind('instance'),788 'name': instance_context['instance'],789 'description': self._flags.description,790 'networkInterfaces': [],791 'disks': disks,792 'metadata': [],793 }794 if self._flags.machine_type:795 instance_resource['machineType'] = self._context_parser.NormalizeOrPrompt(796 'machineTypes', self._flags.machine_type)797 if self._flags['can_ip_forward'].present:798 instance_resource['canIpForward'] = self._flags.can_ip_forward799 if self._flags.network:800 network_interface = {801 'network': self._context_parser.NormalizeOrPrompt(802 'networks', self._flags.network)803 }804 external_ip_address = self._flags.external_ip_address805 if external_ip_address and external_ip_address.lower() != 'none':806 access_config = {807 'name': self.DEFAULT_ACCESS_CONFIG_NAME,808 'type': self.ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE,809 }810 if external_ip_address.lower() != self.EPHEMERAL_ACCESS_CONFIG_NAT_IP:811 access_config['natIP'] = self._flags.external_ip_address812 network_interface['accessConfigs'] = [access_config]813 instance_resource['networkInterfaces'].append(network_interface)814 metadata_subresource = {815 'kind': self._GetResourceApiKind('metadata'),816 'items': []}817 metadata_subresource['items'].extend(instance_metadata)818 instance_resource['metadata'] = metadata_subresource819 if self._flags.service_account and (820 len(self._flags.service_account_scopes)):821 instance_resource['serviceAccounts'] = []822 expanded_scopes = scopes.ExpandScopeAliases(823 self._flags.service_account_scopes)824 instance_resource['serviceAccounts'].append({825 'email': self._flags.service_account,826 'scopes': expanded_scopes})827 instance_resource['tags'] = {'items': sorted(set(self._flags.tags))}828 scheduling = _GetSchedulingFromFlags(self._flags)829 # Only set the instance's scheduling if it is populated.830 if scheduling:831 instance_resource['scheduling'] = scheduling832 return self.api.instances.insert(833 project=instance_context['project'],834 zone=instance_context['zone'],835 body=instance_resource)836 def _IsImageFromWindowsProject(self, image_path):837 """Determines whether an image resource is from a Windows project.838 Currently, we check if the image comes from the public Google839 windows-cloud project.840 Args:841 image_path: Path to the image resource.842 Returns:843 True if the image is from public Windows project; False otherwise.844 """845 if not image_path:846 return False847 image_context = self._context_parser.ParseContextOrPrompt(848 'images', image_path)849 return image_context['project'] in command_base.WINDOWS_IMAGE_PROJECTS850 def _EnsureWindowsUserNameInMetadata(self, instance_metadata):851 """Ensures that the initial windows user account name is set in metadata.852 Args:853 instance_metadata: The instance metadata.854 Returns:855 The user account name.856 """857 windows_user = metadata.GetMetadataValue(858 instance_metadata,859 metadata.INITIAL_WINDOWS_USER_METADATA_NAME)860 if windows_user is None:861 windows_user = windows_user_name.GenerateLocalUserNameBasedOnProject(862 self._project, self.api)863 instance_metadata.append({864 'key': metadata.INITIAL_WINDOWS_USER_METADATA_NAME,865 'value': windows_user})866 LOGGER.info(867 'The initial Windows login user name is %s.', windows_user)868 else:869 windows_user_name.ValidateUserName(windows_user)870 return windows_user871 def _EnsureWindowsPasswordInMetadata(872 self, instance_metadata, user_account_name):873 """Ensures that the initial Windows password is set in the metadata.874 If the metadata does not contain password, a random password will be875 generated.876 Args:877 instance_metadata: The instance metadata.878 user_account_name: The user account name.879 Raises:880 CommandError: The password does not meet strong password requirement.881 """882 password = metadata.GetMetadataValue(883 instance_metadata,884 metadata.INITIAL_WINDOWS_PASSWORD_METADATA_NAME)885 if password is None:886 password = windows_password.GeneratePassword(user_account_name)887 instance_metadata.append({888 'key': metadata.INITIAL_WINDOWS_PASSWORD_METADATA_NAME,889 'value': password})890 LOGGER.info(891 'Generated password for user account %s. The password can be '892 'retrieved from %s key in instance metadata.' %893 (user_account_name, metadata.INITIAL_WINDOWS_PASSWORD_METADATA_NAME))894 # Print the password on screen.895 print 'Generated password is %s' % password896 else:897 windows_password.ValidateStrongPasswordRequirement(898 password, user_account_name)899class GetInstance(InstanceCommand):900 """Get a VM instance."""901 positional_args = '<instance-name>'902 def Handle(self, instance_name):903 """Get the specified instance.904 Args:905 instance_name: The name of the instance to get.906 Returns:907 The result of getting the instance.908 """909 self._AutoDetectZone()910 instance_context = self._context_parser.ParseContextOrPrompt('instances',911 instance_name)912 instance_request = self.api.instances.get(913 **self._PrepareRequestArgs(instance_context))914 return instance_request.execute()915class DeleteInstance(InstanceCommand):916 """Delete one or more VM instances.917 Specify multiple instances as multiple arguments. Instances will be deleted918 in parallel.919 """920 positional_args = '<instance-name-1> ... <instance-name-n>'921 safety_prompt = 'Delete instance'922 def __init__(self, name, flag_values):923 super(DeleteInstance, self).__init__(name, flag_values)924 flags.DEFINE_boolean('delete_boot_pd',925 None,926 'Delete the attached boot persistent disk.',927 flag_values=flag_values)928 def _FindBootPersistentDisks(self, instance_context_list):929 """Find any persistent boot disks for the specified instances.930 Args:931 instance_context_list: A list of instance contexts.932 Returns:933 The list of (instance, attached boot disk) pairs.934 """935 bootdisks = []936 for instance_context in instance_context_list:937 instance_list_info = (938 self.GetListResultForResourceWithZone(939 self.api.instances, instance_context['instance'],940 zone=instance_context['zone'],941 project=instance_context['project']))942 bootdisks.extend((instance_context, disk) for disk943 in instance_list_info['disks']944 if (disk['type'] == 'PERSISTENT' and 'boot' in disk945 and disk['boot']))946 return bootdisks947 def Handle(self, *instance_names):948 """Delete the specified instances.949 Args:950 *instance_names: Names of the instances to delete.951 Returns:952 The result of deleting the instance.953 Raises:954 UsageError: Required flags were missing.955 """956 requests = []957 disk_requests = []958 autodelete_disk_requests = []959 bootdisks = []960 disk_names = []961 # Check to see if there is no instance, and just quit.962 if not instance_names:963 raise app.UsageError('You must specify an instance to delete.')964 self._AutoDetectZone()965 instance_contexts = [self._context_parser.ParseContextOrPrompt(966 'instances', name) for name in instance_names]967 bootdisks = self._FindBootPersistentDisks(instance_contexts)968 disk_names = [self.DenormalizeResourceName(disk['source'])969 for (_, disk) in bootdisks]970 # Check to see which persistent disks are attached,971 # and also which are boot.972 if self._flags.delete_boot_pd is not False:973 # User didn't specify nodeleteboot_pd. Either:974 # - specified delete_boot_pd975 # - gave neither, so we should prompt976 if bootdisks:977 # If the -f flag is specified, then delete_boot_pd flag is required.978 if self._flags.force and self._flags.delete_boot_pd is None:979 raise app.UsageError(980 'Some of your instances have boot disks attached. To use the '981 '--force flag when deleting instances with boot disks, you '982 'must also specify the --[no]delete_boot_pd flag explicitly.')983 if self._flags.delete_boot_pd is None:984 # User didn't set delete_boot_pd, also didn't set985 # nodelete_boot_pd986 self._flags.delete_boot_pd = self._PresentSafetyPrompt(987 'Delete persistent boot disk ' + ', '.join(disk_names), False)988 for (instance_context, disk) in bootdisks:989 if self._flags.delete_boot_pd:990 if 'autoDelete' in disk and disk['autoDelete'] is True:991 LOGGER.info('Auto-delete on %s (%s) is already enabled.',992 instance_context['instance'], disk['deviceName'])993 else:994 LOGGER.info('Enabling auto-delete on %s (%s).',995 instance_context['instance'], disk['deviceName'])996 autodelete_disk_requests.append(997 self.api.instances.setDiskAutoDelete(998 **self._PrepareRequestArgs(999 instance_context,1000 deviceName=disk['deviceName'],1001 autoDelete=True)))1002 else:1003 # User didn't set delete_boot_pd, but chose no in the prompt1004 # We must reset the autoDelete flag to false since the user does1005 # not want disks to be auto-deleted.1006 if 'autoDelete' in disk and disk['autoDelete'] is False:1007 LOGGER.info('Auto-delete on %s (%s) is already disabled.',1008 instance_context['instance'], disk['deviceName'])1009 else:1010 LOGGER.info('INFO: Disabling auto-delete on %s (%s).',1011 instance_context['instance'], disk['deviceName'])1012 autodelete_disk_requests.append(1013 self.api.instances.setDiskAutoDelete(1014 **self._PrepareRequestArgs(1015 instance_context,1016 deviceName=disk['deviceName'],1017 autoDelete=False)))1018 elif bootdisks:1019 for (instance_context, disk) in bootdisks:1020 # We must reset the autoDelete flag to false since the user does1021 # not want disks to be auto-deleted.1022 if 'autoDelete' in disk and disk['autoDelete'] is False:1023 LOGGER.info('Auto-delete on %s (%s) is already disabled.',1024 instance_context['instance'], disk['deviceName'])1025 else:1026 LOGGER.info('INFO: Disabling auto-delete on %s (%s).',1027 instance_context['instance'], disk['deviceName'])1028 autodelete_disk_requests.append(1029 self.api.instances.setDiskAutoDelete(1030 **self._PrepareRequestArgs(1031 instance_context,1032 deviceName=disk['deviceName'],1033 autoDelete=False)))1034 (results, exceptions) = self.ExecuteRequests(1035 autodelete_disk_requests, wait_for_operations=True)1036 # Fail early if we got any errors when trying to set disk auto-delete.1037 have_operation_errors = False1038 for operation in results:1039 if 'error' in operation:1040 have_operation_errors = True1041 if have_operation_errors or exceptions:1042 return (self.MakeListResult(results, 'operationList'), exceptions)1043 # All of the instance deletions must go through first.1044 for instance_context in instance_contexts:1045 requests.append(self.api.instances.delete(1046 **self._PrepareRequestArgs(instance_context)))1047 wait_for_operations = self._flags.synchronous_mode or disk_requests1048 (results, exceptions) = self.ExecuteRequests(1049 requests, wait_for_operations=wait_for_operations)1050 # Now all of the disk deletions go through.1051 if disk_requests:1052 (disk_results, disk_exceptions) = self.ExecuteRequests(1053 disk_requests, collection_name='disks')1054 results.extend(disk_results)1055 exceptions.extend(disk_exceptions)1056 # If the user had boot disks that they didn't want to be deleted,1057 # remind them that they have newly orphaned boot PDs.1058 if disk_names and self._flags.delete_boot_pd is False:1059 print 'INFO: The following boot persistent disks were orphaned:'1060 print ', '.join(disk_names)1061 return (self.MakeListResult(results, 'operationList'), exceptions)1062class ListInstances(InstanceCommand, command_base.GoogleComputeListCommand):1063 """List the VM instances for a project."""1064 def IsZoneLevelCollection(self):1065 return True1066 def IsGlobalLevelCollection(self):1067 return False1068 def ListFunc(self):1069 """Returns the function for listing instances."""1070 return self.api.instances.list1071 def ListZoneFunc(self):1072 """Returns the function for listing instances in a zone."""1073 return self.api.instances.list1074 def ListAggregatedFunc(self):1075 """Returns the function for listing instances across all zones."""1076 return self.api.instances.aggregatedList1077class AddAccessConfig(InstanceCommand):1078 """Create an access config for a VM instance's network interface."""1079 positional_args = '<instance-name>'1080 def __init__(self, name, flag_values):1081 super(AddAccessConfig, self).__init__(name, flag_values)1082 flags.DEFINE_string('network_interface_name',1083 self.DEFAULT_NETWORK_INTERFACE_NAME,1084 '[Required] Specifies the name of the instance\'s '1085 'network interface to add the new access config.',1086 flag_values=flag_values)1087 flags.DEFINE_string('access_config_name',1088 self.DEFAULT_ACCESS_CONFIG_NAME,1089 '[Required] Specifies the name of the new access '1090 'config.',1091 flag_values=flag_values)1092 flags.DEFINE_string('access_config_type',1093 self.ONE_TO_ONE_NAT_ACCESS_CONFIG_TYPE,1094 '[Required] Specifies the type of the new access '1095 'config. Currently only type "ONE_TO_ONE_NAT" is '1096 'supported.',1097 flag_values=flag_values)1098 flags.DEFINE_string('access_config_nat_ip',1099 self.EPHEMERAL_ACCESS_CONFIG_NAT_IP,1100 'The external NAT IP of the new access config. The '1101 'default value "ephemeral" indicates the service '1102 'should choose any available ephemeral IP. If an '1103 'explicit IP is given, that IP must be reserved '1104 'by the project and not in use by another resource.',1105 flag_values=flag_values)1106 def Handle(self, instance_name):1107 """Adds an access config to an instance's network interface.1108 Args:1109 instance_name: The instance name to which to add the new access config.1110 Returns:1111 An operation resource.1112 """1113 access_config_resource = {1114 'name': self._flags.access_config_name,1115 'type': self._flags.access_config_type,1116 }1117 if (self._flags.access_config_nat_ip.lower() !=1118 self.EPHEMERAL_ACCESS_CONFIG_NAT_IP):1119 access_config_resource['natIP'] = self._flags.access_config_nat_ip1120 kwargs = {'networkInterface': self._flags.network_interface_name}1121 self._AutoDetectZone()1122 instance_context = self._context_parser.ParseContextOrPrompt('instances',1123 instance_name)1124 add_access_config_request = self.api.instances.addAccessConfig(1125 **self._PrepareRequestArgs(1126 instance_context,1127 body=access_config_resource,1128 **kwargs))1129 return add_access_config_request.execute()1130class DeleteAccessConfig(InstanceCommand):1131 """Delete an access config from a VM instance's network interface."""1132 positional_args = '<instance-name>'1133 def __init__(self, name, flag_values):1134 super(DeleteAccessConfig, self).__init__(name, flag_values)1135 flags.DEFINE_string('network_interface_name',1136 self.DEFAULT_NETWORK_INTERFACE_NAME,1137 '[Required] The name of the instance\'s network '1138 'interface from which to delete the access config.',1139 flag_values=flag_values)1140 flags.DEFINE_string('access_config_name',1141 self.DEFAULT_ACCESS_CONFIG_NAME,1142 '[Required] The name of the access config to delete.',1143 flag_values=flag_values)1144 def Handle(self, instance_name):1145 """Deletes an access config from an instance's network interface.1146 Args:1147 instance_name: The instance name from which to delete the access config.1148 Returns:1149 An operation resource.1150 """1151 kwargs = {'accessConfig': self._flags.access_config_name,1152 'networkInterface': self._flags.network_interface_name}1153 self._AutoDetectZone()1154 instance_context = self._context_parser.ParseContextOrPrompt('instances',1155 instance_name)1156 delete_access_config_request = self.api.instances.deleteAccessConfig(1157 **self._PrepareRequestArgs(instance_context, **kwargs))1158 return delete_access_config_request.execute()1159class SetScheduling(InstanceCommand):1160 """Set scheduling options for an instance."""1161 positional_args = '<instance-name>'1162 def __init__(self, name, flag_values):1163 super(SetScheduling, self).__init__(name, flag_values)1164 _DefineSchedulingFlags(flag_values)1165 def Handle(self, instance_name):1166 """Set scheduling options for an instance.1167 Args:1168 instance_name: The instance name for which to set scheduling options.1169 Returns:1170 An operation resource.1171 """1172 scheduling = _GetSchedulingFromFlags(self._flags)1173 self._AutoDetectZone()1174 instance_context = self._context_parser.ParseContextOrPrompt(1175 'instances', instance_name)1176 set_scheduling_request = self.api.instances.setScheduling(1177 **self._PrepareRequestArgs(instance_context, body=scheduling))1178 return set_scheduling_request.execute()1179class SshInstanceBase(InstanceCommand):1180 """Base class for SSH-based commands."""1181 # We want everything after 'ssh <instance>' to be passed on to the1182 # ssh command in question. As such, all arguments to the utility1183 # must come before the 'ssh' command.1184 sort_args_and_flags = False1185 def __init__(self, name, flag_values):1186 super(SshInstanceBase, self).__init__(name, flag_values)1187 flags.DEFINE_integer(1188 'ssh_port',1189 22,1190 'TCP port to connect to',1191 flag_values=flag_values)1192 flags.DEFINE_multistring(1193 'ssh_arg',1194 [],1195 'Additional arguments to pass to ssh',1196 flag_values=flag_values)1197 flags.DEFINE_integer(1198 'ssh_key_push_wait_time',1199 10, # 10 seconds1200 '[Deprecated] Number of seconds to wait for updates to project-wide '1201 'ssh keys to cascade to the instances within the project. This value '1202 'is no longer used. Instead, the instance is polled periodically '1203 'until it accepts SSH connections using the new key.',1204 flag_values=flag_values)1205 flags.DEFINE_integer(1206 'ssh_key_push_timeout',1207 60, # 60 seconds1208 'Number of seconds to wait for a newly added SSH key to cascade '1209 'to the instance before timing out.',1210 flag_values=flag_values)1211 def PrintResult(self, _):1212 """Override the PrintResult to be a noop."""1213 pass1214 def _GetInstanceResource(self, instance_name):1215 """Get the instance resource. This is the dictionary returned by the API.1216 Args:1217 instance_name: The name of the instance to retrieve the ssh address for.1218 Returns:1219 The data for the instance resource as returned by the API.1220 Raises:1221 gcutil_errors.CommandError: If the instance does not exist.1222 """1223 self._AutoDetectZone()1224 instance_context = self._context_parser.ParseContextOrPrompt('instances',1225 instance_name)1226 request = self.api.instances.get(1227 **self._PrepareRequestArgs(instance_context))1228 result = request.execute()1229 if not result:1230 raise gcutil_errors.CommandError(1231 'Unable to find the instance %s.' % (instance_name))1232 return result1233 def _GetSshAddress(self, instance_resource):1234 """Retrieve the ssh address from the passed instance resource data.1235 Args:1236 instance_resource: The resource data of the instance for which1237 to retrieve the ssh address.1238 Returns:1239 The ssh address and port.1240 Raises:1241 gcutil_errors.CommandError: If the instance has no external address.1242 """1243 external_addresses = self._ExtractExternalIpFromInstanceRecord(1244 instance_resource)1245 if len(external_addresses) < 1:1246 raise gcutil_errors.CommandError(1247 'Cannot connect to an instance with no external address')1248 return (external_addresses[0], self._flags.ssh_port)1249 def _WaitForSshKeyPropagation(self, instance_resource):1250 command = self._BuildSshCmd(1251 instance_resource, 'ssh',1252 ['-A', '-p', '%(port)d', '%(user)s@%(host)s', 'true'])1253 deadline_sec = time.time() + self._flags.ssh_key_push_timeout1254 finished = False1255 while (not finished) and (time.time() < deadline_sec):1256 retval = subprocess.call(1257 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)1258 finished = retval == 01259 if not finished:1260 time.sleep(5)1261 if not finished:1262 raise gcutil_errors.CommandError('SSH key failed to propagate to the VM')1263 def _EnsureSshable(self, instance_resource):1264 """Ensure that the user can ssh into the specified instance.1265 This method returns a context manager which checks if the instance has SSH1266 keys defined for it, and if it does not this makes sure the enclosing1267 project contains a metadata entry for the user's public ssh key.1268 If the project is updated to add the user's ssh key, then the entry point1269 will attempt to connect to the instance repeatedly until the ssh keys1270 have been propagated.1271 Args:1272 instance_resource: The resource data for the instance to which to connect.1273 Returns:1274 A context manager which takes care of adding ephemeral SSH keys to the1275 project and removing them later on.1276 Raises:1277 gcutil_errors.CommandError: If the instance is not in the RUNNING state.1278 """1279 class SshableContextManager(object):1280 """Context manager for an instance being sshable.1281 This ensures that an instance can be ssh'ed to on entry (including1282 adding an SSH key pair to the project if necessary).1283 """1284 def __init__(self, add_method, wait_method):1285 self.add_method = add_method1286 self.wait_method = wait_method1287 def __enter__(self):1288 instance_status = instance_resource.get('status')1289 if instance_status != 'RUNNING':1290 raise gcutil_errors.CommandError(1291 'Cannot connect to the instance since its current status is %s.'1292 % instance_status)1293 instance_metadata = instance_resource.get('metadata', {})1294 instance_ssh_key_entries = (1295 [entry for entry in instance_metadata.get('items', [])1296 if entry.get('key') == 'sshKeys'])1297 self.added_ssh_key = None1298 if not instance_ssh_key_entries:1299 self.added_ssh_key = self.add_method()1300 if self.added_ssh_key:1301 self.wait_method(instance_resource)1302 return self.added_ssh_key1303 def __exit__(self, *args, **kwargs):1304 pass1305 return SshableContextManager(1306 self._AddComputeKeyToProject,1307 self._WaitForSshKeyPropagation)1308 def _BuildSshCmd(self, instance_resource, command, args):1309 """Builds the given SSH-based command line with the given arguments.1310 A complete SSH-based command line is built from the given command,1311 any common arguments, and the arguments provided. The value of1312 each argument is formatted using a dictionary that contains the1313 following keys: host and port.1314 Args:1315 instance_resource: The resource data of the instance for which1316 to build the ssh command.1317 command: the ssh-based command to run (e.g. ssh or scp)1318 args: arguments for the command1319 Returns:1320 The command line used to perform the requested ssh operation.1321 Raises:1322 IOError: An error occured accessing SSH details.1323 """1324 (host, port) = self._GetSshAddress(instance_resource)1325 values = {'host': host,1326 'port': port,1327 'user': self._flags.ssh_user}1328 command_line = [1329 command,1330 '-o', 'UserKnownHostsFile=/dev/null',1331 '-o', 'CheckHostIP=no',1332 '-o', 'StrictHostKeyChecking=no',1333 '-i', self._flags.private_key_file,1334 ] + self._flags.ssh_arg1335 if LOGGER.level <= logging.DEBUG:1336 command_line.append('-v')1337 for arg in args:1338 command_line.append(arg % values)1339 return command_line1340 def _IsInstanceRootDiskPersistent(self, instance_resource):1341 """Determines if instance's root disk is persistent.1342 Args:1343 instance_resource: Dictionary result from a get instance json request.1344 Returns:1345 True if the root disk of the VM instance is persistent, otherwise False.1346 """1347 boot_disk_is_persistent = False1348 for disk in instance_resource.get('disks', []):1349 if disk.get('boot', False) and disk.get('type', '') == 'PERSISTENT':1350 boot_disk_is_persistent = True1351 return boot_disk_is_persistent1352 def _PrintEphemeralDiskWarning(self, instance_resource):1353 """Prints a warning message the instance is running on an ephemeral disk.1354 Args:1355 instance_resource: Dictionary result from a get instance json request.1356 """1357 if not self._IsInstanceRootDiskPersistent(instance_resource):1358 LOGGER.warn(EPHEMERAL_ROOT_DISK_WARNING_MESSAGE)1359 def _RunSshCmd(self, instance_name, command, args):1360 """Run the given SSH-based command line with the given arguments.1361 The specified SSH-base command is run for the arguments provided.1362 The value of each argument is formatted using a dictionary that1363 contains the following keys: host and port.1364 Args:1365 instance_name: The name of the instance for which to run the ssh command.1366 command: the ssh-based command to run (e.g. ssh or scp)1367 args: arguments for the command1368 Raises:1369 IOError: An error occured accessing SSH details.1370 """1371 instance_resource = self._GetInstanceResource(instance_name)1372 command_line = self._BuildSshCmd(instance_resource, command, args)1373 self._PrintEphemeralDiskWarning(instance_resource)1374 try:1375 proc = None1376 with self._EnsureSshable(instance_resource):1377 LOGGER.info('Running command line: %s', ' '.join(command_line))1378 proc = subprocess.Popen(command_line)1379 sys.exit(proc.wait())1380 except ssh_keys.UserSetupError as e:1381 LOGGER.warn('Could not generate compute ssh key: %s', e)1382 return1383 except OSError as e:1384 LOGGER.error('There was a problem executing the command: %s', e)1385class SshToInstance(SshInstanceBase):1386 """Connect to a VM instance with ssh."""1387 positional_args = '<instance-name> <ssh-args>'1388 def _GenerateSshArgs(self, *argv):1389 """Generates the command line arguments for the ssh command.1390 Args:1391 *argv: List of additional ssh command line args, if any.1392 Returns:1393 The complete ssh argument list.1394 """1395 ssh_args = ['-A', '-p', '%(port)d', '%(user)s@%(host)s', '--']1396 escaped_args = [a.replace('%', '%%') for a in argv]1397 ssh_args.extend(escaped_args)1398 return ssh_args1399 def Handle(self, instance_name, *argv):1400 """SSH into the instance.1401 Args:1402 instance_name: The name of the instance to ssh to.1403 *argv: The remaining unhandled arguments.1404 Returns:1405 The result of the ssh command1406 """1407 ssh_args = self._GenerateSshArgs(*argv)1408 self._RunSshCmd(instance_name, 'ssh', ssh_args)1409class PushToInstance(SshInstanceBase):1410 """Push one or more files to a VM instance."""1411 positional_args = '<instance-name> <file-1> ... <file-n> <destination>'1412 def _GenerateScpArgs(self, *argv):1413 """Generates the command line arguments for the scp command.1414 Args:1415 *argv: List of files to push and instance-relative destination.1416 Returns:1417 The scp argument list.1418 Raises:1419 gcutil_errors.CommandError: If an invalid number of arguments are passed1420 in.1421 """1422 if len(argv) < 2:1423 raise gcutil_errors.CommandError('Invalid number of arguments passed.')1424 scp_args = ['-r', '-P', '%(port)d', '--']1425 escaped_args = [a.replace('%', '%%') for a in argv]1426 scp_args.extend(escaped_args[0:-1])1427 scp_args.append('%(user)s@%(host)s:' + escaped_args[-1])1428 return scp_args1429 def Handle(self, instance_name, *argv):1430 """Pushes one or more files into the instance.1431 Args:1432 instance_name: The name of the instance to push files to.1433 *argv: The remaining unhandled arguments.1434 Returns:1435 The result of the scp command1436 Raises:1437 gcutil_errors.CommandError: If an invalid number of arguments are passed1438 in.1439 """1440 scp_args = self._GenerateScpArgs(*argv)1441 self._RunSshCmd(instance_name, 'scp', scp_args)1442class PullFromInstance(SshInstanceBase):1443 """Pull one or more files from a VM instance."""1444 positional_args = '<instance-name> <file-1> ... <file-n> <destination>'1445 def _GenerateScpArgs(self, *argv):1446 """Generates the command line arguments for the scp command.1447 Args:1448 *argv: List of files to pull and local-relative destination.1449 Returns:1450 The scp argument list.1451 Raises:1452 gcutil_errors.CommandError: If an invalid number of arguments are passed1453 in.1454 """1455 if len(argv) < 2:1456 raise gcutil_errors.CommandError('Invalid number of arguments passed.')1457 scp_args = ['-r', '-P', '%(port)d', '--']1458 escaped_args = [a.replace('%', '%%') for a in argv]1459 for arg in escaped_args[0:-1]:1460 scp_args.append('%(user)s@%(host)s:' + arg)1461 scp_args.append(escaped_args[-1])1462 return scp_args1463 def Handle(self, instance_name, *argv):1464 """Pulls one or more files from the instance.1465 Args:1466 instance_name: The name of the instance to pull files from.1467 *argv: The remaining unhandled arguments.1468 Returns:1469 The result of the scp command1470 Raises:1471 gcutil_errors.CommandError: If an invalid number of arguments are passed1472 in.1473 """1474 scp_args = self._GenerateScpArgs(*argv)1475 self._RunSshCmd(instance_name, 'scp', scp_args)1476class GetSerialPortOutput(InstanceCommand):1477 """Get the output of a VM instance's serial port."""1478 positional_args = '<instance-name>'1479 def Handle(self, instance_name):1480 """Get the specified instance's serial port output.1481 Args:1482 instance_name: The name of the instance.1483 Returns:1484 The output of the instance's serial port.1485 """1486 self._AutoDetectZone()1487 instance_context = self._context_parser.ParseContextOrPrompt('instances',1488 instance_name)1489 instance_request = self.api.instances.getSerialPortOutput(1490 **self._PrepareRequestArgs(instance_context))1491 return instance_request.execute()1492 def PrintResult(self, result):1493 """Override the PrintResult to be a noop."""1494 if self._flags.print_json:1495 super(GetSerialPortOutput, self).PrintResult(result)1496 else:1497 print result['contents'].encode('utf-8')1498class ResetInstance(InstanceCommand):1499 """Reset a VM instance, resulting in a reboot."""1500 positional_args = '<instance-name>'1501 def Handle(self, instance_name):1502 """Initiate a hard reset on the instance.1503 Args:1504 instance_name: The instance name to reset.1505 Returns:1506 An operation resource.1507 """1508 self._AutoDetectZone()1509 instance_context = self._context_parser.ParseContextOrPrompt('instances',1510 instance_name)1511 reset_request = self.api.instances.reset(1512 **self._PrepareRequestArgs(instance_context))1513 return reset_request.execute()1514class OptimisticallyLockedInstanceCommand(InstanceCommand):1515 """Base class for instance commands that require a fingerprint."""1516 def __init__(self, name, flag_values):1517 super(OptimisticallyLockedInstanceCommand, self).__init__(name, flag_values)1518 flags.DEFINE_string('fingerprint',1519 None,1520 '[Required] Fingerprint of the data to be '1521 'overwritten. This fingerprint provides optimistic '1522 'locking. Data will only be set if the given '1523 'fingerprint matches the state of the data prior to '1524 'this request.',1525 flag_values=flag_values)1526 def Handle(self, instance_name):1527 """Invokes the HandleCommand method of the subclass."""1528 if not self._flags.fingerprint:1529 raise app.UsageError('You must provide a fingerprint with your request.')1530 return self.HandleCommand(instance_name)1531class SetMetadata(OptimisticallyLockedInstanceCommand):1532 """Set the metadata for a VM instance.1533 This method overwrites existing instance metadata with new metadata.1534 Common metadata (project-wide) is preserved.1535 For example, running:1536 gcutil --project=<project-name> setinstancemetadata my-instance \1537 --metadata="key1:value1" \1538 --fingerprint=<original-fingerprint>1539 ...1540 gcutil --project=<project-name> setinstancemetadata my-instance \1541 --metadata="key2:value2" \1542 --fingerprint=<new-fingerprint>1543 will result in 'my-instance' having 'key2:value2' as its metadata.1544 """1545 positional_args = '<instance-name>'1546 def __init__(self, name, flag_values):1547 super(SetMetadata, self).__init__(name, flag_values)1548 flags.DEFINE_bool('force',1549 None,1550 'Set new metadata even if the key \'sshKeys\' will '1551 'no longer be present.',1552 flag_values=flag_values,1553 short_name='f')1554 self._metadata_flags_processor = metadata.MetadataFlagsProcessor(1555 flag_values)1556 def HandleCommand(self, instance_name):1557 """Set instance-specific metadata.1558 Args:1559 instance_name: The name of the instance scoping this request.1560 Returns:1561 An operation resource.1562 """1563 self._AutoDetectZone()1564 instance_context = self._context_parser.ParseContextOrPrompt('instances',1565 instance_name)1566 new_metadata = self._metadata_flags_processor.GatherMetadata()1567 if not self._flags.force:1568 new_keys = set([entry['key'] for entry in new_metadata])1569 get_project = self.api.projects.get(project=instance_context['project'])1570 project_resource = get_project.execute()1571 project_metadata = project_resource.get('commonInstanceMetadata', {})1572 project_metadata = project_metadata.get('items', [])1573 project_keys = set([entry['key'] for entry in project_metadata])1574 get_instance = self.api.instances.get(1575 **self._PrepareRequestArgs(instance_context))1576 instance_resource = get_instance.execute()1577 instance_metadata = instance_resource.get('metadata', {})1578 instance_metadata = instance_metadata.get('items', [])1579 instance_keys = set([entry['key'] for entry in instance_metadata])1580 if ('sshKeys' in instance_keys and 'sshKeys' not in new_keys1581 and 'sshKeys' not in project_keys):1582 raise gcutil_errors.CommandError(1583 'Discarding update that would have erased instance sshKeys.'1584 '\n\nRe-run with the -f flag to force the update.')1585 metadata_resource = {'kind': self._GetResourceApiKind('metadata'),1586 'items': new_metadata,1587 'fingerprint': self._flags.fingerprint}1588 set_metadata_request = self.api.instances.setMetadata(1589 **self._PrepareRequestArgs(instance_context, body=metadata_resource))1590 return set_metadata_request.execute()1591class SetTags(OptimisticallyLockedInstanceCommand):1592 """Set the tags for a VM instance.1593 This method overwrites existing instance tags.1594 For example, running:1595 gcutil --project=<project-name> setinstancetags my-instance \1596 --tags="tag-1" \1597 --fingerprint=<original-fingerprint>1598 ...1599 gcutil --project=<project-name> setinstancetags my-instance \1600 --tags="tag-2,tag-3" \1601 --fingerprint=<new-fingerprint>1602 will result in 'my-instance' having tags 'tag-2' and 'tag-3'.1603 """1604 def __init__(self, name, flag_values):1605 super(SetTags, self).__init__(name, flag_values)1606 flags.DEFINE_list('tags',1607 [],1608 '[Required] A set of tags applied to this instance. '1609 'Used for filtering and to configure network firewall '1610 'rules To specify multiple tags, provide them as '1611 'comma-separated entries.',1612 flag_values=flag_values)1613 def HandleCommand(self, instance_name):1614 """Set instance tags.1615 Args:1616 instance_name: The name of the instance scoping this request.1617 Returns:1618 An operation resource.1619 """1620 self._AutoDetectZone()1621 instance_context = self._context_parser.ParseContextOrPrompt('instances',1622 instance_name)1623 tag_resource = {'items': sorted(set(self._flags.tags)),1624 'fingerprint': self._flags.fingerprint}1625 set_tags_request = self.api.instances.setTags(1626 **self._PrepareRequestArgs(instance_context, body=tag_resource))1627 return set_tags_request.execute()1628class AttachDisk(InstanceCommand):1629 """Attach a persistent disk to a VM instance."""1630 positional_args = '<instance-name>'1631 def __init__(self, name, flag_values):1632 super(AttachDisk, self).__init__(name, flag_values)1633 flags.DEFINE_multistring(1634 'disk',1635 '',1636 '[Required] The name of a disk to be attached to the '1637 'instance. The name may be followed by a '1638 'comma-separated list of name=value pairs '1639 'specifying options. Legal option names are '1640 '\'deviceName\', to specify the disk\'s device '1641 'name, and \'mode\', to indicate whether the disk '1642 'should be attached READ_WRITE (the default) or '1643 'READ_ONLY',1644 flag_values=flag_values)1645 def Handle(self, instance_name):1646 """Attach a persistent disk to the instance.1647 Args:1648 instance_name: The instance name to attach to.1649 Returns:1650 An operation resource.1651 """1652 self._AutoDetectZone()1653 instance_context = self._context_parser.ParseContextOrPrompt('instances',1654 instance_name)1655 disks = [self._BuildAttachedDisk(disk) for disk in self._flags.disk]1656 attach_requests = []1657 for disk in disks:1658 attach_requests.append(1659 self.api.instances.attachDisk(1660 **self._PrepareRequestArgs(1661 instance_context,1662 body=disk)))1663 return self.ExecuteRequests(attach_requests)1664class DetachDisk(InstanceCommand):1665 """Detach a persistent disk from a VM instance."""1666 positional_args = '<instance-name>'1667 def __init__(self, name, flag_values):1668 super(DetachDisk, self).__init__(name, flag_values)1669 flags.DEFINE_multistring(1670 'device_name',1671 '',1672 '[Required] The device name of a persistent disk to '1673 'detach from the instance. The device name is '1674 'specified at instance creation time and may not '1675 'be the same as the persistent disk name.',1676 flag_values=flag_values)1677 def Handle(self, instance_name):1678 """Detach a persistent disk from the instance.1679 Args:1680 instance_name: The instance name to detach from.1681 Returns:1682 An operation resource.1683 """1684 self._AutoDetectZone()1685 instance_context = self._context_parser.ParseContextOrPrompt('instances',1686 instance_name)1687 detach_requests = []1688 for device_name in self._flags.device_name:1689 detach_requests.append(1690 self.api.instances.detachDisk(1691 **self._PrepareRequestArgs(1692 instance_context,1693 deviceName=device_name)))1694 return self.ExecuteRequests(detach_requests)1695class SetInstanceDiskAutoDelete(InstanceCommand):1696 """Changes the auto-delete flag on a disk attached to instance."""1697 positional_args = '<instance-name>'1698 def __init__(self, name, flag_values):1699 super(SetInstanceDiskAutoDelete, self).__init__(name, flag_values)1700 flags.DEFINE_string(1701 'device_name',1702 None,1703 'The device name of a persistent disk to '1704 'update on the instance. If not specified, '1705 'this will default to the instance name.',1706 flag_values=flag_values)1707 flags.DEFINE_boolean(1708 'auto_delete',1709 None,1710 '[Required] The new value of auto-delete flag for the given disk.',1711 flag_values=flag_values)1712 def Handle(self, instance_name):1713 """Changes the auto-delete flag on a disk attached to instance.1714 Args:1715 instance_name: The instance name to detach from.1716 Returns:1717 An operation resource.1718 """1719 self._AutoDetectZone()1720 instance_context = self._context_parser.ParseContextOrPrompt('instances',1721 instance_name)1722 request = self.api.instances.setDiskAutoDelete(1723 **self._PrepareRequestArgs(1724 instance_context,1725 deviceName=self._flags.device_name or instance_name,1726 autoDelete=self._flags.auto_delete))1727 return request.execute()1728def AddCommands():1729 """Add all of the instance related commands."""1730 appcommands.AddCmd('addinstance', AddInstance)1731 appcommands.AddCmd('getinstance', GetInstance)1732 appcommands.AddCmd('deleteinstance', DeleteInstance)1733 appcommands.AddCmd('listinstances', ListInstances)1734 appcommands.AddCmd('addaccessconfig', AddAccessConfig)1735 appcommands.AddCmd('deleteaccessconfig', DeleteAccessConfig)1736 appcommands.AddCmd('setscheduling', SetScheduling)1737 appcommands.AddCmd('ssh', SshToInstance)1738 appcommands.AddCmd('push', PushToInstance)1739 appcommands.AddCmd('pull', PullFromInstance)1740 appcommands.AddCmd('getserialportoutput', GetSerialPortOutput)1741 appcommands.AddCmd('setinstancemetadata', SetMetadata)1742 appcommands.AddCmd('setinstancediskautodelete', SetInstanceDiskAutoDelete)1743 appcommands.AddCmd('setinstancetags', SetTags)1744 appcommands.AddCmd('attachdisk', AttachDisk)1745 appcommands.AddCmd('detachdisk', DetachDisk)...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Testify automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful