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  }