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