Example #1
0
  def _CheckDiskPreconditions(self, instances_to_ignore, disk_names):
    if len(disk_names) > MAX_DISKS_TO_MOVE:
      raise command_base.CommandError(
          'At most %s disks can be moved at a '
          'time. Refine your query and try again.' % MAX_DISKS_TO_MOVE)

    res = self._CheckForDisksInUseByOtherInstances(
        instances_to_ignore, disk_names)
    if res:
      offending_instances = ['%s: %s' % (instance, ', '.join(disks))
                             for instance, disks in res]
      raise command_base.CommandError(
          'Some of the instances you\'d like to move have disks that are in '
          'use by other instances: (Offending instance: disks attached)\n%s' %
          (utils.ListStrings(offending_instances)))
Example #2
0
    def _CheckQuotas(self,
                     instances_to_mv,
                     disks_to_mv,
                     src_zone,
                     dest_zone,
                     snapshots_to_create=None):
        """Raises a CommandError if the quota to perform the move does not exist."""
        print 'Checking project and destination zone quotas...'

        dest_zone_resource = self._zones_api.get(project=self._project,
                                                 zone=dest_zone).execute()
        requirements = self._CreateQuotaRequirementsDict(
            instances_to_mv,
            disks_to_mv,
            src_zone,
            snapshots_to_create=snapshots_to_create)
        available = self._ExtractAvailableQuota(
            self._project_resource.get('quotas', []),
            dest_zone_resource.get('quotas', []), requirements)

        LOGGER.debug('Required quota for move is: %s', requirements)
        LOGGER.debug('Available quota is: %s', available)

        for metric, required in requirements.iteritems():
            if available.get(metric, 0) - required < 0:
                raise command_base.CommandError(
                    'You do not have enough quota for %s in %s or your project.'
                    % (metric, dest_zone))
Example #3
0
    def _DeleteDisks(self, disk_names, zone):
        """Deletes the given disks.

    Args:
      disk_names: A list of disk names to delete.
      zone: The zone to which the disks belong.
    """
        if not disk_names:
            return

        print 'Deleting disks...'
        requests = []
        for name in disk_names:
            requests.append(
                self._disks_api.delete(project=self._project,
                                       disk=name,
                                       zone=zone))

        results, exceptions = self.ExecuteRequests(requests,
                                                   collection_name='disks')
        if exceptions:
            raise command_base.CommandError(
                'Aborting due to errors while deleting disks:\n%s' %
                utils.ListStrings(exceptions))
        self._CheckForErrorsInOps(self.MakeListResult(results,
                                                      'operationList'))
Example #4
0
    def _GenerateScpArgs(self, *argv):
        """Generates the command line arguments for the scp command.

    Args:
      *argv: List of files to pull and local-relative destination.

    Returns:
      The scp argument list.

    Raises:
      command_base.CommandError: If an invalid number of arguments are passed
          in.
    """
        if len(argv) < 2:
            raise command_base.CommandError(
                'Invalid number of arguments passed.')

        scp_args = ['-r', '-P', '%(port)d', '--']

        escaped_args = [a.replace('%', '%%') for a in argv]
        for arg in escaped_args[0:-1]:
            scp_args.append('%(user)s@%(host)s:' + arg)
        scp_args.append(escaped_args[-1])

        return scp_args
Example #5
0
  def __call__(self, args):
    """Entry point to the command.

    Based on the arguments (see Args section), this method will either start
    a new move or resume a previously-failed one.

    New moves will result in the creation of a replay log. If any
    failures happen, the replay log will not be deleted and
    instructions for resuming the move will be displayed to the user.

    Args:
      args: An object with attributes name_regexes and dest_zone OR
        replay_log_file. With the former set of args, a fresh move
        is invoked. With the latter, a previously failed move can be
        resumed. If specified, name_regexes should be a list of regular
        expressions describing the names of instances to move and dest_zone
        should be the name of the destination zone. replay_log_file
        should be the path to a log file that has recorded the the state of
        a previous invocation.
    """

    if args.name_regexes and args.dest_zone:
      self._move_for_the_first_time(args.name_regexes, args.dest_zone)
    elif args.replay_log_file:
      self._replay_log(args.replay_log_file)
    else:
      raise command_base.CommandError(
          'Expected either instance names and destination zone or replay log.')
