Esempio n. 1
0
def WaitForCondition(poller, error_class):
  """Wait for a configuration to be ready in latest revision.

  Args:
    poller: A serverless_operations.ConditionPoller object.
    error_class: Error to raise on timeout failure

  Returns:
    A googlecloudsdk.command_lib.run.condition.Conditions object.

  Raises:
    error_class: Max retry limit exceeded.
  """

  try:
    return waiter.PollUntilDone(
        poller,
        None,
        max_wait_ms=_POLLING_TIMEOUT_MS,
        wait_ceiling_ms=_RETRY_TIMEOUT_MS)
  except retry.RetryException:
    conditions = poller.GetConditions()
    # err.message already indicates timeout. Check ready_cond_type for more
    # information.
    msg = conditions.DescriptiveMessage() if conditions else None
    raise error_class(msg)
Esempio n. 2
0
    def WaitForCondition(self, poller):
        """Wait for a configuration to be ready in latest revision.

    Args:
      poller: A ConditionPoller object.

    Returns:
      A condition.Conditions object.

    Raises:
      RetryException: Max retry limit exceeded.
      ConfigurationError: configuration failed to
    """

        try:
            return waiter.PollUntilDone(poller, None, wait_ceiling_ms=1000)
        except retry.RetryException as err:
            conditions = poller.GetConditions()
            # err.message already indicates timeout. Check ready_cond_type for more
            # information.
            msg = conditions.DescriptiveMessage() if conditions else None
            if msg:
                log.error('Still waiting: {}'.format(msg))
            raise err
        if not conditions.IsReady():
            raise serverless_exceptions.ConfigurationError(
                conditions.DescriptiveMessage())
Esempio n. 3
0
 def WaitForCondition(self, getter):
   """Wait for a configuration to be ready in latest revision."""
   stages = _ServiceStages()
   with progress_tracker.StagedProgressTracker(
       'Deploying...',
       stages.values(),
       failure_message='Deployment failed') as tracker:
     config_poller = ConditionPoller(getter, tracker, stages, dependencies={
         'RoutesReady': {'ConfigurationsReady'},
     })
     try:
       conditions = waiter.PollUntilDone(
           config_poller, None,
           wait_ceiling_ms=1000)
     except retry.RetryException as err:
       conditions = config_poller.GetConditions()
       # err.message already indicates timeout. Check ready_cond_type for more
       # information.
       msg = conditions.DescriptiveMessage() if conditions else None
       if msg:
         log.error('Still waiting: {}'.format(msg))
       raise err
     if not conditions.IsReady():
       raise serverless_exceptions.ConfigurationError(
           conditions.DescriptiveMessage())
def _WaitForOperation(client, operation, resource_type):
  """Waits for an operation to complete.

  Args:
    client:  GAPIC API client, client used to make requests.
    operation: run_apps.v1alpha1.operation, object to wait for.
    resource_type: type, the expected type of resource response

  Returns:
    The resulting resource of input paramater resource_type.
  """
  poller = waiter.CloudOperationPoller(resource_type,
                                       client.projects_locations_operations)
  operation_ref = resources.REGISTRY.ParseRelativeName(
      operation.name,
      collection='{}.projects.locations.operations'.format(API_NAME))
  try:
    return poller.GetResult(waiter.PollUntilDone(
        poller,
        operation_ref,
        max_wait_ms=_POLLING_TIMEOUT_MS,
        wait_ceiling_ms=_RETRY_TIMEOUT_MS))
  except waiter.OperationError:
    operation = poller.Poll(operation_ref)
    raise exceptions.IntegrationsOperationError(
        'OperationError: code={0}, message={1}'.format(
            operation.error.code, encoding.Decode(operation.error.message)))
Esempio n. 5
0
  def CreateDomainMapping(self,
                          domain_mapping_ref,
                          service_name,
                          config_changes,
                          force_override=False):
    """Create a domain mapping.

    Args:
      domain_mapping_ref: Resource, domainmapping resource.
      service_name: str, the service to which to map domain.
      config_changes: list of ConfigChanger to modify the domainmapping with
      force_override: bool, override an existing mapping of this domain.

    Returns:
      A domain_mapping.DomainMapping object.
    """

    messages = self.messages_module
    new_mapping = domain_mapping.DomainMapping.New(
        self._client, domain_mapping_ref.namespacesId)
    new_mapping.name = domain_mapping_ref.domainmappingsId
    new_mapping.route_name = service_name
    new_mapping.force_override = force_override

    for config_change in config_changes:
      new_mapping = config_change.Adjust(new_mapping)

    request = messages.RunNamespacesDomainmappingsCreateRequest(
        domainMapping=new_mapping.Message(),
        parent=domain_mapping_ref.Parent().RelativeName())
    with metrics.RecordDuration(metric_names.CREATE_DOMAIN_MAPPING):
      try:
        response = self._client.namespaces_domainmappings.Create(request)
      except api_exceptions.HttpConflictError:
        raise serverless_exceptions.DomainMappingCreationError(
            'Domain mapping to [{}] for service [{}] already exists.'.format(
                domain_mapping_ref.Name(), service_name))
      # 'run domain-mappings create' is synchronous. Poll for its completion.x
      with progress_tracker.ProgressTracker('Creating...'):
        mapping = waiter.PollUntilDone(
            DomainMappingResourceRecordPoller(self), domain_mapping_ref)
      ready = mapping.conditions.get('Ready')
      message = None
      if ready and ready.get('message'):
        message = ready['message']
      if not mapping.records:
        if (mapping.ready_condition['reason'] ==
            domain_mapping.MAPPING_ALREADY_EXISTS_CONDITION_REASON):
          raise serverless_exceptions.DomainMappingAlreadyExistsError(
              'Domain mapping to [{}] is already in use elsewhere.'.format(
                  domain_mapping_ref.Name()))
        raise serverless_exceptions.DomainMappingCreationError(
            message or 'Could not create domain mapping.')
      if message:
        log.status.Print(message)
      return mapping

    return domain_mapping.DomainMapping(response, messages)
Esempio n. 6
0
    def _GetBaseRevision(self, config, metadata, status):
        """Return a Revision for use as the "base revision" for a change.

    When making a change that should not affect the code running, the
    "base revision" is the revision that we should lock the code to - it's where
    we get the digest for the image to run.

    Getting this revision:
      * If there's a nonce in the revisonTemplate metadata, use that
      * If that query produces >1 or produces 0 after a short timeout, use
        the latestCreatedRevision in status.

    Arguments:
      config: Configuration, the configuration to get the base revision of.
        May have been derived from a Service.
      metadata: ObjectMeta, the metadata from the top-level object
      status: Union[ConfigurationStatus, ServiceStatus], the status of the top-
        level object.

    Returns:
      The base revision of the configuration.
    """
        # Or returns None if not available by nonce & the control plane has not
        # implemented latestCreatedRevisionName on the Service object yet.
        base_revision_nonce = config.revision_labels.get(NONCE_LABEL, None)
        base_revision = None
        if base_revision_nonce:
            try:
                namespace_ref = self._registry.Parse(
                    metadata.namespace, collection='run.namespaces')
                poller = NonceBasedRevisionPoller(self, namespace_ref)
                base_revision = poller.GetResult(
                    waiter.PollUntilDone(poller,
                                         base_revision_nonce,
                                         sleep_ms=500,
                                         max_wait_ms=2000))
            except retry.WaitException:
                pass
        # Nonce polling didn't work, because some client didn't post one or didn't
        # change one. Fall back to the (slightly racy) `latestCreatedRevisionName`.
        if not base_revision:
            # TODO(b/117663680) Getattr -> normal access.
            if getattr(status, 'latestCreatedRevisionName', None):
                # Get by latestCreatedRevisionName
                revision_ref = self._registry.Parse(
                    status.latestCreatedRevisionName,
                    params={'namespacesId': metadata.namespace},
                    collection='run.namespaces.revisions')
                base_revision = self.GetRevision(revision_ref)
        return base_revision
Esempio n. 7
0
    def CreateDomainMapping(self, domain_mapping_ref, service_name):
        """Create a domain mapping.

    Args:
      domain_mapping_ref: Resource, domainmapping resource.
      service_name: str, the service to which to map domain.

    Returns:
      A domain_mapping.DomainMapping object.
    """

        messages = self._messages_module
        new_mapping = domain_mapping.DomainMapping.New(
            self._client, domain_mapping_ref.namespacesId)
        new_mapping.name = domain_mapping_ref.domainmappingsId
        new_mapping.route_name = service_name

        request = messages.RunNamespacesDomainmappingsCreateRequest(
            domainMapping=new_mapping.Message(),
            parent=domain_mapping_ref.Parent().RelativeName())
        with metrics.RecordDuration(metric_names.CREATE_DOMAIN_MAPPING):
            try:
                response = self._client.namespaces_domainmappings.Create(
                    request)
            except api_exceptions.HttpConflictError:
                raise serverless_exceptions.DomainMappingCreationError(
                    'Domain mapping to [{}] for service [{}] already exists.'.
                    format(domain_mapping_ref.Name(), service_name))
            # 'run domain-mappings create' is synchronous. Poll for its completion.x
            with progress_tracker.ProgressTracker('Creating...'):
                mapping = waiter.PollUntilDone(
                    DomainMappingResourceRecordPoller(self),
                    domain_mapping_ref)
            ready = mapping.conditions.get('Ready')
            records = getattr(mapping.status, 'resourceRecords', None)
            message = None
            if ready and ready.get('message'):
                message = ready['message']
            if not records:
                raise serverless_exceptions.DomainMappingCreationError(
                    message or 'Could not create domain mapping.')
            if message:
                log.status.Print(message)
            return records

        return domain_mapping.DomainMapping(response, messages)
