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 }