github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils/arch" 17 "github.com/juju/utils/series" 18 "github.com/juju/utils/voyeur" 19 "github.com/juju/version" 20 gc "gopkg.in/check.v1" 21 "gopkg.in/natefinch/lumberjack.v2" 22 23 "github.com/juju/juju/agent" 24 agenttools "github.com/juju/juju/agent/tools" 25 agenttesting "github.com/juju/juju/cmd/jujud/agent/testing" 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 "github.com/juju/juju/status" 31 coretesting "github.com/juju/juju/testing" 32 "github.com/juju/juju/tools" 33 jujuversion "github.com/juju/juju/version" 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.AddControllerMachine(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) 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(coretesting.LongWait) 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 status.StatusMaintenance, status.StatusWaiting, status.StatusBlocked: 154 c.Logf("waiting...") 155 continue 156 case status.StatusActive: 157 c.Logf("active!") 158 return 159 case status.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 status.StatusAllocating, status.StatusExecuting, status.StatusRebooting, status.StatusIdle: 171 c.Logf("waiting...") 172 continue 173 case status.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.Binary{ 195 Number: jujuversion.Current, 196 Arch: arch.HostArch(), 197 Series: series.HostSeries(), 198 } 199 newVers.Patch++ 200 envtesting.AssertUploadFakeToolsVersions( 201 c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers) 202 203 // The machine agent downloads the tools; fake this by 204 // creating downloaded-tools.txt in data-dir/tools/<version>. 205 toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers) 206 err := os.MkdirAll(toolsDir, 0755) 207 c.Assert(err, jc.ErrorIsNil) 208 toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt") 209 testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"} 210 data, err := json.Marshal(testTools) 211 c.Assert(err, jc.ErrorIsNil) 212 err = ioutil.WriteFile(toolsPath, data, 0644) 213 c.Assert(err, jc.ErrorIsNil) 214 215 // Set the machine agent version to trigger an upgrade. 216 err = machine.SetAgentVersion(newVers) 217 c.Assert(err, jc.ErrorIsNil) 218 err = runWithTimeout(agent) 219 envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ 220 AgentName: unit.Tag().String(), 221 OldTools: currentTools.Version, 222 NewTools: newVers, 223 DataDir: s.DataDir(), 224 }) 225 } 226 227 func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) { 228 machine, unit, _, _ := s.primeAgent(c) 229 agent := s.newAgent(c, unit) 230 newVers := version.Binary{ 231 Number: jujuversion.Current, 232 Arch: arch.HostArch(), 233 Series: series.HostSeries(), 234 } 235 newVers.Patch++ 236 err := machine.SetAgentVersion(newVers) 237 c.Assert(err, jc.ErrorIsNil) 238 err = runWithTimeout(agent) 239 c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*") 240 } 241 242 func (s *UnitSuite) TestWithDeadUnit(c *gc.C) { 243 _, unit, _, _ := s.primeAgent(c) 244 err := unit.EnsureDead() 245 c.Assert(err, jc.ErrorIsNil) 246 a := s.newAgent(c, unit) 247 err = runWithTimeout(a) 248 c.Assert(err, jc.ErrorIsNil) 249 250 // try again when the unit has been removed. 251 err = unit.Remove() 252 c.Assert(err, jc.ErrorIsNil) 253 a = s.newAgent(c, unit) 254 err = runWithTimeout(a) 255 c.Assert(err, jc.ErrorIsNil) 256 } 257 258 func (s *UnitSuite) TestOpenStateFails(c *gc.C) { 259 // Start a unit agent and make sure it doesn't set a mongo password 260 // we can use to connect to state with. 261 _, unit, conf, _ := s.primeAgent(c) 262 a := s.newAgent(c, unit) 263 go func() { c.Check(a.Run(nil), gc.IsNil) }() 264 defer func() { c.Check(a.Stop(), gc.IsNil) }() 265 waitForUnitActive(s.State, unit, c) 266 267 s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir()) 268 } 269 270 func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) { 271 _, unit, _, _ := s.primeAgent(c) 272 vers := version.Binary{ 273 Number: jujuversion.Current, 274 Arch: arch.HostArch(), 275 Series: series.HostSeries(), 276 } 277 vers.Minor++ 278 err := unit.SetAgentVersion(vers) 279 c.Assert(err, jc.ErrorIsNil) 280 281 a := s.newAgent(c, unit) 282 go func() { c.Check(a.Run(nil), gc.IsNil) }() 283 defer func() { c.Check(a.Stop(), gc.IsNil) }() 284 285 timeout := time.After(coretesting.LongWait) 286 for done := false; !done; { 287 select { 288 case <-timeout: 289 c.Fatalf("timeout while waiting for agent version to be set") 290 case <-time.After(coretesting.ShortWait): 291 err := unit.Refresh() 292 c.Assert(err, jc.ErrorIsNil) 293 agentTools, err := unit.AgentTools() 294 c.Assert(err, jc.ErrorIsNil) 295 if agentTools.Version.Minor != jujuversion.Current.Minor { 296 continue 297 } 298 current := version.Binary{ 299 Number: jujuversion.Current, 300 Arch: arch.HostArch(), 301 Series: series.HostSeries(), 302 } 303 c.Assert(agentTools.Version, gc.DeepEquals, current) 304 done = true 305 } 306 } 307 } 308 309 func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) { 310 _, unit, _, _ := s.primeAgent(c) 311 a := s.newAgent(c, unit) 312 go func() { c.Check(a.Run(nil), gc.IsNil) }() 313 defer func() { c.Check(a.Stop(), gc.IsNil) }() 314 315 // Update the API addresses. 316 updatedServers := [][]network.HostPort{ 317 network.NewHostPorts(1234, "localhost"), 318 } 319 err := s.BackingState.SetAPIHostPorts(updatedServers) 320 c.Assert(err, jc.ErrorIsNil) 321 322 // Wait for config to be updated. 323 s.BackingState.StartSync() 324 for attempt := coretesting.LongAttempt.Start(); attempt.Next(); { 325 addrs, err := a.CurrentConfig().APIAddresses() 326 c.Assert(err, jc.ErrorIsNil) 327 if reflect.DeepEqual(addrs, []string{"localhost:1234"}) { 328 return 329 } 330 } 331 c.Fatalf("timeout while waiting for agent config to change") 332 } 333 334 func (s *UnitSuite) TestUseLumberjack(c *gc.C) { 335 ctx, err := cmd.DefaultContext() 336 c.Assert(err, gc.IsNil) 337 338 a := UnitAgent{ 339 AgentConf: FakeAgentConfig{}, 340 ctx: ctx, 341 UnitName: "mysql/25", 342 } 343 344 err = a.Init(nil) 345 c.Assert(err, gc.IsNil) 346 347 l, ok := ctx.Stderr.(*lumberjack.Logger) 348 c.Assert(ok, jc.IsTrue) 349 c.Check(l.MaxAge, gc.Equals, 0) 350 c.Check(l.MaxBackups, gc.Equals, 2) 351 c.Check(l.Filename, gc.Equals, filepath.FromSlash("/var/log/juju/machine-42.log")) 352 c.Check(l.MaxSize, gc.Equals, 300) 353 } 354 355 func (s *UnitSuite) TestDontUseLumberjack(c *gc.C) { 356 ctx, err := cmd.DefaultContext() 357 c.Assert(err, gc.IsNil) 358 359 a := UnitAgent{ 360 AgentConf: FakeAgentConfig{}, 361 ctx: ctx, 362 UnitName: "mysql/25", 363 364 // this is what would get set by the CLI flags to tell us not to log to 365 // the file. 366 logToStdErr: true, 367 } 368 369 err = a.Init(nil) 370 c.Assert(err, gc.IsNil) 371 372 _, ok := ctx.Stderr.(*lumberjack.Logger) 373 c.Assert(ok, jc.IsFalse) 374 } 375 376 func (s *UnitSuite) TestChangeConfig(c *gc.C) { 377 config := FakeAgentConfig{} 378 configChanged := voyeur.NewValue(true) 379 a := UnitAgent{ 380 AgentConf: config, 381 configChangedVal: configChanged, 382 } 383 384 var mutateCalled bool 385 mutate := func(config agent.ConfigSetter) error { 386 mutateCalled = true 387 return nil 388 } 389 390 configChangedCh := make(chan bool) 391 watcher := configChanged.Watch() 392 watcher.Next() // consume initial event 393 go func() { 394 configChangedCh <- watcher.Next() 395 }() 396 397 err := a.ChangeConfig(mutate) 398 c.Assert(err, jc.ErrorIsNil) 399 400 c.Check(mutateCalled, jc.IsTrue) 401 select { 402 case result := <-configChangedCh: 403 c.Check(result, jc.IsTrue) 404 case <-time.After(coretesting.LongWait): 405 c.Fatal("timed out waiting for config changed signal") 406 } 407 }