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