storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/event/target/nats.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 "crypto/x509" 23 "encoding/json" 24 "errors" 25 "net/url" 26 "os" 27 "path/filepath" 28 29 "github.com/nats-io/nats.go" 30 "github.com/nats-io/stan.go" 31 32 "storj.io/minio/pkg/event" 33 xnet "storj.io/minio/pkg/net" 34 ) 35 36 // NATS related constants 37 const ( 38 NATSAddress = "address" 39 NATSSubject = "subject" 40 NATSUsername = "username" 41 NATSPassword = "password" 42 NATSToken = "token" 43 NATSTLS = "tls" 44 NATSTLSSkipVerify = "tls_skip_verify" 45 NATSPingInterval = "ping_interval" 46 NATSQueueDir = "queue_dir" 47 NATSQueueLimit = "queue_limit" 48 NATSCertAuthority = "cert_authority" 49 NATSClientCert = "client_cert" 50 NATSClientKey = "client_key" 51 52 // Streaming constants 53 NATSStreaming = "streaming" 54 NATSStreamingClusterID = "streaming_cluster_id" 55 NATSStreamingAsync = "streaming_async" 56 NATSStreamingMaxPubAcksInFlight = "streaming_max_pub_acks_in_flight" 57 58 EnvNATSEnable = "MINIO_NOTIFY_NATS_ENABLE" 59 EnvNATSAddress = "MINIO_NOTIFY_NATS_ADDRESS" 60 EnvNATSSubject = "MINIO_NOTIFY_NATS_SUBJECT" 61 EnvNATSUsername = "MINIO_NOTIFY_NATS_USERNAME" 62 EnvNATSPassword = "MINIO_NOTIFY_NATS_PASSWORD" 63 EnvNATSToken = "MINIO_NOTIFY_NATS_TOKEN" 64 EnvNATSTLS = "MINIO_NOTIFY_NATS_TLS" 65 EnvNATSTLSSkipVerify = "MINIO_NOTIFY_NATS_TLS_SKIP_VERIFY" 66 EnvNATSPingInterval = "MINIO_NOTIFY_NATS_PING_INTERVAL" 67 EnvNATSQueueDir = "MINIO_NOTIFY_NATS_QUEUE_DIR" 68 EnvNATSQueueLimit = "MINIO_NOTIFY_NATS_QUEUE_LIMIT" 69 EnvNATSCertAuthority = "MINIO_NOTIFY_NATS_CERT_AUTHORITY" 70 EnvNATSClientCert = "MINIO_NOTIFY_NATS_CLIENT_CERT" 71 EnvNATSClientKey = "MINIO_NOTIFY_NATS_CLIENT_KEY" 72 73 // Streaming constants 74 EnvNATSStreaming = "MINIO_NOTIFY_NATS_STREAMING" 75 EnvNATSStreamingClusterID = "MINIO_NOTIFY_NATS_STREAMING_CLUSTER_ID" 76 EnvNATSStreamingAsync = "MINIO_NOTIFY_NATS_STREAMING_ASYNC" 77 EnvNATSStreamingMaxPubAcksInFlight = "MINIO_NOTIFY_NATS_STREAMING_MAX_PUB_ACKS_IN_FLIGHT" 78 ) 79 80 // NATSArgs - NATS target arguments. 81 type NATSArgs struct { 82 Enable bool `json:"enable"` 83 Address xnet.Host `json:"address"` 84 Subject string `json:"subject"` 85 Username string `json:"username"` 86 Password string `json:"password"` 87 Token string `json:"token"` 88 TLS bool `json:"tls"` 89 TLSSkipVerify bool `json:"tlsSkipVerify"` 90 Secure bool `json:"secure"` 91 CertAuthority string `json:"certAuthority"` 92 ClientCert string `json:"clientCert"` 93 ClientKey string `json:"clientKey"` 94 PingInterval int64 `json:"pingInterval"` 95 QueueDir string `json:"queueDir"` 96 QueueLimit uint64 `json:"queueLimit"` 97 Streaming struct { 98 Enable bool `json:"enable"` 99 ClusterID string `json:"clusterID"` 100 Async bool `json:"async"` 101 MaxPubAcksInflight int `json:"maxPubAcksInflight"` 102 } `json:"streaming"` 103 104 RootCAs *x509.CertPool `json:"-"` 105 } 106 107 // Validate NATSArgs fields 108 func (n NATSArgs) Validate() error { 109 if !n.Enable { 110 return nil 111 } 112 113 if n.Address.IsEmpty() { 114 return errors.New("empty address") 115 } 116 117 if n.Subject == "" { 118 return errors.New("empty subject") 119 } 120 121 if n.ClientCert != "" && n.ClientKey == "" || n.ClientCert == "" && n.ClientKey != "" { 122 return errors.New("cert and key must be specified as a pair") 123 } 124 125 if n.Username != "" && n.Password == "" || n.Username == "" && n.Password != "" { 126 return errors.New("username and password must be specified as a pair") 127 } 128 129 if n.Streaming.Enable { 130 if n.Streaming.ClusterID == "" { 131 return errors.New("empty cluster id") 132 } 133 } 134 135 if n.QueueDir != "" { 136 if !filepath.IsAbs(n.QueueDir) { 137 return errors.New("queueDir path should be absolute") 138 } 139 } 140 141 return nil 142 } 143 144 // To obtain a nats connection from args. 145 func (n NATSArgs) connectNats() (*nats.Conn, error) { 146 connOpts := []nats.Option{nats.Name("Minio Notification")} 147 if n.Username != "" && n.Password != "" { 148 connOpts = append(connOpts, nats.UserInfo(n.Username, n.Password)) 149 } 150 if n.Token != "" { 151 connOpts = append(connOpts, nats.Token(n.Token)) 152 } 153 if n.Secure || n.TLS && n.TLSSkipVerify { 154 connOpts = append(connOpts, nats.Secure(nil)) 155 } else if n.TLS { 156 connOpts = append(connOpts, nats.Secure(&tls.Config{RootCAs: n.RootCAs})) 157 } 158 if n.CertAuthority != "" { 159 connOpts = append(connOpts, nats.RootCAs(n.CertAuthority)) 160 } 161 if n.ClientCert != "" && n.ClientKey != "" { 162 connOpts = append(connOpts, nats.ClientCert(n.ClientCert, n.ClientKey)) 163 } 164 return nats.Connect(n.Address.String(), connOpts...) 165 } 166 167 // To obtain a streaming connection from args. 168 func (n NATSArgs) connectStan() (stan.Conn, error) { 169 scheme := "nats" 170 if n.Secure { 171 scheme = "tls" 172 } 173 174 var addressURL string 175 if n.Username != "" && n.Password != "" { 176 addressURL = scheme + "://" + n.Username + ":" + n.Password + "@" + n.Address.String() 177 } else if n.Token != "" { 178 addressURL = scheme + "://" + n.Token + "@" + n.Address.String() 179 } else { 180 addressURL = scheme + "://" + n.Address.String() 181 } 182 183 clientID, err := getNewUUID() 184 if err != nil { 185 return nil, err 186 } 187 188 connOpts := []stan.Option{stan.NatsURL(addressURL)} 189 if n.Streaming.MaxPubAcksInflight > 0 { 190 connOpts = append(connOpts, stan.MaxPubAcksInflight(n.Streaming.MaxPubAcksInflight)) 191 } 192 193 return stan.Connect(n.Streaming.ClusterID, clientID, connOpts...) 194 } 195 196 // NATSTarget - NATS target. 197 type NATSTarget struct { 198 id event.TargetID 199 args NATSArgs 200 natsConn *nats.Conn 201 stanConn stan.Conn 202 store Store 203 loggerOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{}) 204 } 205 206 // ID - returns target ID. 207 func (target *NATSTarget) ID() event.TargetID { 208 return target.id 209 } 210 211 // HasQueueStore - Checks if the queueStore has been configured for the target 212 func (target *NATSTarget) HasQueueStore() bool { 213 return target.store != nil 214 } 215 216 // IsActive - Return true if target is up and active 217 func (target *NATSTarget) IsActive() (bool, error) { 218 var connErr error 219 if target.args.Streaming.Enable { 220 if target.stanConn == nil || target.stanConn.NatsConn() == nil { 221 target.stanConn, connErr = target.args.connectStan() 222 } else { 223 if !target.stanConn.NatsConn().IsConnected() { 224 return false, errNotConnected 225 } 226 } 227 } else { 228 if target.natsConn == nil { 229 target.natsConn, connErr = target.args.connectNats() 230 } else { 231 if !target.natsConn.IsConnected() { 232 return false, errNotConnected 233 } 234 } 235 } 236 237 if connErr != nil { 238 if connErr.Error() == nats.ErrNoServers.Error() { 239 return false, errNotConnected 240 } 241 return false, connErr 242 } 243 244 return true, nil 245 } 246 247 // Save - saves the events to the store which will be replayed when the Nats connection is active. 248 func (target *NATSTarget) Save(eventData event.Event) error { 249 if target.store != nil { 250 return target.store.Put(eventData) 251 } 252 _, err := target.IsActive() 253 if err != nil { 254 return err 255 } 256 return target.send(eventData) 257 } 258 259 // send - sends an event to the Nats. 260 func (target *NATSTarget) send(eventData event.Event) error { 261 objectName, err := url.QueryUnescape(eventData.S3.Object.Key) 262 if err != nil { 263 return err 264 } 265 key := eventData.S3.Bucket.Name + "/" + objectName 266 267 data, err := json.Marshal(event.Log{EventName: eventData.EventName, Key: key, Records: []event.Event{eventData}}) 268 if err != nil { 269 return err 270 } 271 272 if target.stanConn != nil { 273 if target.args.Streaming.Async { 274 _, err = target.stanConn.PublishAsync(target.args.Subject, data, nil) 275 } else { 276 err = target.stanConn.Publish(target.args.Subject, data) 277 } 278 } else { 279 err = target.natsConn.Publish(target.args.Subject, data) 280 } 281 return err 282 } 283 284 // Send - sends event to Nats. 285 func (target *NATSTarget) Send(eventKey string) error { 286 _, err := target.IsActive() 287 if err != nil { 288 return err 289 } 290 291 eventData, eErr := target.store.Get(eventKey) 292 if eErr != nil { 293 // The last event key in a successful batch will be sent in the channel atmost once by the replayEvents() 294 // Such events will not exist and wouldve been already been sent successfully. 295 if os.IsNotExist(eErr) { 296 return nil 297 } 298 return eErr 299 } 300 301 if err := target.send(eventData); err != nil { 302 return err 303 } 304 305 return target.store.Del(eventKey) 306 } 307 308 // Close - closes underneath connections to NATS server. 309 func (target *NATSTarget) Close() (err error) { 310 if target.stanConn != nil { 311 // closing the streaming connection does not close the provided NATS connection. 312 if target.stanConn.NatsConn() != nil { 313 target.stanConn.NatsConn().Close() 314 } 315 err = target.stanConn.Close() 316 } 317 318 if target.natsConn != nil { 319 target.natsConn.Close() 320 } 321 322 return err 323 } 324 325 // NewNATSTarget - creates new NATS target. 326 func NewNATSTarget(id string, args NATSArgs, doneCh <-chan struct{}, loggerOnce func(ctx context.Context, err error, id interface{}, kind ...interface{}), test bool) (*NATSTarget, error) { 327 var natsConn *nats.Conn 328 var stanConn stan.Conn 329 330 var err error 331 332 var store Store 333 334 target := &NATSTarget{ 335 id: event.TargetID{ID: id, Name: "nats"}, 336 args: args, 337 loggerOnce: loggerOnce, 338 } 339 340 if args.QueueDir != "" { 341 queueDir := filepath.Join(args.QueueDir, storePrefix+"-nats-"+id) 342 store = NewQueueStore(queueDir, args.QueueLimit) 343 if oErr := store.Open(); oErr != nil { 344 target.loggerOnce(context.Background(), oErr, target.ID()) 345 return target, oErr 346 } 347 target.store = store 348 } 349 350 if args.Streaming.Enable { 351 stanConn, err = args.connectStan() 352 target.stanConn = stanConn 353 } else { 354 natsConn, err = args.connectNats() 355 target.natsConn = natsConn 356 } 357 358 if err != nil { 359 if store == nil || err.Error() != nats.ErrNoServers.Error() { 360 target.loggerOnce(context.Background(), err, target.ID()) 361 return target, err 362 } 363 } 364 365 if target.store != nil && !test { 366 // Replays the events from the store. 367 eventKeyCh := replayEvents(target.store, doneCh, target.loggerOnce, target.ID()) 368 // Start replaying events from the store. 369 go sendEvents(target, eventKeyCh, doneCh, target.loggerOnce) 370 } 371 372 return target, nil 373 }