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