github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/jujud/agent/agenttest/engine.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agenttest
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/juju/collections/set"
    12  	"github.com/juju/errors"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/worker.v1"
    16  	"gopkg.in/juju/worker.v1/dependency"
    17  	goyaml "gopkg.in/yaml.v2"
    18  
    19  	coretesting "github.com/juju/juju/testing"
    20  )
    21  
    22  // NewWorkerMatcher takes an EngineTracker, an engine manager id to
    23  // monitor and the workers that are expected to be running and sets up
    24  // a WorkerMatcher.
    25  func NewWorkerMatcher(c *gc.C, tracker *EngineTracker, id string, workers []string) *WorkerMatcher {
    26  	return &WorkerMatcher{
    27  		c:       c,
    28  		tracker: tracker,
    29  		id:      id,
    30  		expect:  set.NewStrings(workers...),
    31  	}
    32  }
    33  
    34  // WorkerMatcher monitors the workers of a single engine manager,
    35  // using an EngineTracker, for a given set of workers to be running.
    36  type WorkerMatcher struct {
    37  	c         *gc.C
    38  	tracker   *EngineTracker
    39  	id        string
    40  	expect    set.Strings
    41  	matchTime time.Time
    42  }
    43  
    44  // Check returns true if the workers which are expected to be running
    45  // (as specified in the call to NewWorkerMatcher) are running and have
    46  // been running for a short period (i.e. some indication of stability).
    47  func (m *WorkerMatcher) Check() bool {
    48  	if m.checkOnce() {
    49  		now := time.Now()
    50  		if m.matchTime.IsZero() {
    51  			m.matchTime = now
    52  			return false
    53  		}
    54  		// Only return that the required workers have started if they
    55  		// have been stable for a little while.
    56  		return now.Sub(m.matchTime) >= time.Second
    57  	}
    58  	// Required workers not running, reset the timestamp.
    59  	m.matchTime = time.Time{}
    60  	return false
    61  }
    62  
    63  func (m *WorkerMatcher) checkOnce() bool {
    64  	actual := m.tracker.Workers(m.id)
    65  	m.c.Logf("\n%s: has workers %v", m.id, actual.SortedValues())
    66  	extras := actual.Difference(m.expect)
    67  	missed := m.expect.Difference(actual)
    68  	if len(extras) == 0 && len(missed) == 0 {
    69  		return true
    70  	}
    71  	m.c.Logf("%s: waiting for %v", m.id, missed.SortedValues())
    72  	m.c.Logf("%s: unexpected %v", m.id, extras.SortedValues())
    73  	report, _ := goyaml.Marshal(m.tracker.Report(m.id))
    74  	m.c.Logf("%s: report: \n%s\n", m.id, report)
    75  	return false
    76  }
    77  
    78  // WaitMatch returns only when the match func succeeds, or it times out.
    79  func WaitMatch(c *gc.C, match func() bool, maxWait time.Duration, sync func()) {
    80  	timeout := time.After(maxWait)
    81  	for {
    82  		if match() {
    83  			return
    84  		}
    85  		select {
    86  		case <-time.After(coretesting.ShortWait):
    87  			sync()
    88  		case <-timeout:
    89  			c.Fatalf("timed out waiting for workers")
    90  		}
    91  	}
    92  }
    93  
    94  // NewEngineTracker creates a type that can Install itself into a
    95  // Manifolds map, and expose recent snapshots of running Workers.
    96  func NewEngineTracker() *EngineTracker {
    97  	return &EngineTracker{
    98  		current: make(map[string]set.Strings),
    99  		reports: make(map[string]map[string]interface{}),
   100  	}
   101  }
   102  
   103  // EngineTracker tracks workers which have started.
   104  type EngineTracker struct {
   105  	mu      sync.Mutex
   106  	current map[string]set.Strings
   107  	reports map[string]map[string]interface{}
   108  }
   109  
   110  // Workers returns the most-recently-reported set of running workers.
   111  func (tracker *EngineTracker) Workers(id string) set.Strings {
   112  	tracker.mu.Lock()
   113  	defer tracker.mu.Unlock()
   114  	return tracker.current[id]
   115  }
   116  
   117  // Report returns the most-recently-reported self-report. It will
   118  // only work if you hack up the relevant engine-starting code to
   119  // include:
   120  //
   121  //    manifolds["self"] = dependency.SelfManifold(engine)
   122  //
   123  // or otherwise inject a suitable "self" manifold.
   124  func (tracker *EngineTracker) Report(id string) map[string]interface{} {
   125  	tracker.mu.Lock()
   126  	defer tracker.mu.Unlock()
   127  	return tracker.reports[id]
   128  }
   129  
   130  // Install injects a manifold named TEST-TRACKER into raw, which will
   131  // depend on all other manifolds in raw and write currently-available
   132  // worker information to the tracker (differentiating it from other
   133  // tracked engines via the id param).
   134  func (tracker *EngineTracker) Install(raw dependency.Manifolds, id string) error {
   135  	const trackerName = "TEST-TRACKER"
   136  
   137  	names := make([]string, 0, len(raw))
   138  	for name := range raw {
   139  		if name == trackerName {
   140  			return errors.New("engine tracker installed repeatedly")
   141  		}
   142  		names = append(names, name)
   143  	}
   144  
   145  	tracker.mu.Lock()
   146  	defer tracker.mu.Unlock()
   147  	if _, exists := tracker.current[id]; exists {
   148  		return errors.Errorf("manifolds for %s created repeatedly", id)
   149  	}
   150  	raw[trackerName] = dependency.Manifold{
   151  		Inputs: append(names, "self"),
   152  		Start:  tracker.startFunc(id, names),
   153  	}
   154  	return nil
   155  }
   156  
   157  func (tracker *EngineTracker) startFunc(id string, names []string) dependency.StartFunc {
   158  	return func(context dependency.Context) (worker.Worker, error) {
   159  
   160  		seen := set.NewStrings()
   161  		for _, name := range names {
   162  			err := context.Get(name, nil)
   163  			switch errors.Cause(err) {
   164  			case nil:
   165  			case dependency.ErrMissing:
   166  				continue
   167  			default:
   168  				name = fmt.Sprintf("%s [%v]", name, err)
   169  			}
   170  			seen.Add(name)
   171  		}
   172  
   173  		var report map[string]interface{}
   174  		var reporter dependency.Reporter
   175  		if err := context.Get("self", &reporter); err == nil {
   176  			report = reporter.Report()
   177  		}
   178  
   179  		select {
   180  		case <-context.Abort():
   181  			// don't bother to report if it's about to change
   182  		default:
   183  			tracker.mu.Lock()
   184  			defer tracker.mu.Unlock()
   185  			tracker.current[id] = seen
   186  			tracker.reports[id] = report
   187  		}
   188  		return nil, dependency.ErrMissing
   189  	}
   190  }
   191  
   192  // AssertManifoldsDependencies asserts that given manifolds have expected dependencies.
   193  func AssertManifoldsDependencies(c *gc.C, manifolds dependency.Manifolds, expected map[string][]string) {
   194  	dependencies := make(map[string][]string, len(manifolds))
   195  	manifoldNames := set.NewStrings()
   196  
   197  	for name, manifold := range manifolds {
   198  		manifoldNames.Add(name)
   199  		dependencies[name] = ManifoldDependencies(manifolds, name, manifold).SortedValues()
   200  	}
   201  
   202  	empty := set.NewStrings()
   203  	names := set.NewStrings(keys(dependencies)...)
   204  	expectedNames := set.NewStrings(keys(expected)...)
   205  	// Unexpected names...
   206  	c.Assert(names.Difference(expectedNames), gc.DeepEquals, empty)
   207  	// Missing names...
   208  	c.Assert(expectedNames.Difference(names), gc.DeepEquals, empty)
   209  
   210  	for _, n := range manifoldNames.SortedValues() {
   211  		c.Check(dependencies[n], jc.SameContents, expected[n], gc.Commentf("mismatched dependencies for worker %q", n))
   212  	}
   213  }
   214  
   215  // ManifoldDependencies returns all - direct and indirect - manifold dependencies.
   216  func ManifoldDependencies(all dependency.Manifolds, name string, manifold dependency.Manifold) set.Strings {
   217  	result := set.NewStrings()
   218  	for _, input := range manifold.Inputs {
   219  		result.Add(input)
   220  		result = result.Union(ManifoldDependencies(all, input, all[input]))
   221  	}
   222  	return result
   223  }
   224  
   225  func keys(items map[string][]string) []string {
   226  	result := make([]string, 0, len(items))
   227  	for key := range items {
   228  		result = append(result, key)
   229  	}
   230  	return result
   231  }