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 }