github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/client/api_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client_test 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/api" 16 commontesting "github.com/juju/juju/apiserver/common/testing" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/constraints" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/juju/testing" 22 "github.com/juju/juju/mongo" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/state/multiwatcher" 25 "github.com/juju/juju/state/presence" 26 "github.com/juju/juju/status" 27 coretesting "github.com/juju/juju/testing" 28 "github.com/juju/juju/testing/factory" 29 "github.com/juju/juju/worker" 30 ) 31 32 type baseSuite struct { 33 testing.JujuConnSuite 34 commontesting.BlockHelper 35 } 36 37 func (s *baseSuite) SetUpTest(c *gc.C) { 38 s.JujuConnSuite.SetUpTest(c) 39 s.BlockHelper = commontesting.NewBlockHelper(s.APIState) 40 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 41 } 42 43 var _ = gc.Suite(&baseSuite{}) 44 45 func chanReadEmpty(c *gc.C, ch <-chan struct{}, what string) bool { 46 select { 47 case _, ok := <-ch: 48 return ok 49 case <-time.After(10 * time.Second): 50 c.Fatalf("timed out reading from %s", what) 51 } 52 panic("unreachable") 53 } 54 55 func chanReadStrings(c *gc.C, ch <-chan []string, what string) ([]string, bool) { 56 select { 57 case changes, ok := <-ch: 58 return changes, ok 59 case <-time.After(10 * time.Second): 60 c.Fatalf("timed out reading from %s", what) 61 } 62 panic("unreachable") 63 } 64 65 func chanReadConfig(c *gc.C, ch <-chan *config.Config, what string) (*config.Config, bool) { 66 select { 67 case envConfig, ok := <-ch: 68 return envConfig, ok 69 case <-time.After(10 * time.Second): 70 c.Fatalf("timed out reading from %s", what) 71 } 72 panic("unreachable") 73 } 74 75 func removeServiceAndUnits(c *gc.C, service *state.Application) { 76 // Destroy all units for the application. 77 units, err := service.AllUnits() 78 c.Assert(err, jc.ErrorIsNil) 79 for _, unit := range units { 80 err = unit.EnsureDead() 81 c.Assert(err, jc.ErrorIsNil) 82 err = unit.Remove() 83 c.Assert(err, jc.ErrorIsNil) 84 } 85 err = service.Destroy() 86 c.Assert(err, jc.ErrorIsNil) 87 88 err = service.Refresh() 89 c.Assert(err, jc.Satisfies, errors.IsNotFound) 90 } 91 92 // apiAuthenticator represents a simple authenticator object with only the 93 // SetPassword and Tag methods. This will fit types from both the state 94 // and api packages, as those in the api package do not have PasswordValid(). 95 type apiAuthenticator interface { 96 state.Entity 97 SetPassword(string) error 98 } 99 100 func setDefaultPassword(c *gc.C, e apiAuthenticator) { 101 err := e.SetPassword(defaultPassword(e)) 102 c.Assert(err, jc.ErrorIsNil) 103 } 104 105 func defaultPassword(e apiAuthenticator) string { 106 return e.Tag().String() + " password-1234567890" 107 } 108 109 type setStatuser interface { 110 SetStatus(status.StatusInfo) error 111 } 112 113 func setDefaultStatus(c *gc.C, entity setStatuser) { 114 now := time.Now() 115 s := status.StatusInfo{ 116 Status: status.Started, 117 Message: "", 118 Since: &now, 119 } 120 err := entity.SetStatus(s) 121 c.Assert(err, jc.ErrorIsNil) 122 } 123 124 func (s *baseSuite) tryOpenState(c *gc.C, e apiAuthenticator, password string) error { 125 stateInfo := s.MongoInfo(c) 126 stateInfo.Tag = e.Tag() 127 stateInfo.Password = password 128 st, err := state.Open(s.State.ModelTag(), s.State.ControllerTag(), stateInfo, mongo.DialOpts{ 129 Timeout: 25 * time.Millisecond, 130 }, nil) 131 if err == nil { 132 st.Close() 133 } 134 return err 135 } 136 137 // openAs connects to the API state as the given entity 138 // with the default password for that entity. 139 func (s *baseSuite) openAs(c *gc.C, tag names.Tag) api.Connection { 140 info := s.APIInfo(c) 141 info.Tag = tag 142 // Must match defaultPassword() 143 info.Password = fmt.Sprintf("%s password-1234567890", tag) 144 // Set this always, so that the login attempts as a machine will 145 // not fail with ErrNotProvisioned; it's not used otherwise. 146 info.Nonce = "fake_nonce" 147 c.Logf("opening state; entity %q; password %q", info.Tag, info.Password) 148 st, err := api.Open(info, api.DialOpts{}) 149 c.Assert(err, jc.ErrorIsNil) 150 c.Assert(st, gc.NotNil) 151 return st 152 } 153 154 // scenarioStatus describes the expected state 155 // of the juju environment set up by setUpScenario. 156 // 157 // NOTE: AgentState: "down", AgentStateInfo: "(started)" here is due 158 // to the scenario not calling SetAgentPresence on the respective entities, 159 // but this behavior is already tested in cmd/juju/status_test.go and 160 // also tested live and it works. 161 var scenarioStatus = ¶ms.FullStatus{ 162 Model: params.ModelStatusInfo{ 163 Name: "controller", 164 CloudTag: "cloud-dummy", 165 CloudRegion: "dummy-region", 166 Version: "1.2.3", 167 }, 168 Machines: map[string]params.MachineStatus{ 169 "0": { 170 Id: "0", 171 InstanceId: instance.Id("i-machine-0"), 172 AgentStatus: params.DetailedStatus{ 173 Status: "started", 174 Data: make(map[string]interface{}), 175 }, 176 InstanceStatus: params.DetailedStatus{ 177 Status: status.Pending.String(), 178 Data: make(map[string]interface{}), 179 }, 180 Series: "quantal", 181 Containers: map[string]params.MachineStatus{}, 182 Jobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel}, 183 HasVote: false, 184 WantsVote: true, 185 }, 186 "1": { 187 Id: "1", 188 InstanceId: instance.Id("i-machine-1"), 189 AgentStatus: params.DetailedStatus{ 190 Status: "started", 191 Data: make(map[string]interface{}), 192 }, 193 InstanceStatus: params.DetailedStatus{ 194 Status: status.Pending.String(), 195 Data: make(map[string]interface{}), 196 }, 197 Series: "quantal", 198 Containers: map[string]params.MachineStatus{}, 199 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 200 HasVote: false, 201 WantsVote: false, 202 }, 203 "2": { 204 Id: "2", 205 InstanceId: instance.Id("i-machine-2"), 206 AgentStatus: params.DetailedStatus{ 207 Status: "started", 208 Data: make(map[string]interface{}), 209 }, 210 InstanceStatus: params.DetailedStatus{ 211 Status: status.Pending.String(), 212 Data: make(map[string]interface{}), 213 }, 214 Series: "quantal", 215 Containers: map[string]params.MachineStatus{}, 216 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 217 HasVote: false, 218 WantsVote: false, 219 }, 220 }, 221 Applications: map[string]params.ApplicationStatus{ 222 "logging": { 223 Charm: "local:quantal/logging-1", 224 Series: "quantal", 225 Relations: map[string][]string{ 226 "logging-directory": {"wordpress"}, 227 }, 228 SubordinateTo: []string{"wordpress"}, 229 Status: params.DetailedStatus{ 230 Status: "waiting", 231 Info: "waiting for machine", 232 Data: map[string]interface{}{}, 233 }, 234 }, 235 "mysql": { 236 Charm: "local:quantal/mysql-1", 237 Series: "quantal", 238 Relations: map[string][]string{}, 239 SubordinateTo: []string{}, 240 Units: map[string]params.UnitStatus{}, 241 Status: params.DetailedStatus{ 242 Status: "waiting", 243 Info: "waiting for machine", 244 Data: map[string]interface{}{}, 245 }, 246 }, 247 "wordpress": { 248 Charm: "local:quantal/wordpress-3", 249 Series: "quantal", 250 Relations: map[string][]string{ 251 "logging-dir": {"logging"}, 252 }, 253 SubordinateTo: []string{}, 254 Status: params.DetailedStatus{ 255 Status: "error", 256 Info: "blam", 257 Data: map[string]interface{}{"remote-unit": "logging/0", "foo": "bar", "relation-id": "0"}, 258 }, 259 Units: map[string]params.UnitStatus{ 260 "wordpress/0": { 261 WorkloadStatus: params.DetailedStatus{ 262 Status: "error", 263 Info: "blam", 264 Data: map[string]interface{}{"relation-id": "0"}, 265 }, 266 AgentStatus: params.DetailedStatus{ 267 Status: "idle", 268 Data: make(map[string]interface{}), 269 }, 270 Machine: "1", 271 Subordinates: map[string]params.UnitStatus{ 272 "logging/0": { 273 WorkloadStatus: params.DetailedStatus{ 274 Status: "waiting", 275 Info: "waiting for machine", 276 Data: make(map[string]interface{}), 277 }, 278 AgentStatus: params.DetailedStatus{ 279 Status: "allocating", 280 Data: map[string]interface{}{}, 281 }, 282 }, 283 }, 284 }, 285 "wordpress/1": { 286 WorkloadStatus: params.DetailedStatus{ 287 Status: "waiting", 288 Info: "waiting for machine", 289 Data: make(map[string]interface{}), 290 }, 291 AgentStatus: params.DetailedStatus{ 292 Status: "allocating", 293 Info: "", 294 Data: make(map[string]interface{}), 295 }, 296 297 Machine: "2", 298 Subordinates: map[string]params.UnitStatus{ 299 "logging/1": { 300 WorkloadStatus: params.DetailedStatus{ 301 Status: "waiting", 302 Info: "waiting for machine", 303 Data: make(map[string]interface{}), 304 }, 305 AgentStatus: params.DetailedStatus{ 306 Status: "allocating", 307 Info: "", 308 Data: make(map[string]interface{}), 309 }, 310 }, 311 }, 312 }, 313 }, 314 }, 315 }, 316 Relations: []params.RelationStatus{ 317 { 318 Id: 0, 319 Key: "logging:logging-directory wordpress:logging-dir", 320 Endpoints: []params.EndpointStatus{ 321 { 322 ApplicationName: "logging", 323 Name: "logging-directory", 324 Role: "requirer", 325 Subordinate: true, 326 }, 327 { 328 ApplicationName: "wordpress", 329 Name: "logging-dir", 330 Role: "provider", 331 Subordinate: false, 332 }, 333 }, 334 Interface: "logging", 335 Scope: "container", 336 }, 337 }, 338 } 339 340 // setUpScenario makes an environment scenario suitable for 341 // testing most kinds of access scenario. It returns 342 // a list of all the entities in the scenario. 343 // 344 // When the scenario is initialized, we have: 345 // user-admin 346 // user-other 347 // machine-0 348 // instance-id="i-machine-0" 349 // nonce="fake_nonce" 350 // jobs=manage-environ 351 // status=started, info="" 352 // machine-1 353 // instance-id="i-machine-1" 354 // nonce="fake_nonce" 355 // jobs=host-units 356 // status=started, info="" 357 // constraints=mem=1G 358 // machine-2 359 // instance-id="i-machine-2" 360 // nonce="fake_nonce" 361 // jobs=host-units 362 // status=started, info="" 363 // application-wordpress 364 // application-logging 365 // unit-wordpress-0 366 // deployer-name=machine-1 367 // status=down with error and status data attached 368 // unit-logging-0 369 // deployer-name=unit-wordpress-0 370 // unit-wordpress-1 371 // deployer-name=machine-2 372 // unit-logging-1 373 // deployer-name=unit-wordpress-1 374 // 375 // The passwords for all returned entities are 376 // set to the entity name with a " password" suffix. 377 // 378 // Note that there is nothing special about machine-0 379 // here - it's the environment manager in this scenario 380 // just because machine 0 has traditionally been the 381 // environment manager (bootstrap machine), so is 382 // hopefully easier to remember as such. 383 func (s *baseSuite) setUpScenario(c *gc.C) (entities []names.Tag) { 384 add := func(e state.Entity) { 385 entities = append(entities, e.Tag()) 386 } 387 u, err := s.State.User(s.AdminUserTag(c)) 388 c.Assert(err, jc.ErrorIsNil) 389 setDefaultPassword(c, u) 390 add(u) 391 err = s.State.UpdateModelConfig(map[string]interface{}{ 392 config.AgentVersionKey: "1.2.3"}, nil, nil) 393 c.Assert(err, jc.ErrorIsNil) 394 395 u = s.Factory.MakeUser(c, &factory.UserParams{Name: "other"}) 396 setDefaultPassword(c, u) 397 add(u) 398 399 m, err := s.State.AddMachine("quantal", state.JobManageModel) 400 c.Assert(err, jc.ErrorIsNil) 401 c.Assert(m.Tag(), gc.Equals, names.NewMachineTag("0")) 402 err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil) 403 c.Assert(err, jc.ErrorIsNil) 404 setDefaultPassword(c, m) 405 setDefaultStatus(c, m) 406 add(m) 407 s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) 408 wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 409 s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging")) 410 eps, err := s.State.InferEndpoints("logging", "wordpress") 411 c.Assert(err, jc.ErrorIsNil) 412 rel, err := s.State.AddRelation(eps...) 413 c.Assert(err, jc.ErrorIsNil) 414 415 for i := 0; i < 2; i++ { 416 wu, err := wordpress.AddUnit() 417 c.Assert(err, jc.ErrorIsNil) 418 c.Assert(wu.Tag(), gc.Equals, names.NewUnitTag(fmt.Sprintf("wordpress/%d", i))) 419 setDefaultPassword(c, wu) 420 add(wu) 421 422 m, err := s.State.AddMachine("quantal", state.JobHostUnits) 423 c.Assert(err, jc.ErrorIsNil) 424 c.Assert(m.Tag(), gc.Equals, names.NewMachineTag(fmt.Sprintf("%d", i+1))) 425 if i == 1 { 426 err = m.SetConstraints(constraints.MustParse("mem=1G")) 427 c.Assert(err, jc.ErrorIsNil) 428 } 429 err = m.SetProvisioned(instance.Id("i-"+m.Tag().String()), "fake_nonce", nil) 430 c.Assert(err, jc.ErrorIsNil) 431 setDefaultPassword(c, m) 432 setDefaultStatus(c, m) 433 add(m) 434 435 err = wu.AssignToMachine(m) 436 c.Assert(err, jc.ErrorIsNil) 437 438 deployer, ok := wu.DeployerTag() 439 c.Assert(ok, jc.IsTrue) 440 c.Assert(deployer, gc.Equals, names.NewMachineTag(fmt.Sprintf("%d", i+1))) 441 442 wru, err := rel.Unit(wu) 443 c.Assert(err, jc.ErrorIsNil) 444 445 // Put wordpress/0 in error state (with extra status data set) 446 if i == 0 { 447 sd := map[string]interface{}{ 448 "relation-id": "0", 449 // these this should get filtered out 450 // (not in StatusData whitelist) 451 "remote-unit": "logging/0", 452 "foo": "bar", 453 } 454 now := time.Now() 455 sInfo := status.StatusInfo{ 456 Status: status.Error, 457 Message: "blam", 458 Data: sd, 459 Since: &now, 460 } 461 err := wu.SetAgentStatus(sInfo) 462 c.Assert(err, jc.ErrorIsNil) 463 } 464 465 // Create the subordinate unit as a side-effect of entering 466 // scope in the principal's relation-unit. 467 err = wru.EnterScope(nil) 468 c.Assert(err, jc.ErrorIsNil) 469 470 lu, err := s.State.Unit(fmt.Sprintf("logging/%d", i)) 471 c.Assert(err, jc.ErrorIsNil) 472 c.Assert(lu.IsPrincipal(), jc.IsFalse) 473 deployer, ok = lu.DeployerTag() 474 c.Assert(ok, jc.IsTrue) 475 c.Assert(deployer, gc.Equals, names.NewUnitTag(fmt.Sprintf("wordpress/%d", i))) 476 setDefaultPassword(c, lu) 477 s.setAgentPresence(c, wu) 478 add(lu) 479 } 480 allMachines, err := s.State.AllMachines() 481 c.Assert(err, jc.ErrorIsNil) 482 for _, m := range allMachines { 483 s.setAgentPresence(c, m) 484 } 485 return 486 } 487 488 type presenceEntity interface { 489 SetAgentPresence() (*presence.Pinger, error) 490 WaitAgentPresence(timeout time.Duration) (err error) 491 } 492 493 func (s *baseSuite) setAgentPresence(c *gc.C, e presenceEntity) { 494 pinger, err := e.SetAgentPresence() 495 c.Assert(err, jc.ErrorIsNil) 496 s.AddCleanup(func(c *gc.C) { 497 c.Assert(worker.Stop(pinger), jc.ErrorIsNil) 498 }) 499 500 s.State.StartSync() 501 s.BackingState.StartSync() 502 err = e.WaitAgentPresence(coretesting.LongWait) 503 c.Assert(err, jc.ErrorIsNil) 504 }