github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/jujud/agent/unit_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package agent 5 6 import ( 7 "encoding/json" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "reflect" 12 "time" 13 14 "github.com/juju/cmd" 15 "github.com/juju/names" 16 jc "github.com/juju/testing/checkers" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/natefinch/lumberjack.v2" 19 20 "github.com/juju/juju/agent" 21 agenttools "github.com/juju/juju/agent/tools" 22 apirsyslog "github.com/juju/juju/api/rsyslog" 23 agenttesting "github.com/juju/juju/cmd/jujud/agent/testing" 24 envtesting "github.com/juju/juju/environs/testing" 25 jujutesting "github.com/juju/juju/juju/testing" 26 "github.com/juju/juju/network" 27 "github.com/juju/juju/state" 28 coretesting "github.com/juju/juju/testing" 29 "github.com/juju/juju/tools" 30 "github.com/juju/juju/version" 31 "github.com/juju/juju/worker" 32 "github.com/juju/juju/worker/apicaller" 33 "github.com/juju/juju/worker/rsyslog" 34 "github.com/juju/juju/worker/upgrader" 35 ) 36 37 type UnitSuite struct { 38 coretesting.GitSuite 39 agenttesting.AgentSuite 40 } 41 42 var _ = gc.Suite(&UnitSuite{}) 43 44 func (s *UnitSuite) SetUpSuite(c *gc.C) { 45 s.GitSuite.SetUpSuite(c) 46 s.AgentSuite.SetUpSuite(c) 47 } 48 49 func (s *UnitSuite) TearDownSuite(c *gc.C) { 50 s.AgentSuite.TearDownSuite(c) 51 s.GitSuite.TearDownSuite(c) 52 } 53 54 func (s *UnitSuite) SetUpTest(c *gc.C) { 55 s.GitSuite.SetUpTest(c) 56 s.AgentSuite.SetUpTest(c) 57 } 58 59 func (s *UnitSuite) TearDownTest(c *gc.C) { 60 s.AgentSuite.TearDownTest(c) 61 s.GitSuite.TearDownTest(c) 62 } 63 64 const initialUnitPassword = "unit-password-1234567890" 65 66 // primeAgent creates a unit, and sets up the unit agent's directory. 67 // It returns the assigned machine, new unit and the agent's configuration. 68 func (s *UnitSuite) primeAgent(c *gc.C) (*state.Machine, *state.Unit, agent.Config, *tools.Tools) { 69 jujutesting.AddStateServerMachine(c, s.State) 70 svc := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 71 unit, err := svc.AddUnit() 72 c.Assert(err, jc.ErrorIsNil) 73 err = unit.SetPassword(initialUnitPassword) 74 c.Assert(err, jc.ErrorIsNil) 75 // Assign the unit to a machine. 76 err = unit.AssignToNewMachine() 77 c.Assert(err, jc.ErrorIsNil) 78 id, err := unit.AssignedMachineId() 79 c.Assert(err, jc.ErrorIsNil) 80 machine, err := s.State.Machine(id) 81 c.Assert(err, jc.ErrorIsNil) 82 inst, md := jujutesting.AssertStartInstance(c, s.Environ, id) 83 err = machine.SetProvisioned(inst.Id(), agent.BootstrapNonce, md) 84 c.Assert(err, jc.ErrorIsNil) 85 conf, tools := s.PrimeAgent(c, unit.Tag(), initialUnitPassword, version.Current) 86 return machine, unit, conf, tools 87 } 88 89 func (s *UnitSuite) newAgent(c *gc.C, unit *state.Unit) *UnitAgent { 90 a := NewUnitAgent(nil, nil) 91 s.InitAgent(c, a, "--unit-name", unit.Name(), "--log-to-stderr=true") 92 err := a.ReadConfig(unit.Tag().String()) 93 c.Assert(err, jc.ErrorIsNil) 94 return a 95 } 96 97 func (s *UnitSuite) TestParseSuccess(c *gc.C) { 98 a := NewUnitAgent(nil, nil) 99 err := coretesting.InitCommand(a, []string{ 100 "--data-dir", "jd", 101 "--unit-name", "w0rd-pre55/1", 102 "--log-to-stderr", 103 }) 104 105 c.Assert(err, gc.IsNil) 106 c.Check(a.AgentConf.DataDir(), gc.Equals, "jd") 107 c.Check(a.UnitName, gc.Equals, "w0rd-pre55/1") 108 } 109 110 func (s *UnitSuite) TestParseMissing(c *gc.C) { 111 uc := NewUnitAgent(nil, nil) 112 err := coretesting.InitCommand(uc, []string{ 113 "--data-dir", "jc", 114 }) 115 116 c.Assert(err, gc.ErrorMatches, "--unit-name option must be set") 117 } 118 119 func (s *UnitSuite) TestParseNonsense(c *gc.C) { 120 for _, args := range [][]string{ 121 {"--unit-name", "wordpress"}, 122 {"--unit-name", "wordpress/seventeen"}, 123 {"--unit-name", "wordpress/-32"}, 124 {"--unit-name", "wordpress/wild/9"}, 125 {"--unit-name", "20/20"}, 126 } { 127 err := coretesting.InitCommand(NewUnitAgent(nil, nil), append(args, "--data-dir", "jc")) 128 c.Check(err, gc.ErrorMatches, `--unit-name option expects "<service>/<n>" argument`) 129 } 130 } 131 132 func (s *UnitSuite) TestParseUnknown(c *gc.C) { 133 err := coretesting.InitCommand(NewUnitAgent(nil, nil), []string{ 134 "--unit-name", "wordpress/1", 135 "thundering typhoons", 136 }) 137 c.Check(err, gc.ErrorMatches, `unrecognized args: \["thundering typhoons"\]`) 138 } 139 140 func waitForUnitActive(stateConn *state.State, unit *state.Unit, c *gc.C) { 141 timeout := time.After(5 * time.Second) 142 143 for { 144 select { 145 case <-timeout: 146 c.Fatalf("no activity detected") 147 case <-time.After(coretesting.ShortWait): 148 err := unit.Refresh() 149 c.Assert(err, jc.ErrorIsNil) 150 statusInfo, err := unit.Status() 151 c.Assert(err, jc.ErrorIsNil) 152 switch statusInfo.Status { 153 case state.StatusMaintenance, state.StatusWaiting, state.StatusBlocked: 154 c.Logf("waiting...") 155 continue 156 case state.StatusActive: 157 c.Logf("active!") 158 return 159 case state.StatusUnknown: 160 // Active units may have a status of unknown if they have 161 // started but not run status-set. 162 c.Logf("unknown but active!") 163 return 164 default: 165 c.Fatalf("unexpected status %s %s %v", statusInfo.Status, statusInfo.Message, statusInfo.Data) 166 } 167 statusInfo, err = unit.AgentStatus() 168 c.Assert(err, jc.ErrorIsNil) 169 switch statusInfo.Status { 170 case state.StatusAllocating, state.StatusExecuting, state.StatusRebooting, state.StatusIdle: 171 c.Logf("waiting...") 172 continue 173 case state.StatusError: 174 stateConn.StartSync() 175 c.Logf("unit is still down") 176 default: 177 c.Fatalf("unexpected status %s %s %v", statusInfo.Status, statusInfo.Message, statusInfo.Data) 178 } 179 } 180 } 181 } 182 183 func (s *UnitSuite) TestRunStop(c *gc.C) { 184 _, unit, _, _ := s.primeAgent(c) 185 a := s.newAgent(c, unit) 186 go func() { c.Check(a.Run(nil), gc.IsNil) }() 187 defer func() { c.Check(a.Stop(), gc.IsNil) }() 188 waitForUnitActive(s.State, unit, c) 189 } 190 191 func (s *UnitSuite) TestUpgrade(c *gc.C) { 192 machine, unit, _, currentTools := s.primeAgent(c) 193 agent := s.newAgent(c, unit) 194 newVers := version.Current 195 newVers.Patch++ 196 envtesting.AssertUploadFakeToolsVersions( 197 c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers) 198 199 // The machine agent downloads the tools; fake this by 200 // creating downloaded-tools.txt in data-dir/tools/<version>. 201 toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers) 202 err := os.MkdirAll(toolsDir, 0755) 203 c.Assert(err, jc.ErrorIsNil) 204 toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt") 205 testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"} 206 data, err := json.Marshal(testTools) 207 c.Assert(err, jc.ErrorIsNil) 208 err = ioutil.WriteFile(toolsPath, data, 0644) 209 c.Assert(err, jc.ErrorIsNil) 210 211 // Set the machine agent version to trigger an upgrade. 212 err = machine.SetAgentVersion(newVers) 213 c.Assert(err, jc.ErrorIsNil) 214 err = runWithTimeout(agent) 215 envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ 216 AgentName: unit.Tag().String(), 217 OldTools: currentTools.Version, 218 NewTools: newVers, 219 DataDir: s.DataDir(), 220 }) 221 } 222 223 func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) { 224 machine, unit, _, _ := s.primeAgent(c) 225 agent := s.newAgent(c, unit) 226 newVers := version.Current 227 newVers.Patch++ 228 err := machine.SetAgentVersion(newVers) 229 c.Assert(err, jc.ErrorIsNil) 230 err = runWithTimeout(agent) 231 c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*") 232 } 233 234 func (s *UnitSuite) TestWithDeadUnit(c *gc.C) { 235 _, unit, _, _ := s.primeAgent(c) 236 err := unit.EnsureDead() 237 c.Assert(err, jc.ErrorIsNil) 238 a := s.newAgent(c, unit) 239 err = runWithTimeout(a) 240 c.Assert(err, jc.ErrorIsNil) 241 242 // try again when the unit has been removed. 243 err = unit.Remove() 244 c.Assert(err, jc.ErrorIsNil) 245 a = s.newAgent(c, unit) 246 err = runWithTimeout(a) 247 c.Assert(err, jc.ErrorIsNil) 248 } 249 250 func (s *UnitSuite) TestOpenAPIState(c *gc.C) { 251 _, unit, conf, _ := s.primeAgent(c) 252 configPath := agent.ConfigPath(conf.DataDir(), conf.Tag()) 253 254 // Set an invalid password (but the old initial password will still work). 255 // This test is a sort of unsophisticated simulation of what might happen 256 // if a previous cycle had picked, and locally recorded, a new password; 257 // but failed to set it on the state server. Would be better to test that 258 // code path explicitly in future, but this suffices for now. 259 confW, err := agent.ReadConfig(configPath) 260 c.Assert(err, gc.IsNil) 261 confW.SetPassword("nonsense-borken") 262 err = confW.Write() 263 c.Assert(err, jc.ErrorIsNil) 264 265 // Check that it successfully connects (with the conf's old password). 266 assertOpen := func() { 267 agent := NewAgentConf(conf.DataDir()) 268 err := agent.ReadConfig(conf.Tag().String()) 269 c.Assert(err, jc.ErrorIsNil) 270 st, gotEntity, err := apicaller.OpenAPIState(agent) 271 c.Assert(err, jc.ErrorIsNil) 272 c.Assert(st, gc.NotNil) 273 st.Close() 274 c.Assert(gotEntity.Tag(), gc.Equals, unit.Tag().String()) 275 } 276 assertOpen() 277 278 // Check that the old password has been invalidated. 279 assertPassword := func(password string, valid bool) { 280 err := unit.Refresh() 281 c.Assert(err, jc.ErrorIsNil) 282 c.Check(unit.PasswordValid(password), gc.Equals, valid) 283 } 284 assertPassword(initialUnitPassword, false) 285 286 // Read the stored password and check it's valid. 287 confR, err := agent.ReadConfig(configPath) 288 c.Assert(err, gc.IsNil) 289 newPassword := confR.APIInfo().Password 290 assertPassword(newPassword, true) 291 292 // Double-check that we can open a fresh connection with the stored 293 // conf ... and that the password hasn't been changed again. 294 assertOpen() 295 assertPassword(newPassword, true) 296 } 297 298 func (s *UnitSuite) TestOpenAPIStateWithBadCredsTerminates(c *gc.C) { 299 conf, _ := s.PrimeAgent(c, names.NewUnitTag("missing/0"), "no-password", version.Current) 300 301 _, _, err := apicaller.OpenAPIState(fakeConfAgent{conf: conf}) 302 c.Assert(err, gc.Equals, worker.ErrTerminateAgent) 303 } 304 305 func (s *UnitSuite) TestOpenAPIStateWithDeadEntityTerminates(c *gc.C) { 306 _, unit, conf, _ := s.primeAgent(c) 307 err := unit.EnsureDead() 308 c.Assert(err, jc.ErrorIsNil) 309 310 _, _, err = apicaller.OpenAPIState(fakeConfAgent{conf: conf}) 311 c.Assert(err, gc.Equals, worker.ErrTerminateAgent) 312 } 313 314 type fakeConfAgent struct { 315 agent.Agent 316 conf agent.Config 317 } 318 319 func (f fakeConfAgent) CurrentConfig() agent.Config { 320 return f.conf 321 } 322 323 func (s *UnitSuite) TestOpenStateFails(c *gc.C) { 324 // Start a unit agent and make sure it doesn't set a mongo password 325 // we can use to connect to state with. 326 _, unit, conf, _ := s.primeAgent(c) 327 a := s.newAgent(c, unit) 328 go func() { c.Check(a.Run(nil), gc.IsNil) }() 329 defer func() { c.Check(a.Stop(), gc.IsNil) }() 330 waitForUnitActive(s.State, unit, c) 331 332 s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir()) 333 } 334 335 func (s *UnitSuite) TestRsyslogConfigWorker(c *gc.C) { 336 created := make(chan rsyslog.RsyslogMode, 1) 337 s.PatchValue(&rsyslog.NewRsyslogConfigWorker, func(_ *apirsyslog.State, mode rsyslog.RsyslogMode, _ names.Tag, _ string, _ []string, _ string) (worker.Worker, error) { 338 created <- mode 339 return newDummyWorker(), nil 340 }) 341 342 _, unit, _, _ := s.primeAgent(c) 343 a := s.newAgent(c, unit) 344 go func() { c.Check(a.Run(nil), gc.IsNil) }() 345 defer func() { c.Check(a.Stop(), gc.IsNil) }() 346 347 select { 348 case <-time.After(coretesting.LongWait): 349 c.Fatalf("timeout while waiting for rsyslog worker to be created") 350 case mode := <-created: 351 c.Assert(mode, gc.Equals, rsyslog.RsyslogModeForwarding) 352 } 353 } 354 355 func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) { 356 _, unit, _, _ := s.primeAgent(c) 357 vers := version.Current 358 vers.Minor = version.Current.Minor + 1 359 err := unit.SetAgentVersion(vers) 360 c.Assert(err, jc.ErrorIsNil) 361 362 a := s.newAgent(c, unit) 363 go func() { c.Check(a.Run(nil), gc.IsNil) }() 364 defer func() { c.Check(a.Stop(), gc.IsNil) }() 365 366 timeout := time.After(coretesting.LongWait) 367 for done := false; !done; { 368 select { 369 case <-timeout: 370 c.Fatalf("timeout while waiting for agent version to be set") 371 case <-time.After(coretesting.ShortWait): 372 err := unit.Refresh() 373 c.Assert(err, jc.ErrorIsNil) 374 agentTools, err := unit.AgentTools() 375 c.Assert(err, jc.ErrorIsNil) 376 if agentTools.Version.Minor != version.Current.Minor { 377 continue 378 } 379 c.Assert(agentTools.Version, gc.DeepEquals, version.Current) 380 done = true 381 } 382 } 383 } 384 385 func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) { 386 _, unit, _, _ := s.primeAgent(c) 387 a := s.newAgent(c, unit) 388 go func() { c.Check(a.Run(nil), gc.IsNil) }() 389 defer func() { c.Check(a.Stop(), gc.IsNil) }() 390 391 // Update the API addresses. 392 updatedServers := [][]network.HostPort{ 393 network.NewHostPorts(1234, "localhost"), 394 } 395 err := s.BackingState.SetAPIHostPorts(updatedServers) 396 c.Assert(err, jc.ErrorIsNil) 397 398 // Wait for config to be updated. 399 s.BackingState.StartSync() 400 for attempt := coretesting.LongAttempt.Start(); attempt.Next(); { 401 addrs, err := a.CurrentConfig().APIAddresses() 402 c.Assert(err, jc.ErrorIsNil) 403 if reflect.DeepEqual(addrs, []string{"localhost:1234"}) { 404 return 405 } 406 } 407 c.Fatalf("timeout while waiting for agent config to change") 408 } 409 410 func (s *UnitSuite) TestUseLumberjack(c *gc.C) { 411 ctx, err := cmd.DefaultContext() 412 c.Assert(err, gc.IsNil) 413 414 a := UnitAgent{ 415 AgentConf: FakeAgentConfig{}, 416 ctx: ctx, 417 UnitName: "mysql/25", 418 } 419 420 err = a.Init(nil) 421 c.Assert(err, gc.IsNil) 422 423 l, ok := ctx.Stderr.(*lumberjack.Logger) 424 c.Assert(ok, jc.IsTrue) 425 c.Check(l.MaxAge, gc.Equals, 0) 426 c.Check(l.MaxBackups, gc.Equals, 2) 427 c.Check(l.Filename, gc.Equals, filepath.FromSlash("/var/log/juju/machine-42.log")) 428 c.Check(l.MaxSize, gc.Equals, 300) 429 } 430 431 func (s *UnitSuite) TestDontUseLumberjack(c *gc.C) { 432 ctx, err := cmd.DefaultContext() 433 c.Assert(err, gc.IsNil) 434 435 a := UnitAgent{ 436 AgentConf: FakeAgentConfig{}, 437 ctx: ctx, 438 UnitName: "mysql/25", 439 440 // this is what would get set by the CLI flags to tell us not to log to 441 // the file. 442 logToStdErr: true, 443 } 444 445 err = a.Init(nil) 446 c.Assert(err, gc.IsNil) 447 448 _, ok := ctx.Stderr.(*lumberjack.Logger) 449 c.Assert(ok, jc.IsFalse) 450 }