Example #6
0
 def _GetKey(self, log, key):
   """Returns log[key] or raises a CommandError if key does not exist."""
   value = log.get(key)
   if value is None:
     raise command_base.CommandError(
         'The log file did not contain a %s key.' % repr(key))
   return value
Example #7
0
  def _DeleteInstances(self, instances, zone):
    """Deletes the given instances.

    Args:
      instances: A list of instance resources.
      zone: The zone to which the instances belong.

    Raises:
      CommandError: If one or more of the deletions fail.
    """
    if not instances:
      return

    print 'Deleting instances...'
    requests = []
    for instance in instances:
      requests.append(self._instances_api.delete(
          project=self._project,
          zone=zone,
          instance=instance['name']))
    results, exceptions = self.ExecuteRequests(
        requests, collection_name='instances')
    if exceptions:
      raise command_base.CommandError(
          'Aborting due to errors while deleting instances:\n%s' %
          utils.ListStrings(exceptions))
    self._CheckForErrorsInOps(self.MakeListResult(results, 'operationList'))
Example #8
0
  def _CreateSnapshots(self, snapshot_mappings, src_zone, dest_zone):
    """Creates snapshots for the disks to be moved.

    Args:
      snapshot_mappings: A map of disk names that should be moved to
        the names that should be used for each disk's snapshot.
      src_zone: The source zone. All disks in snapshot_mappings must be
        in this zone.
      dest_zone: The zone the disks are destined for.
    """
    if not snapshot_mappings:
      return

    print 'Snapshotting disks...'
    requests = []
    for disk_name, snapshot_name in snapshot_mappings.iteritems():
      snapshot_resource = {
          'name': snapshot_name,
          'sourceDisk': self.NormalizePerZoneResourceName(
              self._project, src_zone, 'disks', disk_name),
          'description': ('Snapshot for moving disk %s from %s to %s.' %
                          (disk_name, src_zone, dest_zone))}
      requests.append(self._snapshots_api.insert(
          project=self._project, body=snapshot_resource))

    results, exceptions = self.ExecuteRequests(
        requests, collection_name='snapshots')
    if exceptions:
      raise command_base.CommandError(
          'Aborting due to errors while creating snapshots:\n%s' %
          utils.ListStrings(exceptions))
    self._CheckForErrorsInOps(self.MakeListResult(results, 'operationList'))
    self._WaitForSnapshots(snapshot_mappings.values())
Example #9
0
    def _DeleteSnapshots(self, snapshot_names, zone):
        """Deletes the given snapshots.

    Args:
      snapshot_names: A list of snapshot names to delete.
      zone: The zones to which the snapshots belong.
    """
        if not snapshot_names or self._flags.keep_snapshots:
            return

        print 'Deleting snapshots...'
        requests = []
        for name in snapshot_names:
            requests.append(
                self._snapshots_api.delete(project=self._project,
                                           snapshot=name))

        results, exceptions = self.ExecuteRequests(requests,
                                                   collection_name='snapshots')
        if exceptions:
            raise command_base.CommandError(
                'Aborting due to errors while deleting snapshots:\n%s' %
                utils.ListStrings(exceptions))
        self._CheckForErrorsInOps(self.MakeListResult(results,
                                                      'operationList'))
Example #10
0
  def _CreateDisksFromSnapshots(self, snapshot_mappings, dest_zone):
    """Creates disks in the destination zone from the given snapshots.

    Args:
      snapshot_mappings: A dict of disk names to snapshot names. Disks are
        created in the destination zone from the given snapshot names. The
        disks will assume their previous names as indicated by the key-value
        pairs.
      dest_zone: The zone in which the disks will be created.
    """
    if not snapshot_mappings:
      return

    print 'Recreating disks from snapshots...'
    requests = []
    for disk_name, snapshot_name in snapshot_mappings.iteritems():
      disk_resource = {
          'name': disk_name,
          'sourceSnapshot': self.NormalizeGlobalResourceName(
              self._project, 'snapshots', snapshot_name)}
      requests.append(self._disks_api.insert(
          project=self._project, body=disk_resource, zone=dest_zone))

    results, exceptions = self.ExecuteRequests(
        requests, collection_name='disks')
    if exceptions:
      raise command_base.CommandError(
          'Aborting due to errors while re-creating disks:\n%s' %
          utils.ListStrings(exceptions))
    self._CheckForErrorsInOps(self.MakeListResult(results, 'operationList'))
