github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/cmd/jujud/agent_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 stderrors "errors" 8 "fmt" 9 "net" 10 "strconv" 11 "time" 12 13 jc "github.com/juju/testing/checkers" 14 gc "launchpad.net/gocheck" 15 16 "github.com/juju/juju/agent" 17 agenttools "github.com/juju/juju/agent/tools" 18 "github.com/juju/juju/cmd" 19 "github.com/juju/juju/environs" 20 envtesting "github.com/juju/juju/environs/testing" 21 envtools "github.com/juju/juju/environs/tools" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/juju/testing" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/state/api" 26 "github.com/juju/juju/state/api/params" 27 coretesting "github.com/juju/juju/testing" 28 coretools "github.com/juju/juju/tools" 29 "github.com/juju/juju/version" 30 "github.com/juju/juju/worker" 31 "github.com/juju/juju/worker/upgrader" 32 ) 33 34 var _ = gc.Suite(&toolSuite{}) 35 36 type toolSuite struct { 37 coretesting.BaseSuite 38 } 39 40 var errorImportanceTests = []error{ 41 nil, 42 stderrors.New("foo"), 43 &upgrader.UpgradeReadyError{}, 44 worker.ErrTerminateAgent, 45 } 46 47 func (*toolSuite) TestErrorImportance(c *gc.C) { 48 for i, err0 := range errorImportanceTests { 49 for j, err1 := range errorImportanceTests { 50 c.Assert(moreImportant(err0, err1), gc.Equals, i > j) 51 } 52 } 53 } 54 55 var isFatalTests = []struct { 56 err error 57 isFatal bool 58 }{{ 59 err: worker.ErrTerminateAgent, 60 isFatal: true, 61 }, { 62 err: &upgrader.UpgradeReadyError{}, 63 isFatal: true, 64 }, { 65 err: ¶ms.Error{ 66 Message: "blah", 67 Code: params.CodeNotProvisioned, 68 }, 69 isFatal: false, 70 }, { 71 err: &fatalError{"some fatal error"}, 72 isFatal: true, 73 }, { 74 err: stderrors.New("foo"), 75 isFatal: false, 76 }, { 77 err: ¶ms.Error{ 78 Message: "blah", 79 Code: params.CodeNotFound, 80 }, 81 isFatal: false, 82 }} 83 84 func (*toolSuite) TestIsFatal(c *gc.C) { 85 for i, test := range isFatalTests { 86 c.Logf("test %d: %s", i, test.err) 87 c.Assert(isFatal(test.err), gc.Equals, test.isFatal) 88 } 89 } 90 91 type apiOpenSuite struct { 92 coretesting.BaseSuite 93 } 94 95 type fakeAPIOpenConfig struct { 96 agent.Config 97 } 98 99 func (fakeAPIOpenConfig) APIInfo() *api.Info { 100 return &api.Info{} 101 } 102 103 func (fakeAPIOpenConfig) OldPassword() string { 104 return "old" 105 } 106 107 func (fakeAPIOpenConfig) Jobs() []params.MachineJob { 108 return []params.MachineJob{} 109 } 110 111 var _ = gc.Suite(&apiOpenSuite{}) 112 113 func (s *apiOpenSuite) TestOpenAPIStateReplaceErrors(c *gc.C) { 114 type replaceErrors struct { 115 openErr error 116 replaceErr error 117 } 118 var apiError error 119 s.PatchValue(&apiOpen, func(info *api.Info, opts api.DialOpts) (*api.State, error) { 120 return nil, apiError 121 }) 122 errReplacePairs := []replaceErrors{{ 123 fmt.Errorf("blah"), nil, 124 }, { 125 openErr: ¶ms.Error{Code: params.CodeNotProvisioned}, 126 replaceErr: worker.ErrTerminateAgent, 127 }, { 128 openErr: ¶ms.Error{Code: params.CodeUnauthorized}, 129 replaceErr: worker.ErrTerminateAgent, 130 }} 131 for i, test := range errReplacePairs { 132 c.Logf("test %d", i) 133 apiError = test.openErr 134 _, _, err := openAPIState(fakeAPIOpenConfig{}, nil) 135 if test.replaceErr == nil { 136 c.Check(err, gc.Equals, test.openErr) 137 } else { 138 c.Check(err, gc.Equals, test.replaceErr) 139 } 140 } 141 } 142 143 type testPinger func() error 144 145 func (f testPinger) Ping() error { 146 return f() 147 } 148 149 func (s *toolSuite) TestConnectionIsFatal(c *gc.C) { 150 var ( 151 errPinger testPinger = func() error { 152 return stderrors.New("ping error") 153 } 154 okPinger testPinger = func() error { 155 return nil 156 } 157 ) 158 for i, pinger := range []testPinger{errPinger, okPinger} { 159 for j, test := range isFatalTests { 160 c.Logf("test %d.%d: %s", i, j, test.err) 161 fatal := connectionIsFatal(pinger)(test.err) 162 if test.isFatal { 163 c.Check(fatal, jc.IsTrue) 164 } else { 165 c.Check(fatal, gc.Equals, i == 0) 166 } 167 } 168 } 169 } 170 171 func mkTools(s string) *coretools.Tools { 172 return &coretools.Tools{ 173 Version: version.MustParseBinary(s + "-foo-bar"), 174 } 175 } 176 177 type acCreator func() (cmd.Command, *AgentConf) 178 179 // CheckAgentCommand is a utility function for verifying that common agent 180 // options are handled by a Command; it returns an instance of that 181 // command pre-parsed, with any mandatory flags added. 182 func CheckAgentCommand(c *gc.C, create acCreator, args []string) cmd.Command { 183 com, conf := create() 184 err := coretesting.InitCommand(com, args) 185 c.Assert(conf.dataDir, gc.Equals, "/var/lib/juju") 186 badArgs := append(args, "--data-dir", "") 187 com, conf = create() 188 err = coretesting.InitCommand(com, badArgs) 189 c.Assert(err, gc.ErrorMatches, "--data-dir option must be set") 190 191 args = append(args, "--data-dir", "jd") 192 com, conf = create() 193 c.Assert(coretesting.InitCommand(com, args), gc.IsNil) 194 c.Assert(conf.dataDir, gc.Equals, "jd") 195 return com 196 } 197 198 // ParseAgentCommand is a utility function that inserts the always-required args 199 // before parsing an agent command and returning the result. 200 func ParseAgentCommand(ac cmd.Command, args []string) error { 201 common := []string{ 202 "--data-dir", "jd", 203 } 204 return coretesting.InitCommand(ac, append(common, args...)) 205 } 206 207 type runner interface { 208 Run(*cmd.Context) error 209 Stop() error 210 } 211 212 // runWithTimeout runs an agent and waits 213 // for it to complete within a reasonable time. 214 func runWithTimeout(r runner) error { 215 done := make(chan error) 216 go func() { 217 done <- r.Run(nil) 218 }() 219 select { 220 case err := <-done: 221 return err 222 case <-time.After(coretesting.LongWait): 223 } 224 err := r.Stop() 225 return fmt.Errorf("timed out waiting for agent to finish; stop error: %v", err) 226 } 227 228 // agentSuite is a fixture to be used by agent test suites. 229 type agentSuite struct { 230 oldRestartDelay time.Duration 231 testing.JujuConnSuite 232 } 233 234 func (s *agentSuite) SetUpSuite(c *gc.C) { 235 s.JujuConnSuite.SetUpSuite(c) 236 237 s.oldRestartDelay = worker.RestartDelay 238 // We could use testing.ShortWait, but this thrashes quite 239 // a bit when some tests are restarting every 50ms for 10 seconds, 240 // so use a slightly more friendly delay. 241 worker.RestartDelay = 250 * time.Millisecond 242 s.PatchValue(&ensureMongoServer, func(string, string, params.StateServingInfo, bool) error { 243 return nil 244 }) 245 } 246 247 func (s *agentSuite) TearDownSuite(c *gc.C) { 248 s.JujuConnSuite.TearDownSuite(c) 249 worker.RestartDelay = s.oldRestartDelay 250 } 251 252 // primeAgent writes the configuration file and tools with version vers 253 // for an agent with the given entity name. It returns the agent's 254 // configuration and the current tools. 255 func (s *agentSuite) primeAgent(c *gc.C, tag, password string, vers version.Binary) (agent.ConfigSetterWriter, *coretools.Tools) { 256 stor := s.Conn.Environ.Storage() 257 agentTools := envtesting.PrimeTools(c, stor, s.DataDir(), vers) 258 err := envtools.MergeAndWriteMetadata(stor, coretools.List{agentTools}, envtools.DoNotWriteMirrors) 259 c.Assert(err, gc.IsNil) 260 tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, vers) 261 c.Assert(err, gc.IsNil) 262 c.Assert(tools1, gc.DeepEquals, agentTools) 263 264 stateInfo := s.StateInfo(c) 265 apiInfo := s.APIInfo(c) 266 conf, err := agent.NewAgentConfig( 267 agent.AgentConfigParams{ 268 DataDir: s.DataDir(), 269 Tag: tag, 270 UpgradedToVersion: vers.Number, 271 Password: password, 272 Nonce: state.BootstrapNonce, 273 StateAddresses: stateInfo.Addrs, 274 APIAddresses: apiInfo.Addrs, 275 CACert: stateInfo.CACert, 276 }) 277 conf.SetPassword(password) 278 c.Assert(conf.Write(), gc.IsNil) 279 s.primeAPIHostPorts(c) 280 return conf, agentTools 281 } 282 283 func (s *agentSuite) primeAPIHostPorts(c *gc.C) { 284 apiInfo := s.APIInfo(c) 285 286 c.Assert(apiInfo.Addrs, gc.HasLen, 1) 287 hostPort, err := parseHostPort(apiInfo.Addrs[0]) 288 c.Assert(err, gc.IsNil) 289 290 err = s.State.SetAPIHostPorts([][]instance.HostPort{{hostPort}}) 291 c.Assert(err, gc.IsNil) 292 } 293 294 func parseHostPort(s string) (instance.HostPort, error) { 295 addr, port, err := net.SplitHostPort(s) 296 if err != nil { 297 return instance.HostPort{}, err 298 } 299 portNum, err := strconv.Atoi(port) 300 if err != nil { 301 return instance.HostPort{}, fmt.Errorf("bad port number %q", port) 302 } 303 addrs := instance.NewAddresses(addr) 304 hostPorts := instance.AddressesWithPort(addrs, portNum) 305 return hostPorts[0], nil 306 } 307 308 // writeStateAgentConfig creates and writes a state agent config. 309 func writeStateAgentConfig(c *gc.C, stateInfo *state.Info, dataDir, tag, password string, vers version.Binary) agent.ConfigSetterWriter { 310 port := coretesting.FindTCPPort() 311 apiAddr := []string{fmt.Sprintf("localhost:%d", port)} 312 conf, err := agent.NewStateMachineConfig( 313 agent.AgentConfigParams{ 314 DataDir: dataDir, 315 Tag: tag, 316 UpgradedToVersion: vers.Number, 317 Password: password, 318 Nonce: state.BootstrapNonce, 319 StateAddresses: stateInfo.Addrs, 320 APIAddresses: apiAddr, 321 CACert: stateInfo.CACert, 322 }, 323 params.StateServingInfo{ 324 Cert: coretesting.ServerCert, 325 PrivateKey: coretesting.ServerKey, 326 StatePort: coretesting.MgoServer.Port(), 327 APIPort: port, 328 }) 329 c.Assert(err, gc.IsNil) 330 conf.SetPassword(password) 331 c.Assert(conf.Write(), gc.IsNil) 332 return conf 333 } 334 335 // primeStateAgent writes the configuration file and tools with version vers 336 // for an agent with the given entity name. It returns the agent's configuration 337 // and the current tools. 338 func (s *agentSuite) primeStateAgent( 339 c *gc.C, tag, password string, vers version.Binary) (agent.ConfigSetterWriter, *coretools.Tools) { 340 341 agentTools := envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), vers) 342 tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, vers) 343 c.Assert(err, gc.IsNil) 344 c.Assert(tools1, gc.DeepEquals, agentTools) 345 346 stateInfo := s.StateInfo(c) 347 conf := writeStateAgentConfig(c, stateInfo, s.DataDir(), tag, password, vers) 348 s.primeAPIHostPorts(c) 349 return conf, agentTools 350 } 351 352 // initAgent initialises the given agent command with additional 353 // arguments as provided. 354 func (s *agentSuite) initAgent(c *gc.C, a cmd.Command, args ...string) { 355 args = append([]string{"--data-dir", s.DataDir()}, args...) 356 err := coretesting.InitCommand(a, args) 357 c.Assert(err, gc.IsNil) 358 } 359 360 func (s *agentSuite) testOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd Agent, initialPassword string) { 361 conf, err := agent.ReadConfig(agent.ConfigPath(s.DataDir(), ent.Tag())) 362 c.Assert(err, gc.IsNil) 363 364 conf.SetPassword("") 365 err = conf.Write() 366 c.Assert(err, gc.IsNil) 367 368 // Check that it starts initially and changes the password 369 assertOpen := func(conf agent.Config) { 370 st, gotEnt, err := openAPIState(conf, agentCmd) 371 c.Assert(err, gc.IsNil) 372 c.Assert(st, gc.NotNil) 373 st.Close() 374 c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag()) 375 } 376 assertOpen(conf) 377 378 // Check that the initial password is no longer valid. 379 err = ent.Refresh() 380 c.Assert(err, gc.IsNil) 381 c.Assert(ent.PasswordValid(initialPassword), gc.Equals, false) 382 383 // Read the configuration and check that we can connect with it. 384 conf = refreshConfig(c, conf) 385 // Check we can open the API with the new configuration. 386 assertOpen(conf) 387 } 388 389 type errorAPIOpener struct { 390 err error 391 } 392 393 func (e *errorAPIOpener) OpenAPI(_ api.DialOpts) (*api.State, string, error) { 394 return nil, "", e.err 395 } 396 397 func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) { 398 config, err := agent.ReadConfig(agent.ConfigPath(dataDir, tag)) 399 c.Assert(err, gc.IsNil) 400 info, ok := config.StateInfo() 401 c.Assert(ok, jc.IsTrue) 402 st, err := state.Open(info, state.DialOpts{}, environs.NewStatePolicy()) 403 c.Assert(err, gc.IsNil) 404 st.Close() 405 } 406 407 func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) { 408 config, err := agent.ReadConfig(agent.ConfigPath(dataDir, tag)) 409 c.Assert(err, gc.IsNil) 410 _, ok := config.StateInfo() 411 c.Assert(ok, jc.IsFalse) 412 } 413 414 func refreshConfig(c *gc.C, config agent.Config) agent.ConfigSetterWriter { 415 config1, err := agent.ReadConfig(agent.ConfigPath(config.DataDir(), config.Tag())) 416 c.Assert(err, gc.IsNil) 417 return config1 418 }