launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/relation/hookqueue.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package relation
     5  
     6  import (
     7  	"launchpad.net/tomb"
     8  	"sort"
     9  
    10  	"launchpad.net/juju-core/charm/hooks"
    11  	"launchpad.net/juju-core/state/api/params"
    12  	"launchpad.net/juju-core/state/watcher"
    13  	"launchpad.net/juju-core/worker/uniter/hook"
    14  )
    15  
    16  // HookQueue is the minimal interface implemented by both AliveHookQueue and
    17  // DyingHookQueue.
    18  type HookQueue interface {
    19  	hookQueue()
    20  	Stop() error
    21  }
    22  
    23  // RelationUnitsWatcher is used to enable deterministic testing of
    24  // AliveHookQueue, by supplying a reliable stream of RelationUnitsChange
    25  // events; usually, it will be a *state.RelationUnitsWatcher.
    26  type RelationUnitsWatcher interface {
    27  	Err() error
    28  	Stop() error
    29  	Changes() <-chan params.RelationUnitsChange
    30  }
    31  
    32  // AliveHookQueue aggregates values obtained from a relation units watcher
    33  // and sends out details about hooks that must be executed in the unit.
    34  type AliveHookQueue struct {
    35  	tomb       tomb.Tomb
    36  	w          RelationUnitsWatcher
    37  	out        chan<- hook.Info
    38  	relationId int
    39  
    40  	// info holds information about all units that were added to the
    41  	// queue and haven't had a "relation-departed" event popped. This
    42  	// means the unit may be in info and not currently in the queue
    43  	// itself.
    44  	info map[string]*unitInfo
    45  
    46  	// head and tail are the ends of the queue.
    47  	head, tail *unitInfo
    48  
    49  	// changedPending, if not empty, indicates that the most recently
    50  	// popped event was a "relation-joined" for the named unit, and
    51  	// therefore that the next event must be a "relation-changed"
    52  	// for that same unit.
    53  	// If changedPending is not empty, the queue is considered non-
    54  	// empty, even if head is nil.
    55  	changedPending string
    56  }
    57  
    58  // unitInfo holds unit information for management by AliveHookQueue.
    59  type unitInfo struct {
    60  	// unit holds the name of the unit.
    61  	unit string
    62  
    63  	// version and settings hold the most recent settings known
    64  	// to the AliveHookQueue.
    65  	version int64
    66  
    67  	// joined is set to true when a "relation-joined" is popped for this unit.
    68  	joined bool
    69  
    70  	// hookKind holds the current idea of the next hook that should
    71  	// be run for the unit, and is empty if and only if the unit
    72  	// is not queued.
    73  	hookKind hooks.Kind
    74  
    75  	// prev and next define the position in the queue of the
    76  	// unit's next hook.
    77  	prev, next *unitInfo
    78  }
    79  
    80  // NewAliveHookQueue returns a new AliveHookQueue that aggregates the values
    81  // obtained from the w watcher and sends into out the details about hooks that
    82  // must be executed in the unit. It guarantees that the stream of hooks will
    83  // respect the guarantees Juju makes about hook execution order. If any values
    84  // have previously been received from w's Changes channel, the AliveHookQueue's
    85  // behaviour is undefined.
    86  func NewAliveHookQueue(initial *State, out chan<- hook.Info, w RelationUnitsWatcher) *AliveHookQueue {
    87  	q := &AliveHookQueue{
    88  		w:          w,
    89  		out:        out,
    90  		relationId: initial.RelationId,
    91  		info:       map[string]*unitInfo{},
    92  	}
    93  	go q.loop(initial)
    94  	return q
    95  }
    96  
    97  func (q *AliveHookQueue) loop(initial *State) {
    98  	defer q.tomb.Done()
    99  	defer watcher.Stop(q.w, &q.tomb)
   100  
   101  	// Consume initial event, and reconcile with initial state, by inserting
   102  	// a new RelationUnitsChange before the initial event, which schedules
   103  	// every missing unit for immediate departure before anything else happens
   104  	// (apart from a single potential required post-joined changed event).
   105  	ch1, ok := <-q.w.Changes()
   106  	if !ok {
   107  		q.tomb.Kill(watcher.MustErr(q.w))
   108  		return
   109  	}
   110  	if len(ch1.Departed) != 0 {
   111  		panic("AliveHookQueue must be started with a fresh RelationUnitsWatcher")
   112  	}
   113  	q.changedPending = initial.ChangedPending
   114  	ch0 := params.RelationUnitsChange{}
   115  	for unit, version := range initial.Members {
   116  		q.info[unit] = &unitInfo{
   117  			unit:    unit,
   118  			version: version,
   119  			joined:  true,
   120  		}
   121  		if _, found := ch1.Changed[unit]; !found {
   122  			ch0.Departed = append(ch0.Departed, unit)
   123  		}
   124  	}
   125  	q.update(ch0)
   126  	q.update(ch1)
   127  
   128  	var next hook.Info
   129  	var out chan<- hook.Info
   130  	for {
   131  		if q.empty() {
   132  			out = nil
   133  		} else {
   134  			out = q.out
   135  			next = q.next()
   136  		}
   137  		select {
   138  		case <-q.tomb.Dying():
   139  			return
   140  		case ch, ok := <-q.w.Changes():
   141  			if !ok {
   142  				q.tomb.Kill(watcher.MustErr(q.w))
   143  				return
   144  			}
   145  			q.update(ch)
   146  		case out <- next:
   147  			q.pop()
   148  		}
   149  	}
   150  }
   151  
   152  func (q *AliveHookQueue) hookQueue() {
   153  	panic("interface sentinel method, do not call")
   154  }
   155  
   156  // Stop stops the AliveHookQueue and returns any errors encountered during
   157  // operation or while shutting down.
   158  func (q *AliveHookQueue) Stop() error {
   159  	q.tomb.Kill(nil)
   160  	return q.tomb.Wait()
   161  }
   162  
   163  // empty returns true if the queue is empty.
   164  func (q *AliveHookQueue) empty() bool {
   165  	return q.head == nil && q.changedPending == ""
   166  }
   167  
   168  // update modifies the queue such that the hook.Info values it sends will
   169  // reflect the supplied change.
   170  func (q *AliveHookQueue) update(ruc params.RelationUnitsChange) {
   171  	// Enforce consistent addition order, mainly for testing purposes.
   172  	changedUnits := []string{}
   173  	for unit := range ruc.Changed {
   174  		changedUnits = append(changedUnits, unit)
   175  	}
   176  	sort.Strings(changedUnits)
   177  
   178  	for _, unit := range changedUnits {
   179  		settings := ruc.Changed[unit]
   180  		info, found := q.info[unit]
   181  		if !found {
   182  			info = &unitInfo{unit: unit}
   183  			q.info[unit] = info
   184  			q.queue(unit, hooks.RelationJoined)
   185  		} else if info.hookKind != hooks.RelationJoined {
   186  			if settings.Version != info.version {
   187  				q.queue(unit, hooks.RelationChanged)
   188  			} else {
   189  				q.unqueue(unit)
   190  			}
   191  		}
   192  		info.version = settings.Version
   193  	}
   194  
   195  	for _, unit := range ruc.Departed {
   196  		if q.info[unit].hookKind == hooks.RelationJoined {
   197  			q.unqueue(unit)
   198  		} else {
   199  			q.queue(unit, hooks.RelationDeparted)
   200  		}
   201  	}
   202  }
   203  
   204  // pop advances the queue. It will panic if the queue is already empty.
   205  func (q *AliveHookQueue) pop() {
   206  	if q.empty() {
   207  		panic("queue is empty")
   208  	}
   209  	if q.changedPending != "" {
   210  		if q.info[q.changedPending].hookKind == hooks.RelationChanged {
   211  			// We just ran this very hook; no sense keeping it queued.
   212  			q.unqueue(q.changedPending)
   213  		}
   214  		q.changedPending = ""
   215  	} else {
   216  		old := *q.head
   217  		q.unqueue(q.head.unit)
   218  		if old.hookKind == hooks.RelationJoined {
   219  			q.changedPending = old.unit
   220  			q.info[old.unit].joined = true
   221  		} else if old.hookKind == hooks.RelationDeparted {
   222  			delete(q.info, old.unit)
   223  		}
   224  	}
   225  }
   226  
   227  // next returns the next hook.Info value to send.
   228  func (q *AliveHookQueue) next() hook.Info {
   229  	if q.empty() {
   230  		panic("queue is empty")
   231  	}
   232  	var unit string
   233  	var kind hooks.Kind
   234  	if q.changedPending != "" {
   235  		unit = q.changedPending
   236  		kind = hooks.RelationChanged
   237  	} else {
   238  		unit = q.head.unit
   239  		kind = q.head.hookKind
   240  	}
   241  	version := q.info[unit].version
   242  	return hook.Info{
   243  		Kind:          kind,
   244  		RelationId:    q.relationId,
   245  		RemoteUnit:    unit,
   246  		ChangeVersion: version,
   247  	}
   248  }
   249  
   250  // queue sets the next hook to be run for the named unit, and places it
   251  // at the tail of the queue if it is not already queued. It will panic
   252  // if the unit is not in q.info.
   253  func (q *AliveHookQueue) queue(unit string, kind hooks.Kind) {
   254  	// If the unit is not in the queue, place it at the tail.
   255  	info := q.info[unit]
   256  	if info.hookKind == "" {
   257  		info.prev = q.tail
   258  		if q.tail != nil {
   259  			q.tail.next = info
   260  		}
   261  		q.tail = info
   262  
   263  		// If the queue is empty, the tail is also the head.
   264  		if q.head == nil {
   265  			q.head = info
   266  		}
   267  	}
   268  	info.hookKind = kind
   269  }
   270  
   271  // unqueue removes the named unit from the queue. It is fine to
   272  // unqueue a unit that is not in the queue, but it will panic if
   273  // the unit is not in q.info.
   274  func (q *AliveHookQueue) unqueue(unit string) {
   275  	if q.head == nil {
   276  		// The queue is empty, nothing to do.
   277  		return
   278  	}
   279  
   280  	// Get the unit info and clear its next action.
   281  	info := q.info[unit]
   282  	if info.hookKind == "" {
   283  		// The unit is not in the queue, nothing to do.
   284  		return
   285  	}
   286  	info.hookKind = ""
   287  
   288  	// Update queue pointers.
   289  	if info.prev == nil {
   290  		q.head = info.next
   291  	} else {
   292  		info.prev.next = info.next
   293  	}
   294  	if info.next == nil {
   295  		q.tail = info.prev
   296  	} else {
   297  		info.next.prev = info.prev
   298  	}
   299  	info.prev = nil
   300  	info.next = nil
   301  }
   302  
   303  // DyingHookQueue is a hook queue that deals with a relation that is being
   304  // shut down. It honours the obligations of an AliveHookQueue with respect to
   305  // relation hook execution order; as soon as those obligations are fulfilled,
   306  // it sends a "relation-departed" hook for every relation member, and finally a
   307  // "relation-broken" hook for the relation itself.
   308  type DyingHookQueue struct {
   309  	tomb           tomb.Tomb
   310  	out            chan<- hook.Info
   311  	relationId     int
   312  	members        map[string]int64
   313  	changedPending string
   314  }
   315  
   316  // NewDyingHookQueue returns a new DyingHookQueue that shuts down the state in
   317  // initial.
   318  func NewDyingHookQueue(initial *State, out chan<- hook.Info) *DyingHookQueue {
   319  	q := &DyingHookQueue{
   320  		out:            out,
   321  		relationId:     initial.RelationId,
   322  		members:        map[string]int64{},
   323  		changedPending: initial.ChangedPending,
   324  	}
   325  	for m, v := range initial.Members {
   326  		q.members[m] = v
   327  	}
   328  	go q.loop()
   329  	return q
   330  }
   331  
   332  func (q *DyingHookQueue) loop() {
   333  	defer q.tomb.Done()
   334  
   335  	// Honour any expected relation-changed hook.
   336  	if q.changedPending != "" {
   337  		select {
   338  		case <-q.tomb.Dying():
   339  			return
   340  		case q.out <- q.hookInfo(hooks.RelationChanged, q.changedPending):
   341  		}
   342  	}
   343  
   344  	// Depart in consistent order, mainly for testing purposes.
   345  	departs := []string{}
   346  	for m := range q.members {
   347  		departs = append(departs, m)
   348  	}
   349  	sort.Strings(departs)
   350  	for _, unit := range departs {
   351  		select {
   352  		case <-q.tomb.Dying():
   353  			return
   354  		case q.out <- q.hookInfo(hooks.RelationDeparted, unit):
   355  		}
   356  	}
   357  
   358  	// Finally break the relation.
   359  	select {
   360  	case <-q.tomb.Dying():
   361  		return
   362  	case q.out <- hook.Info{Kind: hooks.RelationBroken, RelationId: q.relationId}:
   363  	}
   364  	q.tomb.Kill(nil)
   365  	return
   366  }
   367  
   368  // hookInfo updates the queue's internal membership state according to the
   369  // supplied information, and returns a hook.Info reflecting that change.
   370  func (q *DyingHookQueue) hookInfo(kind hooks.Kind, unit string) hook.Info {
   371  	hi := hook.Info{
   372  		Kind:          kind,
   373  		RelationId:    q.relationId,
   374  		RemoteUnit:    unit,
   375  		ChangeVersion: q.members[unit],
   376  	}
   377  	return hi
   378  }
   379  
   380  func (q *DyingHookQueue) hookQueue() {
   381  	panic("interface sentinel method, do not call")
   382  }
   383  
   384  // Stop stops the DyingHookQueue and returns any errors encountered
   385  // during operation or while shutting down.
   386  func (q *DyingHookQueue) Stop() error {
   387  	q.tomb.Kill(nil)
   388  	return q.tomb.Wait()
   389  }