github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/jujud/unit_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     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  
    19  	"fmt"
    20  	"github.com/juju/juju/agent"
    21  	agenttools "github.com/juju/juju/agent/tools"
    22  	apirsyslog "github.com/juju/juju/api/rsyslog"
    23  	agentcmd "github.com/juju/juju/cmd/jujud/agent"
    24  	agenttesting "github.com/juju/juju/cmd/jujud/agent/testing"
    25  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    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  	coretesting "github.com/juju/juju/testing"
    31  	"github.com/juju/juju/tools"
    32  	"github.com/juju/juju/version"
    33  	"github.com/juju/juju/worker"
    34  	"github.com/juju/juju/worker/rsyslog"
    35  	"github.com/juju/juju/worker/upgrader"
    36  )
    37  
    38  type UnitSuite struct {
    39  	coretesting.GitSuite
    40  	agenttesting.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  const initialUnitPassword = "unit-password-1234567890"
    66  
    67  // primeAgent creates a unit, and sets up the unit agent's directory.
    68  // It returns the assigned machine, new unit and the agent's configuration.
    69  func (s *UnitSuite) primeAgent(c *gc.C) (*state.Machine, *state.Unit, agent.Config, *tools.Tools) {
    70  	jujutesting.AddStateServerMachine(c, s.State)
    71  	svc := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    72  	unit, err := svc.AddUnit()
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	err = unit.SetPassword(initialUnitPassword)
    75  	c.Assert(err, jc.ErrorIsNil)
    76  	// Assign the unit to a machine.
    77  	err = unit.AssignToNewMachine()
    78  	c.Assert(err, jc.ErrorIsNil)
    79  	id, err := unit.AssignedMachineId()
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	machine, err := s.State.Machine(id)
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	inst, md := jujutesting.AssertStartInstance(c, s.Environ, id)
    84  	err = machine.SetProvisioned(inst.Id(), agent.BootstrapNonce, md)
    85  	c.Assert(err, jc.ErrorIsNil)
    86  	conf, tools := s.PrimeAgent(c, unit.Tag(), initialUnitPassword, version.Current)
    87  	return machine, unit, conf, tools
    88  }
    89  
    90  func (s *UnitSuite) newAgent(c *gc.C, unit *state.Unit) *UnitAgent {
    91  	a := &UnitAgent{}
    92  	s.InitAgent(c, a, "--unit-name", unit.Name(), "--log-to-stderr=true")
    93  	err := a.ReadConfig(unit.Tag().String())
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	return a
    96  }
    97  
    98  func (s *UnitSuite) TestParseSuccess(c *gc.C) {
    99  	a := &UnitAgent{}
   100  	err := coretesting.InitCommand(a, []string{
   101  		"--data-dir", "jd",
   102  		"--unit-name", "w0rd-pre55/1",
   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 := &UnitAgent{}
   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(&UnitAgent{}, 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(&UnitAgent{}, []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  			st, info, data, err := unit.Status()
   151  			c.Assert(err, jc.ErrorIsNil)
   152  			switch st {
   153  			case state.StatusAllocating, state.StatusInstalling:
   154  				c.Logf("waiting...")
   155  				continue
   156  			case state.StatusActive:
   157  				c.Logf("active!")
   158  				return
   159  			case state.StatusFailed:
   160  				stateConn.StartSync()
   161  				c.Logf("unit is still down")
   162  			default:
   163  				c.Fatalf("unexpected status %s %s %v", st, info, data)
   164  			}
   165  		}
   166  	}
   167  }
   168  
   169  func (s *UnitSuite) TestRunStop(c *gc.C) {
   170  	_, unit, _, _ := s.primeAgent(c)
   171  	a := s.newAgent(c, unit)
   172  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   173  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   174  	waitForUnitActive(s.State, unit, c)
   175  }
   176  
   177  func (s *UnitSuite) TestUpgrade(c *gc.C) {
   178  	machine, unit, _, currentTools := s.primeAgent(c)
   179  	agent := s.newAgent(c, unit)
   180  	newVers := version.Current
   181  	newVers.Patch++
   182  	envtesting.AssertUploadFakeToolsVersions(
   183  		c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers)
   184  
   185  	// The machine agent downloads the tools; fake this by
   186  	// creating downloaded-tools.txt in data-dir/tools/<version>.
   187  	toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers)
   188  	err := os.MkdirAll(toolsDir, 0755)
   189  	c.Assert(err, jc.ErrorIsNil)
   190  	toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt")
   191  	testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"}
   192  	data, err := json.Marshal(testTools)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	err = ioutil.WriteFile(toolsPath, data, 0644)
   195  	c.Assert(err, jc.ErrorIsNil)
   196  
   197  	// Set the machine agent version to trigger an upgrade.
   198  	err = machine.SetAgentVersion(newVers)
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	err = runWithTimeout(agent)
   201  	envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{
   202  		AgentName: unit.Tag().String(),
   203  		OldTools:  currentTools.Version,
   204  		NewTools:  newVers,
   205  		DataDir:   s.DataDir(),
   206  	})
   207  }
   208  
   209  func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) {
   210  	machine, unit, _, _ := s.primeAgent(c)
   211  	agent := s.newAgent(c, unit)
   212  	newVers := version.Current
   213  	newVers.Patch++
   214  	err := machine.SetAgentVersion(newVers)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	err = runWithTimeout(agent)
   217  	c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*")
   218  }
   219  
   220  func (s *UnitSuite) TestWithDeadUnit(c *gc.C) {
   221  	_, unit, _, _ := s.primeAgent(c)
   222  	err := unit.EnsureDead()
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	a := s.newAgent(c, unit)
   225  	err = runWithTimeout(a)
   226  	c.Assert(err, jc.ErrorIsNil)
   227  
   228  	// try again when the unit has been removed.
   229  	err = unit.Remove()
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	a = s.newAgent(c, unit)
   232  	err = runWithTimeout(a)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  }
   235  
   236  func (s *UnitSuite) TestOpenAPIState(c *gc.C) {
   237  	_, unit, _, _ := s.primeAgent(c)
   238  	s.RunTestOpenAPIState(c, unit, s.newAgent(c, unit), initialUnitPassword)
   239  }
   240  
   241  func (s *UnitSuite) RunTestOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd agentcmd.Agent, initialPassword string) {
   242  	conf, err := agent.ReadConfig(agent.ConfigPath(s.DataDir(), ent.Tag()))
   243  	c.Assert(err, jc.ErrorIsNil)
   244  
   245  	conf.SetPassword("")
   246  	err = conf.Write()
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	// Check that it starts initially and changes the password
   250  	assertOpen := func(conf agent.Config) {
   251  		st, gotEnt, err := agentcmd.OpenAPIState(conf, agentCmd)
   252  		c.Assert(err, jc.ErrorIsNil)
   253  		c.Assert(st, gc.NotNil)
   254  		st.Close()
   255  		c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag().String())
   256  	}
   257  	assertOpen(conf)
   258  
   259  	// Check that the initial password is no longer valid.
   260  	err = ent.Refresh()
   261  	c.Assert(err, jc.ErrorIsNil)
   262  	c.Assert(ent.PasswordValid(initialPassword), jc.IsFalse)
   263  
   264  	// Read the configuration and check that we can connect with it.
   265  	conf, err = agent.ReadConfig(agent.ConfigPath(conf.DataDir(), conf.Tag()))
   266  	//conf = refreshConfig(c, conf)
   267  	c.Assert(err, gc.IsNil)
   268  	// Check we can open the API with the new configuration.
   269  	assertOpen(conf)
   270  }
   271  
   272  func (s *UnitSuite) TestOpenAPIStateWithBadCredsTerminates(c *gc.C) {
   273  	conf, _ := s.PrimeAgent(c, names.NewUnitTag("missing/0"), "no-password", version.Current)
   274  	_, _, err := agentcmd.OpenAPIState(conf, nil)
   275  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   276  }
   277  
   278  type fakeUnitAgent struct {
   279  	unitName string
   280  }
   281  
   282  func (f *fakeUnitAgent) Tag() names.Tag {
   283  	return names.NewUnitTag(f.unitName)
   284  }
   285  
   286  func (f *fakeUnitAgent) ChangeConfig(agentcmd.AgentConfigMutator) error {
   287  	panic("fakeUnitAgent.ChangeConfig called unexpectedly")
   288  }
   289  
   290  func (s *UnitSuite) TestOpenAPIStateWithDeadEntityTerminates(c *gc.C) {
   291  	_, unit, conf, _ := s.primeAgent(c)
   292  	err := unit.EnsureDead()
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	_, _, err = agentcmd.OpenAPIState(conf, &fakeUnitAgent{"wordpress/0"})
   295  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   296  }
   297  
   298  func (s *UnitSuite) TestOpenStateFails(c *gc.C) {
   299  	// Start a unit agent and make sure it doesn't set a mongo password
   300  	// we can use to connect to state with.
   301  	_, unit, conf, _ := s.primeAgent(c)
   302  	a := s.newAgent(c, unit)
   303  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   304  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   305  	waitForUnitActive(s.State, unit, c)
   306  
   307  	s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir())
   308  }
   309  
   310  func (s *UnitSuite) TestRsyslogConfigWorker(c *gc.C) {
   311  	created := make(chan rsyslog.RsyslogMode, 1)
   312  	s.PatchValue(&cmdutil.NewRsyslogConfigWorker, func(_ *apirsyslog.State, _ agent.Config, mode rsyslog.RsyslogMode) (worker.Worker, error) {
   313  		created <- mode
   314  		return newDummyWorker(), nil
   315  	})
   316  
   317  	_, unit, _, _ := s.primeAgent(c)
   318  	a := s.newAgent(c, unit)
   319  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   320  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   321  
   322  	select {
   323  	case <-time.After(coretesting.LongWait):
   324  		c.Fatalf("timeout while waiting for rsyslog worker to be created")
   325  	case mode := <-created:
   326  		c.Assert(mode, gc.Equals, rsyslog.RsyslogModeForwarding)
   327  	}
   328  }
   329  
   330  func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) {
   331  	_, unit, _, _ := s.primeAgent(c)
   332  	vers := version.Current
   333  	vers.Minor = version.Current.Minor + 1
   334  	err := unit.SetAgentVersion(vers)
   335  	c.Assert(err, jc.ErrorIsNil)
   336  
   337  	a := s.newAgent(c, unit)
   338  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   339  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   340  
   341  	timeout := time.After(coretesting.LongWait)
   342  	for done := false; !done; {
   343  		select {
   344  		case <-timeout:
   345  			c.Fatalf("timeout while waiting for agent version to be set")
   346  		case <-time.After(coretesting.ShortWait):
   347  			err := unit.Refresh()
   348  			c.Assert(err, jc.ErrorIsNil)
   349  			agentTools, err := unit.AgentTools()
   350  			c.Assert(err, jc.ErrorIsNil)
   351  			if agentTools.Version.Minor != version.Current.Minor {
   352  				continue
   353  			}
   354  			c.Assert(agentTools.Version, gc.DeepEquals, version.Current)
   355  			done = true
   356  		}
   357  	}
   358  }
   359  
   360  func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) {
   361  	_, unit, _, _ := s.primeAgent(c)
   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  	// Update the API addresses.
   367  	updatedServers := [][]network.HostPort{
   368  		network.NewHostPorts(1234, "localhost"),
   369  	}
   370  	err := s.BackingState.SetAPIHostPorts(updatedServers)
   371  	c.Assert(err, jc.ErrorIsNil)
   372  
   373  	// Wait for config to be updated.
   374  	s.BackingState.StartSync()
   375  	for attempt := coretesting.LongAttempt.Start(); attempt.Next(); {
   376  		addrs, err := a.CurrentConfig().APIAddresses()
   377  		c.Assert(err, jc.ErrorIsNil)
   378  		if reflect.DeepEqual(addrs, []string{"localhost:1234"}) {
   379  			return
   380  		}
   381  	}
   382  	c.Fatalf("timeout while waiting for agent config to change")
   383  }
   384  
   385  type runner interface {
   386  	Run(*cmd.Context) error
   387  	Stop() error
   388  }
   389  
   390  // runWithTimeout runs an agent and waits
   391  // for it to complete within a reasonable time.
   392  func runWithTimeout(r runner) error {
   393  	done := make(chan error)
   394  	go func() {
   395  		done <- r.Run(nil)
   396  	}()
   397  	select {
   398  	case err := <-done:
   399  		return err
   400  	case <-time.After(coretesting.LongWait):
   401  	}
   402  	err := r.Stop()
   403  	return fmt.Errorf("timed out waiting for agent to finish; stop error: %v", err)
   404  }
   405  
   406  func newDummyWorker() worker.Worker {
   407  	return worker.NewSimpleWorker(func(stop <-chan struct{}) error {
   408  		<-stop
   409  		return nil
   410  	})
   411  }