storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/event/target/nsq.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package target 18 19 import ( 20 "context" 21 "crypto/tls" 22 "encoding/json" 23 "errors" 24 "net/url" 25 "os" 26 "path/filepath" 27 28 "github.com/nsqio/go-nsq" 29 30 "storj.io/minio/pkg/event" 31 xnet "storj.io/minio/pkg/net" 32 ) 33 34 // NSQ constants 35 const ( 36 NSQAddress = "nsqd_address" 37 NSQTopic = "topic" 38 NSQTLS = "tls" 39 NSQTLSSkipVerify = "tls_skip_verify" 40 NSQQueueDir = "queue_dir" 41 NSQQueueLimit = "queue_limit" 42 43 EnvNSQEnable = "MINIO_NOTIFY_NSQ_ENABLE" 44 EnvNSQAddress = "MINIO_NOTIFY_NSQ_NSQD_ADDRESS" 45 EnvNSQTopic = "MINIO_NOTIFY_NSQ_TOPIC" 46 EnvNSQTLS = "MINIO_NOTIFY_NSQ_TLS" 47 EnvNSQTLSSkipVerify = "MINIO_NOTIFY_NSQ_TLS_SKIP_VERIFY" 48 EnvNSQQueueDir = "MINIO_NOTIFY_NSQ_QUEUE_DIR" 49 EnvNSQQueueLimit = "MINIO_NOTIFY_NSQ_QUEUE_LIMIT" 50 ) 51 52 // NSQArgs - NSQ target arguments. 53 type NSQArgs struct { 54 Enable bool `json:"enable"` 55 NSQDAddress xnet.Host `json:"nsqdAddress"` 56 Topic string `json:"topic"` 57 TLS struct { 58 Enable bool `json:"enable"` 59 SkipVerify bool `json:"skipVerify"` 60 } `json:"tls"` 61 QueueDir string `json:"queueDir"` 62 QueueLimit uint64 `json:"queueLimit"` 63 } 64 65 // Validate NSQArgs fields 66 func (n NSQArgs) Validate() error { 67 if !n.Enable { 68 return nil 69 } 70 71 if n.NSQDAddress.IsEmpty() { 72 return errors.New("empty nsqdAddress") 73 } 74 75 if n.Topic == "" { 76 return errors.New("empty topic") 77 } 78 if n.QueueDir != "" { 79 if !filepath.IsAbs(n.QueueDir) { 80 return errors.New("queueDir path should be absolute") 81 } 82 } 83 84 return nil 85 } 86 87 // NSQTarget - NSQ target. 88 type NSQTarget struct { 89 id event.TargetID 90 args NSQArgs 91 producer *nsq.Producer 92 store Store 93 config *nsq.Config 94 loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{}) 95 } 96 97 // ID - returns target ID. 98 func (target *NSQTarget) ID() event.TargetID { 99 return target.id 100 } 101 102 // HasQueueStore - Checks if the queueStore has been configured for the target 103 func (target *NSQTarget) HasQueueStore() bool { 104 return target.store != nil 105 } 106 107 // IsActive - Return true if target is up and active 108 func (target *NSQTarget) IsActive() (bool, error) { 109 if target.producer == nil { 110 producer, err := nsq.NewProducer(target.args.NSQDAddress.String(), target.config) 111 if err != nil { 112 return false, err 113 } 114 target.producer = producer 115 } 116 117 if err := target.producer.Ping(); err != nil { 118 // To treat "connection refused" errors as errNotConnected. 119 if IsConnRefusedErr(err) { 120 return false, errNotConnected 121 } 122 return false, err 123 } 124 return true, nil 125 } 126 127 // Save - saves the events to the store which will be replayed when the nsq connection is active. 128 func (target *NSQTarget) Save(eventData event.Event) error { 129 if target.store != nil { 130 return target.store.Put(eventData) 131 } 132 _, err := target.IsActive() 133 if err != nil { 134 return err 135 } 136 return target.send(eventData) 137 } 138 139 // send - sends an event to the NSQ. 140 func (target *NSQTarget) send(eventData event.Event) error { 141 objectName, err := url.QueryUnescape(eventData.S3.Object.Key) 142 if err != nil { 143 return err 144 } 145 key := eventData.S3.Bucket.Name + "/" + objectName 146 147 data, err := json.Marshal(event.Log{EventName: eventData.EventName, Key: key, Records: []event.Event{eventData}}) 148 if err != nil { 149 return err 150 } 151 152 return target.producer.Publish(target.args.Topic, data) 153 } 154 155 // Send - reads an event from store and sends it to NSQ. 156 func (target *NSQTarget) Send(eventKey string) error { 157 _, err := target.IsActive() 158 if err != nil { 159 return err 160 } 161 162 eventData, eErr := target.store.Get(eventKey) 163 if eErr != nil { 164 // The last event key in a successful batch will be sent in the channel atmost once by the replayEvents() 165 // Such events will not exist and wouldve been already been sent successfully. 166 if os.IsNotExist(eErr) { 167 return nil 168 } 169 return eErr 170 } 171 172 if err := target.send(eventData); err != nil { 173 return err 174 } 175 176 // Delete the event from store. 177 return target.store.Del(eventKey) 178 } 179 180 // Close - closes underneath connections to NSQD server. 181 func (target *NSQTarget) Close() (err error) { 182 if target.producer != nil { 183 // this blocks until complete: 184 target.producer.Stop() 185 } 186 return nil 187 } 188 189 // NewNSQTarget - creates new NSQ target. 190 func NewNSQTarget(id string, args NSQArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{}, kind ...interface{}), test bool) (*NSQTarget, error) { 191 config := nsq.NewConfig() 192 if args.TLS.Enable { 193 config.TlsV1 = true 194 config.TlsConfig = &tls.Config{ 195 InsecureSkipVerify: args.TLS.SkipVerify, 196 } 197 } 198 199 var store Store 200 201 target := &NSQTarget{ 202 id: event.TargetID{ID: id, Name: "nsq"}, 203 args: args, 204 config: config, 205 loggerOnce: loggerOnce, 206 } 207 208 if args.QueueDir != "" { 209 queueDir := filepath.Join(args.QueueDir, storePrefix+"-nsq-"+id) 210 store = NewQueueStore(queueDir, args.QueueLimit) 211 if oErr := store.Open(); oErr != nil { 212 target.loggerOnce(context.Background(), oErr, target.ID()) 213 return target, oErr 214 } 215 target.store = store 216 } 217 218 producer, err := nsq.NewProducer(args.NSQDAddress.String(), config) 219 if err != nil { 220 target.loggerOnce(context.Background(), err, target.ID()) 221 return target, err 222 } 223 target.producer = producer 224 225 if err := target.producer.Ping(); err != nil { 226 // To treat "connection refused" errors as errNotConnected. 227 if target.store == nil || !(IsConnRefusedErr(err) || IsConnResetErr(err)) { 228 target.loggerOnce(context.Background(), err, target.ID()) 229 return target, err 230 } 231 } 232 233 if target.store != nil && !test { 234 // Replays the events from the store. 235 eventKeyCh := replayEvents(target.store, doneCh, target.loggerOnce, target.ID()) 236 // Start replaying events from the store. 237 go sendEvents(target, eventKeyCh, doneCh, target.loggerOnce) 238 } 239 240 return target, nil 241 }