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