github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/cmd/jujud/agent_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  	stderrors "errors"
     8  	"fmt"
     9  	"net"
    10  	"strconv"
    11  	"time"
    12  
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "launchpad.net/gocheck"
    15  
    16  	"github.com/juju/juju/agent"
    17  	agenttools "github.com/juju/juju/agent/tools"
    18  	"github.com/juju/juju/cmd"
    19  	"github.com/juju/juju/environs"
    20  	envtesting "github.com/juju/juju/environs/testing"
    21  	envtools "github.com/juju/juju/environs/tools"
    22  	"github.com/juju/juju/instance"
    23  	"github.com/juju/juju/juju/testing"
    24  	"github.com/juju/juju/state"
    25  	"github.com/juju/juju/state/api"
    26  	"github.com/juju/juju/state/api/params"
    27  	coretesting "github.com/juju/juju/testing"
    28  	coretools "github.com/juju/juju/tools"
    29  	"github.com/juju/juju/version"
    30  	"github.com/juju/juju/worker"
    31  	"github.com/juju/juju/worker/upgrader"
    32  )
    33  
    34  var _ = gc.Suite(&toolSuite{})
    35  
    36  type toolSuite struct {
    37  	coretesting.BaseSuite
    38  }
    39  
    40  var errorImportanceTests = []error{
    41  	nil,
    42  	stderrors.New("foo"),
    43  	&upgrader.UpgradeReadyError{},
    44  	worker.ErrTerminateAgent,
    45  }
    46  
    47  func (*toolSuite) TestErrorImportance(c *gc.C) {
    48  	for i, err0 := range errorImportanceTests {
    49  		for j, err1 := range errorImportanceTests {
    50  			c.Assert(moreImportant(err0, err1), gc.Equals, i > j)
    51  		}
    52  	}
    53  }
    54  
    55  var isFatalTests = []struct {
    56  	err     error
    57  	isFatal bool
    58  }{{
    59  	err:     worker.ErrTerminateAgent,
    60  	isFatal: true,
    61  }, {
    62  	err:     &upgrader.UpgradeReadyError{},
    63  	isFatal: true,
    64  }, {
    65  	err: &params.Error{
    66  		Message: "blah",
    67  		Code:    params.CodeNotProvisioned,
    68  	},
    69  	isFatal: false,
    70  }, {
    71  	err:     &fatalError{"some fatal error"},
    72  	isFatal: true,
    73  }, {
    74  	err:     stderrors.New("foo"),
    75  	isFatal: false,
    76  }, {
    77  	err: &params.Error{
    78  		Message: "blah",
    79  		Code:    params.CodeNotFound,
    80  	},
    81  	isFatal: false,
    82  }}
    83  
    84  func (*toolSuite) TestIsFatal(c *gc.C) {
    85  	for i, test := range isFatalTests {
    86  		c.Logf("test %d: %s", i, test.err)
    87  		c.Assert(isFatal(test.err), gc.Equals, test.isFatal)
    88  	}
    89  }
    90  
    91  type apiOpenSuite struct {
    92  	coretesting.BaseSuite
    93  }
    94  
    95  type fakeAPIOpenConfig struct {
    96  	agent.Config
    97  }
    98  
    99  func (fakeAPIOpenConfig) APIInfo() *api.Info {
   100  	return &api.Info{}
   101  }
   102  
   103  func (fakeAPIOpenConfig) OldPassword() string {
   104  	return "old"
   105  }
   106  
   107  func (fakeAPIOpenConfig) Jobs() []params.MachineJob {
   108  	return []params.MachineJob{}
   109  }
   110  
   111  var _ = gc.Suite(&apiOpenSuite{})
   112  
   113  func (s *apiOpenSuite) TestOpenAPIStateReplaceErrors(c *gc.C) {
   114  	type replaceErrors struct {
   115  		openErr    error
   116  		replaceErr error
   117  	}
   118  	var apiError error
   119  	s.PatchValue(&apiOpen, func(info *api.Info, opts api.DialOpts) (*api.State, error) {
   120  		return nil, apiError
   121  	})
   122  	errReplacePairs := []replaceErrors{{
   123  		fmt.Errorf("blah"), nil,
   124  	}, {
   125  		openErr:    &params.Error{Code: params.CodeNotProvisioned},
   126  		replaceErr: worker.ErrTerminateAgent,
   127  	}, {
   128  		openErr:    &params.Error{Code: params.CodeUnauthorized},
   129  		replaceErr: worker.ErrTerminateAgent,
   130  	}}
   131  	for i, test := range errReplacePairs {
   132  		c.Logf("test %d", i)
   133  		apiError = test.openErr
   134  		_, _, err := openAPIState(fakeAPIOpenConfig{}, nil)
   135  		if test.replaceErr == nil {
   136  			c.Check(err, gc.Equals, test.openErr)
   137  		} else {
   138  			c.Check(err, gc.Equals, test.replaceErr)
   139  		}
   140  	}
   141  }
   142  
   143  type testPinger func() error
   144  
   145  func (f testPinger) Ping() error {
   146  	return f()
   147  }
   148  
   149  func (s *toolSuite) TestConnectionIsFatal(c *gc.C) {
   150  	var (
   151  		errPinger testPinger = func() error {
   152  			return stderrors.New("ping error")
   153  		}
   154  		okPinger testPinger = func() error {
   155  			return nil
   156  		}
   157  	)
   158  	for i, pinger := range []testPinger{errPinger, okPinger} {
   159  		for j, test := range isFatalTests {
   160  			c.Logf("test %d.%d: %s", i, j, test.err)
   161  			fatal := connectionIsFatal(pinger)(test.err)
   162  			if test.isFatal {
   163  				c.Check(fatal, jc.IsTrue)
   164  			} else {
   165  				c.Check(fatal, gc.Equals, i == 0)
   166  			}
   167  		}
   168  	}
   169  }
   170  
   171  func mkTools(s string) *coretools.Tools {
   172  	return &coretools.Tools{
   173  		Version: version.MustParseBinary(s + "-foo-bar"),
   174  	}
   175  }
   176  
   177  type acCreator func() (cmd.Command, *AgentConf)
   178  
   179  // CheckAgentCommand is a utility function for verifying that common agent
   180  // options are handled by a Command; it returns an instance of that
   181  // command pre-parsed, with any mandatory flags added.
   182  func CheckAgentCommand(c *gc.C, create acCreator, args []string) cmd.Command {
   183  	com, conf := create()
   184  	err := coretesting.InitCommand(com, args)
   185  	c.Assert(conf.dataDir, gc.Equals, "/var/lib/juju")
   186  	badArgs := append(args, "--data-dir", "")
   187  	com, conf = create()
   188  	err = coretesting.InitCommand(com, badArgs)
   189  	c.Assert(err, gc.ErrorMatches, "--data-dir option must be set")
   190  
   191  	args = append(args, "--data-dir", "jd")
   192  	com, conf = create()
   193  	c.Assert(coretesting.InitCommand(com, args), gc.IsNil)
   194  	c.Assert(conf.dataDir, gc.Equals, "jd")
   195  	return com
   196  }
   197  
   198  // ParseAgentCommand is a utility function that inserts the always-required args
   199  // before parsing an agent command and returning the result.
   200  func ParseAgentCommand(ac cmd.Command, args []string) error {
   201  	common := []string{
   202  		"--data-dir", "jd",
   203  	}
   204  	return coretesting.InitCommand(ac, append(common, args...))
   205  }
   206  
   207  type runner interface {
   208  	Run(*cmd.Context) error
   209  	Stop() error
   210  }
   211  
   212  // runWithTimeout runs an agent and waits
   213  // for it to complete within a reasonable time.
   214  func runWithTimeout(r runner) error {
   215  	done := make(chan error)
   216  	go func() {
   217  		done <- r.Run(nil)
   218  	}()
   219  	select {
   220  	case err := <-done:
   221  		return err
   222  	case <-time.After(coretesting.LongWait):
   223  	}
   224  	err := r.Stop()
   225  	return fmt.Errorf("timed out waiting for agent to finish; stop error: %v", err)
   226  }
   227  
   228  // agentSuite is a fixture to be used by agent test suites.
   229  type agentSuite struct {
   230  	oldRestartDelay time.Duration
   231  	testing.JujuConnSuite
   232  }
   233  
   234  func (s *agentSuite) SetUpSuite(c *gc.C) {
   235  	s.JujuConnSuite.SetUpSuite(c)
   236  
   237  	s.oldRestartDelay = worker.RestartDelay
   238  	// We could use testing.ShortWait, but this thrashes quite
   239  	// a bit when some tests are restarting every 50ms for 10 seconds,
   240  	// so use a slightly more friendly delay.
   241  	worker.RestartDelay = 250 * time.Millisecond
   242  	s.PatchValue(&ensureMongoServer, func(string, string, params.StateServingInfo, bool) error {
   243  		return nil
   244  	})
   245  }
   246  
   247  func (s *agentSuite) TearDownSuite(c *gc.C) {
   248  	s.JujuConnSuite.TearDownSuite(c)
   249  	worker.RestartDelay = s.oldRestartDelay
   250  }
   251  
   252  // primeAgent writes the configuration file and tools with version vers
   253  // for an agent with the given entity name.  It returns the agent's
   254  // configuration and the current tools.
   255  func (s *agentSuite) primeAgent(c *gc.C, tag, password string, vers version.Binary) (agent.ConfigSetterWriter, *coretools.Tools) {
   256  	stor := s.Conn.Environ.Storage()
   257  	agentTools := envtesting.PrimeTools(c, stor, s.DataDir(), vers)
   258  	err := envtools.MergeAndWriteMetadata(stor, coretools.List{agentTools}, envtools.DoNotWriteMirrors)
   259  	c.Assert(err, gc.IsNil)
   260  	tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, vers)
   261  	c.Assert(err, gc.IsNil)
   262  	c.Assert(tools1, gc.DeepEquals, agentTools)
   263  
   264  	stateInfo := s.StateInfo(c)
   265  	apiInfo := s.APIInfo(c)
   266  	conf, err := agent.NewAgentConfig(
   267  		agent.AgentConfigParams{
   268  			DataDir:           s.DataDir(),
   269  			Tag:               tag,
   270  			UpgradedToVersion: vers.Number,
   271  			Password:          password,
   272  			Nonce:             state.BootstrapNonce,
   273  			StateAddresses:    stateInfo.Addrs,
   274  			APIAddresses:      apiInfo.Addrs,
   275  			CACert:            stateInfo.CACert,
   276  		})
   277  	conf.SetPassword(password)
   278  	c.Assert(conf.Write(), gc.IsNil)
   279  	s.primeAPIHostPorts(c)
   280  	return conf, agentTools
   281  }
   282  
   283  func (s *agentSuite) primeAPIHostPorts(c *gc.C) {
   284  	apiInfo := s.APIInfo(c)
   285  
   286  	c.Assert(apiInfo.Addrs, gc.HasLen, 1)
   287  	hostPort, err := parseHostPort(apiInfo.Addrs[0])
   288  	c.Assert(err, gc.IsNil)
   289  
   290  	err = s.State.SetAPIHostPorts([][]instance.HostPort{{hostPort}})
   291  	c.Assert(err, gc.IsNil)
   292  }
   293  
   294  func parseHostPort(s string) (instance.HostPort, error) {
   295  	addr, port, err := net.SplitHostPort(s)
   296  	if err != nil {
   297  		return instance.HostPort{}, err
   298  	}
   299  	portNum, err := strconv.Atoi(port)
   300  	if err != nil {
   301  		return instance.HostPort{}, fmt.Errorf("bad port number %q", port)
   302  	}
   303  	addrs := instance.NewAddresses(addr)
   304  	hostPorts := instance.AddressesWithPort(addrs, portNum)
   305  	return hostPorts[0], nil
   306  }
   307  
   308  // writeStateAgentConfig creates and writes a state agent config.
   309  func writeStateAgentConfig(c *gc.C, stateInfo *state.Info, dataDir, tag, password string, vers version.Binary) agent.ConfigSetterWriter {
   310  	port := coretesting.FindTCPPort()
   311  	apiAddr := []string{fmt.Sprintf("localhost:%d", port)}
   312  	conf, err := agent.NewStateMachineConfig(
   313  		agent.AgentConfigParams{
   314  			DataDir:           dataDir,
   315  			Tag:               tag,
   316  			UpgradedToVersion: vers.Number,
   317  			Password:          password,
   318  			Nonce:             state.BootstrapNonce,
   319  			StateAddresses:    stateInfo.Addrs,
   320  			APIAddresses:      apiAddr,
   321  			CACert:            stateInfo.CACert,
   322  		},
   323  		params.StateServingInfo{
   324  			Cert:       coretesting.ServerCert,
   325  			PrivateKey: coretesting.ServerKey,
   326  			StatePort:  coretesting.MgoServer.Port(),
   327  			APIPort:    port,
   328  		})
   329  	c.Assert(err, gc.IsNil)
   330  	conf.SetPassword(password)
   331  	c.Assert(conf.Write(), gc.IsNil)
   332  	return conf
   333  }
   334  
   335  // primeStateAgent writes the configuration file and tools with version vers
   336  // for an agent with the given entity name.  It returns the agent's configuration
   337  // and the current tools.
   338  func (s *agentSuite) primeStateAgent(
   339  	c *gc.C, tag, password string, vers version.Binary) (agent.ConfigSetterWriter, *coretools.Tools) {
   340  
   341  	agentTools := envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), vers)
   342  	tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, vers)
   343  	c.Assert(err, gc.IsNil)
   344  	c.Assert(tools1, gc.DeepEquals, agentTools)
   345  
   346  	stateInfo := s.StateInfo(c)
   347  	conf := writeStateAgentConfig(c, stateInfo, s.DataDir(), tag, password, vers)
   348  	s.primeAPIHostPorts(c)
   349  	return conf, agentTools
   350  }
   351  
   352  // initAgent initialises the given agent command with additional
   353  // arguments as provided.
   354  func (s *agentSuite) initAgent(c *gc.C, a cmd.Command, args ...string) {
   355  	args = append([]string{"--data-dir", s.DataDir()}, args...)
   356  	err := coretesting.InitCommand(a, args)
   357  	c.Assert(err, gc.IsNil)
   358  }
   359  
   360  func (s *agentSuite) testOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd Agent, initialPassword string) {
   361  	conf, err := agent.ReadConfig(agent.ConfigPath(s.DataDir(), ent.Tag()))
   362  	c.Assert(err, gc.IsNil)
   363  
   364  	conf.SetPassword("")
   365  	err = conf.Write()
   366  	c.Assert(err, gc.IsNil)
   367  
   368  	// Check that it starts initially and changes the password
   369  	assertOpen := func(conf agent.Config) {
   370  		st, gotEnt, err := openAPIState(conf, agentCmd)
   371  		c.Assert(err, gc.IsNil)
   372  		c.Assert(st, gc.NotNil)
   373  		st.Close()
   374  		c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag())
   375  	}
   376  	assertOpen(conf)
   377  
   378  	// Check that the initial password is no longer valid.
   379  	err = ent.Refresh()
   380  	c.Assert(err, gc.IsNil)
   381  	c.Assert(ent.PasswordValid(initialPassword), gc.Equals, false)
   382  
   383  	// Read the configuration and check that we can connect with it.
   384  	conf = refreshConfig(c, conf)
   385  	// Check we can open the API with the new configuration.
   386  	assertOpen(conf)
   387  }
   388  
   389  type errorAPIOpener struct {
   390  	err error
   391  }
   392  
   393  func (e *errorAPIOpener) OpenAPI(_ api.DialOpts) (*api.State, string, error) {
   394  	return nil, "", e.err
   395  }
   396  
   397  func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) {
   398  	config, err := agent.ReadConfig(agent.ConfigPath(dataDir, tag))
   399  	c.Assert(err, gc.IsNil)
   400  	info, ok := config.StateInfo()
   401  	c.Assert(ok, jc.IsTrue)
   402  	st, err := state.Open(info, state.DialOpts{}, environs.NewStatePolicy())
   403  	c.Assert(err, gc.IsNil)
   404  	st.Close()
   405  }
   406  
   407  func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) {
   408  	config, err := agent.ReadConfig(agent.ConfigPath(dataDir, tag))
   409  	c.Assert(err, gc.IsNil)
   410  	_, ok := config.StateInfo()
   411  	c.Assert(ok, jc.IsFalse)
   412  }
   413  
   414  func refreshConfig(c *gc.C, config agent.Config) agent.ConfigSetterWriter {
   415  	config1, err := agent.ReadConfig(agent.ConfigPath(config.DataDir(), config.Tag()))
   416  	c.Assert(err, gc.IsNil)
   417  	return config1
   418  }