github.com/argoproj/argo-events@v1.9.1/eventsources/sources/pulsar/start.go (about)

     1  /*
     2  Copyright 2018 BlackRock, 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  package pulsar
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/apache/pulsar-client-go/pulsar"
    25  	"go.uber.org/zap"
    26  
    27  	"github.com/argoproj/argo-events/common"
    28  	"github.com/argoproj/argo-events/common/logging"
    29  	eventsourcecommon "github.com/argoproj/argo-events/eventsources/common"
    30  	"github.com/argoproj/argo-events/eventsources/sources"
    31  	metrics "github.com/argoproj/argo-events/metrics"
    32  	apicommon "github.com/argoproj/argo-events/pkg/apis/common"
    33  	"github.com/argoproj/argo-events/pkg/apis/events"
    34  	"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    35  )
    36  
    37  // EventListener implements Eventing for the Pulsar event source
    38  type EventListener struct {
    39  	EventSourceName   string
    40  	EventName         string
    41  	PulsarEventSource v1alpha1.PulsarEventSource
    42  	Metrics           *metrics.Metrics
    43  }
    44  
    45  // GetEventSourceName returns name of event source
    46  func (el *EventListener) GetEventSourceName() string {
    47  	return el.EventSourceName
    48  }
    49  
    50  // GetEventName returns name of event
    51  func (el *EventListener) GetEventName() string {
    52  	return el.EventName
    53  }
    54  
    55  // GetEventSourceType return type of event server
    56  func (el *EventListener) GetEventSourceType() apicommon.EventSourceType {
    57  	return apicommon.PulsarEvent
    58  }
    59  
    60  // StartListening listens Pulsar events
    61  func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error {
    62  	log := logging.FromContext(ctx).
    63  		With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName())
    64  	log.Info("started processing the Pulsar event source...")
    65  	defer sources.Recover(el.GetEventName())
    66  
    67  	msgChannel := make(chan pulsar.ConsumerMessage)
    68  
    69  	pulsarEventSource := &el.PulsarEventSource
    70  
    71  	subscriptionType := pulsar.Exclusive
    72  	if pulsarEventSource.Type == "shared" {
    73  		subscriptionType = pulsar.Shared
    74  	}
    75  
    76  	log.Info("setting consumer options...")
    77  	consumerOpt := pulsar.ConsumerOptions{
    78  		Topics:           pulsarEventSource.Topics,
    79  		SubscriptionName: el.EventName,
    80  		Type:             subscriptionType,
    81  		MessageChannel:   msgChannel,
    82  	}
    83  
    84  	log.Info("setting client options...")
    85  	var err error
    86  	tlsTrustCertsFilePath := ""
    87  	if pulsarEventSource.TLSTrustCertsSecret != nil {
    88  		tlsTrustCertsFilePath, err = common.GetSecretVolumePath(pulsarEventSource.TLSTrustCertsSecret)
    89  		if err != nil {
    90  			log.Errorw("failed to get TLSTrustCertsFilePath from the volume", zap.Error(err))
    91  			return err
    92  		}
    93  	}
    94  	clientOpt := pulsar.ClientOptions{
    95  		URL:                        pulsarEventSource.URL,
    96  		TLSTrustCertsFilePath:      tlsTrustCertsFilePath,
    97  		TLSAllowInsecureConnection: pulsarEventSource.TLSAllowInsecureConnection,
    98  		TLSValidateHostname:        pulsarEventSource.TLSValidateHostname,
    99  	}
   100  
   101  	if pulsarEventSource.AuthTokenSecret != nil {
   102  		token, err := common.GetSecretFromVolume(pulsarEventSource.AuthTokenSecret)
   103  		if err != nil {
   104  			log.Errorw("failed to get AuthTokenSecret from the volume", zap.Error(err))
   105  			return err
   106  		}
   107  		clientOpt.Authentication = pulsar.NewAuthenticationToken(token)
   108  	}
   109  
   110  	if len(pulsarEventSource.AuthAthenzParams) > 0 {
   111  		log.Info("setting athenz auth option...")
   112  		if pulsarEventSource.AuthAthenzSecret != nil {
   113  			authAthenzFilePath, err := common.GetSecretVolumePath(pulsarEventSource.AuthAthenzSecret)
   114  			if err != nil {
   115  				log.Errorw("failed to get authAthenzSecret from the volume", zap.Error(err))
   116  				return err
   117  			}
   118  			pulsarEventSource.AuthAthenzParams["privateKey"] = "file://" + authAthenzFilePath
   119  		}
   120  		clientOpt.Authentication = pulsar.NewAuthenticationAthenz(pulsarEventSource.AuthAthenzParams)
   121  	}
   122  
   123  	if pulsarEventSource.TLS != nil {
   124  		log.Info("setting tls auth option...")
   125  		var clientCertPath, clientKeyPath string
   126  		switch {
   127  		case pulsarEventSource.TLS.ClientCertSecret != nil && pulsarEventSource.TLS.ClientKeySecret != nil:
   128  			clientCertPath, err = common.GetSecretVolumePath(pulsarEventSource.TLS.ClientCertSecret)
   129  			if err != nil {
   130  				log.Errorw("failed to get ClientCertPath from the volume", zap.Error(err))
   131  				return err
   132  			}
   133  			clientKeyPath, err = common.GetSecretVolumePath(pulsarEventSource.TLS.ClientKeySecret)
   134  			if err != nil {
   135  				log.Errorw("failed to get ClientKeyPath from the volume", zap.Error(err))
   136  				return err
   137  			}
   138  		default:
   139  			return fmt.Errorf("invalid TLS config")
   140  		}
   141  		clientOpt.Authentication = pulsar.NewAuthenticationTLS(clientCertPath, clientKeyPath)
   142  	}
   143  
   144  	var client pulsar.Client
   145  
   146  	if err := common.DoWithRetry(pulsarEventSource.ConnectionBackoff, func() error {
   147  		var err error
   148  		if client, err = pulsar.NewClient(clientOpt); err != nil {
   149  			return err
   150  		}
   151  		return nil
   152  	}); err != nil {
   153  		return fmt.Errorf("failed to connect to %s for event source %s, %w", pulsarEventSource.URL, el.GetEventName(), err)
   154  	}
   155  
   156  	log.Info("subscribing to messages on the topic...")
   157  	consumer, err := client.Subscribe(consumerOpt)
   158  	if err != nil {
   159  		return fmt.Errorf("failed to connect to topic %+v for event source %s, %w", pulsarEventSource.Topics, el.GetEventName(), err)
   160  	}
   161  
   162  consumeMessages:
   163  	for {
   164  		select {
   165  		case msg, ok := <-msgChannel:
   166  			if !ok {
   167  				log.Error("failed to read a message, channel might have been closed")
   168  				return fmt.Errorf("channel might have been closed")
   169  			}
   170  
   171  			if err := el.handleOne(msg, dispatch, log); err != nil {
   172  				log.Errorw("failed to process a Pulsar event", zap.Error(err))
   173  				el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   174  			}
   175  
   176  			if err := consumer.Ack(msg.Message); err != nil {
   177  				return fmt.Errorf("failed to process a consumer ack, %w", err)
   178  			}
   179  		case <-ctx.Done():
   180  			consumer.Close()
   181  			client.Close()
   182  			break consumeMessages
   183  		}
   184  	}
   185  
   186  	log.Info("event source is stopped")
   187  	return nil
   188  }
   189  
   190  func (el *EventListener) handleOne(msg pulsar.Message, dispatch func([]byte, ...eventsourcecommon.Option) error, log *zap.SugaredLogger) error {
   191  	defer func(start time.Time) {
   192  		el.Metrics.EventProcessingDuration(el.GetEventSourceName(), el.GetEventName(), float64(time.Since(start)/time.Millisecond))
   193  	}(time.Now())
   194  
   195  	log.Infof("received a message on the topic %s", msg.Topic())
   196  	payload := msg.Payload()
   197  	eventData := &events.PulsarEventData{
   198  		Key:         msg.Key(),
   199  		PublishTime: msg.PublishTime().UTC().String(),
   200  		Body:        payload,
   201  		Metadata:    el.PulsarEventSource.Metadata,
   202  	}
   203  	if el.PulsarEventSource.JSONBody {
   204  		eventData.Body = (*json.RawMessage)(&payload)
   205  	}
   206  
   207  	eventBody, err := json.Marshal(eventData)
   208  	if err != nil {
   209  		return fmt.Errorf("failed to marshal the event data. rejecting the event, %w", err)
   210  	}
   211  
   212  	log.Infof("dispatching the message received on the topic %s to eventbus", msg.Topic())
   213  	if err = dispatch(eventBody); err != nil {
   214  		return fmt.Errorf("failed to dispatch a Pulsar event, %w", err)
   215  	}
   216  	return nil
   217  }