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  }