github.com/google/cloudprober@v0.11.3/rds/gcp/pubsub.go (about)

     1  // Copyright 2018 The Cloudprober Authors.
     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 at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // 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 and
    13  // limitations under the License.
    14  
    15  package gcp
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sort"
    21  	"sync"
    22  	"time"
    23  
    24  	"cloud.google.com/go/pubsub"
    25  	"github.com/golang/protobuf/proto"
    26  	"github.com/google/cloudprober/logger"
    27  	configpb "github.com/google/cloudprober/rds/gcp/proto"
    28  	pb "github.com/google/cloudprober/rds/proto"
    29  	"github.com/google/cloudprober/rds/server/filter"
    30  	"google.golang.org/api/option"
    31  )
    32  
    33  /*
    34  PubSubFilters defines filters supported by the pubsub_messages resource type.
    35   Example:
    36   filter {
    37  	 key: "subscription"
    38  	 value: "sub1.*"
    39   }
    40   filter {
    41  	 key: "updated_within"
    42  	 value: "5m"
    43   }
    44  */
    45  var PubSubFilters = struct {
    46  	RegexFilterKeys    []string
    47  	FreshnessFilterKey string
    48  }{
    49  	[]string{"subscription"},
    50  	"updated_within",
    51  }
    52  
    53  // pubsubMsgsLister is a PubSub Messages lister. It implements a cache,
    54  // that's populated at a regular interval by making the GCP API calls.
    55  // Listing actually only returns the current contents of that cache.
    56  type pubsubMsgsLister struct {
    57  	project    string
    58  	c          *configpb.PubSubMessages
    59  	apiVersion string
    60  	l          *logger.Logger
    61  
    62  	mu     sync.RWMutex // Mutex for names and cache
    63  	cache  map[string]map[string]time.Time
    64  	subs   map[string]*pubsub.Subscription
    65  	maxAge time.Duration
    66  	client *pubsub.Client
    67  }
    68  
    69  // listResources returns the list of resource records, where each record
    70  // consists of a PubSub message name.
    71  func (lister *pubsubMsgsLister) listResources(req *pb.ListResourcesRequest) ([]*pb.Resource, error) {
    72  	allFilters, err := filter.ParseFilters(req.GetFilter(), PubSubFilters.RegexFilterKeys, PubSubFilters.FreshnessFilterKey)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	subFilter, freshnessFilter := allFilters.RegexFilters["subscription"], allFilters.FreshnessFilter
    78  
    79  	lister.mu.RLock()
    80  	defer lister.mu.RUnlock()
    81  
    82  	type msg struct {
    83  		name      string
    84  		timestamp int64
    85  	}
    86  	var result []msg
    87  	for subName, msgs := range lister.cache {
    88  		if subFilter != nil && !subFilter.Match(subName, lister.l) {
    89  			continue
    90  		}
    91  
    92  		for name, publishTime := range msgs {
    93  			if freshnessFilter != nil && !freshnessFilter.Match(publishTime, lister.l) {
    94  				continue
    95  			}
    96  			result = append(result, msg{name, publishTime.Unix()})
    97  		}
    98  	}
    99  
   100  	sort.Slice(result, func(i, j int) bool { return result[i].name < result[j].name })
   101  	resources := make([]*pb.Resource, len(result))
   102  	for i, msg := range result {
   103  		resources[i] = &pb.Resource{
   104  			Name:        proto.String(msg.name),
   105  			LastUpdated: proto.Int64(msg.timestamp),
   106  		}
   107  	}
   108  	return resources, nil
   109  }
   110  
   111  func (lister *pubsubMsgsLister) initSubscriber(ctx context.Context, sub *configpb.PubSubMessages_Subscription) (*pubsub.Subscription, error) {
   112  	s := lister.client.Subscription(sub.GetName())
   113  	seekBackDuration := time.Duration(int(sub.GetSeekBackDurationSec())) * time.Second
   114  
   115  	ok, err := s.Exists(ctx)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	// If subscriber exists already, seek back by seek_back_duration_sec.
   121  	if ok {
   122  		err := s.SeekToTime(ctx, time.Now().Add(-seekBackDuration))
   123  		if err != nil {
   124  			return nil, fmt.Errorf("pubsub: error seeking back time for the subscription: %s", sub.GetName())
   125  		}
   126  		return s, nil
   127  	}
   128  
   129  	s, err = lister.client.CreateSubscription(ctx, sub.GetName(), pubsub.SubscriptionConfig{
   130  		Topic:               lister.client.Topic(sub.GetTopicName()),
   131  		RetainAckedMessages: true,
   132  		RetentionDuration:   seekBackDuration,
   133  		ExpirationPolicy:    24 * time.Hour,
   134  	})
   135  
   136  	if err != nil {
   137  		return nil, fmt.Errorf("pubsub: error creating subscription (%s): %v", sub.GetName(), err)
   138  	}
   139  
   140  	return s, nil
   141  }
   142  
   143  func (lister *pubsubMsgsLister) cleanup() {
   144  	for range time.Tick(lister.maxAge) {
   145  		lister.mu.Lock()
   146  
   147  		for _, msgs := range lister.cache {
   148  			for name, publishTime := range msgs {
   149  				if time.Now().Sub(publishTime) > lister.maxAge {
   150  					lister.l.Infof("pubsub.cleanup: deleting the expired message: %s, publish time: %s", name, publishTime)
   151  					delete(msgs, name)
   152  				}
   153  			}
   154  		}
   155  
   156  		lister.mu.Unlock()
   157  	}
   158  }
   159  
   160  func newPubSubMsgsLister(project string, c *configpb.PubSubMessages, l *logger.Logger) (*pubsubMsgsLister, error) {
   161  	ctx := context.Background()
   162  
   163  	var opts []option.ClientOption
   164  	if c.GetApiEndpoint() != "" {
   165  		opts = append(opts, option.WithEndpoint(c.GetApiEndpoint()))
   166  	}
   167  
   168  	client, err := pubsub.NewClient(ctx, project, opts...)
   169  	if err != nil {
   170  		return nil, fmt.Errorf("pubsub.NewClient: %v", err)
   171  	}
   172  
   173  	lister := &pubsubMsgsLister{
   174  		project: project,
   175  		c:       c,
   176  		cache:   make(map[string]map[string]time.Time),
   177  		client:  client,
   178  		subs:    make(map[string]*pubsub.Subscription),
   179  		l:       l,
   180  		maxAge:  time.Hour,
   181  	}
   182  
   183  	// Start a cleanup goroutine to remove expired entries from the in-memory
   184  	// storage.
   185  	go lister.cleanup()
   186  
   187  	for _, sub := range lister.c.GetSubscription() {
   188  		s, err := lister.initSubscriber(ctx, sub)
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  
   193  		name := sub.GetName()
   194  		lister.subs[name] = s
   195  		lister.cache[name] = make(map[string]time.Time)
   196  
   197  		lister.l.Infof("pubsub: Receiving pub/sub messages for project (%s) and subscription (%s)", lister.project, name)
   198  		go s.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
   199  			lister.mu.Lock()
   200  			defer lister.mu.Unlock()
   201  
   202  			lister.l.Infof("pubsub: Adding message with name: %s, message id: %s, publish time: %s", msg.Attributes["name"], msg.ID, msg.PublishTime)
   203  			lister.cache[name][msg.Attributes["name"]] = msg.PublishTime
   204  			msg.Ack()
   205  		})
   206  	}
   207  
   208  	return lister, nil
   209  }