def WaitForOperation(operation):
  """Silently waits for the given google.longrunning.Operation to complete.

  Args:
    operation: The operation to poll.

  Raises:
    apitools.base.py.HttpError: if the request returns an HTTP error

  Returns:
    The response field of the completed operation.
  """
  op_service = ods_util.GetClient().projects_locations_operations
  op_resource = resources.REGISTRY.ParseRelativeName(
      operation.name,
      collection='ondemandscanning.projects.locations.operations')
  poller = waiter.CloudOperationPollerNoResources(op_service)
  return waiter.PollUntilDone(poller, op_resource)
Esempio n. 9
0
def Delete(ref, getter, deleter, async_):
    """Deletes a resource for a surface, including a pretty progress tracker."""
    if async_ is None:
        async_ = platforms.GetPlatform() != platforms.PLATFORM_MANAGED
    if async_:
        deleter(ref)
        return
    poller = DeletionPoller(getter)
    with progress_tracker.ProgressTracker(
            message='Deleting [{}]'.format(ref.Name()),
            detail_message_callback=poller.GetMessage):
        deleter(ref)
        res = waiter.PollUntilDone(poller, ref)
        if res:
            if poller.GetMessage():
                raise serverless_exceptions.DeletionFailedError(
                    'Failed to delete [{}]: {}.'.format(
                        ref.Name(), poller.GetMessage()))
            else:
                raise serverless_exceptions.DeletionFailedError(
                    'Failed to delete [{}].'.format(ref.Name()))
