github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"time"
    14  
    15  	"github.com/juju/cmd"
    16  	"github.com/juju/errors"
    17  	"github.com/juju/names"
    18  	jc "github.com/juju/testing/checkers"
    19  	gc "gopkg.in/check.v1"
    20  	"gopkg.in/natefinch/lumberjack.v2"
    21  
    22  	"github.com/juju/juju/agent"
    23  	agenttools "github.com/juju/juju/agent/tools"
    24  	apirsyslog "github.com/juju/juju/api/rsyslog"
    25  	agenttesting "github.com/juju/juju/cmd/jujud/agent/testing"
    26  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    27  	envtesting "github.com/juju/juju/environs/testing"
    28  	jujutesting "github.com/juju/juju/juju/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/tools"
    33  	"github.com/juju/juju/version"
    34  	"github.com/juju/juju/worker"
    35  	"github.com/juju/juju/worker/rsyslog"
    36  	"github.com/juju/juju/worker/upgrader"
    37  )
    38  
    39  type UnitSuite struct {
    40  	coretesting.GitSuite
    41  	agenttesting.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  const initialUnitPassword = "unit-password-1234567890"
    67  
    68  // primeAgent creates a unit, and sets up the unit agent's directory.
    69  // It returns the assigned machine, new unit and the agent's configuration.
    70  func (s *UnitSuite) primeAgent(c *gc.C) (*state.Machine, *state.Unit, agent.Config, *tools.Tools) {
    71  	jujutesting.AddStateServerMachine(c, s.State)
    72  	svc := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    73  	unit, err := svc.AddUnit()
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	err = unit.SetPassword(initialUnitPassword)
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	// Assign the unit to a machine.
    78  	err = unit.AssignToNewMachine()
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	id, err := unit.AssignedMachineId()
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	machine, err := s.State.Machine(id)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	inst, md := jujutesting.AssertStartInstance(c, s.Environ, id)
    85  	err = machine.SetProvisioned(inst.Id(), agent.BootstrapNonce, md)
    86  	c.Assert(err, jc.ErrorIsNil)
    87  	conf, tools := s.PrimeAgent(c, unit.Tag(), initialUnitPassword, version.Current)
    88  	return machine, unit, conf, tools
    89  }
    90  
    91  func (s *UnitSuite) newAgent(c *gc.C, unit *state.Unit) *UnitAgent {
    92  	a := NewUnitAgent(nil)
    93  	s.InitAgent(c, a, "--unit-name", unit.Name(), "--log-to-stderr=true")
    94  	err := a.ReadConfig(unit.Tag().String())
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	return a
    97  }
    98  
    99  func (s *UnitSuite) TestParseSuccess(c *gc.C) {
   100  	a := NewUnitAgent(nil)
   101  	err := coretesting.InitCommand(a, []string{
   102  		"--data-dir", "jd",
   103  		"--unit-name", "w0rd-pre55/1",
   104  		"--log-to-stderr",
   105  	})
   106  
   107  	c.Assert(err, gc.IsNil)
   108  	c.Check(a.AgentConf.DataDir(), gc.Equals, "jd")
   109  	c.Check(a.UnitName, gc.Equals, "w0rd-pre55/1")
   110  }
   111  
   112  func (s *UnitSuite) TestParseMissing(c *gc.C) {
   113  	uc := NewUnitAgent(nil)
   114  	err := coretesting.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  		err := coretesting.InitCommand(NewUnitAgent(nil), append(args, "--data-dir", "jc"))
   130  		c.Check(err, gc.ErrorMatches, `--unit-name option expects "<service>/<n>" argument`)
   131  	}
   132  }
   133  
   134  func (s *UnitSuite) TestParseUnknown(c *gc.C) {
   135  	err := coretesting.InitCommand(NewUnitAgent(nil), []string{
   136  		"--unit-name", "wordpress/1",
   137  		"thundering typhoons",
   138  	})
   139  	c.Check(err, gc.ErrorMatches, `unrecognized args: \["thundering typhoons"\]`)
   140  }
   141  
   142  func waitForUnitActive(stateConn *state.State, unit *state.Unit, c *gc.C) {
   143  	timeout := time.After(5 * time.Second)
   144  
   145  	for {
   146  		select {
   147  		case <-timeout:
   148  			c.Fatalf("no activity detected")
   149  		case <-time.After(coretesting.ShortWait):
   150  			err := unit.Refresh()
   151  			c.Assert(err, jc.ErrorIsNil)
   152  			statusInfo, err := unit.Status()
   153  			c.Assert(err, jc.ErrorIsNil)
   154  			switch statusInfo.Status {
   155  			case state.StatusMaintenance, state.StatusWaiting, state.StatusBlocked:
   156  				c.Logf("waiting...")
   157  				continue
   158  			case state.StatusActive:
   159  				c.Logf("active!")
   160  				return
   161  			case state.StatusUnknown:
   162  				// Active units may have a status of unknown if they have
   163  				// started but not run status-set.
   164  				c.Logf("unknown but active!")
   165  				return
   166  			default:
   167  				c.Fatalf("unexpected status %s %s %v", statusInfo.Status, statusInfo.Message, statusInfo.Data)
   168  			}
   169  			statusInfo, err = unit.AgentStatus()
   170  			c.Assert(err, jc.ErrorIsNil)
   171  			switch statusInfo.Status {
   172  			case state.StatusAllocating, state.StatusExecuting, state.StatusRebooting, state.StatusIdle:
   173  				c.Logf("waiting...")
   174  				continue
   175  			case state.StatusError:
   176  				stateConn.StartSync()
   177  				c.Logf("unit is still down")
   178  			default:
   179  				c.Fatalf("unexpected status %s %s %v", statusInfo.Status, statusInfo.Message, statusInfo.Data)
   180  			}
   181  		}
   182  	}
   183  }
   184  
   185  func (s *UnitSuite) TestRunStop(c *gc.C) {
   186  	_, unit, _, _ := s.primeAgent(c)
   187  	a := s.newAgent(c, unit)
   188  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   189  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   190  	waitForUnitActive(s.State, unit, c)
   191  }
   192  
   193  func (s *UnitSuite) TestUpgrade(c *gc.C) {
   194  	machine, unit, _, currentTools := s.primeAgent(c)
   195  	agent := s.newAgent(c, unit)
   196  	newVers := version.Current
   197  	newVers.Patch++
   198  	envtesting.AssertUploadFakeToolsVersions(
   199  		c, s.DefaultToolsStorage, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), newVers)
   200  
   201  	// The machine agent downloads the tools; fake this by
   202  	// creating downloaded-tools.txt in data-dir/tools/<version>.
   203  	toolsDir := agenttools.SharedToolsDir(s.DataDir(), newVers)
   204  	err := os.MkdirAll(toolsDir, 0755)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	toolsPath := filepath.Join(toolsDir, "downloaded-tools.txt")
   207  	testTools := tools.Tools{Version: newVers, URL: "http://testing.invalid/tools"}
   208  	data, err := json.Marshal(testTools)
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	err = ioutil.WriteFile(toolsPath, data, 0644)
   211  	c.Assert(err, jc.ErrorIsNil)
   212  
   213  	// Set the machine agent version to trigger an upgrade.
   214  	err = machine.SetAgentVersion(newVers)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	err = runWithTimeout(agent)
   217  	envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{
   218  		AgentName: unit.Tag().String(),
   219  		OldTools:  currentTools.Version,
   220  		NewTools:  newVers,
   221  		DataDir:   s.DataDir(),
   222  	})
   223  }
   224  
   225  func (s *UnitSuite) TestUpgradeFailsWithoutTools(c *gc.C) {
   226  	machine, unit, _, _ := s.primeAgent(c)
   227  	agent := s.newAgent(c, unit)
   228  	newVers := version.Current
   229  	newVers.Patch++
   230  	err := machine.SetAgentVersion(newVers)
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	err = runWithTimeout(agent)
   233  	c.Assert(err, gc.ErrorMatches, "timed out waiting for agent to finish.*")
   234  }
   235  
   236  func (s *UnitSuite) TestWithDeadUnit(c *gc.C) {
   237  	_, unit, _, _ := s.primeAgent(c)
   238  	err := unit.EnsureDead()
   239  	c.Assert(err, jc.ErrorIsNil)
   240  	a := s.newAgent(c, unit)
   241  	err = runWithTimeout(a)
   242  	c.Assert(err, jc.ErrorIsNil)
   243  
   244  	// try again when the unit has been removed.
   245  	err = unit.Remove()
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	a = s.newAgent(c, unit)
   248  	err = runWithTimeout(a)
   249  	c.Assert(err, jc.ErrorIsNil)
   250  }
   251  
   252  func (s *UnitSuite) TestOpenAPIState(c *gc.C) {
   253  	_, unit, _, _ := s.primeAgent(c)
   254  	s.RunTestOpenAPIState(c, unit, s.newAgent(c, unit), initialUnitPassword)
   255  }
   256  
   257  func (s *UnitSuite) RunTestOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd Agent, initialPassword string) {
   258  	conf, err := agent.ReadConfig(agent.ConfigPath(s.DataDir(), ent.Tag()))
   259  	c.Assert(err, jc.ErrorIsNil)
   260  
   261  	conf.SetPassword("")
   262  	err = conf.Write()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  
   265  	// Check that it starts initially and changes the password
   266  	assertOpen := func(conf agent.Config) {
   267  		st, gotEnt, err := OpenAPIState(conf, agentCmd)
   268  		c.Assert(err, jc.ErrorIsNil)
   269  		c.Assert(st, gc.NotNil)
   270  		st.Close()
   271  		c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag().String())
   272  	}
   273  	assertOpen(conf)
   274  
   275  	// Check that the initial password is no longer valid.
   276  	err = ent.Refresh()
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	c.Assert(ent.PasswordValid(initialPassword), jc.IsFalse)
   279  
   280  	// Read the configuration and check that we can connect with it.
   281  	conf, err = agent.ReadConfig(agent.ConfigPath(conf.DataDir(), conf.Tag()))
   282  	//conf = refreshConfig(c, conf)
   283  	c.Assert(err, gc.IsNil)
   284  	// Check we can open the API with the new configuration.
   285  	assertOpen(conf)
   286  }
   287  
   288  func (s *UnitSuite) TestOpenAPIStateWithBadCredsTerminates(c *gc.C) {
   289  	conf, _ := s.PrimeAgent(c, names.NewUnitTag("missing/0"), "no-password", version.Current)
   290  	_, _, err := OpenAPIState(conf, nil)
   291  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   292  }
   293  
   294  type fakeUnitAgent struct {
   295  	unitName string
   296  }
   297  
   298  func (f *fakeUnitAgent) Tag() names.Tag {
   299  	return names.NewUnitTag(f.unitName)
   300  }
   301  
   302  func (f *fakeUnitAgent) ChangeConfig(agent.ConfigMutator) error {
   303  	panic("fakeUnitAgent.ChangeConfig called unexpectedly")
   304  }
   305  
   306  func (s *UnitSuite) TestOpenAPIStateWithDeadEntityTerminates(c *gc.C) {
   307  	_, unit, conf, _ := s.primeAgent(c)
   308  	err := unit.EnsureDead()
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	_, _, err = OpenAPIState(conf, &fakeUnitAgent{"wordpress/0"})
   311  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   312  }
   313  
   314  func (s *UnitSuite) TestOpenStateFails(c *gc.C) {
   315  	// Start a unit agent and make sure it doesn't set a mongo password
   316  	// we can use to connect to state with.
   317  	_, unit, conf, _ := 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  	waitForUnitActive(s.State, unit, c)
   322  
   323  	s.AssertCannotOpenState(c, conf.Tag(), conf.DataDir())
   324  }
   325  
   326  func (s *UnitSuite) TestRsyslogConfigWorker(c *gc.C) {
   327  	created := make(chan rsyslog.RsyslogMode, 1)
   328  	s.PatchValue(&cmdutil.NewRsyslogConfigWorker, func(_ *apirsyslog.State, _ agent.Config, mode rsyslog.RsyslogMode) (worker.Worker, error) {
   329  		created <- mode
   330  		return newDummyWorker(), nil
   331  	})
   332  
   333  	_, unit, _, _ := s.primeAgent(c)
   334  	a := s.newAgent(c, unit)
   335  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   336  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   337  
   338  	select {
   339  	case <-time.After(coretesting.LongWait):
   340  		c.Fatalf("timeout while waiting for rsyslog worker to be created")
   341  	case mode := <-created:
   342  		c.Assert(mode, gc.Equals, rsyslog.RsyslogModeForwarding)
   343  	}
   344  }
   345  
   346  func (s *UnitSuite) TestAgentSetsToolsVersion(c *gc.C) {
   347  	_, unit, _, _ := s.primeAgent(c)
   348  	vers := version.Current
   349  	vers.Minor = version.Current.Minor + 1
   350  	err := unit.SetAgentVersion(vers)
   351  	c.Assert(err, jc.ErrorIsNil)
   352  
   353  	a := s.newAgent(c, unit)
   354  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   355  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   356  
   357  	timeout := time.After(coretesting.LongWait)
   358  	for done := false; !done; {
   359  		select {
   360  		case <-timeout:
   361  			c.Fatalf("timeout while waiting for agent version to be set")
   362  		case <-time.After(coretesting.ShortWait):
   363  			err := unit.Refresh()
   364  			c.Assert(err, jc.ErrorIsNil)
   365  			agentTools, err := unit.AgentTools()
   366  			c.Assert(err, jc.ErrorIsNil)
   367  			if agentTools.Version.Minor != version.Current.Minor {
   368  				continue
   369  			}
   370  			c.Assert(agentTools.Version, gc.DeepEquals, version.Current)
   371  			done = true
   372  		}
   373  	}
   374  }
   375  
   376  func (s *UnitSuite) TestUnitAgentRunsAPIAddressUpdaterWorker(c *gc.C) {
   377  	_, unit, _, _ := s.primeAgent(c)
   378  	a := s.newAgent(c, unit)
   379  	go func() { c.Check(a.Run(nil), gc.IsNil) }()
   380  	defer func() { c.Check(a.Stop(), gc.IsNil) }()
   381  
   382  	// Update the API addresses.
   383  	updatedServers := [][]network.HostPort{
   384  		network.NewHostPorts(1234, "localhost"),
   385  	}
   386  	err := s.BackingState.SetAPIHostPorts(updatedServers)
   387  	c.Assert(err, jc.ErrorIsNil)
   388  
   389  	// Wait for config to be updated.
   390  	s.BackingState.StartSync()
   391  	for attempt := coretesting.LongAttempt.Start(); attempt.Next(); {
   392  		addrs, err := a.CurrentConfig().APIAddresses()
   393  		c.Assert(err, jc.ErrorIsNil)
   394  		if reflect.DeepEqual(addrs, []string{"localhost:1234"}) {
   395  			return
   396  		}
   397  	}
   398  	c.Fatalf("timeout while waiting for agent config to change")
   399  }
   400  
   401  func (s *UnitSuite) TestUnitAgentAPIWorkerErrorClosesAPI(c *gc.C) {
   402  	_, unit, _, _ := s.primeAgent(c)
   403  	a := s.newAgent(c, unit)
   404  	a.apiStateUpgrader = &unitAgentUpgrader{}
   405  
   406  	closedAPI := make(chan io.Closer, 1)
   407  	s.AgentSuite.PatchValue(&reportClosedUnitAPI, func(st io.Closer) {
   408  		select {
   409  		case closedAPI <- st:
   410  			close(closedAPI)
   411  		default:
   412  		}
   413  	})
   414  
   415  	worker, err := a.APIWorkers()
   416  
   417  	select {
   418  	case closed := <-closedAPI:
   419  		c.Assert(closed, gc.NotNil)
   420  	case <-time.After(coretesting.LongWait):
   421  		c.Fatalf("API not opened")
   422  	}
   423  
   424  	c.Assert(worker, gc.IsNil)
   425  	c.Assert(err, gc.ErrorMatches, "cannot set unit agent version: test failure")
   426  }
   427  
   428  type unitAgentUpgrader struct{}
   429  
   430  func (u *unitAgentUpgrader) SetVersion(s string, v version.Binary) error {
   431  	return errors.New("test failure")
   432  }
   433  
   434  func (s *UnitSuite) TestUseLumberjack(c *gc.C) {
   435  	ctx, err := cmd.DefaultContext()
   436  	c.Assert(err, gc.IsNil)
   437  
   438  	a := UnitAgent{
   439  		AgentConf: FakeAgentConfig{},
   440  		ctx:       ctx,
   441  		UnitName:  "mysql/25",
   442  	}
   443  
   444  	err = a.Init(nil)
   445  	c.Assert(err, gc.IsNil)
   446  
   447  	l, ok := ctx.Stderr.(*lumberjack.Logger)
   448  	c.Assert(ok, jc.IsTrue)
   449  	c.Check(l.MaxAge, gc.Equals, 0)
   450  	c.Check(l.MaxBackups, gc.Equals, 2)
   451  	c.Check(l.Filename, gc.Equals, filepath.FromSlash("/var/log/juju/machine-42.log"))
   452  	c.Check(l.MaxSize, gc.Equals, 300)
   453  }
   454  
   455  func (s *UnitSuite) TestDontUseLumberjack(c *gc.C) {
   456  	ctx, err := cmd.DefaultContext()
   457  	c.Assert(err, gc.IsNil)
   458  
   459  	a := UnitAgent{
   460  		AgentConf: FakeAgentConfig{},
   461  		ctx:       ctx,
   462  		UnitName:  "mysql/25",
   463  
   464  		// this is what would get set by the CLI flags to tell us not to log to
   465  		// the file.
   466  		logToStdErr: true,
   467  	}
   468  
   469  	err = a.Init(nil)
   470  	c.Assert(err, gc.IsNil)
   471  
   472  	_, ok := ctx.Stderr.(*lumberjack.Logger)
   473  	c.Assert(ok, jc.IsFalse)
   474  }