Example #11
0
  def Handle(self, firewall_name):
    """Add the specified firewall.

    Args:
      firewall_name: The name of the firewall to add.

    Returns:
      The result of inserting the firewall.

    Raises:
      command_base.CommandError: If the passed flag values cannot be
          interpreted.
    """
    if not self._flags.allowed:
      raise command_base.CommandError(
          'You must specify at least one rule through --allowed.')

    firewall_resource = {
        'kind': self._GetResourceApiKind('firewall'),
        'name': self.DenormalizeResourceName(firewall_name),
        'description': self._flags.description,
        'rules': []
        }

    if self._flags.network is not None:
      firewall_resource['network'] = (self.NormalizeGlobalResourceName(
          self._project,
          'networks',
          self._flags.network))

    if (not self._flags.allowed_ip_sources and
        not self._flags.allowed_tag_sources):
      self._flags.allowed_ip_sources.append('0.0.0.0/0')

    try:
      firewall_rules = FirewallRules(self._flags.allowed,
                                     self._flags.allowed_ip_sources)
      firewall_rules.SetTags(self._flags.allowed_tag_sources,
                             self._flags.target_tags)
      firewall_rules.AddToFirewall(firewall_resource)
      firewall_request = self._firewalls_api.insert(project=self._project,
                                                    body=firewall_resource)
      return firewall_request.execute()
    except ValueError, e:
      raise command_base.CommandError(e)
Example #12
0
  def _CheckInstancePreconditions(self, instances_to_mv, instances_in_dest):
    if not instances_to_mv:
      raise command_base.CommandError('No matching instances were found.')

    if len(instances_to_mv) > MAX_INSTANCES_TO_MOVE:
      raise command_base.CommandError(
          'At most %s instances can be moved at a '
          'time. Refine your query and try again.' % MAX_INSTANCES_TO_MOVE)

    # Checks for name collisions.
    src_names = [i['name'] for i in instances_to_mv]
    dest_names = [i['name'] for i in instances_in_dest]
    common_names = set(src_names) & set(dest_names)
    if common_names:
      raise command_base.CommandError(
          'Encountered name collisions. Instances with the following names '
          'exist in both the source and destination zones: \n%s' %
          utils.ListStrings(common_names))
Example #13
0
  def _replay_log(self, log_path):
    """Replays a previous move.

    This method first checks the current state of the project to see
    which instances have already been moved before moving the
    instances that were left behind in a previous failed move.

    The user is prompted to continue before any changes are made.

    Args:
      log_path: The path to the replay log.
    """
    if not os.path.exists(log_path):
      raise command_base.CommandError('File not found: {0}'.format(log_path))

    log = self._parse_log(log_path)
    dest_zone = log.get('dest_zone')
    if not dest_zone:
      raise command_base.CommandError(
          '{0} did not contain destination zone.'.format(log_path))
    print 'Destination zone is {0}.'.format(dest_zone)

    instances_to_mv = log.get('instances')

    instances_in_dest = [i.to_json() for i in self._api.all_instances(
        filter='zone eq .*{0}'.format(dest_zone))]

    # Note that we cannot use normal set intersection and subtraction
    # because two different instance resources could be referring to
    # the same instance (e.g., the instance was restarted by the
    # system).
    instances_to_ignore = self._intersect(instances_to_mv, instances_in_dest)
    instances_to_mv = self._subtract(instances_to_mv, instances_in_dest)

    if instances_to_mv:
      self._delete_and_recreate_instances(
          instances_to_mv, instances_to_ignore, dest_zone)
    else:
      print 'All instances have already been moved to their destination.'

    # We have succeeded, so it's safe to delete the log file.
    self._delete_log_file(log_path)
Example #14
0
 def _CheckForErrorsInOps(self, results):
   """Raises CommandError if any operations in results contains an error."""
   _, ops = self._PartitionResults(results)
   errors = []
   for op in (ops or []):
     if 'error' in op and 'errors' in op['error'] and op['error']['errors']:
       error = op['error']['errors'][0].get('message')
       if error:
         errors.append(error)
   if errors:
     raise command_base.CommandError(
         'Encountered errors:\n%s' % utils.ListStrings(errors))
Example #15
0
  def _raise_command_error(self, text, *args, **kwargs):
    """Prints a new-line character, then raises a CommandError.

    Args:
      text: The format string of the error message.
      args: The args passed to the format string.
      kwargs: The keyword args passed to the format string.

    Raises:
      CommandError
    """
    print
    raise command_base.CommandError(text.format(*args, **kwargs))
