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