launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"time"
    10  
    11  	gc "launchpad.net/gocheck"
    12  
    13  	"launchpad.net/juju-core/agent"
    14  	agenttools "launchpad.net/juju-core/agent/tools"
    15  	"launchpad.net/juju-core/cmd"
    16  	envtesting "launchpad.net/juju-core/environs/testing"
    17  	envtools "launchpad.net/juju-core/environs/tools"
    18  	"launchpad.net/juju-core/juju/testing"
    19  	"launchpad.net/juju-core/state"
    20  	"launchpad.net/juju-core/state/api"
    21  	"launchpad.net/juju-core/state/api/params"
    22  	coretesting "launchpad.net/juju-core/testing"
    23  	jc "launchpad.net/juju-core/testing/checkers"
    24  	"launchpad.net/juju-core/testing/testbase"
    25  	coretools "launchpad.net/juju-core/tools"
    26  	"launchpad.net/juju-core/version"
    27  	"launchpad.net/juju-core/worker"
    28  	"launchpad.net/juju-core/worker/upgrader"
    29  )
    30  
    31  var _ = gc.Suite(&toolSuite{})
    32  
    33  type toolSuite struct {
    34  	testbase.LoggingSuite
    35  }
    36  
    37  var errorImportanceTests = []error{
    38  	nil,
    39  	stderrors.New("foo"),
    40  	&upgrader.UpgradeReadyError{},
    41  	worker.ErrTerminateAgent,
    42  }
    43  
    44  func (*toolSuite) TestErrorImportance(c *gc.C) {
    45  	for i, err0 := range errorImportanceTests {
    46  		for j, err1 := range errorImportanceTests {
    47  			c.Assert(moreImportant(err0, err1), gc.Equals, i > j)
    48  		}
    49  	}
    50  }
    51  
    52  var isFatalTests = []struct {
    53  	err     error
    54  	isFatal bool
    55  }{{
    56  	err:     worker.ErrTerminateAgent,
    57  	isFatal: true,
    58  }, {
    59  	err:     &upgrader.UpgradeReadyError{},
    60  	isFatal: true,
    61  }, {
    62  	err: &params.Error{
    63  		Message: "blah",
    64  		Code:    params.CodeNotProvisioned,
    65  	},
    66  	isFatal: false,
    67  }, {
    68  	err:     &fatalError{"some fatal error"},
    69  	isFatal: true,
    70  }, {
    71  	err:     stderrors.New("foo"),
    72  	isFatal: false,
    73  }, {
    74  	err: &params.Error{
    75  		Message: "blah",
    76  		Code:    params.CodeNotFound,
    77  	},
    78  	isFatal: false,
    79  }}
    80  
    81  func (*toolSuite) TestIsFatal(c *gc.C) {
    82  	for i, test := range isFatalTests {
    83  		c.Logf("test %d: %s", i, test.err)
    84  		c.Assert(isFatal(test.err), gc.Equals, test.isFatal)
    85  	}
    86  }
    87  
    88  type testPinger func() error
    89  
    90  func (f testPinger) Ping() error {
    91  	return f()
    92  }
    93  
    94  func (s *toolSuite) TestConnectionIsFatal(c *gc.C) {
    95  	var (
    96  		errPinger testPinger = func() error {
    97  			return stderrors.New("ping error")
    98  		}
    99  		okPinger testPinger = func() error {
   100  			return nil
   101  		}
   102  	)
   103  	for i, pinger := range []testPinger{errPinger, okPinger} {
   104  		for j, test := range isFatalTests {
   105  			c.Logf("test %d.%d: %s", i, j, test.err)
   106  			fatal := connectionIsFatal(pinger)(test.err)
   107  			if test.isFatal {
   108  				c.Check(fatal, jc.IsTrue)
   109  			} else {
   110  				c.Check(fatal, gc.Equals, i == 0)
   111  			}
   112  		}
   113  	}
   114  }
   115  
   116  func mkTools(s string) *coretools.Tools {
   117  	return &coretools.Tools{
   118  		Version: version.MustParseBinary(s + "-foo-bar"),
   119  	}
   120  }
   121  
   122  type acCreator func() (cmd.Command, *AgentConf)
   123  
   124  // CheckAgentCommand is a utility function for verifying that common agent
   125  // options are handled by a Command; it returns an instance of that
   126  // command pre-parsed, with any mandatory flags added.
   127  func CheckAgentCommand(c *gc.C, create acCreator, args []string) cmd.Command {
   128  	com, conf := create()
   129  	err := coretesting.InitCommand(com, args)
   130  	c.Assert(conf.dataDir, gc.Equals, "/var/lib/juju")
   131  	badArgs := append(args, "--data-dir", "")
   132  	com, conf = create()
   133  	err = coretesting.InitCommand(com, badArgs)
   134  	c.Assert(err, gc.ErrorMatches, "--data-dir option must be set")
   135  
   136  	args = append(args, "--data-dir", "jd")
   137  	com, conf = create()
   138  	c.Assert(coretesting.InitCommand(com, args), gc.IsNil)
   139  	c.Assert(conf.dataDir, gc.Equals, "jd")
   140  	return com
   141  }
   142  
   143  // ParseAgentCommand is a utility function that inserts the always-required args
   144  // before parsing an agent command and returning the result.
   145  func ParseAgentCommand(ac cmd.Command, args []string) error {
   146  	common := []string{
   147  		"--data-dir", "jd",
   148  	}
   149  	return coretesting.InitCommand(ac, append(common, args...))
   150  }
   151  
   152  type runner interface {
   153  	Run(*cmd.Context) error
   154  	Stop() error
   155  }
   156  
   157  // runWithTimeout runs an agent and waits
   158  // for it to complete within a reasonable time.
   159  func runWithTimeout(r runner) error {
   160  	done := make(chan error)
   161  	go func() {
   162  		done <- r.Run(nil)
   163  	}()
   164  	select {
   165  	case err := <-done:
   166  		return err
   167  	case <-time.After(5 * time.Second):
   168  	}
   169  	err := r.Stop()
   170  	return fmt.Errorf("timed out waiting for agent to finish; stop error: %v", err)
   171  }
   172  
   173  // agentSuite is a fixture to be used by agent test suites.
   174  type agentSuite struct {
   175  	oldRestartDelay time.Duration
   176  	testing.JujuConnSuite
   177  }
   178  
   179  func (s *agentSuite) SetUpSuite(c *gc.C) {
   180  	s.oldRestartDelay = worker.RestartDelay
   181  	worker.RestartDelay = coretesting.ShortWait
   182  	s.JujuConnSuite.SetUpSuite(c)
   183  }
   184  
   185  func (s *agentSuite) TearDownSuite(c *gc.C) {
   186  	s.JujuConnSuite.TearDownSuite(c)
   187  	worker.RestartDelay = s.oldRestartDelay
   188  }
   189  
   190  // primeAgent writes the configuration file and tools for an agent with the
   191  // given entity name.  It returns the agent's configuration and the current
   192  // tools.
   193  func (s *agentSuite) primeAgent(c *gc.C, tag, password string) (agent.Config, *coretools.Tools) {
   194  	stor := s.Conn.Environ.Storage()
   195  	agentTools := envtesting.PrimeTools(c, stor, s.DataDir(), version.Current)
   196  	err := envtools.MergeAndWriteMetadata(stor, coretools.List{agentTools}, envtools.DoNotWriteMirrors)
   197  	c.Assert(err, gc.IsNil)
   198  	tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, version.Current)
   199  	c.Assert(err, gc.IsNil)
   200  	c.Assert(tools1, gc.DeepEquals, agentTools)
   201  
   202  	stateInfo := s.StateInfo(c)
   203  	apiInfo := s.APIInfo(c)
   204  	conf, err := agent.NewAgentConfig(
   205  		agent.AgentConfigParams{
   206  			DataDir:        s.DataDir(),
   207  			Tag:            tag,
   208  			Password:       password,
   209  			Nonce:          state.BootstrapNonce,
   210  			StateAddresses: stateInfo.Addrs,
   211  			APIAddresses:   apiInfo.Addrs,
   212  			CACert:         stateInfo.CACert,
   213  		})
   214  	c.Assert(conf.Write(), gc.IsNil)
   215  	return conf, agentTools
   216  }
   217  
   218  // makeStateAgentConfig creates and writes a state agent config.
   219  func writeStateAgentConfig(c *gc.C, stateInfo *state.Info, dataDir, tag, password string) agent.Config {
   220  	port := coretesting.FindTCPPort()
   221  	apiAddr := []string{fmt.Sprintf("localhost:%d", port)}
   222  	conf, err := agent.NewStateMachineConfig(
   223  		agent.StateMachineConfigParams{
   224  			AgentConfigParams: agent.AgentConfigParams{
   225  				DataDir:        dataDir,
   226  				Tag:            tag,
   227  				Password:       password,
   228  				Nonce:          state.BootstrapNonce,
   229  				StateAddresses: stateInfo.Addrs,
   230  				APIAddresses:   apiAddr,
   231  				CACert:         stateInfo.CACert,
   232  			},
   233  			StateServerCert: []byte(coretesting.ServerCert),
   234  			StateServerKey:  []byte(coretesting.ServerKey),
   235  			StatePort:       coretesting.MgoServer.Port(),
   236  			APIPort:         port,
   237  		})
   238  	c.Assert(err, gc.IsNil)
   239  	c.Assert(conf.Write(), gc.IsNil)
   240  	return conf
   241  }
   242  
   243  // primeStateAgent writes the configuration file and tools for an agent with the
   244  // given entity name.  It returns the agent's configuration and the current
   245  // tools.
   246  func (s *agentSuite) primeStateAgent(c *gc.C, tag, password string) (agent.Config, *coretools.Tools) {
   247  	agentTools := envtesting.PrimeTools(c, s.Conn.Environ.Storage(), s.DataDir(), version.Current)
   248  	tools1, err := agenttools.ChangeAgentTools(s.DataDir(), tag, version.Current)
   249  	c.Assert(err, gc.IsNil)
   250  	c.Assert(tools1, gc.DeepEquals, agentTools)
   251  
   252  	stateInfo := s.StateInfo(c)
   253  	conf := writeStateAgentConfig(c, stateInfo, s.DataDir(), tag, password)
   254  	return conf, agentTools
   255  }
   256  
   257  // initAgent initialises the given agent command with additional
   258  // arguments as provided.
   259  func (s *agentSuite) initAgent(c *gc.C, a cmd.Command, args ...string) {
   260  	args = append([]string{"--data-dir", s.DataDir()}, args...)
   261  	err := coretesting.InitCommand(a, args)
   262  	c.Assert(err, gc.IsNil)
   263  }
   264  
   265  func (s *agentSuite) proposeVersion(c *gc.C, vers version.Number) {
   266  	oldcfg, err := s.State.EnvironConfig()
   267  	c.Assert(err, gc.IsNil)
   268  	cfg, err := oldcfg.Apply(map[string]interface{}{
   269  		"agent-version": vers.String(),
   270  	})
   271  	c.Assert(err, gc.IsNil)
   272  	err = s.State.SetEnvironConfig(cfg, oldcfg)
   273  	c.Assert(err, gc.IsNil)
   274  }
   275  
   276  func (s *agentSuite) testOpenAPIState(c *gc.C, ent state.AgentEntity, agentCmd Agent, initialPassword string) {
   277  	conf, err := agent.ReadConf(s.DataDir(), ent.Tag())
   278  	c.Assert(err, gc.IsNil)
   279  
   280  	// Check that it starts initially and changes the password
   281  	assertOpen := func(conf agent.Config) {
   282  		st, gotEnt, err := openAPIState(conf, agentCmd)
   283  		c.Assert(err, gc.IsNil)
   284  		c.Assert(st, gc.NotNil)
   285  		st.Close()
   286  		c.Assert(gotEnt.Tag(), gc.Equals, ent.Tag())
   287  	}
   288  	assertOpen(conf)
   289  
   290  	// Check that the initial password is no longer valid.
   291  	err = ent.Refresh()
   292  	c.Assert(err, gc.IsNil)
   293  	c.Assert(ent.PasswordValid(initialPassword), gc.Equals, false)
   294  
   295  	// Read the configuration and check that we can connect with it.
   296  	conf = refreshConfig(c, conf)
   297  	// Check we can open the API with the new configuration.
   298  	assertOpen(conf)
   299  }
   300  
   301  type errorAPIOpener struct {
   302  	err error
   303  }
   304  
   305  func (e *errorAPIOpener) OpenAPI(_ api.DialOpts) (*api.State, string, error) {
   306  	return nil, "", e.err
   307  }
   308  
   309  func (s *agentSuite) testOpenAPIStateReplaceErrors(c *gc.C) {
   310  	for i, test := range []struct {
   311  		openErr    error
   312  		replaceErr error
   313  	}{{
   314  		fmt.Errorf("blah"), nil,
   315  	}, {
   316  		&params.Error{Code: params.CodeNotProvisioned}, worker.ErrTerminateAgent,
   317  	}, {
   318  		&params.Error{Code: params.CodeUnauthorized}, worker.ErrTerminateAgent,
   319  	}} {
   320  		c.Logf("test %d", i)
   321  		opener := &errorAPIOpener{test.openErr}
   322  		_, _, err := openAPIState(opener, nil)
   323  		if test.replaceErr == nil {
   324  			c.Check(err, gc.Equals, test.openErr)
   325  		} else {
   326  			c.Check(err, gc.Equals, test.replaceErr)
   327  		}
   328  	}
   329  }
   330  
   331  func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) {
   332  	config, err := agent.ReadConf(dataDir, tag)
   333  	c.Assert(err, gc.IsNil)
   334  	st, err := config.OpenState()
   335  	c.Assert(err, gc.IsNil)
   336  	st.Close()
   337  }
   338  
   339  func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) {
   340  	config, err := agent.ReadConf(dataDir, tag)
   341  	c.Assert(err, gc.IsNil)
   342  	_, err = config.OpenState()
   343  	expectErr := fmt.Sprintf("cannot log in to juju database as %q: unauthorized mongo access: auth fails", tag)
   344  	c.Assert(err, gc.ErrorMatches, expectErr)
   345  }
   346  
   347  func (s *agentSuite) testUpgrade(c *gc.C, agent runner, tag string, currentTools *coretools.Tools) {
   348  	newVers := version.Current
   349  	newVers.Patch++
   350  	newTools := envtesting.AssertUploadFakeToolsVersions(c, s.Conn.Environ.Storage(), newVers)[0]
   351  	s.proposeVersion(c, newVers.Number)
   352  	err := runWithTimeout(agent)
   353  	envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{
   354  		AgentName: tag,
   355  		OldTools:  currentTools,
   356  		NewTools:  newTools,
   357  		DataDir:   s.DataDir(),
   358  	})
   359  }
   360  
   361  func refreshConfig(c *gc.C, config agent.Config) agent.Config {
   362  	config, err := agent.ReadConf(config.DataDir(), config.Tag())
   363  	c.Assert(err, gc.IsNil)
   364  	return config
   365  }