launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "time" 10 11 gc "launchpad.net/gocheck" 12 13 "launchpad.net/juju-core/agent" 14 agenttools "launchpad.net/juju-core/agent/tools" 15 "launchpad.net/juju-core/cmd" 16 envtesting "launchpad.net/juju-core/environs/testing" 17 envtools "launchpad.net/juju-core/environs/tools" 18 "launchpad.net/juju-core/juju/testing" 19 "launchpad.net/juju-core/state" 20 "launchpad.net/juju-core/state/api" 21 "launchpad.net/juju-core/state/api/params" 22 coretesting "launchpad.net/juju-core/testing" 23 jc "launchpad.net/juju-core/testing/checkers" 24 "launchpad.net/juju-core/testing/testbase" 25 coretools "launchpad.net/juju-core/tools" 26 "launchpad.net/juju-core/version" 27 "launchpad.net/juju-core/worker" 28 "launchpad.net/juju-core/worker/upgrader" 29 ) 30 31 var _ = gc.Suite(&toolSuite{}) 32 33 type toolSuite struct { 34 testbase.LoggingSuite 35 } 36 37 var errorImportanceTests = []error{ 38 nil, 39 stderrors.New("foo"), 40 &upgrader.UpgradeReadyError{}, 41 worker.ErrTerminateAgent, 42 } 43 44 func (*toolSuite) TestErrorImportance(c *gc.C) { 45 for i, err0 := range errorImportanceTests { 46 for j, err1 := range errorImportanceTests { 47 c.Assert(moreImportant(err0, err1), gc.Equals, i > j) 48 } 49 } 50 } 51 52 var isFatalTests = []struct { 53 err error 54 isFatal bool 55 }{{ 56 err: worker.ErrTerminateAgent, 57 isFatal: true, 58 }, { 59 err: &upgrader.UpgradeReadyError{}, 60 isFatal: true, 61 }, { 62 err: ¶ms.Error{ 63 Message: "blah", 64 Code: params.CodeNotProvisioned, 65 }, 66 isFatal: false, 67 }, { 68 err: &fatalError{"some fatal error"}, 69 isFatal: true, 70 }, { 71 err: stderrors.New("foo"), 72 isFatal: false, 73 }, { 74 err: ¶ms.Error{ 75 Message: "blah", 76 Code: params.CodeNotFound, 77 }, 78 isFatal: false, 79 }} 80 81 func (*toolSuite) TestIsFatal(c *gc.C) { 82 for i, test := range isFatalTests { 83 c.Logf("test %d: %s", i, test.err) 84 c.Assert(isFatal(test.err), gc.Equals, test.isFatal) 85 } 86 } 87 88 type testPinger func() error 89 90 func (f testPinger) Ping() error { 91 return f() 92 } 93 94 func (s *toolSuite) TestConnectionIsFatal(c *gc.C) { 95 var ( 96 errPinger testPinger = func() error { 97 return stderrors.New("ping error") 98 } 99 okPinger testPinger = func() error { 100 return nil 101 } 102 ) 103 for i, pinger := range []testPinger{errPinger, okPinger} { 104 for j, test := range isFatalTests { 105 c.Logf("test %d.%d: %s", i, j, test.err) 106 fatal := connectionIsFatal(pinger)(test.err) 107 if test.isFatal { 108 c.Check(fatal, jc.IsTrue) 109 } else { 110 c.Check(fatal, gc.Equals, i == 0) 111 } 112 } 113 } 114 } 115 116 func mkTools(s string) *coretools.Tools { 117 return &coretools.Tools{ 118 Version: version.MustParseBinary(s + "-foo-bar"), 119 } 120 } 121 122 type acCreator func() (cmd.Command, *AgentConf) 123 124 // CheckAgentCommand is a utility function for verifying that common agent 125 // options are handled by a Command; it returns an instance of that 126 // command pre-parsed, with any mandatory flags added. 127 func CheckAgentCommand(c *gc.C, create acCreator, args []string) cmd.Command { 128 com, conf := create() 129 err := coretesting.InitCommand(com, args) 130 c.Assert(conf.dataDir, gc.Equals, "/var/lib/juju") 131 badArgs := append(args, "--data-dir", "") 132 com, conf = create() 133 err = coretesting.InitCommand(com, badArgs) 134 c.Assert(err, gc.ErrorMatches, "--data-dir option must be set") 135 136 args = append(args, "--data-dir", "jd") 137 com, conf = create() 138 c.Assert(coretesting.InitCommand(com, args), gc.IsNil) 139 c.Assert(conf.dataDir, gc.Equals, "jd") 140 return com 141 } 142 143 // ParseAgentCommand is a utility function that inserts the always-required args 144 // before parsing an agent command and returning the result. 145 func ParseAgentCommand(ac cmd.Command, args []string) error { 146 common := []string{ 147 "--data-dir", "jd", 148 } 149 return coretesting.InitCommand(ac, append(common, args...)) 150 } 151 152 type runner interface { 153 Run(*cmd.Context) error 154 Stop() error 155 } 156 157 // runWithTimeout runs an agent and waits 158 // for it to complete within a reasonable time. 159 func runWithTimeout(r runner) error { 160 done := make(chan error) 161 go func() { 162 done <- r.Run(nil) 163 }() 164 select { 165 case err := <-done: 166 return err 167 case <-time.After(5 * time.Second): 168 } 169 err := r.Stop() 170 return fmt.Errorf("timed out waiting for agent to finish; stop error: %v", err) 171 } 172 173 // agentSuite is a fixture to be used by agent test suites. 174 type agentSuite struct { 175 oldRestartDelay time.Duration 176 testing.JujuConnSuite 177 } 178 179 func (s *agentSuite) SetUpSuite(c *gc.C) { 180 s.oldRestartDelay = worker.RestartDelay 181 worker.RestartDelay = coretesting.ShortWait 182 s.JujuConnSuite.SetUpSuite(c) 183 } 184 185 func (s *agentSuite) TearDownSuite(c *gc.C) { 186 s.JujuConnSuite.TearDownSuite(c) 187 worker.RestartDelay = s.oldRestartDelay 188 } 189 190 // primeAgent writes the configuration file and tools for an agent with the 191 // given entity name. It returns the agent's configuration and the current 192 // tools. 193 func (s *agentSuite) primeAgent(c *gc.C, tag, password string) (agent.Config, *coretools.Tools) { 194 stor := s.Conn.Environ.Storage() 195 agentTools := envtesting.PrimeTools(c, stor, s.DataDir(), version.Current) 196 err := envtools.MergeAndWriteMetadata(stor, coretools.List{agentTools}, envtools.DoNotWriteMirrors) 197 c.Assert(err, gc.IsNil) 198 tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, version.Current) 199 c.Assert(err, gc.IsNil) 200 c.Assert(tools1, gc.DeepEquals, agentTools) 201 202 stateInfo := s.StateInfo(c) 203 apiInfo := s.APIInfo(c) 204 conf, err := agent.NewAgentConfig( 205 agent.AgentConfigParams{ 206 DataDir: s.DataDir(), 207 Tag: tag, 208 Password: password, 209 Nonce: state.BootstrapNonce, 210 StateAddresses: stateInfo.Addrs, 211 APIAddresses: apiInfo.Addrs, 212 CACert: stateInfo.CACert, 213 }) 214 c.Assert(conf.Write(), gc.IsNil) 215 return conf, agentTools 216 } 217 218 // makeStateAgentConfig creates and writes a state agent config. 219 func writeStateAgentConfig(c *gc.C, stateInfo *state.Info, dataDir, tag, password string) agent.Config { 220 port := coretesting.FindTCPPort() 221 apiAddr := []string{fmt.Sprintf("localhost:%d", port)} 222 conf, err := agent.NewStateMachineConfig( 223 agent.StateMachineConfigParams{ 224 AgentConfigParams: agent.AgentConfigParams{ 225 DataDir: dataDir, 226 Tag: tag, 227 Password: password, 228 Nonce: state.BootstrapNonce, 229 StateAddresses: stateInfo.Addrs, 230 APIAddresses: apiAddr, 231 CACert: stateInfo.CACert, 232 }, 233 StateServerCert: []byte(coretesting.ServerCert), 234 StateServerKey: []byte(coretesting.ServerKey), 235 StatePort: coretesting.MgoServer.Port(), 236 APIPort: port, 237 }) 238 c.Assert(err, gc.IsNil) 239 c.Assert(conf.Write(), gc.IsNil) 240 return conf 241 } 242 243 // primeStateAgent writes the configuration file and tools for an agent with the 244 // given entity name. It returns the agent's configuration and the current 245 // tools. 246 func (s *agentSuite) primeStateAgent(c *gc.C, tag, password string) (agent.Config, *coretools.Tools) { 247 agentTools := envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), version.Current) 248 tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, version.Current) 249 c.Assert(err, gc.IsNil) 250 c.Assert(tools1, gc.DeepEquals, agentTools) 251 252 stateInfo := s.StateInfo(c) 253 conf := writeStateAgentConfig(c, stateInfo, s.DataDir(), tag, password) 254 return conf, agentTools 255 } 256 257 // initAgent initialises the given agent command with additional 258 // arguments as provided. 259 func (s *agentSuite) initAgent(c *gc.C, a cmd.Command, args ...string) { 260 args = append([]string{"--data-dir", s.DataDir()}, args...) 261 err := coretesting.InitCommand(a, args) 262 c.Assert(err, gc.IsNil) 263 } 264 265 func (s *agentSuite) proposeVersion(c *gc.C, vers version.Number) { 266 oldcfg, err := s.State.EnvironConfig() 267 c.Assert(err, gc.IsNil) 268 cfg, err := oldcfg.Apply(map[string]interface{}{ 269 "agent-version": vers.String(), 270 }) 271 c.Assert(err, gc.IsNil) 272 err = s.State.SetEnvironConfig(cfg, oldcfg) 273 c.Assert(err, gc.IsNil) 274 } 275 276 func (s *agentSuite) testOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd Agent, initialPassword string) { 277 conf, err := agent.ReadConf(s.DataDir(), ent.Tag()) 278 c.Assert(err, gc.IsNil) 279 280 // Check that it starts initially and changes the password 281 assertOpen := func(conf agent.Config) { 282 st, gotEnt, err := openAPIState(conf, agentCmd) 283 c.Assert(err, gc.IsNil) 284 c.Assert(st, gc.NotNil) 285 st.Close() 286 c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag()) 287 } 288 assertOpen(conf) 289 290 // Check that the initial password is no longer valid. 291 err = ent.Refresh() 292 c.Assert(err, gc.IsNil) 293 c.Assert(ent.PasswordValid(initialPassword), gc.Equals, false) 294 295 // Read the configuration and check that we can connect with it. 296 conf = refreshConfig(c, conf) 297 // Check we can open the API with the new configuration. 298 assertOpen(conf) 299 } 300 301 type errorAPIOpener struct { 302 err error 303 } 304 305 func (e *errorAPIOpener) OpenAPI(_ api.DialOpts) (*api.State, string, error) { 306 return nil, "", e.err 307 } 308 309 func (s *agentSuite) testOpenAPIStateReplaceErrors(c *gc.C) { 310 for i, test := range []struct { 311 openErr error 312 replaceErr error 313 }{{ 314 fmt.Errorf("blah"), nil, 315 }, { 316 ¶ms.Error{Code: params.CodeNotProvisioned}, worker.ErrTerminateAgent, 317 }, { 318 ¶ms.Error{Code: params.CodeUnauthorized}, worker.ErrTerminateAgent, 319 }} { 320 c.Logf("test %d", i) 321 opener := &errorAPIOpener{test.openErr} 322 _, _, err := openAPIState(opener, nil) 323 if test.replaceErr == nil { 324 c.Check(err, gc.Equals, test.openErr) 325 } else { 326 c.Check(err, gc.Equals, test.replaceErr) 327 } 328 } 329 } 330 331 func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) { 332 config, err := agent.ReadConf(dataDir, tag) 333 c.Assert(err, gc.IsNil) 334 st, err := config.OpenState() 335 c.Assert(err, gc.IsNil) 336 st.Close() 337 } 338 339 func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) { 340 config, err := agent.ReadConf(dataDir, tag) 341 c.Assert(err, gc.IsNil) 342 _, err = config.OpenState() 343 expectErr := fmt.Sprintf("cannot log in to juju database as %q: unauthorized mongo access: auth fails", tag) 344 c.Assert(err, gc.ErrorMatches, expectErr) 345 } 346 347 func (s *agentSuite) testUpgrade(c *gc.C, agent runner, tag string, currentTools *coretools.Tools) { 348 newVers := version.Current 349 newVers.Patch++ 350 newTools := envtesting.AssertUploadFakeToolsVersions(c, s.Conn.Environ.Storage(), newVers)[0] 351 s.proposeVersion(c, newVers.Number) 352 err := runWithTimeout(agent) 353 envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ 354 AgentName: tag, 355 OldTools: currentTools, 356 NewTools: newTools, 357 DataDir: s.DataDir(), 358 }) 359 } 360 361 func refreshConfig(c *gc.C, config agent.Config) agent.Config { 362 config, err := agent.ReadConf(config.DataDir(), config.Tag()) 363 c.Assert(err, gc.IsNil) 364 return config 365 }