github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/api/watcher/watcher_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package watcher_test 5 6 import ( 7 "time" 8 9 jc "github.com/juju/testing/checkers" 10 "github.com/juju/utils" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/api" 15 "github.com/juju/juju/api/migrationminion" 16 "github.com/juju/juju/api/watcher" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/core/migration" 19 "github.com/juju/juju/juju/testing" 20 "github.com/juju/juju/state" 21 coretesting "github.com/juju/juju/testing" 22 "github.com/juju/juju/testing/factory" 23 corewatcher "github.com/juju/juju/watcher" 24 "github.com/juju/juju/watcher/watchertest" 25 "github.com/juju/juju/worker" 26 ) 27 28 type watcherSuite struct { 29 testing.JujuConnSuite 30 31 stateAPI api.Connection 32 33 // These are raw State objects. Use them for setup and assertions, but 34 // should never be touched by the API calls themselves 35 rawMachine *state.Machine 36 } 37 38 var _ = gc.Suite(&watcherSuite{}) 39 40 func (s *watcherSuite) SetUpTest(c *gc.C) { 41 s.JujuConnSuite.SetUpTest(c) 42 s.stateAPI, s.rawMachine = s.OpenAPIAsNewMachine(c, state.JobManageModel, state.JobHostUnits) 43 } 44 45 func (s *watcherSuite) TestWatchInitialEventConsumed(c *gc.C) { 46 // Machiner.Watch should send the initial event as part of the Watch 47 // call (for NotifyWatchers there is no state to be transmitted). So a 48 // call to Next() should not have anything to return. 49 var results params.NotifyWatchResults 50 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 51 err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) 52 c.Assert(err, jc.ErrorIsNil) 53 c.Assert(results.Results, gc.HasLen, 1) 54 result := results.Results[0] 55 c.Assert(result.Error, gc.IsNil) 56 57 // We expect the Call() to "Next" to block, so run it in a goroutine. 58 done := make(chan error) 59 go func() { 60 ignored := struct{}{} 61 done <- s.stateAPI.APICall("NotifyWatcher", s.stateAPI.BestFacadeVersion("NotifyWatcher"), result.NotifyWatcherId, "Next", nil, &ignored) 62 }() 63 64 select { 65 case err := <-done: 66 c.Errorf("Call(Next) did not block immediately after Watch(): err %v", err) 67 case <-time.After(coretesting.ShortWait): 68 } 69 } 70 71 func (s *watcherSuite) TestWatchMachine(c *gc.C) { 72 var results params.NotifyWatchResults 73 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 74 err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) 75 c.Assert(err, jc.ErrorIsNil) 76 c.Assert(results.Results, gc.HasLen, 1) 77 result := results.Results[0] 78 c.Assert(result.Error, gc.IsNil) 79 80 w := watcher.NewNotifyWatcher(s.stateAPI, result) 81 wc := watchertest.NewNotifyWatcherC(c, w, s.BackingState.StartSync) 82 defer wc.AssertStops() 83 wc.AssertOneChange() 84 } 85 86 func (s *watcherSuite) TestNotifyWatcherStopsWithPendingSend(c *gc.C) { 87 var results params.NotifyWatchResults 88 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 89 err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) 90 c.Assert(err, jc.ErrorIsNil) 91 c.Assert(results.Results, gc.HasLen, 1) 92 result := results.Results[0] 93 c.Assert(result.Error, gc.IsNil) 94 95 // params.NotifyWatcher conforms to the watcher.NotifyWatcher interface 96 w := watcher.NewNotifyWatcher(s.stateAPI, result) 97 wc := watchertest.NewNotifyWatcherC(c, w, s.BackingState.StartSync) 98 wc.AssertStops() 99 } 100 101 func (s *watcherSuite) TestWatchUnitsKeepsEvents(c *gc.C) { 102 // Create two services, relate them, and add one unit to each - a 103 // principal and a subordinate. 104 mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) 105 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 106 eps, err := s.State.InferEndpoints("mysql", "logging") 107 c.Assert(err, jc.ErrorIsNil) 108 rel, err := s.State.AddRelation(eps...) 109 c.Assert(err, jc.ErrorIsNil) 110 principal, err := mysql.AddUnit() 111 c.Assert(err, jc.ErrorIsNil) 112 err = principal.AssignToMachine(s.rawMachine) 113 c.Assert(err, jc.ErrorIsNil) 114 relUnit, err := rel.Unit(principal) 115 c.Assert(err, jc.ErrorIsNil) 116 err = relUnit.EnterScope(nil) 117 c.Assert(err, jc.ErrorIsNil) 118 subordinate, err := s.State.Unit("logging/0") 119 c.Assert(err, jc.ErrorIsNil) 120 121 // Call the Deployer facade's WatchUnits for machine-0. 122 var results params.StringsWatchResults 123 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 124 err = s.stateAPI.APICall("Deployer", s.stateAPI.BestFacadeVersion("Deployer"), "", "WatchUnits", args, &results) 125 c.Assert(err, jc.ErrorIsNil) 126 c.Assert(results.Results, gc.HasLen, 1) 127 result := results.Results[0] 128 c.Assert(result.Error, gc.IsNil) 129 130 // Start a StringsWatcher and check the initial event. 131 w := watcher.NewStringsWatcher(s.stateAPI, result) 132 wc := watchertest.NewStringsWatcherC(c, w, s.BackingState.StartSync) 133 defer wc.AssertStops() 134 135 wc.AssertChange("mysql/0", "logging/0") 136 wc.AssertNoChange() 137 138 // Now, without reading any changes advance the lifecycle of both 139 // units, inducing an update server-side after each two changes to 140 // ensure they're reported as separate events over the API. 141 err = subordinate.EnsureDead() 142 c.Assert(err, jc.ErrorIsNil) 143 s.BackingState.StartSync() 144 err = subordinate.Remove() 145 c.Assert(err, jc.ErrorIsNil) 146 err = principal.EnsureDead() 147 c.Assert(err, jc.ErrorIsNil) 148 s.BackingState.StartSync() 149 150 // Expect these changes as 2 separate events, so that 151 // nothing gets lost. 152 wc.AssertChange("logging/0") 153 wc.AssertChange("mysql/0") 154 wc.AssertNoChange() 155 } 156 157 func (s *watcherSuite) TestStringsWatcherStopsWithPendingSend(c *gc.C) { 158 // Call the Deployer facade's WatchUnits for machine-0. 159 var results params.StringsWatchResults 160 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 161 err := s.stateAPI.APICall("Deployer", s.stateAPI.BestFacadeVersion("Deployer"), "", "WatchUnits", args, &results) 162 c.Assert(err, jc.ErrorIsNil) 163 c.Assert(results.Results, gc.HasLen, 1) 164 result := results.Results[0] 165 c.Assert(result.Error, gc.IsNil) 166 167 // Start a StringsWatcher and check the initial event. 168 w := watcher.NewStringsWatcher(s.stateAPI, result) 169 wc := watchertest.NewStringsWatcherC(c, w, s.BackingState.StartSync) 170 defer wc.AssertStops() 171 172 // Create a service, deploy a unit of it on the machine. 173 mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) 174 principal, err := mysql.AddUnit() 175 c.Assert(err, jc.ErrorIsNil) 176 err = principal.AssignToMachine(s.rawMachine) 177 c.Assert(err, jc.ErrorIsNil) 178 } 179 180 // TODO(fwereade): 2015-11-18 lp:1517391 181 func (s *watcherSuite) TestWatchMachineStorage(c *gc.C) { 182 f := factory.NewFactory(s.BackingState) 183 f.MakeMachine(c, &factory.MachineParams{ 184 Volumes: []state.MachineVolumeParams{{ 185 Volume: state.VolumeParams{ 186 Pool: "environscoped", 187 Size: 1024, 188 }, 189 }}, 190 }) 191 192 var results params.MachineStorageIdsWatchResults 193 args := params.Entities{Entities: []params.Entity{{ 194 Tag: s.State.ModelTag().String(), 195 }}} 196 err := s.stateAPI.APICall( 197 "StorageProvisioner", 198 s.stateAPI.BestFacadeVersion("StorageProvisioner"), 199 "", "WatchVolumeAttachments", args, &results) 200 c.Assert(err, jc.ErrorIsNil) 201 c.Assert(results.Results, gc.HasLen, 1) 202 result := results.Results[0] 203 c.Assert(result.Error, gc.IsNil) 204 205 w := watcher.NewVolumeAttachmentsWatcher(s.stateAPI, result) 206 defer func() { 207 208 // Check we can stop the watcher... 209 w.Kill() 210 wait := make(chan error) 211 go func() { 212 wait <- w.Wait() 213 }() 214 select { 215 case err := <-wait: 216 c.Assert(err, jc.ErrorIsNil) 217 case <-time.After(coretesting.LongWait): 218 c.Fatalf("watcher never stopped") 219 } 220 221 // ...and that its channel hasn't been closed. 222 s.BackingState.StartSync() 223 select { 224 case change, ok := <-w.Changes(): 225 c.Fatalf("watcher sent unexpected change: (%#v, %v)", change, ok) 226 default: 227 } 228 229 }() 230 231 // Check initial event; 232 s.BackingState.StartSync() 233 select { 234 case changes, ok := <-w.Changes(): 235 c.Assert(ok, jc.IsTrue) 236 c.Assert(changes, jc.SameContents, []corewatcher.MachineStorageId{{ 237 MachineTag: "machine-1", 238 AttachmentTag: "volume-0", 239 }}) 240 case <-time.After(coretesting.LongWait): 241 c.Fatalf("timed out waiting for change") 242 } 243 244 // check no subsequent event. 245 s.BackingState.StartSync() 246 select { 247 case <-w.Changes(): 248 c.Fatalf("received unexpected change") 249 case <-time.After(coretesting.ShortWait): 250 } 251 } 252 253 type migrationSuite struct { 254 testing.JujuConnSuite 255 } 256 257 var _ = gc.Suite(&migrationSuite{}) 258 259 func (s *migrationSuite) startSync(c *gc.C, st *state.State) { 260 backingSt, err := s.BackingStatePool.Get(st.ModelUUID()) 261 c.Assert(err, jc.ErrorIsNil) 262 backingSt.StartSync() 263 } 264 265 func (s *migrationSuite) TestMigrationStatusWatcher(c *gc.C) { 266 const nonce = "noncey" 267 268 // Create a model to migrate. 269 hostedState := s.Factory.MakeModel(c, &factory.ModelParams{}) 270 defer hostedState.Close() 271 hostedFactory := factory.NewFactory(hostedState) 272 273 // Create a machine in the hosted model to connect as. 274 m, password := hostedFactory.MakeMachineReturningPassword(c, &factory.MachineParams{ 275 Nonce: nonce, 276 }) 277 278 // Connect as the machine to watch for migration status. 279 apiInfo := s.APIInfo(c) 280 apiInfo.Tag = m.Tag() 281 apiInfo.Password = password 282 apiInfo.ModelTag = hostedState.ModelTag() 283 apiInfo.Nonce = nonce 284 285 apiConn, err := api.Open(apiInfo, api.DialOpts{}) 286 c.Assert(err, jc.ErrorIsNil) 287 defer apiConn.Close() 288 289 // Start watching for a migration. 290 client := migrationminion.NewClient(apiConn) 291 w, err := client.Watch() 292 c.Assert(err, jc.ErrorIsNil) 293 defer func() { 294 c.Assert(worker.Stop(w), jc.ErrorIsNil) 295 }() 296 297 assertNoChange := func() { 298 s.startSync(c, hostedState) 299 select { 300 case _, ok := <-w.Changes(): 301 c.Fatalf("watcher sent unexpected change: (_, %v)", ok) 302 case <-time.After(coretesting.ShortWait): 303 } 304 } 305 306 assertChange := func(id string, phase migration.Phase) { 307 s.startSync(c, hostedState) 308 select { 309 case status, ok := <-w.Changes(): 310 c.Assert(ok, jc.IsTrue) 311 c.Check(status.MigrationId, gc.Equals, id) 312 c.Check(status.Phase, gc.Equals, phase) 313 case <-time.After(coretesting.LongWait): 314 c.Fatalf("watcher didn't emit an event") 315 } 316 assertNoChange() 317 } 318 319 // Initial event with no migration in progress. 320 assertChange("", migration.NONE) 321 322 // Now create a migration, should trigger watcher. 323 spec := state.MigrationSpec{ 324 InitiatedBy: names.NewUserTag("someone"), 325 TargetInfo: migration.TargetInfo{ 326 ControllerTag: names.NewControllerTag(utils.MustNewUUID().String()), 327 Addrs: []string{"1.2.3.4:5"}, 328 CACert: "cert", 329 AuthTag: names.NewUserTag("dog"), 330 Password: "sekret", 331 }, 332 } 333 mig, err := hostedState.CreateMigration(spec) 334 c.Assert(err, jc.ErrorIsNil) 335 assertChange(mig.Id(), migration.QUIESCE) 336 337 // Now abort the migration, this should be reported too. 338 c.Assert(mig.SetPhase(migration.ABORT), jc.ErrorIsNil) 339 assertChange(mig.Id(), migration.ABORT) 340 c.Assert(mig.SetPhase(migration.ABORTDONE), jc.ErrorIsNil) 341 assertChange(mig.Id(), migration.ABORTDONE) 342 343 // Start a new migration, this should also trigger. 344 mig2, err := hostedState.CreateMigration(spec) 345 c.Assert(err, jc.ErrorIsNil) 346 assertChange(mig2.Id(), migration.QUIESCE) 347 }