Example #16
0
  def _delete_and_recreate_instances(
      self, instances_to_mv, instances_to_ignore, dest_zone):
    """Deletes instances_to_mv and re-creates them in dest_zone.

    Args:
      instances_to_mv: The instances to delete and recreated in dest_zone.
      dest_zone: The destination zone.

    Raises:
      CommandError: If either the deletion or the insertion of instances
        fails or if the user aborts the move.
    """
    if not self._confirm(instances_to_mv, instances_to_ignore, dest_zone):
      self._raise_command_error('Move aborted.')

    utils.simple_print('Deleting instances...')
    res = self._api.delete_instances(instances_to_mv)
    errors = sorted(set(r.message for r in res
                        if isinstance(r, gce.GceError) and r.status != 404))
    if errors:
      raise command_base.CommandError(
          'Aborting due to errors while deleting instances:\n{0}'.format(
              utils.list_strings(errors)))
    print 'Done.'

    utils.simple_print('Clearing unreserved IP addresses...')
    ip_addresses = set(self._api.get_project().externalIpAddresses or [])
    self._set_ips(instances_to_mv, ip_addresses)
    print 'Done.'

    utils.simple_print('Recreating instances in {0}...', dest_zone)
    res = self._api.insert_instances(instances_to_mv, zone=dest_zone)
    errors = sorted(set(r.message for r in res if isinstance(r, gce.GceError)))
    if errors:
      raise command_base.CommandError(
          'Aborting due to errors while creating instances:\n{0}'.format(
              utils.list_strings(errors)))
    LOGGER.debug('Insert results: %s', res)
    print 'Done.'
Example #17
0
  def Handle(self, snapshot_name):
    """Add the specified snapshot.

    Args:
      snapshot_name: The name of the snapshot to add

    Returns:
      The result of inserting the snapshot.
    """
    if not self._flags.source_disk:
      disk = self._PromptForDisk()
      if not disk:
        raise command_base.CommandError(
            'You cannot create a snapshot if you have no disks.')
      self._flags.source_disk = disk['name']

    zone = 'unused'
    if self._IsUsingAtLeastApiVersion('v1beta14'):
      zone = self.GetZoneForResource(self._disks_api, self._flags.source_disk)

    source_disk = self.NormalizePerZoneResourceName(
        self._project,
        zone,
        'disks',
        self._flags.source_disk)

    kwargs = {
        'project': self._project,
    }

    snapshot_resource = {
        'kind': self._GetResourceApiKind('snapshot'),
        'name': self.DenormalizeResourceName(snapshot_name),
        'description': self._flags.description,
        'sourceDisk': source_disk
        }

    kwargs['body'] = snapshot_resource
    snapshot_request = self._snapshots_api.insert(**kwargs)

    result = snapshot_request.execute()

    if self._flags.wait_until_complete:
      result = self.WaitForOperation(self._flags, time, result)
      if not result.get('error'):
        result = self._InternalGetSnapshot(snapshot_name)
        result = self._WaitUntilSnapshotIsComplete(result, snapshot_name)

    return result
Example #18
0
  def _WaitForSnapshots(self, snapshots):
    """Waits for the given snapshots to be in the READY state."""
    snapshots = set(snapshots)
    start_sec = time.time()
    while True:
      if time.time() - start_sec > self._flags.max_wait_time:
        raise command_base.CommandError(
            'Timeout reached while waiting for snapshots to be ready.')

      all_snapshots = [
          s for s in utils.All(self._snapshots_api.list, self._project)['items']
          if s['name'] in snapshots and s['status'] != 'READY']
      if not all_snapshots:
        break
      LOGGER.info('Waiting for snapshots to be READY. Sleeping for %ss' %
                  self._flags.sleep_between_polls)
      time.sleep(self._flags.sleep_between_polls)
