github.com/containerd/Containerd@v1.4.13/events/exchange/exchange.go (about) 1 /* 2 Copyright The containerd Authors. 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 exchange 18 19 import ( 20 "context" 21 "strings" 22 "time" 23 24 "github.com/containerd/containerd/errdefs" 25 "github.com/containerd/containerd/events" 26 "github.com/containerd/containerd/filters" 27 "github.com/containerd/containerd/identifiers" 28 "github.com/containerd/containerd/log" 29 "github.com/containerd/containerd/namespaces" 30 "github.com/containerd/typeurl" 31 goevents "github.com/docker/go-events" 32 "github.com/gogo/protobuf/types" 33 "github.com/pkg/errors" 34 "github.com/sirupsen/logrus" 35 ) 36 37 // Exchange broadcasts events 38 type Exchange struct { 39 broadcaster *goevents.Broadcaster 40 } 41 42 // NewExchange returns a new event Exchange 43 func NewExchange() *Exchange { 44 return &Exchange{ 45 broadcaster: goevents.NewBroadcaster(), 46 } 47 } 48 49 var _ events.Publisher = &Exchange{} 50 var _ events.Forwarder = &Exchange{} 51 var _ events.Subscriber = &Exchange{} 52 53 // Forward accepts an envelope to be directly distributed on the exchange. 54 // 55 // This is useful when an event is forwarded on behalf of another namespace or 56 // when the event is propagated on behalf of another publisher. 57 func (e *Exchange) Forward(ctx context.Context, envelope *events.Envelope) (err error) { 58 if err := validateEnvelope(envelope); err != nil { 59 return err 60 } 61 62 defer func() { 63 logger := log.G(ctx).WithFields(logrus.Fields{ 64 "topic": envelope.Topic, 65 "ns": envelope.Namespace, 66 "type": envelope.Event.TypeUrl, 67 }) 68 69 if err != nil { 70 logger.WithError(err).Error("error forwarding event") 71 } else { 72 logger.Debug("event forwarded") 73 } 74 }() 75 76 return e.broadcaster.Write(envelope) 77 } 78 79 // Publish packages and sends an event. The caller will be considered the 80 // initial publisher of the event. This means the timestamp will be calculated 81 // at this point and this method may read from the calling context. 82 func (e *Exchange) Publish(ctx context.Context, topic string, event events.Event) (err error) { 83 var ( 84 namespace string 85 encoded *types.Any 86 envelope events.Envelope 87 ) 88 89 namespace, err = namespaces.NamespaceRequired(ctx) 90 if err != nil { 91 return errors.Wrapf(err, "failed publishing event") 92 } 93 if err := validateTopic(topic); err != nil { 94 return errors.Wrapf(err, "envelope topic %q", topic) 95 } 96 97 encoded, err = typeurl.MarshalAny(event) 98 if err != nil { 99 return err 100 } 101 102 envelope.Timestamp = time.Now().UTC() 103 envelope.Namespace = namespace 104 envelope.Topic = topic 105 envelope.Event = encoded 106 107 defer func() { 108 logger := log.G(ctx).WithFields(logrus.Fields{ 109 "topic": envelope.Topic, 110 "ns": envelope.Namespace, 111 "type": envelope.Event.TypeUrl, 112 }) 113 114 if err != nil { 115 logger.WithError(err).Error("error publishing event") 116 } else { 117 logger.Debug("event published") 118 } 119 }() 120 121 return e.broadcaster.Write(&envelope) 122 } 123 124 // Subscribe to events on the exchange. Events are sent through the returned 125 // channel ch. If an error is encountered, it will be sent on channel errs and 126 // errs will be closed. To end the subscription, cancel the provided context. 127 // 128 // Zero or more filters may be provided as strings. Only events that match 129 // *any* of the provided filters will be sent on the channel. The filters use 130 // the standard containerd filters package syntax. 131 func (e *Exchange) Subscribe(ctx context.Context, fs ...string) (ch <-chan *events.Envelope, errs <-chan error) { 132 var ( 133 evch = make(chan *events.Envelope) 134 errq = make(chan error, 1) 135 channel = goevents.NewChannel(0) 136 queue = goevents.NewQueue(channel) 137 dst goevents.Sink = queue 138 ) 139 140 closeAll := func() { 141 channel.Close() 142 queue.Close() 143 e.broadcaster.Remove(dst) 144 close(errq) 145 } 146 147 ch = evch 148 errs = errq 149 150 if len(fs) > 0 { 151 filter, err := filters.ParseAll(fs...) 152 if err != nil { 153 errq <- errors.Wrapf(err, "failed parsing subscription filters") 154 closeAll() 155 return 156 } 157 158 dst = goevents.NewFilter(queue, goevents.MatcherFunc(func(gev goevents.Event) bool { 159 return filter.Match(adapt(gev)) 160 })) 161 } 162 163 e.broadcaster.Add(dst) 164 165 go func() { 166 defer closeAll() 167 168 var err error 169 loop: 170 for { 171 select { 172 case ev := <-channel.C: 173 env, ok := ev.(*events.Envelope) 174 if !ok { 175 // TODO(stevvooe): For the most part, we are well protected 176 // from this condition. Both Forward and Publish protect 177 // from this. 178 err = errors.Errorf("invalid envelope encountered %#v; please file a bug", ev) 179 break 180 } 181 182 select { 183 case evch <- env: 184 case <-ctx.Done(): 185 break loop 186 } 187 case <-ctx.Done(): 188 break loop 189 } 190 } 191 192 if err == nil { 193 if cerr := ctx.Err(); cerr != context.Canceled { 194 err = cerr 195 } 196 } 197 198 errq <- err 199 }() 200 201 return 202 } 203 204 func validateTopic(topic string) error { 205 if topic == "" { 206 return errors.Wrap(errdefs.ErrInvalidArgument, "must not be empty") 207 } 208 209 if topic[0] != '/' { 210 return errors.Wrapf(errdefs.ErrInvalidArgument, "must start with '/'") 211 } 212 213 if len(topic) == 1 { 214 return errors.Wrapf(errdefs.ErrInvalidArgument, "must have at least one component") 215 } 216 217 components := strings.Split(topic[1:], "/") 218 for _, component := range components { 219 if err := identifiers.Validate(component); err != nil { 220 return errors.Wrapf(err, "failed validation on component %q", component) 221 } 222 } 223 224 return nil 225 } 226 227 func validateEnvelope(envelope *events.Envelope) error { 228 if err := identifiers.Validate(envelope.Namespace); err != nil { 229 return errors.Wrapf(err, "event envelope has invalid namespace") 230 } 231 232 if err := validateTopic(envelope.Topic); err != nil { 233 return errors.Wrapf(err, "envelope topic %q", envelope.Topic) 234 } 235 236 if envelope.Timestamp.IsZero() { 237 return errors.Wrapf(errdefs.ErrInvalidArgument, "timestamp must be set on forwarded event") 238 } 239 240 return nil 241 } 242 243 func adapt(ev interface{}) filters.Adaptor { 244 if adaptor, ok := ev.(filters.Adaptor); ok { 245 return adaptor 246 } 247 248 return filters.AdapterFunc(func(fieldpath []string) (string, bool) { 249 return "", false 250 }) 251 }