github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/cmd/cmdtesting" 16 "github.com/juju/os/series" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils/arch" 19 "github.com/juju/utils/voyeur" 20 "github.com/juju/version" 21 gc "gopkg.in/check.v1" 22 "gopkg.in/natefinch/lumberjack.v2" 23 24 "github.com/juju/juju/agent" 25 agenttools "github.com/juju/juju/agent/tools" 26 "github.com/juju/juju/cmd/jujud/agent/agenttest" 27 "github.com/juju/juju/core/status" 28 envtesting "github.com/juju/juju/environs/testing" 29 "github.com/juju/juju/network" 30 "github.com/juju/juju/state" 31 coretesting "github.com/juju/juju/testing" 32 "github.com/juju/juju/testing/factory" 33 "github.com/juju/juju/tools" 34 jujuversion "github.com/juju/juju/version" 35 "github.com/juju/juju/worker/logsender" 36 "github.com/juju/juju/worker/upgrader" 37 ) 38 39 type UnitSuite struct { 40 coretesting.GitSuite 41 agenttest.AgentSuite 42 } 43 44 var _ = gc.Suite(&UnitSuite{}) 45 46 func (s *UnitSuite) SetUpSuite(c *gc.C) { 47 s.GitSuite.SetUpSuite(c) 48 s.AgentSuite.SetUpSuite(c) 49 } 50 51 func (s *UnitSuite) TearDownSuite(c *gc.C) { 52 s.AgentSuite.TearDownSuite(c) 53 s.GitSuite.TearDownSuite(c) 54 } 55 56 func (s *UnitSuite) SetUpTest(c *gc.C) { 57 s.GitSuite.SetUpTest(c) 58 s.AgentSuite.SetUpTest(c) 59 } 60 61 func (s *UnitSuite) TearDownTest(c *gc.C) { 62 s.AgentSuite.TearDownTest(c) 63 s.GitSuite.TearDownTest(c) 64 } 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 machine := s.Factory.MakeMachine(c, nil) 70 app := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 71 unit := s.Factory.MakeUnit(c, &factory.UnitParams{ 72 Application: app, 73 Machine: machine, 74 Password: initialUnitPassword, 75 }) 76 conf, tools := s.PrimeAgent(c, unit.Tag(), initialUnitPassword) 77 return machine, unit, conf, tools 78 } 79 80 func (s *UnitSuite) newAgent(c *gc.C, unit *state.Unit) *UnitAgent { 81 a, err := NewUnitAgent(nil, s.newBufferedLogWriter()) 82 c.Assert(err, jc.ErrorIsNil) 83 s.InitAgent(c, a, "--unit-name", unit.Name(), "--log-to-stderr=true") 84 err = a.ReadConfig(unit.Tag().String()) 85 c.Assert(err, jc.ErrorIsNil) 86 return a 87 } 88 89 func (s *UnitSuite) newBufferedLogWriter() *logsender.BufferedLogWriter { 90 logger := logsender.NewBufferedLogWriter(1024) 91 s.AddCleanup(func(*gc.C) { logger.Close() }) 92 return logger 93 } 94 95 func (s *UnitSuite) TestParseSuccess(c *gc.C) { 96 s.primeAgent(c) 97 // Now init actually reads the agent configuration file. 98 // So use the prime agent call which installs a wordpress unit. 99 a, err := NewUnitAgent(nil, s.newBufferedLogWriter()) 100 c.Assert(err, jc.ErrorIsNil) 101 err = cmdtesting.InitCommand(a, []string{ 102 "--data-dir", s.DataDir(), 103 "--unit-name", "wordpress/0", 104 "--log-to-stderr", 105 }) 106 c.Assert(err, jc.ErrorIsNil) 107 c.Check(a.AgentConf.DataDir(), gc.Equals, s.DataDir()) 108 c.Check(a.UnitName, gc.Equals, "wordpress/0") 109 } 110 111 func (s *UnitSuite) TestParseMissing(c *gc.C) { 112 uc, err := NewUnitAgent(nil, s.newBufferedLogWriter()) 113 c.Assert(err, jc.ErrorIsNil) 114 err = cmdtesting.InitCommand(uc, []string{ 115 "--data-dir", "jc", 116 }) 117 118 c.Assert(err, gc.ErrorMatches, "--unit-name option must be set") 119 } 120 121 func (s *UnitSuite) TestParseNonsense(c *gc.C) { 122 for _, args := range [][]string{ 123 {"--unit-name", "wordpress"}, 124 {"--unit-name", "wordpress/seventeen"}, 125 {"--unit-name", "wordpress/-32"}, 126 {"--unit-name", "wordpress/wild/9"}, 127 {"--unit-name", "20/20"}, 128 } { 129 a, err := NewUnitAgent(nil, s.newBufferedLogWriter()) 130 c.Assert(err, jc.ErrorIsNil) 131 132 err = cmdtesting.InitCommand(a, append(args, "--data-dir", "jc")) 133 c.Check(err, gc.ErrorMatches, `--unit-name option expects "<application>/<n>" argument`) 134 } 135 } 136 137 func (s *UnitSuite) TestParseUnknown(c *gc.C) { 138 a, err := NewUnitAgent(nil, s.newBufferedLogWriter()) 139 c.Assert(err, jc.ErrorIsNil) 140 141 err = cmdtesting.InitCommand(a, []string{ 142 "--unit-name", "wordpress/1", 143 "thundering typhoons", 144 }) 145 c.Check(err, gc.ErrorMatches, `unrecognized args: \["thundering typhoons"\]`) 146 } 147 148 func waitForUnitActive(stateConn *state.State, unit *state.Unit, c *gc.C) { 149 timeout := time.After(coretesting.LongWait) 150 151 for { 152 select { 153 case <-timeout: 154 c.Fatalf("no activity detected") 155 case <-time.After(coretesting.ShortWait): 156 err := unit.Refresh() 157 c.Assert(err, jc.ErrorIsNil) 158 statusInfo, err := unit.Status() 159 c.Assert(err, jc.ErrorIsNil) 160 switch statusInfo.Status { 161 case status.Maintenance, status.Waiting, status.Blocked: 162 c.Logf("waiting...") 163 continue 164 case status.Active: 165 c.Logf("active!") 166 return 167 case status.Unknown: 168 // Active units may have a status of unknown if they have 169 // started but not run status-set. 170 c.Logf("unknown but active!") 171 return 172 default: 173 c.Fatalf("unexpected status %s %s %v", statusInfo.Status, statusInfo.Message, statusInfo.Data) 174 } 175 statusInfo, err = unit.AgentStatus() 176 c.Assert(err, jc.ErrorIsNil) 177 switch statusInfo.Status { 178 case status.Allocating, status.Executing, status.Rebooting, status.Idle: 179 c.Logf("waiting...") 180 continue 181 case status.Error: 182 stateConn.StartSync() 183 c.Logf("unit is still down") 184 default: 185 c.Fatalf("unexpected status %s %s %v", statusInfo.Status, statusInfo.Message, statusInfo.Data) 186 } 187 } 188 } 189 } 190 191 func (s *UnitSuite) TestRunStop(c *gc.C) { 192 _, unit, _, _ := s.primeAgent(c) 193 a := s.newAgent(c, unit) 194 go func() { c.Check(a.Run(nil), gc.IsNil) }() 195 defer func() { c.Check(a.Stop(), gc.IsNil) }() 196 waitForUnitActive(s.State, unit, c) 197 } 198 199 func (s *UnitSuite) TestUpgrade(c *gc.C) { 200 machine, unit, _, currentTools := s.primeAgent(c) 201 agent := s.newAgent(c, unit) 202 newVers := version.Binary{ 203 Number: jujuversion.Current, 204 Arch: arch.HostArch(), 205 Series: series.MustHostSeries(), 206 } 207 newVers.Patch++ 208 envtesting.AssertUploadFakeToolsVersions( 209 c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers) 210 211 // The machine agent downloads the tools; fake this by 212 // creating downloaded-tools.txt in data-dir/tools/<version>. 213 toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers) 214 err := os.MkdirAll(toolsDir, 0755) 215 c.Assert(err, jc.ErrorIsNil) 216 toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt") 217 testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"} 218 data, err := json.Marshal(testTools) 219 c.Assert(err, jc.ErrorIsNil) 220 err = ioutil.WriteFile(toolsPath, data, 0644) 221 c.Assert(err, jc.ErrorIsNil) 222 223 // Set the machine agent version to trigger an upgrade. 224 err = machine.SetAgentVersion(newVers) 225 c.Assert(err, jc.ErrorIsNil) 226 err = runWithTimeout(agent) 227 envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{ 228 AgentName: unit.Tag().String(), 229 OldTools: currentTools.Version, 230 NewTools: newVers, 231 DataDir: s.DataDir(), 232 }) 233 } 234 235 func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) { 236 machine, unit, _, _ := s.primeAgent(c) 237 agent := s.newAgent(c, unit) 238 newVers := version.Binary{ 239 Number: jujuversion.Current, 240 Arch: arch.HostArch(), 241 Series: series.MustHostSeries(), 242 } 243 newVers.Patch++ 244 err := machine.SetAgentVersion(newVers) 245 c.Assert(err, jc.ErrorIsNil) 246 err = runWithTimeout(agent) 247 c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*") 248 } 249 250 func (s *UnitSuite) TestWithDeadUnit(c *gc.C) { 251 _, unit, _, _ := s.primeAgent(c) 252 err := unit.EnsureDead() 253 c.Assert(err, jc.ErrorIsNil) 254 a := s.newAgent(c, unit) 255 err = runWithTimeout(a) 256 c.Assert(err, jc.ErrorIsNil) 257 258 // try again when the unit has been removed. 259 err = unit.Remove() 260 c.Assert(err, jc.ErrorIsNil) 261 a = s.newAgent(c, unit) 262 err = runWithTimeout(a) 263 c.Assert(err, jc.ErrorIsNil) 264 } 265 266 func (s *UnitSuite) TestOpenStateFails(c *gc.C) { 267 // Start a unit agent and make sure it doesn't set a mongo password 268 // we can use to connect to state with. 269 _, unit, conf, _ := s.primeAgent(c) 270 a := s.newAgent(c, unit) 271 go func() { c.Check(a.Run(nil), gc.IsNil) }() 272 defer func() { c.Check(a.Stop(), gc.IsNil) }() 273 waitForUnitActive(s.State, unit, c) 274 275 s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir()) 276 } 277 278 func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) { 279 _, unit, _, _ := s.primeAgent(c) 280 vers := version.Binary{ 281 Number: jujuversion.Current, 282 Arch: arch.HostArch(), 283 Series: series.MustHostSeries(), 284 } 285 vers.Minor++ 286 err := unit.SetAgentVersion(vers) 287 c.Assert(err, jc.ErrorIsNil) 288 289 a := s.newAgent(c, unit) 290 go func() { c.Check(a.Run(nil), gc.IsNil) }() 291 defer func() { c.Check(a.Stop(), gc.IsNil) }() 292 293 timeout := time.After(coretesting.LongWait) 294 for done := false; !done; { 295 select { 296 case <-timeout: 297 c.Fatalf("timeout while waiting for agent version to be set") 298 case <-time.After(coretesting.ShortWait): 299 err := unit.Refresh() 300 c.Assert(err, jc.ErrorIsNil) 301 agentTools, err := unit.AgentTools() 302 c.Assert(err, jc.ErrorIsNil) 303 if agentTools.Version.Minor != jujuversion.Current.Minor { 304 continue 305 } 306 current := version.Binary{ 307 Number: jujuversion.Current, 308 Arch: arch.HostArch(), 309 Series: series.MustHostSeries(), 310 } 311 c.Assert(agentTools.Version, gc.DeepEquals, current) 312 done = true 313 } 314 } 315 } 316 317 func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) { 318 _, unit, _, _ := s.primeAgent(c) 319 a := s.newAgent(c, unit) 320 go func() { c.Check(a.Run(nil), gc.IsNil) }() 321 defer func() { c.Check(a.Stop(), gc.IsNil) }() 322 323 // Update the API addresses. 324 updatedServers := [][]network.HostPort{ 325 network.NewHostPorts(1234, "localhost"), 326 } 327 err := s.BackingState.SetAPIHostPorts(updatedServers) 328 c.Assert(err, jc.ErrorIsNil) 329 330 // Wait for config to be updated. 331 s.BackingState.StartSync() 332 for attempt := coretesting.LongAttempt.Start(); attempt.Next(); { 333 addrs, err := a.CurrentConfig().APIAddresses() 334 c.Assert(err, jc.ErrorIsNil) 335 if reflect.DeepEqual(addrs, []string{"localhost:1234"}) { 336 return 337 } 338 } 339 c.Fatalf("timeout while waiting for agent config to change") 340 } 341 342 func (s *UnitSuite) TestUseLumberjack(c *gc.C) { 343 ctx, err := cmd.DefaultContext() 344 c.Assert(err, gc.IsNil) 345 346 a := UnitAgent{ 347 AgentConf: FakeAgentConfig{}, 348 ctx: ctx, 349 UnitName: "mysql/25", 350 } 351 352 err = a.Init(nil) 353 c.Assert(err, gc.IsNil) 354 355 l, ok := ctx.Stderr.(*lumberjack.Logger) 356 c.Assert(ok, jc.IsTrue) 357 c.Check(l.MaxAge, gc.Equals, 0) 358 c.Check(l.MaxBackups, gc.Equals, 2) 359 c.Check(l.Filename, gc.Equals, filepath.FromSlash("/var/log/juju/machine-42.log")) 360 c.Check(l.MaxSize, gc.Equals, 300) 361 } 362 363 func (s *UnitSuite) TestDontUseLumberjack(c *gc.C) { 364 ctx, err := cmd.DefaultContext() 365 c.Assert(err, gc.IsNil) 366 367 a := UnitAgent{ 368 AgentConf: FakeAgentConfig{}, 369 ctx: ctx, 370 UnitName: "mysql/25", 371 372 // this is what would get set by the CLI flags to tell us not to log to 373 // the file. 374 logToStdErr: true, 375 } 376 377 err = a.Init(nil) 378 c.Assert(err, gc.IsNil) 379 380 _, ok := ctx.Stderr.(*lumberjack.Logger) 381 c.Assert(ok, jc.IsFalse) 382 } 383 384 func (s *UnitSuite) TestChangeConfig(c *gc.C) { 385 config := FakeAgentConfig{} 386 configChanged := voyeur.NewValue(true) 387 a := UnitAgent{ 388 AgentConf: config, 389 configChangedVal: configChanged, 390 } 391 392 var mutateCalled bool 393 mutate := func(config agent.ConfigSetter) error { 394 mutateCalled = true 395 return nil 396 } 397 398 configChangedCh := make(chan bool) 399 watcher := configChanged.Watch() 400 watcher.Next() // consume initial event 401 go func() { 402 configChangedCh <- watcher.Next() 403 }() 404 405 err := a.ChangeConfig(mutate) 406 c.Assert(err, jc.ErrorIsNil) 407 408 c.Check(mutateCalled, jc.IsTrue) 409 select { 410 case result := <-configChangedCh: 411 c.Check(result, jc.IsTrue) 412 case <-time.After(coretesting.LongWait): 413 c.Fatal("timed out waiting for config changed signal") 414 } 415 } 416 417 func (s *UnitSuite) TestWorkers(c *gc.C) { 418 tracker := agenttest.NewEngineTracker() 419 instrumented := TrackUnits(c, tracker, unitManifolds) 420 s.PatchValue(&unitManifolds, instrumented) 421 422 _, unit, _, _ := s.primeAgent(c) 423 ctx := cmdtesting.Context(c) 424 a, err := NewUnitAgent(ctx, s.newBufferedLogWriter()) 425 c.Assert(err, jc.ErrorIsNil) 426 s.InitAgent(c, a, "--unit-name", unit.Name()) 427 428 go func() { c.Check(a.Run(nil), gc.IsNil) }() 429 defer func() { c.Check(a.Stop(), gc.IsNil) }() 430 431 matcher := agenttest.NewWorkerMatcher(c, tracker, a.Tag().String(), 432 append(alwaysUnitWorkers, notMigratingUnitWorkers...)) 433 agenttest.WaitMatch(c, matcher.Check, coretesting.LongWait, s.BackingState.StartSync) 434 }