github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/jujud/agent/engine_test.go (about)

     1  // Copyright 2012-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils/set"
    13  	gc "gopkg.in/check.v1"
    14  	goyaml "gopkg.in/yaml.v2"
    15  
    16  	"github.com/juju/juju/cmd/jujud/agent/machine"
    17  	"github.com/juju/juju/cmd/jujud/agent/model"
    18  	"github.com/juju/juju/cmd/jujud/agent/unit"
    19  	coretesting "github.com/juju/juju/testing"
    20  	"github.com/juju/juju/worker"
    21  	"github.com/juju/juju/worker/dependency"
    22  )
    23  
    24  var (
    25  	// These vars hold the per-model workers we expect to run in
    26  	// various circumstances. Note the absence of worker lists for
    27  	// dying/dead states, because those states are not stable: if
    28  	// they're working correctly the engine will be shut down.
    29  	alwaysModelWorkers = []string{
    30  		"agent",
    31  		"api-caller",
    32  		"api-config-watcher",
    33  		"clock",
    34  		"is-responsible-flag",
    35  		"not-alive-flag",
    36  		"not-dead-flag",
    37  		"spaces-imported-gate",
    38  	}
    39  	aliveModelWorkers = []string{
    40  		"charm-revision-updater",
    41  		"compute-provisioner",
    42  		"environ-tracker",
    43  		"firewaller",
    44  		"instance-poller",
    45  		"machine-undertaker",
    46  		"metric-worker",
    47  		"migration-fortress",
    48  		"migration-inactive-flag",
    49  		"migration-master",
    50  		"application-scaler",
    51  		"space-importer",
    52  		"state-cleaner",
    53  		"status-history-pruner",
    54  		"storage-provisioner",
    55  		"unit-assigner",
    56  	}
    57  	migratingModelWorkers = []string{
    58  		"environ-tracker",
    59  		"migration-fortress",
    60  		"migration-inactive-flag",
    61  		"migration-master",
    62  	}
    63  	// ReallyLongTimeout should be long enough for the model-tracker
    64  	// tests that depend on a hosted model; its backing state is not
    65  	// accessible for StartSyncs, so we generally have to wait for at
    66  	// least two 5s ticks to pass, and should expect rare circumstances
    67  	// to take even longer.
    68  	ReallyLongWait = coretesting.LongWait * 3
    69  
    70  	alwaysUnitWorkers = []string{
    71  		"agent",
    72  		"api-caller",
    73  		"api-config-watcher",
    74  		"log-sender",
    75  		"migration-fortress",
    76  		"migration-inactive-flag",
    77  		"migration-minion",
    78  		"upgrader",
    79  	}
    80  	notMigratingUnitWorkers = []string{
    81  		"api-address-updater",
    82  		"charm-dir",
    83  		"hook-retry-strategy",
    84  		"leadership-tracker",
    85  		"logging-config-updater",
    86  		"meter-status",
    87  		"metric-collect",
    88  		"metric-sender",
    89  		"metric-spool",
    90  		"proxy-config-updater",
    91  		"uniter",
    92  	}
    93  
    94  	alwaysMachineWorkers = []string{
    95  		"agent",
    96  		"api-caller",
    97  		"api-config-watcher",
    98  		"log-forwarder",
    99  		"migration-fortress",
   100  		"migration-inactive-flag",
   101  		"migration-minion",
   102  		"state-config-watcher",
   103  		"termination-signal-handler",
   104  		"upgrade-check-flag",
   105  		"upgrade-check-gate",
   106  		"upgrade-steps-flag",
   107  		"upgrade-steps-gate",
   108  		"upgrader",
   109  	}
   110  	notMigratingMachineWorkers = []string{
   111  		"api-address-updater",
   112  		"disk-manager",
   113  		// "host-key-reporter", not stable, exits when done
   114  		"log-sender",
   115  		"logging-config-updater",
   116  		"machine-action-runner",
   117  		"machiner",
   118  		"proxy-config-updater",
   119  		"reboot-executor",
   120  		"ssh-authkeys-updater",
   121  		"storage-provisioner",
   122  		"unconverted-api-workers",
   123  		"unit-agent-deployer",
   124  	}
   125  )
   126  
   127  type ModelManifoldsFunc func(config model.ManifoldsConfig) dependency.Manifolds
   128  
   129  func TrackModels(c *gc.C, tracker *engineTracker, inner ModelManifoldsFunc) ModelManifoldsFunc {
   130  	return func(config model.ManifoldsConfig) dependency.Manifolds {
   131  		raw := inner(config)
   132  		id := config.Agent.CurrentConfig().Model().Id()
   133  		if err := tracker.Install(raw, id); err != nil {
   134  			c.Errorf("cannot install tracker: %v", err)
   135  		}
   136  		return raw
   137  	}
   138  }
   139  
   140  type MachineManifoldsFunc func(config machine.ManifoldsConfig) dependency.Manifolds
   141  
   142  func TrackMachines(c *gc.C, tracker *engineTracker, inner MachineManifoldsFunc) MachineManifoldsFunc {
   143  	return func(config machine.ManifoldsConfig) dependency.Manifolds {
   144  		raw := inner(config)
   145  		id := config.Agent.CurrentConfig().Tag().String()
   146  		if err := tracker.Install(raw, id); err != nil {
   147  			c.Errorf("cannot install tracker: %v", err)
   148  		}
   149  		return raw
   150  	}
   151  }
   152  
   153  type UnitManifoldsFunc func(config unit.ManifoldsConfig) dependency.Manifolds
   154  
   155  func TrackUnits(c *gc.C, tracker *engineTracker, inner UnitManifoldsFunc) UnitManifoldsFunc {
   156  	return func(config unit.ManifoldsConfig) dependency.Manifolds {
   157  		raw := inner(config)
   158  		id := config.Agent.CurrentConfig().Tag().String()
   159  		if err := tracker.Install(raw, id); err != nil {
   160  			c.Errorf("cannot install tracker: %v", err)
   161  		}
   162  		return raw
   163  	}
   164  }
   165  
   166  // NewWorkerManager takes an engineTracker, an engine manager id to
   167  // monitor and the workers that are expected to be running and sets up
   168  // a WorkerManager.
   169  func NewWorkerMatcher(c *gc.C, tracker *engineTracker, id string, workers []string) *WorkerMatcher {
   170  	return &WorkerMatcher{
   171  		c:       c,
   172  		tracker: tracker,
   173  		id:      id,
   174  		expect:  set.NewStrings(workers...),
   175  	}
   176  }
   177  
   178  // WorkerMatcher monitors the workers of a single engine manager,
   179  // using an engineTracker, for a given set of workers to be running.
   180  type WorkerMatcher struct {
   181  	c         *gc.C
   182  	tracker   *engineTracker
   183  	id        string
   184  	expect    set.Strings
   185  	matchTime time.Time
   186  }
   187  
   188  // Check returns true if the workers which are expected to be running
   189  // (as specified in the call to NewWorkerMatcher) are running and have
   190  // been running for a short period (i.e. some indication of stability).
   191  func (m *WorkerMatcher) Check() bool {
   192  	if m.checkOnce() {
   193  		now := time.Now()
   194  		if m.matchTime.IsZero() {
   195  			m.matchTime = now
   196  			return false
   197  		}
   198  		// Only return that the required workers have started if they
   199  		// have been stable for a little while.
   200  		return now.Sub(m.matchTime) >= time.Second
   201  	}
   202  	// Required workers not running, reset the timestamp.
   203  	m.matchTime = time.Time{}
   204  	return false
   205  }
   206  
   207  func (m *WorkerMatcher) checkOnce() bool {
   208  	actual := m.tracker.Workers(m.id)
   209  	m.c.Logf("\n%s: has workers %v", m.id, actual.SortedValues())
   210  	extras := actual.Difference(m.expect)
   211  	missed := m.expect.Difference(actual)
   212  	if len(extras) == 0 && len(missed) == 0 {
   213  		return true
   214  	}
   215  	m.c.Logf("%s: waiting for %v", m.id, missed.SortedValues())
   216  	m.c.Logf("%s: unexpected %v", m.id, extras.SortedValues())
   217  	report, _ := goyaml.Marshal(m.tracker.Report(m.id))
   218  	m.c.Logf("%s: report: \n%s\n", m.id, report)
   219  	return false
   220  }
   221  
   222  // WaitMatch returns only when the match func succeeds, or it times out.
   223  func WaitMatch(c *gc.C, match func() bool, maxWait time.Duration, sync func()) {
   224  	timeout := time.After(maxWait)
   225  	for {
   226  		if match() {
   227  			return
   228  		}
   229  		select {
   230  		case <-time.After(coretesting.ShortWait):
   231  			sync()
   232  		case <-timeout:
   233  			c.Fatalf("timed out waiting for workers")
   234  		}
   235  	}
   236  }
   237  
   238  // NewEngineTracker creates a type that can Install itself into a
   239  // Manifolds map, and expose recent snapshots of running Workers.
   240  func NewEngineTracker() *engineTracker {
   241  	return &engineTracker{
   242  		current: make(map[string]set.Strings),
   243  		reports: make(map[string]map[string]interface{}),
   244  	}
   245  }
   246  
   247  type engineTracker struct {
   248  	mu      sync.Mutex
   249  	current map[string]set.Strings
   250  	reports map[string]map[string]interface{}
   251  }
   252  
   253  // Workers returns the most-recently-reported set of running workers.
   254  func (tracker *engineTracker) Workers(id string) set.Strings {
   255  	tracker.mu.Lock()
   256  	defer tracker.mu.Unlock()
   257  	return tracker.current[id]
   258  }
   259  
   260  // Report returns the most-recently-reported self-report. It will
   261  // only work if you hack up the relevant engine-starting code to
   262  // include:
   263  //
   264  //    manifolds["self"] = dependency.SelfManifold(engine)
   265  //
   266  // or otherwise inject a suitable "self" manifold.
   267  func (tracker *engineTracker) Report(id string) map[string]interface{} {
   268  	tracker.mu.Lock()
   269  	defer tracker.mu.Unlock()
   270  	return tracker.reports[id]
   271  }
   272  
   273  // Install injects a manifold named TEST-TRACKER into raw, which will
   274  // depend on all other manifolds in raw and write currently-available
   275  // worker information to the tracker (differentiating it from other
   276  // tracked engines via the id param).
   277  func (tracker *engineTracker) Install(raw dependency.Manifolds, id string) error {
   278  	const trackerName = "TEST-TRACKER"
   279  
   280  	names := make([]string, 0, len(raw))
   281  	for name := range raw {
   282  		if name == trackerName {
   283  			return errors.New("engine tracker installed repeatedly")
   284  		}
   285  		names = append(names, name)
   286  	}
   287  
   288  	tracker.mu.Lock()
   289  	defer tracker.mu.Unlock()
   290  	if _, exists := tracker.current[id]; exists {
   291  		return errors.Errorf("manifolds for %s created repeatedly", id)
   292  	}
   293  	raw[trackerName] = dependency.Manifold{
   294  		Inputs: append(names, "self"),
   295  		Start:  tracker.startFunc(id, names),
   296  	}
   297  	return nil
   298  }
   299  
   300  func (tracker *engineTracker) startFunc(id string, names []string) dependency.StartFunc {
   301  	return func(context dependency.Context) (worker.Worker, error) {
   302  
   303  		seen := set.NewStrings()
   304  		for _, name := range names {
   305  			err := context.Get(name, nil)
   306  			switch errors.Cause(err) {
   307  			case nil:
   308  			case dependency.ErrMissing:
   309  				continue
   310  			default:
   311  				name = fmt.Sprintf("%s [%v]", name, err)
   312  			}
   313  			seen.Add(name)
   314  		}
   315  
   316  		var report map[string]interface{}
   317  		var reporter dependency.Reporter
   318  		if err := context.Get("self", &reporter); err == nil {
   319  			report = reporter.Report()
   320  		}
   321  
   322  		select {
   323  		case <-context.Abort():
   324  			// don't bother to report if it's about to change
   325  		default:
   326  			tracker.mu.Lock()
   327  			defer tracker.mu.Unlock()
   328  			tracker.current[id] = seen
   329  			tracker.reports[id] = report
   330  		}
   331  		return nil, dependency.ErrMissing
   332  	}
   333  }