Example #19
0
    def _CreateInstances(self, instances, src_zone, dest_zone):
        """Creates the instance resources in the given list in dest_zone.

    The instance resources are changed in two ways:
      (1) Their zone fields are changed to dest_zone; and
      (2) Their ephemeral IPs are cleared.

    Args:
      instances: A list of instance resources.
      src_zone: The zone to which the instances belong.
      dest_zone: The destination zone.

    Raises:
      CommandError: If one or more of the insertions fail.
    """
        if not instances:
            return

        print 'Recreating instances in %s...' % dest_zone
        ip_addresses = set(
            self._project_resource.get('externalIpAddresses', []))
        self._SetIps(instances, ip_addresses)

        requests = []
        for instance in instances:
            instance['zone'] = self.NormalizeTopLevelResourceName(
                self._project, 'zones', dest_zone)

            # Replaces the zones for the persistent disks.
            for disk in instance['disks']:
                if 'source' in disk:
                    disk['source'] = disk['source'].replace(
                        'zones/' + src_zone, 'zones/' + dest_zone)

            requests.append(
                self._instances_api.insert(project=self._project,
                                           body=instance,
                                           zone=dest_zone))
        results, exceptions = self.ExecuteRequests(requests,
                                                   collection_name='instances')
        if exceptions:
            raise command_base.CommandError(
                'Aborting due to errors while creating instances:\n%s' %
                utils.ListStrings(exceptions))
        self._CheckForErrorsInOps(self.MakeListResult(results,
                                                      'operationList'))
Example #20
0
    def _AddAuthorizedUserKeyToProject(self, authorized_user_key):
        """Update the project to include the specified user/key pair.

    Args:
      authorized_user_key: A dictionary of a user/key pair for the user.

    Returns:
      True iff the ssh key was added to the project.

    Raises:
      command_base.CommandError: If the metadata update fails.
    """
        project = self._projects_api.get(project=self._project).execute()
        common_instance_metadata = project.get('commonInstanceMetadata', {})

        project_metadata = common_instance_metadata.get('items', [])
        project_ssh_keys = ssh_keys.SshKeys.GetAuthorizedUserKeysFromMetadata(
            project_metadata)
        if authorized_user_key in project_ssh_keys:
            return False
        else:
            project_ssh_keys.append(authorized_user_key)
            ssh_keys.SshKeys.SetAuthorizedUserKeysInMetadata(
                project_metadata, project_ssh_keys)

            try:
                request = self._projects_api.setCommonInstanceMetadata(
                    project=self._project,
                    body={
                        'kind': self._GetResourceApiKind('metadata'),
                        'items': project_metadata
                    })
                request.execute()
            except errors.HttpError:
                # A failure to add the ssh key probably means that the project metadata
                # has exceeded the max size. The user needs to either manually
                # clean up their project metadata, or set the ssh keys manually for this
                # instance. Either way, trigger a usage error to let them know.
                raise command_base.CommandError(
                    'Unable to add the local ssh key to the project. Either manually '
                    'remove some entries from the commonInstanceMetadata field of the '
                    'project, or explicitly set the authorized keys for this instance.'
                )
            return True
Example #21
0
    def _GetSshAddress(self, instance_resource):
        """Retrieve the ssh address from the passed instance resource data.

    Args:
      instance_resource: The resource data of the instance for which
        to retrieve the ssh address.

    Returns:
      The ssh address and port.

    Raises:
      command_base.CommandError: If the instance has no external address.
    """
        external_addresses = self._ExtractExternalIpFromInstanceRecord(
            instance_resource)
        if len(external_addresses) < 1:
            raise command_base.CommandError(
                'Cannot connect to an instance with no external address')

        return (external_addresses[0], self._flags.ssh_port)
Example #22
0
    def _GetInstanceResource(self, instance_name):
        """Get the instance resource. This is the dictionary returned by the API.

    Args:
      instance_name: The name of the instance to retrieve the ssh address for.

    Returns:
      The data for the instance resource as returned by the API.

    Raises:
      command_base.CommandError: If the instance does not exist.
    """
        instance_name = self._DenormalizeResourceName(instance_name)
        request = self._instances_api.get(project=self._project,
                                          instance=instance_name)
        result = request.execute()
        if not result:
            raise command_base.CommandError('Unable to find the instance %s.' %
                                            (instance_name))
        return result