Esempio n. 10
0
  def WaitForCondition(self, getter):
    """Wait for a configuration to be ready in latest revision."""
    with progress_tracker.StagedProgressTracker(
        'Deploying...',
        _CONDITION_TO_STAGE.values(),
        failure_message='Deployment failed') as tracker:
      for stage in _CONDITION_TO_STAGE.values():
        tracker.StartStage(stage)

      config_poller = ConditionPoller(getter, tracker)
      try:
        conditions = waiter.PollUntilDone(config_poller, None)
      except retry.RetryException as err:
        conditions = config_poller.GetConditions()
        # err.message already indicates timeout. Check ready_cond_type for more
        # information.
        msg = conditions.DescriptiveMessage() if conditions else None
        if msg:
          log.error('Still waiting: {}'.format(msg))
        raise err
      if not conditions.IsReady():
        raise serverless_exceptions.ConfigurationError(
            conditions.DescriptiveMessage())
  def ReleaseService(self,
                     service_ref,
                     config_changes,
                     tracker=None,
                     asyn=False,
                     allow_unauthenticated=None,
                     for_replace=False,
                     prefetch=False,
                     build_op_ref=None,
                     build_log_url=None):
    """Change the given service in prod using the given config_changes.

    Ensures a new revision is always created, even if the spec of the revision
    has not changed.

    Arguments:
      service_ref: Resource, the service to release.
      config_changes: list, objects that implement Adjust().
      tracker: StagedProgressTracker, to report on the progress of releasing.
      asyn: bool, if True, return without waiting for the service to be updated.
      allow_unauthenticated: bool, True if creating a hosted Cloud Run
        service which should also have its IAM policy set to allow
        unauthenticated access. False if removing the IAM policy to allow
        unauthenticated access from a service.
      for_replace: bool, If the change is for a replacing the service from a
        YAML specification.
      prefetch: the service, pre-fetched for ReleaseService. `False` indicates
        the caller did not perform a prefetch; `None` indicates a nonexistant
        service.
      build_op_ref: The reference to the build.
      build_log_url: The log url of the build result.
    """
    if tracker is None:
      tracker = progress_tracker.NoOpStagedProgressTracker(
          stages.ServiceStages(allow_unauthenticated is not None),
          interruptable=True, aborted_message='aborted')
    if build_op_ref is not None:
      tracker.StartStage(stages.BUILD_READY)
      tracker.UpdateHeaderMessage('Building Container.')
      tracker.UpdateStage(
          stages.BUILD_READY, 'Logs are available at [{build_log_url}].'.format(
              build_log_url=build_log_url))
      client = cloudbuild_util.GetClientInstance()
      poller = waiter.CloudOperationPoller(client.projects_builds,
                                           client.operations)
      operation = waiter.PollUntilDone(poller, build_op_ref)
      response_dict = encoding.MessageToPyValue(operation.response)
      if response_dict and response_dict['status'] != 'SUCCESS':
        tracker.FailStage(
            stages.BUILD_READY, None,
            message='Container build failed and '
            'logs are available at [{build_log_url}].'.format(
                build_log_url=build_log_url))
        return
      else:
        tracker.CompleteStage(stages.BUILD_READY)
    if prefetch is None:
      serv = None
    else:
      serv = prefetch or self.GetService(service_ref)
    if for_replace:
      with_image = True
    else:
      with_image = any(
          isinstance(c, config_changes_mod.ImageChange) for c in config_changes)
      self._AddRevisionForcingChange(serv, config_changes)
      if serv and not with_image:
        # Avoid changing the running code by making the new revision by digest
        self._EnsureImageDigest(serv, config_changes)
    config_changes = [_SetClientNameAndVersion()] + config_changes

    self._UpdateOrCreateService(
        service_ref, config_changes, with_image, serv)

    if allow_unauthenticated is not None:
      try:
        tracker.StartStage(stages.SERVICE_IAM_POLICY_SET)
        tracker.UpdateStage(stages.SERVICE_IAM_POLICY_SET, '')
        self.AddOrRemoveIamPolicyBinding(service_ref, allow_unauthenticated,
                                         ALLOW_UNAUTH_POLICY_BINDING_MEMBER,
                                         ALLOW_UNAUTH_POLICY_BINDING_ROLE)
        tracker.CompleteStage(stages.SERVICE_IAM_POLICY_SET)
      except api_exceptions.HttpError:
        warning_message = (
            'Setting IAM policy failed, try "gcloud beta run services '
            '{}-iam-policy-binding --region={region} --member=allUsers '
            '--role=roles/run.invoker {service}"'.format(
                'add' if allow_unauthenticated else 'remove',
                region=self._region,
                service=service_ref.servicesId))
        tracker.CompleteStageWithWarning(
            stages.SERVICE_IAM_POLICY_SET, warning_message=warning_message)

    if not asyn:
      getter = functools.partial(self.GetService, service_ref)
      poller = ServiceConditionPoller(
          getter,
          tracker,
          dependencies=stages.ServiceDependencies(),
          serv=serv)
      self.WaitForCondition(poller)
      for msg in run_condition.GetNonTerminalMessages(poller.GetConditions()):
        tracker.AddWarning(msg)
  def _GetBaseRevision(self, template, metadata, status):
    """Return a Revision for use as the "base revision" for a change.

    When making a change that should not affect the code running, the
    "base revision" is the revision that we should lock the code to - it's where
    we get the digest for the image to run.

    Getting this revision:
      * If there's a name in the template metadata, use that
      * If there's a nonce in the revisonTemplate metadata, use that
      * If that query produces >1 or 0 after a short timeout, use
        the latestCreatedRevision in status.

    Arguments:
      template: Revision, the revision template to get the base revision of.
        May have been derived from a Service.
      metadata: ObjectMeta, the metadata from the top-level object
      status: Union[ConfigurationStatus, ServiceStatus], the status of the top-
        level object.

    Returns:
      The base revision of the configuration or None if not found by revision
        name nor nonce and latestCreatedRevisionName does not exist on the
        Service object.
    """
    base_revision = None
    # Try to find by revision name
    base_revision_name = template.name
    if base_revision_name:
      try:
        revision_ref_getter = functools.partial(
            self._registry.Parse,
            params={'namespacesId': metadata.namespace},
            collection='run.namespaces.revisions')
        poller = RevisionNameBasedPoller(self, revision_ref_getter)
        base_revision = poller.GetResult(
            waiter.PollUntilDone(
                poller, base_revision_name, sleep_ms=500, max_wait_ms=2000))
      except retry.RetryException:
        pass
    # Name polling didn't work. Fall back to nonce polling
    if not base_revision:
      base_revision_nonce = template.labels.get(revision.NONCE_LABEL, None)
      if base_revision_nonce:
        try:
          # TODO(b/150322097): Remove this when the api has been split.
          # This try/except block is needed because the v1alpha1 and v1 run apis
          # have different collection names for the namespaces.
          try:
            namespace_ref = self._registry.Parse(
                metadata.namespace,
                collection='run.namespaces')
          except resources.InvalidCollectionException:
            namespace_ref = self._registry.Parse(
                metadata.namespace,
                collection='run.api.v1.namespaces')
          poller = NonceBasedRevisionPoller(self, namespace_ref)
          base_revision = poller.GetResult(waiter.PollUntilDone(
              poller, base_revision_nonce,
              sleep_ms=500, max_wait_ms=2000))
        except retry.RetryException:
          pass
    # Nonce polling didn't work, because some client didn't post one or didn't
    # change one. Fall back to the (slightly racy) `latestCreatedRevisionName`.
    if not base_revision:
      # TODO(b/117663680) Getattr -> normal access.
      if getattr(status, 'latestCreatedRevisionName', None):
        # Get by latestCreatedRevisionName
        revision_ref = self._registry.Parse(
            status.latestCreatedRevisionName,
            params={'namespacesId': metadata.namespace},
            collection='run.namespaces.revisions')
        base_revision = self.GetRevision(revision_ref)
    return base_revision
