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  }