Example #23
0
    def RunWithFlagsAndPositionalArgs(self, flag_values,
                                      unused_pos_arg_values):
        """Run the command, returning the result.

    Args:
      flag_values: The parsed FlagValues instance.
      unused_pos_arg_values: The positional args.

    Raises:
      command_base.CommandError: If valid credentials cannot be retrieved.

    Returns:
      0 if the command completes successfully, otherwise 1.

    Raises:
      CommandError: if valid credentials are not located.
    """
        cred = auth_helper.GetCredentialFromStore(
            scopes=scopes.DEFAULT_AUTH_SCOPES,
            ask_user=not flag_values.just_check_auth,
            force_reauth=flag_values.force_reauth)
        if not cred:
            raise command_base.CommandError(
                'Could not get valid credentials for API.')

        if flag_values.confirm_email:
            http = self._AuthenticateWrapper(httplib2.Http())
            resp, content = http.request(
                'https://www.googleapis.com/userinfo/v2/me', 'GET')
            if resp.status != 200:
                LOGGER.info('Could not get user info for token.  <%d %s>',
                            resp.status, resp.reason)
            userinfo = json.loads(content)
            if 'email' in userinfo and userinfo['email']:
                LOGGER.info('Authorization succeeded for user %s',
                            userinfo['email'])
            else:
                LOGGER.info('Could not get email for token.')
        else:
            LOGGER.info('Authentication succeeded.')
        return (None, [])
Example #24
0
    def _Confirm(self, instances_to_mv, instances_to_ignore, disks_to_mv,
                 dest_zone):
        """Displays what is about to happen and prompts the user to proceed.

    Args:
      instances_to_mv: The instances that will be moved.
      instances_to_ignore: Instances that will not be moved because they're
        already in the destination zone.
      disks_to_mv: A list of the disk names that will be moved.
      dest_zone: The destination zone.

     Raises:
       CommandError: If the user declines to proceed.
    """
        # Ensures that the parameters make sense.
        assert instances_to_mv, (
            'Cannot confirm move if there are no instances to move.')
        assert not [
            i for i in instances_to_mv if i['zone'].endswith(dest_zone)
        ], ('Some instances in the move set are already in the destination zone.'
            )
        assert ([
            i for i in instances_to_ignore if i['zone'].endswith(dest_zone)
        ] == instances_to_ignore), (
            'Not all instances in ignore set are in destination zone.')

        if instances_to_ignore:
            print('These instances are already in %s and will not be moved:' %
                  dest_zone)
            print utils.ListStrings(i['name'] for i in instances_to_ignore)

        print 'The following instances will be moved to %s:' % dest_zone
        print utils.ListStrings(i['name'] for i in instances_to_mv)

        if disks_to_mv:
            print 'The following disks will be moved to %s:' % dest_zone
            print utils.ListStrings(disks_to_mv)

        if not self._flags.force and not utils.Proceed():
            raise command_base.CommandError('Move aborted.')
Example #25
0
    def Handle(self, snapshot_name):
        """Add the specified snapshot.

    Args:
      snapshot_name: The name of the snapshot to add

    Returns:
      The result of inserting the snapshot.
    """
        if not self._flags.source_disk:
            disk = self._PromptForDisk()
            if not disk:
                raise command_base.CommandError(
                    'You cannot create a snapshot if you have no disks.')
            self._flags.source_disk = disk['name']

        source_disk = self.NormalizeResourceName(self._project, 'disks',
                                                 self._flags.source_disk)

        snapshot_resource = {
            'kind': self._GetResourceApiKind('snapshot'),
            'name': self._DenormalizeResourceName(snapshot_name),
            'description': self._flags.description,
            'sourceDisk': source_disk
        }

        snapshot_request = self._snapshots_api.insert(project='%s' %
                                                      self._project,
                                                      body=snapshot_resource)

        result = snapshot_request.execute()

        if self._flags.wait_until_complete:
            result = self.WaitForOperation(self._flags, time, result)
            if not result.get('error'):
                result = self._InternalGetSnapshot(snapshot_name)
                result = self._WaitUntilSnapshotIsComplete(
                    result, snapshot_name)

        return result
Example #26
0
    def _EnsureSshable(self, instance_resource):
        """Ensure that the user can ssh into the specified instance.

    This method checks if the instance has SSH keys defined for it, and if
    it does not this makes sure the enclosing project contains a metadata
    entry for the user's public ssh key.

    If the project is updated to add the user's ssh key, then this method
    waits for the amount of time specified by the wait_time_for_ssh_key_push
    flag for the change to cascade down to the instance.

    Args:
      instance_resource: The resource data for the instance to which to connect.

    Raises:
      command_base.CommandError: If the instance is not in the RUNNING state.
    """
        instance_status = instance_resource.get('status')
        if instance_status != 'RUNNING':
            raise command_base.CommandError(
                'Cannot connect to the instance since its current status is %s.'
                % instance_status)

        instance_metadata = instance_resource.get('metadata', {})

        instance_ssh_key_entries = ([
            entry for entry in instance_metadata.get('items', [])
            if entry.get('key') == 'sshKeys'
        ])

        if not instance_ssh_key_entries:
            if self._AddComputeKeyToProject():
                wait_time = self._flags.ssh_key_push_wait_time
                LOGGER.info(
                    'Updated project with new ssh key. It can take several '
                    'minutes for the instance to pick up the key.')
                LOGGER.info('Waiting %s seconds before attempting to connect.',
                            wait_time)
                time.sleep(wait_time)
