github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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  }