github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/names"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/natefinch/lumberjack.v2"
    19  
    20  	"github.com/juju/juju/agent"
    21  	agenttools "github.com/juju/juju/agent/tools"
    22  	apirsyslog "github.com/juju/juju/api/rsyslog"
    23  	agenttesting "github.com/juju/juju/cmd/jujud/agent/testing"
    24  	envtesting "github.com/juju/juju/environs/testing"
    25  	jujutesting "github.com/juju/juju/juju/testing"
    26  	"github.com/juju/juju/network"
    27  	"github.com/juju/juju/state"
    28  	coretesting "github.com/juju/juju/testing"
    29  	"github.com/juju/juju/tools"
    30  	"github.com/juju/juju/version"
    31  	"github.com/juju/juju/worker"
    32  	"github.com/juju/juju/worker/apicaller"
    33  	"github.com/juju/juju/worker/rsyslog"
    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.AddStateServerMachine(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, version.Current)
    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(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  			statusInfo, err := unit.Status()
   151  			c.Assert(err, jc.ErrorIsNil)
   152  			switch statusInfo.Status {
   153  			case state.StatusMaintenance, state.StatusWaiting, state.StatusBlocked:
   154  				c.Logf("waiting...")
   155  				continue
   156  			case state.StatusActive:
   157  				c.Logf("active!")
   158  				return
   159  			case state.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 state.StatusAllocating, state.StatusExecuting, state.StatusRebooting, state.StatusIdle:
   171  				c.Logf("waiting...")
   172  				continue
   173  			case state.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.Current
   195  	newVers.Patch++
   196  	envtesting.AssertUploadFakeToolsVersions(
   197  		c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers)
   198  
   199  	// The machine agent downloads the tools; fake this by
   200  	// creating downloaded-tools.txt in data-dir/tools/<version>.
   201  	toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers)
   202  	err := os.MkdirAll(toolsDir, 0755)
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt")
   205  	testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"}
   206  	data, err := json.Marshal(testTools)
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	err = ioutil.WriteFile(toolsPath, data, 0644)
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	// Set the machine agent version to trigger an upgrade.
   212  	err = machine.SetAgentVersion(newVers)
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	err = runWithTimeout(agent)
   215  	envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{
   216  		AgentName: unit.Tag().String(),
   217  		OldTools:  currentTools.Version,
   218  		NewTools:  newVers,
   219  		DataDir:   s.DataDir(),
   220  	})
   221  }
   222  
   223  func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) {
   224  	machine, unit, _, _ := s.primeAgent(c)
   225  	agent := s.newAgent(c, unit)
   226  	newVers := version.Current
   227  	newVers.Patch++
   228  	err := machine.SetAgentVersion(newVers)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	err = runWithTimeout(agent)
   231  	c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*")
   232  }
   233  
   234  func (s *UnitSuite) TestWithDeadUnit(c *gc.C) {
   235  	_, unit, _, _ := s.primeAgent(c)
   236  	err := unit.EnsureDead()
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	a := s.newAgent(c, unit)
   239  	err = runWithTimeout(a)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  
   242  	// try again when the unit has been removed.
   243  	err = unit.Remove()
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	a = s.newAgent(c, unit)
   246  	err = runWithTimeout(a)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  }
   249  
   250  func (s *UnitSuite) TestOpenAPIState(c *gc.C) {
   251  	_, unit, conf, _ := s.primeAgent(c)
   252  	configPath := agent.ConfigPath(conf.DataDir(), conf.Tag())
   253  
   254  	// Set an invalid password (but the old initial password will still work).
   255  	// This test is a sort of unsophisticated simulation of what might happen
   256  	// if a previous cycle had picked, and locally recorded, a new password;
   257  	// but failed to set it on the state server. Would be better to test that
   258  	// code path explicitly in future, but this suffices for now.
   259  	confW, err := agent.ReadConfig(configPath)
   260  	c.Assert(err, gc.IsNil)
   261  	confW.SetPassword("nonsense-borken")
   262  	err = confW.Write()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  
   265  	// Check that it successfully connects (with the conf's old password).
   266  	assertOpen := func() {
   267  		agent := NewAgentConf(conf.DataDir())
   268  		err := agent.ReadConfig(conf.Tag().String())
   269  		c.Assert(err, jc.ErrorIsNil)
   270  		st, gotEntity, err := apicaller.OpenAPIState(agent)
   271  		c.Assert(err, jc.ErrorIsNil)
   272  		c.Assert(st, gc.NotNil)
   273  		st.Close()
   274  		c.Assert(gotEntity.Tag(), gc.Equals, unit.Tag().String())
   275  	}
   276  	assertOpen()
   277  
   278  	// Check that the old password has been invalidated.
   279  	assertPassword := func(password string, valid bool) {
   280  		err := unit.Refresh()
   281  		c.Assert(err, jc.ErrorIsNil)
   282  		c.Check(unit.PasswordValid(password), gc.Equals, valid)
   283  	}
   284  	assertPassword(initialUnitPassword, false)
   285  
   286  	// Read the stored password and check it's valid.
   287  	confR, err := agent.ReadConfig(configPath)
   288  	c.Assert(err, gc.IsNil)
   289  	newPassword := confR.APIInfo().Password
   290  	assertPassword(newPassword, true)
   291  
   292  	// Double-check that we can open a fresh connection with the stored
   293  	// conf ... and that the password hasn't been changed again.
   294  	assertOpen()
   295  	assertPassword(newPassword, true)
   296  }
   297  
   298  func (s *UnitSuite) TestOpenAPIStateWithBadCredsTerminates(c *gc.C) {
   299  	conf, _ := s.PrimeAgent(c, names.NewUnitTag("missing/0"), "no-password", version.Current)
   300  
   301  	_, _, err := apicaller.OpenAPIState(fakeConfAgent{conf: conf})
   302  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   303  }
   304  
   305  func (s *UnitSuite) TestOpenAPIStateWithDeadEntityTerminates(c *gc.C) {
   306  	_, unit, conf, _ := s.primeAgent(c)
   307  	err := unit.EnsureDead()
   308  	c.Assert(err, jc.ErrorIsNil)
   309  
   310  	_, _, err = apicaller.OpenAPIState(fakeConfAgent{conf: conf})
   311  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   312  }
   313  
   314  type fakeConfAgent struct {
   315  	agent.Agent
   316  	conf agent.Config
   317  }
   318  
   319  func (f fakeConfAgent) CurrentConfig() agent.Config {
   320  	return f.conf
   321  }
   322  
   323  func (s *UnitSuite) TestOpenStateFails(c *gc.C) {
   324  	// Start a unit agent and make sure it doesn't set a mongo password
   325  	// we can use to connect to state with.
   326  	_, unit, conf, _ := s.primeAgent(c)
   327  	a := s.newAgent(c, unit)
   328  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   329  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   330  	waitForUnitActive(s.State, unit, c)
   331  
   332  	s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir())
   333  }
   334  
   335  func (s *UnitSuite) TestRsyslogConfigWorker(c *gc.C) {
   336  	created := make(chan rsyslog.RsyslogMode, 1)
   337  	s.PatchValue(&rsyslog.NewRsyslogConfigWorker, func(_ *apirsyslog.State, mode rsyslog.RsyslogMode, _ names.Tag, _ string, _ []string, _ string) (worker.Worker, error) {
   338  		created <- mode
   339  		return newDummyWorker(), nil
   340  	})
   341  
   342  	_, unit, _, _ := s.primeAgent(c)
   343  	a := s.newAgent(c, unit)
   344  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   345  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   346  
   347  	select {
   348  	case <-time.After(coretesting.LongWait):
   349  		c.Fatalf("timeout while waiting for rsyslog worker to be created")
   350  	case mode := <-created:
   351  		c.Assert(mode, gc.Equals, rsyslog.RsyslogModeForwarding)
   352  	}
   353  }
   354  
   355  func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) {
   356  	_, unit, _, _ := s.primeAgent(c)
   357  	vers := version.Current
   358  	vers.Minor = version.Current.Minor + 1
   359  	err := unit.SetAgentVersion(vers)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  
   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  	timeout := time.After(coretesting.LongWait)
   367  	for done := false; !done; {
   368  		select {
   369  		case <-timeout:
   370  			c.Fatalf("timeout while waiting for agent version to be set")
   371  		case <-time.After(coretesting.ShortWait):
   372  			err := unit.Refresh()
   373  			c.Assert(err, jc.ErrorIsNil)
   374  			agentTools, err := unit.AgentTools()
   375  			c.Assert(err, jc.ErrorIsNil)
   376  			if agentTools.Version.Minor != version.Current.Minor {
   377  				continue
   378  			}
   379  			c.Assert(agentTools.Version, gc.DeepEquals, version.Current)
   380  			done = true
   381  		}
   382  	}
   383  }
   384  
   385  func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) {
   386  	_, unit, _, _ := s.primeAgent(c)
   387  	a := s.newAgent(c, unit)
   388  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   389  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   390  
   391  	// Update the API addresses.
   392  	updatedServers := [][]network.HostPort{
   393  		network.NewHostPorts(1234, "localhost"),
   394  	}
   395  	err := s.BackingState.SetAPIHostPorts(updatedServers)
   396  	c.Assert(err, jc.ErrorIsNil)
   397  
   398  	// Wait for config to be updated.
   399  	s.BackingState.StartSync()
   400  	for attempt := coretesting.LongAttempt.Start(); attempt.Next(); {
   401  		addrs, err := a.CurrentConfig().APIAddresses()
   402  		c.Assert(err, jc.ErrorIsNil)
   403  		if reflect.DeepEqual(addrs, []string{"localhost:1234"}) {
   404  			return
   405  		}
   406  	}
   407  	c.Fatalf("timeout while waiting for agent config to change")
   408  }
   409  
   410  func (s *UnitSuite) TestUseLumberjack(c *gc.C) {
   411  	ctx, err := cmd.DefaultContext()
   412  	c.Assert(err, gc.IsNil)
   413  
   414  	a := UnitAgent{
   415  		AgentConf: FakeAgentConfig{},
   416  		ctx:       ctx,
   417  		UnitName:  "mysql/25",
   418  	}
   419  
   420  	err = a.Init(nil)
   421  	c.Assert(err, gc.IsNil)
   422  
   423  	l, ok := ctx.Stderr.(*lumberjack.Logger)
   424  	c.Assert(ok, jc.IsTrue)
   425  	c.Check(l.MaxAge, gc.Equals, 0)
   426  	c.Check(l.MaxBackups, gc.Equals, 2)
   427  	c.Check(l.Filename, gc.Equals, filepath.FromSlash("/var/log/juju/machine-42.log"))
   428  	c.Check(l.MaxSize, gc.Equals, 300)
   429  }
   430  
   431  func (s *UnitSuite) TestDontUseLumberjack(c *gc.C) {
   432  	ctx, err := cmd.DefaultContext()
   433  	c.Assert(err, gc.IsNil)
   434  
   435  	a := UnitAgent{
   436  		AgentConf: FakeAgentConfig{},
   437  		ctx:       ctx,
   438  		UnitName:  "mysql/25",
   439  
   440  		// this is what would get set by the CLI flags to tell us not to log to
   441  		// the file.
   442  		logToStdErr: true,
   443  	}
   444  
   445  	err = a.Init(nil)
   446  	c.Assert(err, gc.IsNil)
   447  
   448  	_, ok := ctx.Stderr.(*lumberjack.Logger)
   449  	c.Assert(ok, jc.IsFalse)
   450  }