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