Example #27
0
    def Handle(self):
        """Set the metadata common to all instances in the specified project.

    Args:
      None.

    Returns:
      The result of setting the project wide metadata.

    Raises:

      command_base.CommandError: If the update would cause some metadata to
        be deleted.
    """
        new_metadata = self._metadata_flags_processor.GatherMetadata()

        if not self._flags.force:
            get_request = self._projects_api.get(project=self._flags.project)
            project_resource = get_request.execute()
            project_metadata = project_resource.get('commonInstanceMetadata',
                                                    [])
            if 'kind' in project_metadata:
                project_metadata = project_metadata.get('items', [])
            existing_keys = set([entry['key'] for entry in project_metadata])
            new_keys = set([entry['key'] for entry in new_metadata])
            dropped_keys = existing_keys - new_keys
            if dropped_keys:
                raise command_base.CommandError(
                    'Discarding update that would wipe out the following metadata: %s.'
                    '\n\nRe-run with the -f flag to force the update.' %
                    ', '.join(list(dropped_keys)))

        project_request = self._projects_api.setCommonInstanceMetadata(
            project=self._flags.project,
            body={
                'kind': self._GetResourceApiKind('metadata'),
                'items': new_metadata
            })
        return project_request.execute()
Example #28
0
    def _ValidateFlags(self):
        """Validate flags coming in before we start building resources.

    Raises:
      app.UsageError: If service account explicitly given without scopes.
      command_base.CommandError: If scopes contains ' '.
    """
        if (self._flags.service_account
                and self._flags.service_account_scopes):
            # Ensures that the user did not space-delimit his or her scopes
            # list.
            for scope in self._flags.service_account_scopes:
                if ' ' in scope:
                    raise command_base.CommandError(
                        'Scopes list must be comma-delimited, not space-delimited.'
                    )
        elif self._flags['service_account'].present:
            raise app.UsageError(
                '--service_account given without --service_account_scopes.')

        if self._flags.wait_until_running and not self._flags.synchronous_mode:
            LOGGER.warn('wait_until_running set. Implying synchronous_mode.')
            self._flags.synchronous_mode = True
Example #29
0
    def Handle(self, *instance_names):
        """Add the specified instance.

    Args:
      *instance_names: A list of instance names to add.

    Returns:
      A tuple of (result, exceptions)
    """
        if not instance_names:
            raise app.UsageError('You must specify at least one instance name')

        if len(instance_names) > 1 and self._flags.disk:
            raise command_base.CommandError(
                'Specifying a disk when starting multiple instances is not '
                'currently supported')

        if max([len(i) for i in instance_names]) > 32:
            LOGGER.warn(
                'Hostnames longer than 32 characters have known issues with '
                'some linux distributions.')

        self._flags.zone = self._GetZone(
            self._flags.zone or self._FindDefaultZone(self._flags.disk))
        if not self._flags.machine_type:
            self._flags.machine_type = self._PromptForMachineType()['name']

        disks = [self._BuildAttachedDisk(disk) for disk in self._flags.disk]

        instance_metadata = self._metadata_flags_processor.GatherMetadata()
        if self._flags.authorized_ssh_keys or self._flags.use_compute_key:
            instance_metadata = self._AddSshKeysToMetadata(instance_metadata)

        if self._flags.add_compute_key_to_project or (
                self._flags.add_compute_key_to_project is None
                and not 'sshKeys'
                in [entry.get('key', '') for entry in instance_metadata]):
            try:
                self._AddComputeKeyToProject()
            except ssh_keys.UserSetupError as e:
                LOGGER.warn('Could not generate compute ssh key: %s', e)

        self._ValidateFlags()

        requests = []
        for instance_name in instance_names:
            instance_disks = disks
            requests.append(
                self._BuildRequestWithMetadata(instance_name,
                                               instance_metadata,
                                               instance_disks))

        (results, exceptions) = self.ExecuteRequests(requests)

        if self._flags.wait_until_running:
            instances_to_wait = results
            results = []
            for result in instances_to_wait:
                if self.IsResultAnOperation(result):
                    results.append(result)
                else:
                    instance_name = result['name']
                    get_request = self._instances_api.get(
                        project=self._project,
                        instance=self._DenormalizeResourceName(instance_name))
                    instance_result = get_request.execute()
                    instance_result = self._WaitUntilInstanceIsRunning(
                        instance_result, instance_name)
                    results.append(instance_result)

        if self._flags.synchronous_mode:
            return (self.MakeListResult(results, 'instanceList'), exceptions)
        else:
            return (self.MakeListResult(results, 'operationList'), exceptions)
