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 }