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