All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.spinnaker.echo.pubsub.google.GooglePubsubSubscriber Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.spinnaker.echo.pubsub.google;

import com.google.api.core.ApiService;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.Credentials;
import com.google.cloud.pubsub.v1.AckReplyConsumer;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.pubsub.v1.ProjectSubscriptionName;
import com.google.pubsub.v1.PubsubMessage;
import com.netflix.spinnaker.echo.config.GooglePubsubCredentialsProvider;
import com.netflix.spinnaker.echo.config.GooglePubsubProperties.GooglePubsubSubscription;
import com.netflix.spinnaker.echo.model.pubsub.MessageDescription;
import com.netflix.spinnaker.echo.model.pubsub.PubsubSystem;
import com.netflix.spinnaker.echo.pubsub.PubsubMessageHandler;
import com.netflix.spinnaker.echo.pubsub.model.PubsubSubscriber;
import com.netflix.spinnaker.echo.pubsub.utils.NodeIdentity;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class GooglePubsubSubscriber implements PubsubSubscriber {

  private String subscriptionName;

  private String name;

  private String project;

  private Subscriber subscriber;

  private Credentials credentials;

  private MessageReceiver messageReceiver;

  private static final PubsubSystem pubsubSystem = PubsubSystem.GOOGLE;

  public GooglePubsubSubscriber(
      String name,
      String subscriptionName,
      String project,
      Credentials credentials,
      GooglePubsubMessageReceiver messageReceiver) {
    this.name = name;
    this.subscriptionName = subscriptionName;
    this.project = project;
    this.messageReceiver = messageReceiver;
    this.credentials = credentials;
  }

  @Override
  public PubsubSystem getPubsubSystem() {
    return pubsubSystem;
  }

  @Override
  public String getSubscriptionName() {
    return subscriptionName;
  }

  @Override
  public String getName() {
    return name;
  }

  private static String formatSubscriptionName(String project, String name) {
    return String.format("projects/%s/subscriptions/%s", project, name);
  }

  public static GooglePubsubSubscriber buildSubscriber(
      GooglePubsubSubscription subscription, PubsubMessageHandler pubsubMessageHandler) {
    String subscriptionName = subscription.getSubscriptionName();
    String project = subscription.getProject();
    String jsonPath = subscription.getJsonPath();

    GooglePubsubMessageReceiver messageReceiver =
        new GooglePubsubMessageReceiver(
            subscription.getAckDeadlineSeconds(), subscription.getName(), pubsubMessageHandler);

    Credentials credentials = null;
    try {
      credentials = new GooglePubsubCredentialsProvider(jsonPath).getCredentials();
    } catch (IOException e) {
      log.error("Could not create Google Pubsub json credentials: {}", e.getMessage());
    }

    return new GooglePubsubSubscriber(
        subscription.getName(), subscriptionName, project, credentials, messageReceiver);
  }

  public synchronized void start() {
    this.subscriber =
        Subscriber.newBuilder(
                ProjectSubscriptionName.of(project, subscriptionName), messageReceiver)
            .setCredentialsProvider(FixedCredentialsProvider.create(credentials))
            .build();

    subscriber.addListener(
        new GooglePubsubFailureHandler(this, formatSubscriptionName(project, subscriptionName)),
        MoreExecutors.directExecutor());
    subscriber.startAsync().awaitRunning();
    log.info(
        "Google Pubsub subscriber started for {}",
        formatSubscriptionName(project, subscriptionName));
  }

  public void stop() {
    subscriber.stopAsync().awaitTerminated();
  }

  private void restart() {
    try {
      stop();
    } catch (Exception e) {
      log.warn("Failure stopping subscriber: ", e);
    }

    log.info(
        "Waiting to restart Google Pubsub subscriber for {}",
        formatSubscriptionName(project, subscriptionName));
    try {
      // TODO: Use exponential backoff?
      Thread.sleep(1000);
    } catch (InterruptedException e) {
    }

    start();
  }

  private static class GooglePubsubMessageReceiver implements MessageReceiver {

    private int ackDeadlineSeconds;

    private PubsubMessageHandler pubsubMessageHandler;

    /**
     * Logical name given to the subscription by the user, not the locator the pub/sub system uses.
     */
    private String subscriptionName;

    private NodeIdentity identity = new NodeIdentity();

    public GooglePubsubMessageReceiver(
        int ackDeadlineSeconds,
        String subscriptionName,
        PubsubMessageHandler pubsubMessageHandler) {
      this.ackDeadlineSeconds = ackDeadlineSeconds;
      this.subscriptionName = subscriptionName;
      this.pubsubMessageHandler = pubsubMessageHandler;
    }

    @Override
    public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) {
      String messagePayload = message.getData().toStringUtf8();
      String messageId = message.getMessageId();
      Map messageAttributes =
          message.getAttributesMap() == null ? new HashMap<>() : message.getAttributesMap();
      log.debug(
          "Received Google pub/sub message with payload: {}\n and attributes: {}",
          messagePayload,
          messageAttributes);
      MessageDescription description =
          MessageDescription.builder()
              .subscriptionName(subscriptionName)
              .messagePayload(messagePayload)
              .messageAttributes(messageAttributes)
              .pubsubSystem(pubsubSystem)
              .ackDeadlineSeconds(
                  5 * ackDeadlineSeconds) // Set a high upper bound on message processing time.
              .retentionDeadlineSeconds(
                  7 * 24 * 60 * 60) // Expire key after max retention time, which is 7 days.
              .build();
      GoogleMessageAcknowledger acknowledger = new GoogleMessageAcknowledger(consumer);

      pubsubMessageHandler.handleMessage(
          description, acknowledger, identity.getIdentity(), messageId);
    }
  }

  @AllArgsConstructor
  private static class GooglePubsubFailureHandler extends ApiService.Listener {

    private GooglePubsubSubscriber subscriber;
    private String subscriptionName;

    @Override
    public void failed(ApiService.State from, Throwable failure) {
      if (failure.getMessage() != null && failure.getMessage().contains("NOT_FOUND")) {
        log.error(
            "Subscription name {} could not be found (will not retry): ",
            subscriptionName,
            failure);
        return;
      }

      log.error(
          "Google Pubsub listener for subscription name {} failure caused by: ",
          subscriptionName,
          failure);
      subscriber.restart();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy