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