github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/jujud/unit_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 "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 19 "fmt" 20 "github.com/juju/juju/agent" 21 agenttools "github.com/juju/juju/agent/tools" 22 apirsyslog "github.com/juju/juju/api/rsyslog" 23 agentcmd "github.com/juju/juju/cmd/jujud/agent" 24 agenttesting "github.com/juju/juju/cmd/jujud/agent/testing" 25 cmdutil "github.com/juju/juju/cmd/jujud/util" 26 envtesting "github.com/juju/juju/environs/testing" 27 jujutesting "github.com/juju/juju/juju/testing" 28 "github.com/juju/juju/network" 29 "github.com/juju/juju/state" 30 coretesting "github.com/juju/juju/testing" 31 "github.com/juju/juju/tools" 32 "github.com/juju/juju/version" 33 "github.com/juju/juju/worker" 34 "github.com/juju/juju/worker/rsyslog" 35 "github.com/juju/juju/worker/upgrader" 36 ) 37 38 type UnitSuite struct { 39 coretesting.GitSuite 40 agenttesting.AgentSuite 41 } 42 43 var _ = gc.Suite(&UnitSuite{}) 44 45 func (s *UnitSuite) SetUpSuite(c *gc.C) { 46 s.GitSuite.SetUpSuite(c) 47 s.AgentSuite.SetUpSuite(c) 48 } 49 50 func (s *UnitSuite) TearDownSuite(c *gc.C) { 51 s.AgentSuite.TearDownSuite(c) 52 s.GitSuite.TearDownSuite(c) 53 } 54 55 func (s *UnitSuite) SetUpTest(c *gc.C) { 56 s.GitSuite.SetUpTest(c) 57 s.AgentSuite.SetUpTest(c) 58 } 59 60 func (s *UnitSuite) TearDownTest(c *gc.C) { 61 s.AgentSuite.TearDownTest(c) 62 s.GitSuite.TearDownTest(c) 63 } 64 65 const initialUnitPassword = "unit-password-1234567890" 66 67 // primeAgent creates a unit, and sets up the unit agent's directory. 68 // It returns the assigned machine, new unit and the agent's configuration. 69 func (s *UnitSuite) primeAgent(c *gc.C) (*state.Machine, *state.Unit, agent.Config, *tools.Tools) { 70 jujutesting.AddStateServerMachine(c, s.State) 71 svc := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 72 unit, err := svc.AddUnit() 73 c.Assert(err, jc.ErrorIsNil) 74 err = unit.SetPassword(initialUnitPassword) 75 c.Assert(err, jc.ErrorIsNil) 76 // Assign the unit to a machine. 77 err = unit.AssignToNewMachine() 78 c.Assert(err, jc.ErrorIsNil) 79 id, err := unit.AssignedMachineId() 80 c.Assert(err, jc.ErrorIsNil) 81 machine, err := s.State.Machine(id) 82 c.Assert(err, jc.ErrorIsNil) 83 inst, md := jujutesting.AssertStartInstance(c, s.Environ, id) 84 err = machine.SetProvisioned(inst.Id(), agent.BootstrapNonce, md) 85 c.Assert(err, jc.ErrorIsNil) 86 conf, tools := s.PrimeAgent(c, unit.Tag(), initialUnitPassword, version.Current) 87 return machine, unit, conf, tools 88 } 89 90 func (s *UnitSuite) newAgent(c *gc.C, unit *state.Unit) *UnitAgent { 91 a := &UnitAgent{} 92 s.InitAgent(c, a, "--unit-name", unit.Name(), "--log-to-stderr=true") 93 err := a.ReadConfig(unit.Tag().String()) 94 c.Assert(err, jc.ErrorIsNil) 95 return a 96 } 97 98 func (s *UnitSuite) TestParseSuccess(c *gc.C) { 99 a := &UnitAgent{} 100 err := coretesting.InitCommand(a, []string{ 101 "--data-dir", "jd", 102 "--unit-name", "w0rd-pre55/1", 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 := &UnitAgent{} 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(&UnitAgent{}, 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(&UnitAgent{}, []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 st, info, data, err := unit.Status() 151 c.Assert(err, jc.ErrorIsNil) 152 switch st { 153 case state.StatusAllocating, state.StatusInstalling: 154 c.Logf("waiting...") 155 continue 156 case state.StatusActive: 157 c.Logf("active!") 158 return 159 case state.StatusFailed: 160 stateConn.StartSync() 161 c.Logf("unit is still down") 162 default: 163 c.Fatalf("unexpected status %s %s %v", st, info, data) 164 } 165 } 166 } 167 } 168 169 func (s *UnitSuite) TestRunStop(c *gc.C) { 170 _, unit, _, _ := s.primeAgent(c) 171 a := s.newAgent(c, unit) 172 go func() { c.Check(a.Run(nil), gc.IsNil) }() 173 defer func() { c.Check(a.Stop(), gc.IsNil) }() 174 waitForUnitActive(s.State, unit, c) 175 } 176 177 func (s *UnitSuite) TestUpgrade(c *gc.C) { 178 machine, unit, _, currentTools := s.primeAgent(c) 179 agent := s.newAgent(c, unit) 180 newVers := version.Current 181 newVers.Patch++ 182 envtesting.AssertUploadFakeToolsVersions( 183 c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers) 184 185 // The machine agent downloads the tools; fake this by 186 // creating downloaded-tools.txt in data-dir/tools/<version>. 187 toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers) 188 err := os.MkdirAll(toolsDir, 0755) 189 c.Assert(err, jc.ErrorIsNil) 190 toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt") 191 testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"} 192 data, err := json.Marshal(testTools) 193 c.Assert(err, jc.ErrorIsNil) 194 err = ioutil.WriteFile(toolsPath, data, 0644) 195 c.Assert(err, jc.ErrorIsNil) 196 197 // Set the machine agent version to trigger an upgrade. 198 err = machine.SetAgentVersion(newVers) 199 c.Assert(err, jc.ErrorIsNil) 200 err = runWithTimeout(agent) 201 envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ 202 AgentName: unit.Tag().String(), 203 OldTools: currentTools.Version, 204 NewTools: newVers, 205 DataDir: s.DataDir(), 206 }) 207 } 208 209 func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) { 210 machine, unit, _, _ := s.primeAgent(c) 211 agent := s.newAgent(c, unit) 212 newVers := version.Current 213 newVers.Patch++ 214 err := machine.SetAgentVersion(newVers) 215 c.Assert(err, jc.ErrorIsNil) 216 err = runWithTimeout(agent) 217 c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*") 218 } 219 220 func (s *UnitSuite) TestWithDeadUnit(c *gc.C) { 221 _, unit, _, _ := s.primeAgent(c) 222 err := unit.EnsureDead() 223 c.Assert(err, jc.ErrorIsNil) 224 a := s.newAgent(c, unit) 225 err = runWithTimeout(a) 226 c.Assert(err, jc.ErrorIsNil) 227 228 // try again when the unit has been removed. 229 err = unit.Remove() 230 c.Assert(err, jc.ErrorIsNil) 231 a = s.newAgent(c, unit) 232 err = runWithTimeout(a) 233 c.Assert(err, jc.ErrorIsNil) 234 } 235 236 func (s *UnitSuite) TestOpenAPIState(c *gc.C) { 237 _, unit, _, _ := s.primeAgent(c) 238 s.RunTestOpenAPIState(c, unit, s.newAgent(c, unit), initialUnitPassword) 239 } 240 241 func (s *UnitSuite) RunTestOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd agentcmd.Agent, initialPassword string) { 242 conf, err := agent.ReadConfig(agent.ConfigPath(s.DataDir(), ent.Tag())) 243 c.Assert(err, jc.ErrorIsNil) 244 245 conf.SetPassword("") 246 err = conf.Write() 247 c.Assert(err, jc.ErrorIsNil) 248 249 // Check that it starts initially and changes the password 250 assertOpen := func(conf agent.Config) { 251 st, gotEnt, err := agentcmd.OpenAPIState(conf, agentCmd) 252 c.Assert(err, jc.ErrorIsNil) 253 c.Assert(st, gc.NotNil) 254 st.Close() 255 c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag().String()) 256 } 257 assertOpen(conf) 258 259 // Check that the initial password is no longer valid. 260 err = ent.Refresh() 261 c.Assert(err, jc.ErrorIsNil) 262 c.Assert(ent.PasswordValid(initialPassword), jc.IsFalse) 263 264 // Read the configuration and check that we can connect with it. 265 conf, err = agent.ReadConfig(agent.ConfigPath(conf.DataDir(), conf.Tag())) 266 //conf = refreshConfig(c, conf) 267 c.Assert(err, gc.IsNil) 268 // Check we can open the API with the new configuration. 269 assertOpen(conf) 270 } 271 272 func (s *UnitSuite) TestOpenAPIStateWithBadCredsTerminates(c *gc.C) { 273 conf, _ := s.PrimeAgent(c, names.NewUnitTag("missing/0"), "no-password", version.Current) 274 _, _, err := agentcmd.OpenAPIState(conf, nil) 275 c.Assert(err, gc.Equals, worker.ErrTerminateAgent) 276 } 277 278 type fakeUnitAgent struct { 279 unitName string 280 } 281 282 func (f *fakeUnitAgent) Tag() names.Tag { 283 return names.NewUnitTag(f.unitName) 284 } 285 286 func (f *fakeUnitAgent) ChangeConfig(agentcmd.AgentConfigMutator) error { 287 panic("fakeUnitAgent.ChangeConfig called unexpectedly") 288 } 289 290 func (s *UnitSuite) TestOpenAPIStateWithDeadEntityTerminates(c *gc.C) { 291 _, unit, conf, _ := s.primeAgent(c) 292 err := unit.EnsureDead() 293 c.Assert(err, jc.ErrorIsNil) 294 _, _, err = agentcmd.OpenAPIState(conf, &fakeUnitAgent{"wordpress/0"}) 295 c.Assert(err, gc.Equals, worker.ErrTerminateAgent) 296 } 297 298 func (s *UnitSuite) TestOpenStateFails(c *gc.C) { 299 // Start a unit agent and make sure it doesn't set a mongo password 300 // we can use to connect to state with. 301 _, unit, conf, _ := s.primeAgent(c) 302 a := s.newAgent(c, unit) 303 go func() { c.Check(a.Run(nil), gc.IsNil) }() 304 defer func() { c.Check(a.Stop(), gc.IsNil) }() 305 waitForUnitActive(s.State, unit, c) 306 307 s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir()) 308 } 309 310 func (s *UnitSuite) TestRsyslogConfigWorker(c *gc.C) { 311 created := make(chan rsyslog.RsyslogMode, 1) 312 s.PatchValue(&cmdutil.NewRsyslogConfigWorker, func(_ *apirsyslog.State, _ agent.Config, mode rsyslog.RsyslogMode) (worker.Worker, error) { 313 created <- mode 314 return newDummyWorker(), nil 315 }) 316 317 _, unit, _, _ := s.primeAgent(c) 318 a := s.newAgent(c, unit) 319 go func() { c.Check(a.Run(nil), gc.IsNil) }() 320 defer func() { c.Check(a.Stop(), gc.IsNil) }() 321 322 select { 323 case <-time.After(coretesting.LongWait): 324 c.Fatalf("timeout while waiting for rsyslog worker to be created") 325 case mode := <-created: 326 c.Assert(mode, gc.Equals, rsyslog.RsyslogModeForwarding) 327 } 328 } 329 330 func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) { 331 _, unit, _, _ := s.primeAgent(c) 332 vers := version.Current 333 vers.Minor = version.Current.Minor + 1 334 err := unit.SetAgentVersion(vers) 335 c.Assert(err, jc.ErrorIsNil) 336 337 a := s.newAgent(c, unit) 338 go func() { c.Check(a.Run(nil), gc.IsNil) }() 339 defer func() { c.Check(a.Stop(), gc.IsNil) }() 340 341 timeout := time.After(coretesting.LongWait) 342 for done := false; !done; { 343 select { 344 case <-timeout: 345 c.Fatalf("timeout while waiting for agent version to be set") 346 case <-time.After(coretesting.ShortWait): 347 err := unit.Refresh() 348 c.Assert(err, jc.ErrorIsNil) 349 agentTools, err := unit.AgentTools() 350 c.Assert(err, jc.ErrorIsNil) 351 if agentTools.Version.Minor != version.Current.Minor { 352 continue 353 } 354 c.Assert(agentTools.Version, gc.DeepEquals, version.Current) 355 done = true 356 } 357 } 358 } 359 360 func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) { 361 _, unit, _, _ := s.primeAgent(c) 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 // Update the API addresses. 367 updatedServers := [][]network.HostPort{ 368 network.NewHostPorts(1234, "localhost"), 369 } 370 err := s.BackingState.SetAPIHostPorts(updatedServers) 371 c.Assert(err, jc.ErrorIsNil) 372 373 // Wait for config to be updated. 374 s.BackingState.StartSync() 375 for attempt := coretesting.LongAttempt.Start(); attempt.Next(); { 376 addrs, err := a.CurrentConfig().APIAddresses() 377 c.Assert(err, jc.ErrorIsNil) 378 if reflect.DeepEqual(addrs, []string{"localhost:1234"}) { 379 return 380 } 381 } 382 c.Fatalf("timeout while waiting for agent config to change") 383 } 384 385 type runner interface { 386 Run(*cmd.Context) error 387 Stop() error 388 } 389 390 // runWithTimeout runs an agent and waits 391 // for it to complete within a reasonable time. 392 func runWithTimeout(r runner) error { 393 done := make(chan error) 394 go func() { 395 done <- r.Run(nil) 396 }() 397 select { 398 case err := <-done: 399 return err 400 case <-time.After(coretesting.LongWait): 401 } 402 err := r.Stop() 403 return fmt.Errorf("timed out waiting for agent to finish; stop error: %v", err) 404 } 405 406 func newDummyWorker() worker.Worker { 407 return worker.NewSimpleWorker(func(stop <-chan struct{}) error { 408 <-stop 409 return nil 410 }) 411 }