Esempio n. 13
0
 def testPollUntilDone(self):
   waiter.PollUntilDone(poller=OperationPoller(10),
                        operation_ref='operation-X')
   self.AssertOutputEquals('')
   self.AssertErrEquals('')
Esempio n. 14
0
def WaitForApplyLROWithStagedTracker(poller,
                                     operation_ref,
                                     message,
                                     preview=False):
    """Waits for an "apply" deployment/preview LRO using a StagedProgressTracker.

  This function is a wrapper around waiter.PollUntilDone that uses a
  progress_tracker.StagedProgressTracker to display the individual steps of
  an apply deployment or preview LRO.

  Args:
    poller: a waiter.Poller instance
    operation_ref: Reference to the operation to poll on.
    message: string containing the main progress message to display.
    preview: bool, True if it's a preview LRO, False if it's a deployment LRO.

  Returns:
    A response object message from the LRO (i.e. a messages.Preview or
    messages.Deployment).
  """
    messages = GetMessagesModule()
    stages = []
    progress_stages = ApplyProgressStages(preview)
    for key, msg in progress_stages.items():
        stages.append(progress_tracker.Stage(msg, key))

    with progress_tracker.StagedProgressTracker(
            message=message, stages=stages,
            tracker_id='meta.deployment_progress') as tracker:

        def _StatusUpdate(result, status):
            """Updates poller.detailed_message on every tick with an appropriate message.

      Args:
        result: the latest messages.Operation object.
        status: unused.
      """
            del status  # Unused by this logic

            # Need to encode to JSON and then decode to Message to be able to
            # reasonably access attributes.
            json = encoding.MessageToJson(result.metadata)
            deployment_metadata = encoding.JsonToMessage(
                messages.OperationMetadata, json).deploymentMetadata

            if deployment_metadata and deployment_metadata.step and progress_stages.get(
                    deployment_metadata.step.name) is not None:
                tracker.StartStage(deployment_metadata.step.name)

                # Complete all previous stages.
                ordered_stages = list(progress_stages.keys())
                current_index = ordered_stages.index(
                    deployment_metadata.step.name)
                for i in range(current_index):
                    if not tracker.IsComplete(ordered_stages[i]):
                        tracker.CompleteStage(ordered_stages[i])

        try:
            operation = waiter.PollUntilDone(poller,
                                             operation_ref,
                                             status_update=_StatusUpdate,
                                             max_wait_ms=_MAX_WAIT_TIME_MS,
                                             wait_ceiling_ms=_WAIT_CEILING_MS)
        except retry.WaitException:
            # Operation timed out.
            raise waiter.TimeoutError(
                '{0} timed out after {1} seconds. Please retry this operation.'
                .format(operation_ref.Name(), _MAX_WAIT_TIME_MS / 1000))
        result = poller.GetResult(operation)

        if preview:
            # Preview result from the LRO needs to be converted to Message since
            # there is no Get method for preview and poller doesn't automatically
            # do this.
            json = encoding.MessageToJson(result)
            result = encoding.JsonToMessage(messages.Preview, json)
            is_complete = (result.state ==
                           messages.Preview.StateValueValuesEnum.COMPLETED)
        else:
            is_complete = (result.state ==
                           messages.Deployment.StateValueValuesEnum.ACTIVE)

        if result is not None and is_complete:
            for stage in stages:
                if not tracker.IsComplete(stage.key):
                    tracker.CompleteStage(stage.key)

        return result