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