github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/uniter/subordinaterelationwatcher.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"github.com/juju/collections/set"
     8  	"github.com/juju/errors"
     9  	"gopkg.in/juju/charm.v6"
    10  	"gopkg.in/juju/worker.v1/catacomb"
    11  
    12  	"github.com/juju/juju/state"
    13  )
    14  
    15  type subRelationsWatcher struct {
    16  	catacomb      catacomb.Catacomb
    17  	backend       *state.State
    18  	app           *state.Application
    19  	principalName string
    20  
    21  	// Maps relation keys to whether that relation should be
    22  	// included. Needed particularly for when the relation goes away.
    23  	relations map[string]bool
    24  	out       chan []string
    25  }
    26  
    27  // newSubordinateRelationsWatcher creates a watcher that will notify
    28  // about relation lifecycle events for subordinateApp, but filtered to
    29  // be relevant to a unit deployed to a container with the
    30  // principalName app. Global relations will be included, but only
    31  // container-scoped relations for the principal application will be
    32  // emitted - other container-scoped relations will be filtered out.
    33  func newSubordinateRelationsWatcher(backend *state.State, subordinateApp *state.Application, principalName string) (
    34  	state.StringsWatcher, error,
    35  ) {
    36  	w := &subRelationsWatcher{
    37  		backend:       backend,
    38  		app:           subordinateApp,
    39  		principalName: principalName,
    40  		relations:     make(map[string]bool),
    41  		out:           make(chan []string),
    42  	}
    43  	err := catacomb.Invoke(catacomb.Plan{
    44  		Site: &w.catacomb,
    45  		Work: w.loop,
    46  	})
    47  	return w, errors.Trace(err)
    48  }
    49  
    50  func (w *subRelationsWatcher) loop() error {
    51  	defer close(w.out)
    52  	relationsw := w.app.WatchRelations()
    53  	if err := w.catacomb.Add(relationsw); err != nil {
    54  		return errors.Trace(err)
    55  	}
    56  	var (
    57  		sentInitial bool
    58  		out         chan []string
    59  
    60  		currentRelations = set.NewStrings()
    61  	)
    62  	for {
    63  		select {
    64  		case <-w.catacomb.Dying():
    65  			return w.catacomb.ErrDying()
    66  		case out <- currentRelations.Values():
    67  			sentInitial = true
    68  			currentRelations = set.NewStrings()
    69  			out = nil
    70  		case newRelations, ok := <-relationsw.Changes():
    71  			if !ok {
    72  				return w.catacomb.ErrDying()
    73  			}
    74  			for _, relation := range newRelations {
    75  				if currentRelations.Contains(relation) {
    76  					continue
    77  				}
    78  				shouldSend, err := w.shouldSend(relation)
    79  				if err != nil {
    80  					return errors.Trace(err)
    81  				}
    82  				if shouldSend {
    83  					currentRelations.Add(relation)
    84  				}
    85  			}
    86  			if !sentInitial || currentRelations.Size() > 0 {
    87  				out = w.out
    88  			}
    89  		}
    90  	}
    91  }
    92  
    93  func (w *subRelationsWatcher) shouldSend(key string) (bool, error) {
    94  	if shouldSend, found := w.relations[key]; found {
    95  		return shouldSend, nil
    96  	}
    97  	result, err := w.shouldSendCheck(key)
    98  	if err == nil {
    99  		w.relations[key] = result
   100  	}
   101  	return result, errors.Trace(err)
   102  }
   103  
   104  func (w *subRelationsWatcher) shouldSendCheck(key string) (bool, error) {
   105  	rel, err := w.backend.KeyRelation(key)
   106  	if errors.IsNotFound(err) {
   107  		// We never saw it, and it's already gone away, so we can drop it.
   108  		logger.Debugf("couldn't find unknown relation %q", key)
   109  		return false, nil
   110  	} else if err != nil {
   111  		return false, errors.Trace(err)
   112  	}
   113  
   114  	thisEnd, err := rel.Endpoint(w.app.Name())
   115  	if err != nil {
   116  		return false, errors.Trace(err)
   117  	}
   118  	if thisEnd.Scope == charm.ScopeGlobal {
   119  		return true, nil
   120  	}
   121  
   122  	// Only allow container relations if the other end is our
   123  	// principal or the other end is a subordinate.
   124  	otherEnds, err := rel.RelatedEndpoints(w.app.Name())
   125  	if err != nil {
   126  		return false, errors.Trace(err)
   127  	}
   128  	for _, otherEnd := range otherEnds {
   129  		if otherEnd.ApplicationName == w.principalName {
   130  			return true, nil
   131  		}
   132  		otherApp, err := w.backend.Application(otherEnd.ApplicationName)
   133  		if err != nil {
   134  			return false, errors.Trace(err)
   135  		}
   136  		if !otherApp.IsPrincipal() {
   137  			return true, nil
   138  		}
   139  	}
   140  	return false, nil
   141  }
   142  
   143  // Changes implements watcher.StringsWatcher.
   144  func (w *subRelationsWatcher) Changes() <-chan []string {
   145  	return w.out
   146  }
   147  
   148  // Err implements watcher.StringsWatcher.
   149  func (w *subRelationsWatcher) Err() error {
   150  	return w.catacomb.Err()
   151  }
   152  
   153  // Kill implements watcher.StringsWatcher.
   154  func (w *subRelationsWatcher) Kill() {
   155  	w.catacomb.Kill(nil)
   156  }
   157  
   158  // Stop implements watcher.StringsWatcher.
   159  func (w *subRelationsWatcher) Stop() error {
   160  	w.Kill()
   161  	return w.Wait()
   162  }
   163  
   164  // Wait implements watcher.StringsWatcher.
   165  func (w *subRelationsWatcher) Wait() error {
   166  	return w.catacomb.Wait()
   167  }