github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/uniter/relation/livesource.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package relation
     5  
     6  import (
     7  	"sort"
     8  	"sync"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/charm.v5/hooks"
    12  
    13  	"github.com/juju/juju/state/multiwatcher"
    14  	"github.com/juju/juju/worker/uniter/hook"
    15  )
    16  
    17  // liveSource maintains a minimal queue of hooks that need to be run to reflect
    18  // relation state changes exposed via a RelationUnitsWatcher.
    19  type liveSource struct {
    20  	relationId int
    21  
    22  	// info holds information about all units that were added to the
    23  	// queue and haven't had a "relation-departed" event popped. This
    24  	// means the unit may be in info and not currently in the queue
    25  	// itself.
    26  	info map[string]*unitInfo
    27  
    28  	// head and tail are the ends of the queue.
    29  	head, tail *unitInfo
    30  
    31  	// changedPending, if not empty, indicates that the most recently
    32  	// popped event was a "relation-joined" for the named unit, and
    33  	// therefore that the next event must be a "relation-changed"
    34  	// for that same unit.
    35  	// If changedPending is not empty, the queue is considered non-
    36  	// empty, even if head is nil.
    37  	changedPending string
    38  
    39  	started bool
    40  	watcher RelationUnitsWatcher
    41  	changes chan hook.SourceChange
    42  }
    43  
    44  // unitInfo holds unit information for management by liveSource.
    45  type unitInfo struct {
    46  	// unit holds the name of the unit.
    47  	unit string
    48  
    49  	// version and settings hold the most recent settings known
    50  	// to the AliveHookQueue.
    51  	version int64
    52  
    53  	// joined is set to true when a "relation-joined" is popped for this unit.
    54  	joined bool
    55  
    56  	// hookKind holds the current idea of the next hook that should
    57  	// be run for the unit, and is empty if and only if the unit
    58  	// is not queued.
    59  	hookKind hooks.Kind
    60  
    61  	// prev and next define the position in the queue of the
    62  	// unit's next hook.
    63  	prev, next *unitInfo
    64  }
    65  
    66  // NewLiveHookSource returns a new HookSource that aggregates the values
    67  // obtained from the w watcher and generates the hooks that must be executed
    68  // in the unit. It guarantees that the stream of hooks will respect the
    69  // guarantees Juju makes about hook execution order. If any values have
    70  // previously been received from w's Changes channel, the Source's
    71  // behaviour is undefined.
    72  func NewLiveHookSource(initial *State, w RelationUnitsWatcher) hook.Source {
    73  	info := map[string]*unitInfo{}
    74  	for unit, version := range initial.Members {
    75  		info[unit] = &unitInfo{
    76  			unit:    unit,
    77  			version: version,
    78  			joined:  true,
    79  		}
    80  	}
    81  	s := &liveSource{
    82  		watcher:        w,
    83  		info:           info,
    84  		relationId:     initial.RelationId,
    85  		changedPending: initial.ChangedPending,
    86  		changes:        make(chan hook.SourceChange),
    87  	}
    88  	go func() {
    89  		defer close(s.changes)
    90  		// w's out channel will be closed when the source is Stop()ped.
    91  		// We use a waitgroup to ensure the current change is processed
    92  		// before any more changes are accepted.
    93  		var wg sync.WaitGroup
    94  		for c := range w.Changes() {
    95  			wg.Add(1)
    96  			s.changes <- func() error {
    97  				defer wg.Done()
    98  				return s.Update(c)
    99  			}
   100  			wg.Wait()
   101  		}
   102  	}()
   103  	return s
   104  }
   105  
   106  // Changes returns a channel sending a stream of hook.SourceChange events
   107  // that need to be Applied in order for the source to function correctly.
   108  // In particular, the first event represents the ideal state of the relation,
   109  // and must be delivered for the source to be able to calculate the desired
   110  // hooks.
   111  func (q *liveSource) Changes() <-chan hook.SourceChange {
   112  	return q.changes
   113  }
   114  
   115  // Stop cleans up the liveSource's resources and stops sending changes.
   116  func (q *liveSource) Stop() error {
   117  	return q.watcher.Stop()
   118  }
   119  
   120  // Update modifies the queue such that the hook.Info values it sends will
   121  // reflect the supplied change.
   122  func (q *liveSource) Update(change multiwatcher.RelationUnitsChange) error {
   123  	if !q.started {
   124  		q.started = true
   125  		// The first event represents the ideal final state of the system.
   126  		// If it contains any Departed notifications, it cannot be one of
   127  		// those -- most likely the watcher was not a fresh one -- and we're
   128  		// completely hosed.
   129  		if len(change.Departed) != 0 {
   130  			return errors.Errorf("hook source watcher sent bad event: %#v", change)
   131  		}
   132  		// Anyway, before we can generate actual hooks, we have to generate
   133  		// departed hooks for any previously-known members not reflected in
   134  		// the ideal state, and insert those at the head of the queue. The
   135  		// easiest way to do this is to inject a departure update for those
   136  		// missing members before processing the ideal state.
   137  		departs := multiwatcher.RelationUnitsChange{}
   138  		for unit := range q.info {
   139  			if _, found := change.Changed[unit]; !found {
   140  				departs.Departed = append(departs.Departed, unit)
   141  			}
   142  		}
   143  		q.update(departs)
   144  	}
   145  	q.update(change)
   146  	return nil
   147  }
   148  
   149  // Empty returns true if the queue is empty.
   150  func (q *liveSource) Empty() bool {
   151  	// If the first event has not yet been delivered, we cannot correctly
   152  	// determine the schedule, so we pretend to be empty rather than expose
   153  	// an incorrect hook.
   154  	if !q.started {
   155  		return true
   156  	}
   157  	return q.head == nil && q.changedPending == ""
   158  }
   159  
   160  // Next returns the next hook.Info value to send. It will panic if the queue is
   161  // empty.
   162  func (q *liveSource) Next() hook.Info {
   163  	if q.Empty() {
   164  		panic("queue is empty")
   165  	}
   166  	var unit string
   167  	var kind hooks.Kind
   168  	if q.changedPending != "" {
   169  		unit = q.changedPending
   170  		kind = hooks.RelationChanged
   171  	} else {
   172  		unit = q.head.unit
   173  		kind = q.head.hookKind
   174  	}
   175  	version := q.info[unit].version
   176  	return hook.Info{
   177  		Kind:          kind,
   178  		RelationId:    q.relationId,
   179  		RemoteUnit:    unit,
   180  		ChangeVersion: version,
   181  	}
   182  }
   183  
   184  // Pop advances the queue. It will panic if the queue is already empty.
   185  func (q *liveSource) Pop() {
   186  	if q.Empty() {
   187  		panic("queue is empty")
   188  	}
   189  	if q.changedPending != "" {
   190  		if q.info[q.changedPending].hookKind == hooks.RelationChanged {
   191  			// We just ran this very hook; no sense keeping it queued.
   192  			q.unqueue(q.changedPending)
   193  		}
   194  		q.changedPending = ""
   195  	} else {
   196  		old := *q.head
   197  		q.unqueue(q.head.unit)
   198  		if old.hookKind == hooks.RelationJoined {
   199  			q.changedPending = old.unit
   200  			q.info[old.unit].joined = true
   201  		} else if old.hookKind == hooks.RelationDeparted {
   202  			delete(q.info, old.unit)
   203  		}
   204  	}
   205  }
   206  
   207  func (q *liveSource) update(change multiwatcher.RelationUnitsChange) {
   208  	// Enforce consistent addition order, mainly for testing purposes.
   209  	changedUnits := []string{}
   210  	for unit := range change.Changed {
   211  		changedUnits = append(changedUnits, unit)
   212  	}
   213  	sort.Strings(changedUnits)
   214  
   215  	for _, unit := range changedUnits {
   216  		settings := change.Changed[unit]
   217  		info, found := q.info[unit]
   218  		if !found {
   219  			info = &unitInfo{unit: unit}
   220  			q.info[unit] = info
   221  			q.queue(unit, hooks.RelationJoined)
   222  		} else if info.hookKind != hooks.RelationJoined {
   223  			if settings.Version != info.version {
   224  				q.queue(unit, hooks.RelationChanged)
   225  			} else {
   226  				q.unqueue(unit)
   227  			}
   228  		}
   229  		info.version = settings.Version
   230  	}
   231  
   232  	for _, unit := range change.Departed {
   233  		if q.info[unit].hookKind == hooks.RelationJoined {
   234  			q.unqueue(unit)
   235  		} else {
   236  			q.queue(unit, hooks.RelationDeparted)
   237  		}
   238  	}
   239  }
   240  
   241  // queue sets the next hook to be run for the named unit, and places it
   242  // at the tail of the queue if it is not already queued. It will panic
   243  // if the unit is not in q.info.
   244  func (q *liveSource) queue(unit string, kind hooks.Kind) {
   245  	// If the unit is not in the queue, place it at the tail.
   246  	info := q.info[unit]
   247  	if info.hookKind == "" {
   248  		info.prev = q.tail
   249  		if q.tail != nil {
   250  			q.tail.next = info
   251  		}
   252  		q.tail = info
   253  
   254  		// If the queue is empty, the tail is also the head.
   255  		if q.head == nil {
   256  			q.head = info
   257  		}
   258  	}
   259  	info.hookKind = kind
   260  }
   261  
   262  // unqueue removes the named unit from the queue. It is fine to
   263  // unqueue a unit that is not in the queue, but it will panic if
   264  // the unit is not in q.info.
   265  func (q *liveSource) unqueue(unit string) {
   266  	if q.head == nil {
   267  		// The queue is empty, nothing to do.
   268  		return
   269  	}
   270  
   271  	// Get the unit info and clear its next action.
   272  	info := q.info[unit]
   273  	if info.hookKind == "" {
   274  		// The unit is not in the queue, nothing to do.
   275  		return
   276  	}
   277  	info.hookKind = ""
   278  
   279  	// Update queue pointers.
   280  	if info.prev == nil {
   281  		q.head = info.next
   282  	} else {
   283  		info.prev.next = info.next
   284  	}
   285  	if info.next == nil {
   286  		q.tail = info.prev
   287  	} else {
   288  		info.next.prev = info.prev
   289  	}
   290  	info.prev = nil
   291  	info.next = nil
   292  }