github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/juju/testing/conn.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 12 gitjujutesting "github.com/juju/testing" 13 "github.com/juju/utils" 14 gc "launchpad.net/gocheck" 15 "launchpad.net/goyaml" 16 17 "github.com/juju/juju/agent" 18 "github.com/juju/juju/charm" 19 charmtesting "github.com/juju/juju/charm/testing" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/environs/bootstrap" 22 "github.com/juju/juju/environs/config" 23 "github.com/juju/juju/environs/configstore" 24 envtesting "github.com/juju/juju/environs/testing" 25 "github.com/juju/juju/juju" 26 "github.com/juju/juju/juju/osenv" 27 "github.com/juju/juju/provider/dummy" 28 "github.com/juju/juju/state" 29 "github.com/juju/juju/state/api" 30 "github.com/juju/juju/testing" 31 "github.com/juju/juju/version" 32 ) 33 34 // JujuConnSuite provides a freshly bootstrapped juju.Conn 35 // for each test. It also includes testing.BaseSuite. 36 // 37 // It also sets up RootDir to point to a directory hierarchy 38 // mirroring the intended juju directory structure, including 39 // the following: 40 // RootDir/home/ubuntu/.juju/environments.yaml 41 // The dummy environments.yaml file, holding 42 // a default environment named "dummyenv" 43 // which uses the "dummy" environment type. 44 // RootDir/var/lib/juju 45 // An empty directory returned as DataDir - the 46 // root of the juju data storage space. 47 // $HOME is set to point to RootDir/home/ubuntu. 48 type JujuConnSuite struct { 49 // TODO: JujuConnSuite should not be concerned both with JUJU_HOME and with 50 // /var/lib/juju: the use cases are completely non-overlapping, and any tests that 51 // really do need both to exist ought to be embedding distinct fixtures for the 52 // distinct environments. 53 gitjujutesting.FakeHomeSuite 54 testing.MgoSuite 55 envtesting.ToolsFixture 56 Conn *juju.Conn 57 State *state.State 58 APIConn *juju.APIConn 59 APIState *api.State 60 apiStates []*api.State // additional api.States to close on teardown 61 ConfigStore configstore.Storage 62 BackingState *state.State // The State being used by the API server 63 RootDir string // The faked-up root directory. 64 LogDir string 65 oldHome string 66 oldJujuHome string 67 environ environs.Environ 68 DummyConfig testing.Attrs 69 } 70 71 const AdminSecret = "dummy-secret" 72 73 func (s *JujuConnSuite) SetUpSuite(c *gc.C) { 74 s.FakeHomeSuite.SetUpSuite(c) 75 s.MgoSuite.SetUpSuite(c) 76 } 77 78 func (s *JujuConnSuite) TearDownSuite(c *gc.C) { 79 s.MgoSuite.TearDownSuite(c) 80 s.FakeHomeSuite.TearDownSuite(c) 81 } 82 83 func (s *JujuConnSuite) SetUpTest(c *gc.C) { 84 s.FakeHomeSuite.SetUpTest(c) 85 s.MgoSuite.SetUpTest(c) 86 s.ToolsFixture.SetUpTest(c) 87 s.setUpConn(c) 88 } 89 90 func (s *JujuConnSuite) TearDownTest(c *gc.C) { 91 s.tearDownConn(c) 92 s.ToolsFixture.TearDownTest(c) 93 s.MgoSuite.TearDownTest(c) 94 s.FakeHomeSuite.TearDownTest(c) 95 } 96 97 // Reset returns environment state to that which existed at the start of 98 // the test. 99 func (s *JujuConnSuite) Reset(c *gc.C) { 100 s.tearDownConn(c) 101 s.setUpConn(c) 102 } 103 104 func (s *JujuConnSuite) AddUser(c *gc.C, username string) *state.User { 105 user, err := s.State.AddUser(username, "", "password") 106 c.Assert(err, gc.IsNil) 107 return user 108 } 109 110 func (s *JujuConnSuite) StateInfo(c *gc.C) *state.Info { 111 info, _, err := s.Conn.Environ.StateInfo() 112 c.Assert(err, gc.IsNil) 113 info.Password = "dummy-secret" 114 return info 115 } 116 117 func (s *JujuConnSuite) APIInfo(c *gc.C) *api.Info { 118 _, apiInfo, err := s.APIConn.Environ.StateInfo() 119 c.Assert(err, gc.IsNil) 120 apiInfo.Tag = "user-admin" 121 apiInfo.Password = "dummy-secret" 122 return apiInfo 123 } 124 125 // openAPIAs opens the API and ensures that the *api.State returned will be 126 // closed during the test teardown by using a cleanup function. 127 func (s *JujuConnSuite) openAPIAs(c *gc.C, tag, password, nonce string) *api.State { 128 _, info, err := s.APIConn.Environ.StateInfo() 129 c.Assert(err, gc.IsNil) 130 info.Tag = tag 131 info.Password = password 132 info.Nonce = nonce 133 apiState, err := api.Open(info, api.DialOpts{}) 134 c.Assert(err, gc.IsNil) 135 c.Assert(apiState, gc.NotNil) 136 s.apiStates = append(s.apiStates, apiState) 137 return apiState 138 } 139 140 // OpenAPIAs opens the API using the given identity tag and password for 141 // authentication. The returned *api.State should not be closed by the caller 142 // as a cleanup function has been registered to do that. 143 func (s *JujuConnSuite) OpenAPIAs(c *gc.C, tag, password string) *api.State { 144 return s.openAPIAs(c, tag, password, "") 145 } 146 147 // OpenAPIAsMachine opens the API using the given machine tag, password and 148 // nonce for authentication. The returned *api.State should not be closed by 149 // the caller as a cleanup function has been registered to do that. 150 func (s *JujuConnSuite) OpenAPIAsMachine(c *gc.C, tag, password, nonce string) *api.State { 151 return s.openAPIAs(c, tag, password, nonce) 152 } 153 154 // OpenAPIAsNewMachine creates a new machine entry that lives in system state, 155 // and then uses that to open the API. The returned *api.State should not be 156 // closed by the caller as a cleanup function has been registered to do that. 157 // The machine will run the supplied jobs; if none are given, JobHostUnits is assumed. 158 func (s *JujuConnSuite) OpenAPIAsNewMachine(c *gc.C, jobs ...state.MachineJob) (*api.State, *state.Machine) { 159 if len(jobs) == 0 { 160 jobs = []state.MachineJob{state.JobHostUnits} 161 } 162 machine, err := s.State.AddMachine("quantal", jobs...) 163 c.Assert(err, gc.IsNil) 164 password, err := utils.RandomPassword() 165 c.Assert(err, gc.IsNil) 166 err = machine.SetPassword(password) 167 c.Assert(err, gc.IsNil) 168 err = machine.SetProvisioned("foo", "fake_nonce", nil) 169 c.Assert(err, gc.IsNil) 170 return s.openAPIAs(c, machine.Tag(), password, "fake_nonce"), machine 171 } 172 173 func PreferredDefaultVersions(conf *config.Config, template version.Binary) []version.Binary { 174 prefVersion := template 175 prefVersion.Series = config.PreferredSeries(conf) 176 defaultVersion := template 177 defaultVersion.Series = testing.FakeDefaultSeries 178 return []version.Binary{prefVersion, defaultVersion} 179 } 180 181 func (s *JujuConnSuite) setUpConn(c *gc.C) { 182 if s.RootDir != "" { 183 panic("JujuConnSuite.setUpConn without teardown") 184 } 185 s.RootDir = c.MkDir() 186 s.oldHome = utils.Home() 187 home := filepath.Join(s.RootDir, "/home/ubuntu") 188 err := os.MkdirAll(home, 0777) 189 c.Assert(err, gc.IsNil) 190 utils.SetHome(home) 191 s.oldJujuHome = osenv.SetJujuHome(filepath.Join(home, ".juju")) 192 err = os.Mkdir(osenv.JujuHome(), 0777) 193 c.Assert(err, gc.IsNil) 194 195 err = os.MkdirAll(s.DataDir(), 0777) 196 c.Assert(err, gc.IsNil) 197 s.PatchEnvironment(osenv.JujuEnvEnvKey, "") 198 199 // TODO(rog) remove these files and add them only when 200 // the tests specifically need them (in cmd/juju for example) 201 s.writeSampleConfig(c, osenv.JujuHomePath("environments.yaml")) 202 203 err = ioutil.WriteFile(osenv.JujuHomePath("dummyenv-cert.pem"), []byte(testing.CACert), 0666) 204 c.Assert(err, gc.IsNil) 205 206 err = ioutil.WriteFile(osenv.JujuHomePath("dummyenv-private-key.pem"), []byte(testing.CAKey), 0600) 207 c.Assert(err, gc.IsNil) 208 209 store, err := configstore.Default() 210 c.Assert(err, gc.IsNil) 211 s.ConfigStore = store 212 213 ctx := testing.Context(c) 214 environ, err := environs.PrepareFromName("dummyenv", ctx, s.ConfigStore) 215 c.Assert(err, gc.IsNil) 216 // sanity check we've got the correct environment. 217 c.Assert(environ.Name(), gc.Equals, "dummyenv") 218 s.PatchValue(&dummy.DataDir, s.DataDir()) 219 s.LogDir = c.MkDir() 220 s.PatchValue(&dummy.LogDir, s.LogDir) 221 222 versions := PreferredDefaultVersions(environ.Config(), version.Binary{Number: version.Current.Number, Series: "precise", Arch: "amd64"}) 223 versions = append(versions, version.Current) 224 225 // Upload tools for both preferred and fake default series 226 envtesting.MustUploadFakeToolsVersions(environ.Storage(), versions...) 227 c.Assert(bootstrap.Bootstrap(ctx, environ, environs.BootstrapParams{}), gc.IsNil) 228 229 s.BackingState = environ.(GetStater).GetStateInAPIServer() 230 231 conn, err := juju.NewConn(environ) 232 c.Assert(err, gc.IsNil) 233 s.Conn = conn 234 s.State = conn.State 235 236 apiConn, err := juju.NewAPIConn(environ, api.DialOpts{}) 237 c.Assert(err, gc.IsNil) 238 s.APIConn = apiConn 239 s.APIState = apiConn.State 240 s.environ = environ 241 } 242 243 func (s *JujuConnSuite) writeSampleConfig(c *gc.C, path string) { 244 if s.DummyConfig == nil { 245 s.DummyConfig = dummy.SampleConfig() 246 } 247 attrs := s.DummyConfig.Merge(testing.Attrs{ 248 "admin-secret": AdminSecret, 249 "agent-version": version.Current.Number.String(), 250 }).Delete("name") 251 whole := map[string]interface{}{ 252 "environments": map[string]interface{}{ 253 "dummyenv": attrs, 254 }, 255 } 256 data, err := goyaml.Marshal(whole) 257 c.Assert(err, gc.IsNil) 258 s.WriteConfig(string(data)) 259 } 260 261 type GetStater interface { 262 GetStateInAPIServer() *state.State 263 } 264 265 func (s *JujuConnSuite) tearDownConn(c *gc.C) { 266 serverAlive := testing.MgoServer.Addr() != "" 267 268 // Bootstrap will set the admin password, and render non-authorized use 269 // impossible. s.State may still hold the right password, so try to reset 270 // the password so that the MgoSuite soft-resetting works. If that fails, 271 // it will still work, but it will take a while since it has to kill the 272 // whole database and start over. 273 if err := s.State.SetAdminMongoPassword(""); err != nil && serverAlive { 274 c.Logf("cannot reset admin password: %v", err) 275 } 276 for _, st := range s.apiStates { 277 err := st.Close() 278 if serverAlive { 279 c.Assert(err, gc.IsNil) 280 } 281 } 282 err := s.Conn.Close() 283 if serverAlive { 284 c.Assert(err, gc.IsNil) 285 } 286 err = s.APIConn.Close() 287 if serverAlive { 288 c.Assert(err, gc.IsNil) 289 } 290 dummy.Reset() 291 s.apiStates = nil 292 s.Conn = nil 293 s.State = nil 294 utils.SetHome(s.oldHome) 295 osenv.SetJujuHome(s.oldJujuHome) 296 s.oldHome = "" 297 s.RootDir = "" 298 } 299 300 func (s *JujuConnSuite) DataDir() string { 301 if s.RootDir == "" { 302 panic("DataDir called out of test context") 303 } 304 return filepath.Join(s.RootDir, "/var/lib/juju") 305 } 306 307 // WriteConfig writes a juju config file to the "home" directory. 308 func (s *JujuConnSuite) WriteConfig(configData string) { 309 if s.RootDir == "" { 310 panic("SetUpTest has not been called; will not overwrite $JUJU_HOME/environments.yaml") 311 } 312 path := osenv.JujuHomePath("environments.yaml") 313 err := ioutil.WriteFile(path, []byte(configData), 0600) 314 if err != nil { 315 panic(err) 316 } 317 } 318 319 func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm { 320 ch := charmtesting.Charms.Dir(name) 321 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision()) 322 curl := charm.MustParseURL("local:quantal/" + ident) 323 repo, err := charm.InferRepository(curl.Reference, charmtesting.Charms.Path()) 324 c.Assert(err, gc.IsNil) 325 sch, err := s.Conn.PutCharm(curl, repo, false) 326 c.Assert(err, gc.IsNil) 327 return sch 328 } 329 330 func (s *JujuConnSuite) AddTestingService(c *gc.C, name string, ch *state.Charm) *state.Service { 331 return s.AddTestingServiceWithNetworks(c, name, ch, nil) 332 } 333 334 func (s *JujuConnSuite) AddTestingServiceWithNetworks(c *gc.C, name string, ch *state.Charm, networks []string) *state.Service { 335 c.Assert(s.State, gc.NotNil) 336 service, err := s.State.AddService(name, "user-admin", ch, networks) 337 c.Assert(err, gc.IsNil) 338 return service 339 } 340 341 func (s *JujuConnSuite) AgentConfigForTag(c *gc.C, tag string) agent.ConfigSetter { 342 password, err := utils.RandomPassword() 343 c.Assert(err, gc.IsNil) 344 config, err := agent.NewAgentConfig( 345 agent.AgentConfigParams{ 346 DataDir: s.DataDir(), 347 Tag: tag, 348 UpgradedToVersion: version.Current.Number, 349 Password: password, 350 Nonce: "nonce", 351 StateAddresses: s.StateInfo(c).Addrs, 352 APIAddresses: s.APIInfo(c).Addrs, 353 CACert: testing.CACert, 354 }) 355 c.Assert(err, gc.IsNil) 356 return config 357 }