Example #30
0
  def HandleMove(self, log_path):
    """Attempts the move dictated in the given log file.

    This method first checks the current state of the project to see
    which instances have already been moved before moving the
    instances that were left behind in a previous failed move.

    The user is prompted to continue before any changes are made.

    Args:
      log_path: The path to the replay log.
    """
    if not os.path.exists(log_path):
      raise command_base.CommandError('File not found: %s' % log_path)

    log = self._ParseLog(log_path)

    src_zone = self._GetKey(log, 'src_zone')
    print 'Source zone is %s.' % src_zone

    dest_zone = self._GetKey(log, 'dest_zone')
    print 'Destination zone is %s.' % dest_zone

    snapshot_mappings = self._GetKey(log, 'snapshot_mappings')
    instances_to_mv = self._GetKey(log, 'instances')

    instances_in_dest = utils.All(
        self._instances_api.list, self._project, zone=dest_zone)['items']
    instances_in_source = utils.All(
        self._instances_api.list, self._project, zone=src_zone)['items']

    # Note that we cannot use normal set intersection and subtraction
    # because two different instance resources could be referring to
    # the same instance (e.g., the instance was restarted by the
    # system).
    instances_to_ignore = self._Intersect(instances_to_mv, instances_in_dest)
    instances_to_mv = self._Subtract(instances_to_mv, instances_in_dest)

    if not instances_to_mv:
      raise command_base.CommandError(
          'All instances are already in %s.' % dest_zone)

    # Figures out which disks have not been moved.
    disks_in_dest = set(utils.AllNames(
        self._disks_api.list, self._project, zone=dest_zone))
    disks_in_src = set(utils.AllNames(
        self._disks_api.list, self._project, zone=src_zone))

    disks_to_mv = set(snapshot_mappings.keys()) & disks_in_src

    instances_to_delete = self._Intersect(instances_to_mv, instances_in_source)

    # For the disks that are still in the source zone, figures out
    # which ones still need to be snapshotted before being deleted.
    snapshot_mappings_for_unmoved_disks = {}
    if disks_to_mv:
      current_snapshots = utils.AllNames(
          self._snapshots_api.list, self._project)

      for disk, snapshot in snapshot_mappings.iteritems():
        if disk in disks_to_mv and snapshot not in current_snapshots:
          snapshot_mappings_for_unmoved_disks[disk] = snapshot

    # Ensures that the current quotas can support the move and prompts
    # the user for confirmation.
    self._CheckQuotas(instances_to_mv, disks_to_mv, src_zone, dest_zone,
                      snapshots_to_create=snapshot_mappings_for_unmoved_disks)
    self._Confirm(instances_to_mv, instances_to_ignore,
                  disks_to_mv, dest_zone)

    self._DeleteInstances(instances_to_delete, src_zone)
    self._CreateSnapshots(snapshot_mappings_for_unmoved_disks,
                          src_zone, dest_zone)
    self._DeleteDisks(disks_to_mv, src_zone)

    # Create disks in destination zone from snapshots.
    all_snapshots = set(utils.AllNames(
        self._snapshots_api.list, self._project))
    disks_to_create = {}
    for disk, snapshot in snapshot_mappings.iteritems():
      if snapshot in all_snapshots and disk not in disks_in_dest:
        disks_to_create[disk] = snapshot
    self._CreateDisksFromSnapshots(disks_to_create, dest_zone)

    self._CreateInstances(instances_to_mv, src_zone, dest_zone)
    self._DeleteSnapshots(disks_to_create.values(), dest_zone)

    if not self._flags.keep_log_file:
      # We have succeeded, so it's safe to delete the log file.
      os.remove(log_path)