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