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