github.com/argoproj/argo-events@v1.9.1/eventsources/sources/amqp/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  
    17  package amqp
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"time"
    24  
    25  	"sigs.k8s.io/yaml"
    26  
    27  	amqplib "github.com/rabbitmq/amqp091-go"
    28  	"go.uber.org/zap"
    29  
    30  	"github.com/argoproj/argo-events/common"
    31  	"github.com/argoproj/argo-events/common/logging"
    32  	eventsourcecommon "github.com/argoproj/argo-events/eventsources/common"
    33  	"github.com/argoproj/argo-events/eventsources/sources"
    34  	metrics "github.com/argoproj/argo-events/metrics"
    35  	apicommon "github.com/argoproj/argo-events/pkg/apis/common"
    36  	"github.com/argoproj/argo-events/pkg/apis/events"
    37  	"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    38  )
    39  
    40  // EventListener implements Eventing for amqp event source
    41  type EventListener struct {
    42  	EventSourceName string
    43  	EventName       string
    44  	AMQPEventSource v1alpha1.AMQPEventSource
    45  	Metrics         *metrics.Metrics
    46  }
    47  
    48  // GetEventSourceName returns name of event source
    49  func (el *EventListener) GetEventSourceName() string {
    50  	return el.EventSourceName
    51  }
    52  
    53  // GetEventName returns name of event
    54  func (el *EventListener) GetEventName() string {
    55  	return el.EventName
    56  }
    57  
    58  // GetEventSourceType return type of event server
    59  func (el *EventListener) GetEventSourceType() apicommon.EventSourceType {
    60  	return apicommon.AMQPEvent
    61  }
    62  
    63  // StartListening starts listening events
    64  func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error {
    65  	log := logging.FromContext(ctx).
    66  		With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName())
    67  
    68  	log.Info("started processing the AMQP event source...")
    69  	defer sources.Recover(el.GetEventName())
    70  
    71  	amqpEventSource := &el.AMQPEventSource
    72  	var conn *amqplib.Connection
    73  	if err := common.DoWithRetry(amqpEventSource.ConnectionBackoff, func() error {
    74  		c := amqplib.Config{
    75  			Heartbeat: 10 * time.Second,
    76  			Locale:    "en_US",
    77  		}
    78  		if amqpEventSource.TLS != nil {
    79  			tlsConfig, err := common.GetTLSConfig(amqpEventSource.TLS)
    80  			if err != nil {
    81  				return fmt.Errorf("failed to get the tls configuration, %w", err)
    82  			}
    83  			c.TLSClientConfig = tlsConfig
    84  		}
    85  		if amqpEventSource.Auth != nil {
    86  			username, err := common.GetSecretFromVolume(amqpEventSource.Auth.Username)
    87  			if err != nil {
    88  				return fmt.Errorf("username not found, %w", err)
    89  			}
    90  			password, err := common.GetSecretFromVolume(amqpEventSource.Auth.Password)
    91  			if err != nil {
    92  				return fmt.Errorf("password not found, %w", err)
    93  			}
    94  			c.SASL = []amqplib.Authentication{&amqplib.PlainAuth{
    95  				Username: username,
    96  				Password: password,
    97  			}}
    98  		}
    99  		var err error
   100  		var url string
   101  		if amqpEventSource.URLSecret != nil {
   102  			url, err = common.GetSecretFromVolume(amqpEventSource.URLSecret)
   103  			if err != nil {
   104  				return fmt.Errorf("urlSecret not found, %w", err)
   105  			}
   106  		} else {
   107  			url = amqpEventSource.URL
   108  		}
   109  		conn, err = amqplib.DialConfig(url, c)
   110  		if err != nil {
   111  			return err
   112  		}
   113  		return nil
   114  	}); err != nil {
   115  		return fmt.Errorf("failed to connect to amqp broker for the event source %s, %w", el.GetEventName(), err)
   116  	}
   117  
   118  	log.Info("opening the server channel...")
   119  	ch, err := conn.Channel()
   120  	if err != nil {
   121  		return fmt.Errorf("failed to open the channel for the event source %s, %w", el.GetEventName(), err)
   122  	}
   123  
   124  	log.Info("checking parameters and set defaults...")
   125  	setDefaults(amqpEventSource)
   126  
   127  	log.Info("setting up the delivery channel...")
   128  	delivery, err := getDelivery(ch, amqpEventSource)
   129  	if err != nil {
   130  		return fmt.Errorf("failed to get the delivery for the event source %s, %w", el.GetEventName(), err)
   131  	}
   132  
   133  	if amqpEventSource.JSONBody {
   134  		log.Info("assuming all events have a json body...")
   135  	}
   136  
   137  	log.Info("listening to messages on channel...")
   138  	for {
   139  		select {
   140  		case msg, ok := <-delivery:
   141  			if !ok {
   142  				log.Error("failed to read a message, channel might have been closed")
   143  				return fmt.Errorf("channel might have been closed")
   144  			}
   145  			if err := el.handleOne(amqpEventSource, msg, dispatch, log); err != nil {
   146  				log.Errorw("failed to process an AMQP message", zap.Error(err))
   147  				el.Metrics.EventProcessingFailed(el.GetEventSourceName(), el.GetEventName())
   148  			}
   149  		case <-ctx.Done():
   150  			err = conn.Close()
   151  			if err != nil {
   152  				log.Errorw("failed to close connection", zap.Error(err))
   153  			}
   154  			return nil
   155  		}
   156  	}
   157  }
   158  
   159  func (el *EventListener) handleOne(amqpEventSource *v1alpha1.AMQPEventSource, msg amqplib.Delivery, dispatch func([]byte, ...eventsourcecommon.Option) error, log *zap.SugaredLogger) error {
   160  	defer func(start time.Time) {
   161  		el.Metrics.EventProcessingDuration(el.GetEventSourceName(), el.GetEventName(), float64(time.Since(start)/time.Millisecond))
   162  	}(time.Now())
   163  
   164  	log.Infow("received the message", zap.Any("message-id", msg.MessageId))
   165  	body := &events.AMQPEventData{
   166  		ContentType:     msg.ContentType,
   167  		ContentEncoding: msg.ContentEncoding,
   168  		DeliveryMode:    int(msg.DeliveryMode),
   169  		Priority:        int(msg.Priority),
   170  		CorrelationId:   msg.CorrelationId,
   171  		ReplyTo:         msg.ReplyTo,
   172  		Expiration:      msg.Expiration,
   173  		MessageId:       msg.MessageId,
   174  		Timestamp:       msg.Timestamp.String(),
   175  		Type:            msg.Type,
   176  		AppId:           msg.AppId,
   177  		Exchange:        msg.Exchange,
   178  		RoutingKey:      msg.RoutingKey,
   179  		Metadata:        amqpEventSource.Metadata,
   180  	}
   181  	if amqpEventSource.JSONBody {
   182  		body.Body = (*json.RawMessage)(&msg.Body)
   183  	} else {
   184  		body.Body = msg.Body
   185  	}
   186  
   187  	bodyBytes, err := json.Marshal(body)
   188  	if err != nil {
   189  		return fmt.Errorf("failed to marshal the message, message-id: %s, %w", msg.MessageId, err)
   190  	}
   191  
   192  	log.Info("dispatching event ...")
   193  	if err = dispatch(bodyBytes); err != nil {
   194  		return fmt.Errorf("failed to dispatch AMQP event, %w", err)
   195  	}
   196  	return nil
   197  }
   198  
   199  // setDefaults sets the default values in case the user hasn't defined them
   200  // helps also to keep retro-compatibility with current dpeloyments
   201  func setDefaults(eventSource *v1alpha1.AMQPEventSource) {
   202  	if eventSource.ExchangeDeclare == nil {
   203  		eventSource.ExchangeDeclare = &v1alpha1.AMQPExchangeDeclareConfig{
   204  			Durable:    true,
   205  			AutoDelete: false,
   206  			Internal:   false,
   207  			NoWait:     false,
   208  		}
   209  	}
   210  
   211  	if eventSource.QueueDeclare == nil {
   212  		eventSource.QueueDeclare = &v1alpha1.AMQPQueueDeclareConfig{
   213  			Name:       "",
   214  			Durable:    false,
   215  			AutoDelete: false,
   216  			Exclusive:  true,
   217  			NoWait:     false,
   218  		}
   219  	}
   220  
   221  	if eventSource.QueueBind == nil {
   222  		eventSource.QueueBind = &v1alpha1.AMQPQueueBindConfig{
   223  			NoWait: false,
   224  		}
   225  	}
   226  
   227  	if eventSource.Consume == nil {
   228  		eventSource.Consume = &v1alpha1.AMQPConsumeConfig{
   229  			ConsumerTag: "",
   230  			AutoAck:     true,
   231  			Exclusive:   false,
   232  			NoLocal:     false,
   233  			NoWait:      false,
   234  		}
   235  	}
   236  }
   237  
   238  // getDelivery sets up a channel for message deliveries
   239  func getDelivery(ch *amqplib.Channel, eventSource *v1alpha1.AMQPEventSource) (<-chan amqplib.Delivery, error) {
   240  	err := ch.ExchangeDeclare(
   241  		eventSource.ExchangeName,
   242  		eventSource.ExchangeType,
   243  		eventSource.ExchangeDeclare.Durable,
   244  		eventSource.ExchangeDeclare.AutoDelete,
   245  		eventSource.ExchangeDeclare.Internal,
   246  		eventSource.ExchangeDeclare.NoWait,
   247  		nil,
   248  	)
   249  	if err != nil {
   250  		return nil, fmt.Errorf("failed to declare exchange with name %s and type %s. err: %w", eventSource.ExchangeName, eventSource.ExchangeType, err)
   251  	}
   252  	optionalArguments, err := parseYamlTable(eventSource.QueueDeclare.Arguments)
   253  	if err != nil {
   254  		return nil, fmt.Errorf("failed to parse optional queue declare table arguments from Yaml string: %w", err)
   255  	}
   256  
   257  	q, err := ch.QueueDeclare(
   258  		eventSource.QueueDeclare.Name,
   259  		eventSource.QueueDeclare.Durable,
   260  		eventSource.QueueDeclare.AutoDelete,
   261  		eventSource.QueueDeclare.Exclusive,
   262  		eventSource.QueueDeclare.NoWait,
   263  		optionalArguments,
   264  	)
   265  	if err != nil {
   266  		return nil, fmt.Errorf("failed to declare queue: %w", err)
   267  	}
   268  
   269  	err = ch.QueueBind(
   270  		q.Name,
   271  		eventSource.RoutingKey,
   272  		eventSource.ExchangeName,
   273  		eventSource.QueueBind.NoWait,
   274  		nil,
   275  	)
   276  	if err != nil {
   277  		return nil, fmt.Errorf("failed to bind %s exchange '%s' to queue with routingKey: %s: %w", eventSource.ExchangeType, eventSource.ExchangeName, eventSource.RoutingKey, err)
   278  	}
   279  
   280  	delivery, err := ch.Consume(
   281  		q.Name,
   282  		eventSource.Consume.ConsumerTag,
   283  		eventSource.Consume.AutoAck,
   284  		eventSource.Consume.Exclusive,
   285  		eventSource.Consume.NoLocal,
   286  		eventSource.Consume.NoWait,
   287  		nil,
   288  	)
   289  	if err != nil {
   290  		return nil, fmt.Errorf("failed to begin consuming messages: %w", err)
   291  	}
   292  	return delivery, nil
   293  }
   294  
   295  func parseYamlTable(argString string) (amqplib.Table, error) {
   296  	if argString == "" {
   297  		return nil, nil
   298  	}
   299  	var table amqplib.Table
   300  	args := []byte(argString)
   301  	err := yaml.Unmarshal(args, &table)
   302  	if err != nil {
   303  		return nil, fmt.Errorf("unmarshalling Yaml to Table type. Args: %s. Err: %w", argString, err)
   304  	}
   305  	return table, nil
   306  }