github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/jujud/bootstrap_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/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"time"
    16  
    17  	"github.com/juju/cmd"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/loggo"
    20  	gitjujutesting "github.com/juju/testing"
    21  	jc "github.com/juju/testing/checkers"
    22  	"github.com/juju/utils"
    23  	"github.com/juju/utils/arch"
    24  	"github.com/juju/utils/series"
    25  	"github.com/juju/utils/set"
    26  	"github.com/juju/version"
    27  	gc "gopkg.in/check.v1"
    28  	"gopkg.in/juju/names.v2"
    29  	goyaml "gopkg.in/yaml.v2"
    30  
    31  	"github.com/juju/juju/agent"
    32  	"github.com/juju/juju/agent/agentbootstrap"
    33  	agenttools "github.com/juju/juju/agent/tools"
    34  	"github.com/juju/juju/apiserver/params"
    35  	"github.com/juju/juju/cloud"
    36  	"github.com/juju/juju/cloudconfig/instancecfg"
    37  	"github.com/juju/juju/cmd/jujud/agent/agenttest"
    38  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    39  	"github.com/juju/juju/cmd/modelcmd"
    40  	"github.com/juju/juju/constraints"
    41  	"github.com/juju/juju/environs"
    42  	"github.com/juju/juju/environs/config"
    43  	"github.com/juju/juju/environs/filestorage"
    44  	"github.com/juju/juju/environs/imagemetadata"
    45  	"github.com/juju/juju/environs/simplestreams"
    46  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    47  	"github.com/juju/juju/environs/storage"
    48  	envtesting "github.com/juju/juju/environs/testing"
    49  	envtools "github.com/juju/juju/environs/tools"
    50  	"github.com/juju/juju/instance"
    51  	"github.com/juju/juju/juju/keys"
    52  	jujutesting "github.com/juju/juju/juju/testing"
    53  	"github.com/juju/juju/mongo"
    54  	"github.com/juju/juju/mongo/mongotest"
    55  	"github.com/juju/juju/network"
    56  	"github.com/juju/juju/provider/dummy"
    57  	"github.com/juju/juju/state"
    58  	"github.com/juju/juju/state/cloudimagemetadata"
    59  	"github.com/juju/juju/state/multiwatcher"
    60  	"github.com/juju/juju/testing"
    61  	"github.com/juju/juju/tools"
    62  	jujuversion "github.com/juju/juju/version"
    63  )
    64  
    65  // We don't want to use JujuConnSuite because it gives us
    66  // an already-bootstrapped environment.
    67  type BootstrapSuite struct {
    68  	testing.BaseSuite
    69  	gitjujutesting.MgoSuite
    70  
    71  	bootstrapParamsFile string
    72  	bootstrapParams     instancecfg.StateInitializationParams
    73  
    74  	dataDir         string
    75  	logDir          string
    76  	mongoOplogSize  string
    77  	fakeEnsureMongo *agenttest.FakeEnsureMongo
    78  	bootstrapName   string
    79  	hostedModelUUID string
    80  
    81  	toolsStorage storage.Storage
    82  }
    83  
    84  var _ = gc.Suite(&BootstrapSuite{})
    85  
    86  func (s *BootstrapSuite) SetUpSuite(c *gc.C) {
    87  	storageDir := c.MkDir()
    88  	restorer := gitjujutesting.PatchValue(&envtools.DefaultBaseURL, storageDir)
    89  	stor, err := filestorage.NewFileStorageWriter(storageDir)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	s.toolsStorage = stor
    92  
    93  	s.BaseSuite.SetUpSuite(c)
    94  	s.AddCleanup(func(*gc.C) {
    95  		restorer()
    96  	})
    97  	s.MgoSuite.SetUpSuite(c)
    98  	s.PatchValue(&jujuversion.Current, testing.FakeVersionNumber)
    99  }
   100  
   101  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
   102  	s.MgoSuite.TearDownSuite(c)
   103  	s.BaseSuite.TearDownSuite(c)
   104  	dummy.Reset(c)
   105  }
   106  
   107  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
   108  	s.BaseSuite.SetUpTest(c)
   109  	s.PatchValue(&sshGenerateKey, func(name string) (string, string, error) {
   110  		return "private-key", "public-key", nil
   111  	})
   112  
   113  	s.MgoSuite.SetUpTest(c)
   114  	s.dataDir = c.MkDir()
   115  	s.logDir = c.MkDir()
   116  	s.bootstrapParamsFile = filepath.Join(s.dataDir, "bootstrap-params")
   117  	s.mongoOplogSize = "1234"
   118  	s.fakeEnsureMongo = agenttest.InstallFakeEnsureMongo(s)
   119  	s.PatchValue(&initiateMongoServer, s.fakeEnsureMongo.InitiateMongo)
   120  	s.makeTestModel(c)
   121  
   122  	// Create fake tools.tar.gz and downloaded-tools.txt.
   123  	current := version.Binary{
   124  		Number: jujuversion.Current,
   125  		Arch:   arch.HostArch(),
   126  		Series: series.HostSeries(),
   127  	}
   128  	toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, current))
   129  	err := os.MkdirAll(toolsDir, 0755)
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	err = ioutil.WriteFile(filepath.Join(toolsDir, "tools.tar.gz"), nil, 0644)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	s.writeDownloadedTools(c, &tools.Tools{Version: current})
   134  
   135  	// Create fake gui.tar.bz2 and downloaded-gui.txt.
   136  	guiDir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   137  	err = os.MkdirAll(guiDir, 0755)
   138  	c.Assert(err, jc.ErrorIsNil)
   139  	err = ioutil.WriteFile(filepath.Join(guiDir, "gui.tar.bz2"), nil, 0644)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	s.writeDownloadedGUI(c, &tools.GUIArchive{
   142  		Version: version.MustParse("2.0.42"),
   143  	})
   144  }
   145  
   146  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
   147  	s.MgoSuite.TearDownTest(c)
   148  	s.BaseSuite.TearDownTest(c)
   149  }
   150  
   151  func (s *BootstrapSuite) writeDownloadedTools(c *gc.C, tools *tools.Tools) {
   152  	toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, tools.Version))
   153  	err := os.MkdirAll(toolsDir, 0755)
   154  	c.Assert(err, jc.ErrorIsNil)
   155  	data, err := json.Marshal(tools)
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	err = ioutil.WriteFile(filepath.Join(toolsDir, "downloaded-tools.txt"), data, 0644)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  }
   160  
   161  func (s *BootstrapSuite) writeDownloadedGUI(c *gc.C, gui *tools.GUIArchive) {
   162  	guiDir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   163  	err := os.MkdirAll(guiDir, 0755)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	data, err := json.Marshal(gui)
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	err = ioutil.WriteFile(filepath.Join(guiDir, "downloaded-gui.txt"), data, 0644)
   168  	c.Assert(err, jc.ErrorIsNil)
   169  }
   170  
   171  func (s *BootstrapSuite) TestGUIArchiveInfoNotFound(c *gc.C) {
   172  	dir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   173  	info := filepath.Join(dir, "downloaded-gui.txt")
   174  	err := os.Remove(info)
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	_, cmd, err := s.initBootstrapCommand(c, nil)
   177  	c.Assert(err, jc.ErrorIsNil)
   178  
   179  	var tw loggo.TestWriter
   180  	err = loggo.RegisterWriter("bootstrap-test", &tw)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	defer loggo.RemoveWriter("bootstrap-test")
   183  
   184  	err = cmd.Run(nil)
   185  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   186  		loggo.WARNING,
   187  		`cannot set up Juju GUI: cannot fetch GUI info: GUI metadata not found`,
   188  	}})
   189  }
   190  
   191  func (s *BootstrapSuite) TestGUIArchiveInfoError(c *gc.C) {
   192  	if runtime.GOOS == "windows" {
   193  		// TODO frankban: skipping for now due to chmod problems with mode 0000
   194  		// on Windows. We will re-enable this test after further investigation:
   195  		// "jujud bootstrap" is never run on Windows anyway.
   196  		c.Skip("needs chmod investigation")
   197  	}
   198  	dir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   199  	info := filepath.Join(dir, "downloaded-gui.txt")
   200  	err := os.Chmod(info, 0000)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	defer os.Chmod(info, 0600)
   203  	_, cmd, err := s.initBootstrapCommand(c, nil)
   204  	c.Assert(err, jc.ErrorIsNil)
   205  
   206  	var tw loggo.TestWriter
   207  	err = loggo.RegisterWriter("bootstrap-test", &tw)
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	defer loggo.RemoveWriter("bootstrap-test")
   210  
   211  	err = cmd.Run(nil)
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   214  		loggo.WARNING,
   215  		`cannot set up Juju GUI: cannot fetch GUI info: cannot read GUI metadata in tools directory: .*`,
   216  	}})
   217  }
   218  
   219  func (s *BootstrapSuite) TestGUIArchiveError(c *gc.C) {
   220  	dir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   221  	archive := filepath.Join(dir, "gui.tar.bz2")
   222  	err := os.Remove(archive)
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	_, cmd, err := s.initBootstrapCommand(c, nil)
   225  	c.Assert(err, jc.ErrorIsNil)
   226  
   227  	var tw loggo.TestWriter
   228  	err = loggo.RegisterWriter("bootstrap-test", &tw)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	defer loggo.RemoveWriter("bootstrap-test")
   231  
   232  	err = cmd.Run(nil)
   233  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   234  		loggo.WARNING,
   235  		`cannot set up Juju GUI: cannot read GUI archive: .*`,
   236  	}})
   237  }
   238  
   239  func (s *BootstrapSuite) TestGUIArchiveSuccess(c *gc.C) {
   240  	_, cmd, err := s.initBootstrapCommand(c, nil)
   241  	c.Assert(err, jc.ErrorIsNil)
   242  
   243  	var tw loggo.TestWriter
   244  	err = loggo.RegisterWriter("bootstrap-test", &tw)
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	defer loggo.RemoveWriter("bootstrap-test")
   247  
   248  	err = cmd.Run(nil)
   249  	c.Assert(err, jc.ErrorIsNil)
   250  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   251  		loggo.DEBUG,
   252  		`Juju GUI successfully set up`,
   253  	}})
   254  
   255  	// Retrieve the state so that it is possible to access the GUI storage.
   256  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   257  		Info: mongo.Info{
   258  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   259  			CACert: testing.CACert,
   260  		},
   261  		Password: testPassword,
   262  	}, mongotest.DialOpts(), nil)
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	defer st.Close()
   265  
   266  	// The GUI archive has been uploaded to the GUI storage.
   267  	storage, err := st.GUIStorage()
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	defer storage.Close()
   270  	allMeta, err := storage.AllMetadata()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	c.Assert(allMeta, gc.HasLen, 1)
   273  	c.Assert(allMeta[0].Version, gc.Equals, "2.0.42")
   274  
   275  	// The current GUI version has been set.
   276  	vers, err := st.GUIVersion()
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	c.Assert(vers.String(), gc.Equals, "2.0.42")
   279  }
   280  
   281  var testPassword = "my-admin-secret"
   282  
   283  func (s *BootstrapSuite) initBootstrapCommand(c *gc.C, jobs []multiwatcher.MachineJob, args ...string) (machineConf agent.ConfigSetterWriter, cmd *BootstrapCommand, err error) {
   284  	if len(jobs) == 0 {
   285  		// Add default jobs.
   286  		jobs = []multiwatcher.MachineJob{
   287  			multiwatcher.JobManageModel,
   288  			multiwatcher.JobHostUnits,
   289  		}
   290  	}
   291  	// NOTE: the old test used an equivalent of the NewAgentConfig, but it
   292  	// really should be using NewStateMachineConfig.
   293  	agentParams := agent.AgentConfigParams{
   294  		Paths: agent.Paths{
   295  			LogDir:  s.logDir,
   296  			DataDir: s.dataDir,
   297  		},
   298  		Jobs:              jobs,
   299  		Tag:               names.NewMachineTag("0"),
   300  		UpgradedToVersion: jujuversion.Current,
   301  		Password:          testPassword,
   302  		Nonce:             agent.BootstrapNonce,
   303  		Controller:        testing.ControllerTag,
   304  		Model:             testing.ModelTag,
   305  		StateAddresses:    []string{gitjujutesting.MgoServer.Addr()},
   306  		APIAddresses:      []string{"0.1.2.3:1234"},
   307  		CACert:            testing.CACert,
   308  		Values: map[string]string{
   309  			agent.Namespace:      "foobar",
   310  			agent.MongoOplogSize: s.mongoOplogSize,
   311  		},
   312  	}
   313  	servingInfo := params.StateServingInfo{
   314  		Cert:         "some cert",
   315  		PrivateKey:   "some key",
   316  		CAPrivateKey: "another key",
   317  		APIPort:      3737,
   318  		StatePort:    gitjujutesting.MgoServer.Port(),
   319  	}
   320  
   321  	machineConf, err = agent.NewStateMachineConfig(agentParams, servingInfo)
   322  	c.Assert(err, jc.ErrorIsNil)
   323  	err = machineConf.Write()
   324  	c.Assert(err, jc.ErrorIsNil)
   325  
   326  	if len(args) == 0 {
   327  		args = []string{s.bootstrapParamsFile}
   328  	}
   329  	cmd = NewBootstrapCommand()
   330  	err = testing.InitCommand(cmd, append([]string{"--data-dir", s.dataDir}, args...))
   331  	return machineConf, cmd, err
   332  }
   333  
   334  func (s *BootstrapSuite) TestInitializeEnvironment(c *gc.C) {
   335  	machConf, cmd, err := s.initBootstrapCommand(c, nil)
   336  	c.Assert(err, jc.ErrorIsNil)
   337  	err = cmd.Run(nil)
   338  	c.Assert(err, jc.ErrorIsNil)
   339  
   340  	c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
   341  	c.Assert(s.fakeEnsureMongo.InitiateCount, gc.Equals, 1)
   342  	c.Assert(s.fakeEnsureMongo.EnsureCount, gc.Equals, 1)
   343  	c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
   344  	c.Assert(s.fakeEnsureMongo.OplogSize, gc.Equals, 1234)
   345  
   346  	expectInfo, exists := machConf.StateServingInfo()
   347  	c.Assert(exists, jc.IsTrue)
   348  	c.Assert(expectInfo.SharedSecret, gc.Equals, "")
   349  	c.Assert(expectInfo.SystemIdentity, gc.Equals, "")
   350  
   351  	servingInfo := s.fakeEnsureMongo.Info
   352  	c.Assert(len(servingInfo.SharedSecret), gc.Not(gc.Equals), 0)
   353  	c.Assert(len(servingInfo.SystemIdentity), gc.Not(gc.Equals), 0)
   354  	servingInfo.SharedSecret = ""
   355  	servingInfo.SystemIdentity = ""
   356  	expect := cmdutil.ParamsStateServingInfoToStateStateServingInfo(expectInfo)
   357  	c.Assert(servingInfo, jc.DeepEquals, expect)
   358  	expectDialAddrs := []string{fmt.Sprintf("127.0.0.1:%d", expectInfo.StatePort)}
   359  	gotDialAddrs := s.fakeEnsureMongo.InitiateParams.DialInfo.Addrs
   360  	c.Assert(gotDialAddrs, gc.DeepEquals, expectDialAddrs)
   361  
   362  	c.Assert(s.fakeEnsureMongo.InitiateParams.MemberHostPort, gc.Equals, expectDialAddrs[0])
   363  	c.Assert(s.fakeEnsureMongo.InitiateParams.User, gc.Equals, "")
   364  	c.Assert(s.fakeEnsureMongo.InitiateParams.Password, gc.Equals, "")
   365  
   366  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   367  		Info: mongo.Info{
   368  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   369  			CACert: testing.CACert,
   370  		},
   371  		Password: testPassword,
   372  	}, mongotest.DialOpts(), nil)
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	defer st.Close()
   375  	machines, err := st.AllMachines()
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	c.Assert(machines, gc.HasLen, 1)
   378  
   379  	instid, err := machines[0].InstanceId()
   380  	c.Assert(err, jc.ErrorIsNil)
   381  	c.Assert(instid, gc.Equals, s.bootstrapParams.BootstrapMachineInstanceId)
   382  
   383  	stateHw, err := machines[0].HardwareCharacteristics()
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	c.Assert(stateHw, gc.NotNil)
   386  	c.Assert(stateHw, gc.DeepEquals, s.bootstrapParams.BootstrapMachineHardwareCharacteristics)
   387  
   388  	cons, err := st.ModelConstraints()
   389  	c.Assert(err, jc.ErrorIsNil)
   390  	c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
   391  
   392  	cfg, err := st.ModelConfig()
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	c.Assert(cfg.AuthorizedKeys(), gc.Equals, s.bootstrapParams.ControllerModelConfig.AuthorizedKeys()+"\npublic-key")
   395  }
   396  
   397  func (s *BootstrapSuite) TestInitializeEnvironmentInvalidOplogSize(c *gc.C) {
   398  	s.mongoOplogSize = "NaN"
   399  	_, cmd, err := s.initBootstrapCommand(c, nil)
   400  	c.Assert(err, jc.ErrorIsNil)
   401  	err = cmd.Run(nil)
   402  	c.Assert(err, gc.ErrorMatches, `failed to start mongo: invalid oplog size: "NaN"`)
   403  }
   404  
   405  func (s *BootstrapSuite) TestInitializeEnvironmentToolsNotFound(c *gc.C) {
   406  	// bootstrap with 1.99.1 but there will be no tools so version will be reset.
   407  	cfg, err := s.bootstrapParams.ControllerModelConfig.Apply(map[string]interface{}{
   408  		"agent-version": "1.99.1",
   409  	})
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	s.bootstrapParams.ControllerModelConfig = cfg
   412  	s.writeBootstrapParamsFile(c)
   413  
   414  	_, cmd, err := s.initBootstrapCommand(c, nil)
   415  	c.Assert(err, jc.ErrorIsNil)
   416  	err = cmd.Run(nil)
   417  	c.Assert(err, jc.ErrorIsNil)
   418  
   419  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   420  		Info: mongo.Info{
   421  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   422  			CACert: testing.CACert,
   423  		},
   424  		Password: testPassword,
   425  	}, mongotest.DialOpts(), nil)
   426  	c.Assert(err, jc.ErrorIsNil)
   427  	defer st.Close()
   428  
   429  	cfg, err = st.ModelConfig()
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	vers, ok := cfg.AgentVersion()
   432  	c.Assert(ok, jc.IsTrue)
   433  	c.Assert(vers.String(), gc.Equals, "1.99.0")
   434  }
   435  
   436  func (s *BootstrapSuite) TestSetConstraints(c *gc.C) {
   437  	s.bootstrapParams.BootstrapMachineConstraints = constraints.Value{Mem: uint64p(4096), CpuCores: uint64p(4)}
   438  	s.bootstrapParams.ModelConstraints = constraints.Value{Mem: uint64p(2048), CpuCores: uint64p(2)}
   439  	s.writeBootstrapParamsFile(c)
   440  
   441  	_, cmd, err := s.initBootstrapCommand(c, nil)
   442  	c.Assert(err, jc.ErrorIsNil)
   443  	err = cmd.Run(nil)
   444  	c.Assert(err, jc.ErrorIsNil)
   445  
   446  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   447  		Info: mongo.Info{
   448  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   449  			CACert: testing.CACert,
   450  		},
   451  		Password: testPassword,
   452  	}, mongotest.DialOpts(), nil)
   453  	c.Assert(err, jc.ErrorIsNil)
   454  	defer st.Close()
   455  
   456  	cons, err := st.ModelConstraints()
   457  	c.Assert(err, jc.ErrorIsNil)
   458  	c.Assert(cons, gc.DeepEquals, s.bootstrapParams.ModelConstraints)
   459  
   460  	machines, err := st.AllMachines()
   461  	c.Assert(err, jc.ErrorIsNil)
   462  	c.Assert(machines, gc.HasLen, 1)
   463  	cons, err = machines[0].Constraints()
   464  	c.Assert(err, jc.ErrorIsNil)
   465  	c.Assert(cons, gc.DeepEquals, s.bootstrapParams.BootstrapMachineConstraints)
   466  }
   467  
   468  func uint64p(v uint64) *uint64 {
   469  	return &v
   470  }
   471  
   472  func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) {
   473  	expectedJobs := []state.MachineJob{
   474  		state.JobManageModel,
   475  		state.JobHostUnits,
   476  	}
   477  	_, cmd, err := s.initBootstrapCommand(c, nil)
   478  	c.Assert(err, jc.ErrorIsNil)
   479  	err = cmd.Run(nil)
   480  	c.Assert(err, jc.ErrorIsNil)
   481  
   482  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   483  		Info: mongo.Info{
   484  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   485  			CACert: testing.CACert,
   486  		},
   487  		Password: testPassword,
   488  	}, mongotest.DialOpts(), nil)
   489  	c.Assert(err, jc.ErrorIsNil)
   490  	defer st.Close()
   491  	m, err := st.Machine("0")
   492  	c.Assert(err, jc.ErrorIsNil)
   493  	c.Assert(m.Jobs(), gc.DeepEquals, expectedJobs)
   494  }
   495  
   496  func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) {
   497  	jobs := []multiwatcher.MachineJob{multiwatcher.JobManageModel}
   498  	_, cmd, err := s.initBootstrapCommand(c, jobs)
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	err = cmd.Run(nil)
   501  	c.Assert(err, jc.ErrorIsNil)
   502  
   503  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   504  		Info: mongo.Info{
   505  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   506  			CACert: testing.CACert,
   507  		},
   508  		Password: testPassword,
   509  	}, mongotest.DialOpts(), nil)
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	defer st.Close()
   512  	m, err := st.Machine("0")
   513  	c.Assert(err, jc.ErrorIsNil)
   514  	c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel})
   515  }
   516  
   517  func (s *BootstrapSuite) TestInitialPassword(c *gc.C) {
   518  	machineConf, cmd, err := s.initBootstrapCommand(c, nil)
   519  	c.Assert(err, jc.ErrorIsNil)
   520  
   521  	err = cmd.Run(nil)
   522  	c.Assert(err, jc.ErrorIsNil)
   523  
   524  	info := &mongo.MongoInfo{
   525  		Info: mongo.Info{
   526  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   527  			CACert: testing.CACert,
   528  		},
   529  	}
   530  
   531  	// Check we can log in to mongo as admin.
   532  	info.Tag, info.Password = nil, testPassword
   533  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, info, mongotest.DialOpts(), nil)
   534  	c.Assert(err, jc.ErrorIsNil)
   535  	defer st.Close()
   536  
   537  	// We're running Mongo with --noauth; let's explicitly verify
   538  	// that we can login as that user. Even with --noauth, an
   539  	// explicit Login will still be verified.
   540  	adminDB := st.MongoSession().DB("admin")
   541  	err = adminDB.Login("admin", "invalid-password")
   542  	c.Assert(err, gc.ErrorMatches, "(auth|(.*Authentication)) fail(s|ed)\\.?")
   543  	err = adminDB.Login("admin", info.Password)
   544  	c.Assert(err, jc.ErrorIsNil)
   545  
   546  	// Check that the admin user has been given an appropriate
   547  	// password
   548  	u, err := st.User(names.NewLocalUserTag("admin"))
   549  	c.Assert(err, jc.ErrorIsNil)
   550  	c.Assert(u.PasswordValid(testPassword), jc.IsTrue)
   551  
   552  	// Check that the machine configuration has been given a new
   553  	// password and that we can connect to mongo as that machine
   554  	// and that the in-mongo password also verifies correctly.
   555  	machineConf1, err := agent.ReadConfig(agent.ConfigPath(machineConf.DataDir(), names.NewMachineTag("0")))
   556  	c.Assert(err, jc.ErrorIsNil)
   557  
   558  	stateinfo, ok := machineConf1.MongoInfo()
   559  	c.Assert(ok, jc.IsTrue)
   560  	st, err = state.Open(testing.ModelTag, testing.ControllerTag, stateinfo, mongotest.DialOpts(), nil)
   561  	c.Assert(err, jc.ErrorIsNil)
   562  	defer st.Close()
   563  
   564  	m, err := st.Machine("0")
   565  	c.Assert(err, jc.ErrorIsNil)
   566  	c.Assert(m.HasVote(), jc.IsTrue)
   567  }
   568  
   569  var bootstrapArgTests = []struct {
   570  	input                       []string
   571  	err                         string
   572  	expectedBootstrapParamsFile string
   573  }{
   574  	{
   575  		err:   "bootstrap-params file must be specified",
   576  		input: []string{"--data-dir", "/tmp/juju/data/dir"},
   577  	}, {
   578  		input: []string{"/some/where"},
   579  		expectedBootstrapParamsFile: "/some/where",
   580  	},
   581  }
   582  
   583  func (s *BootstrapSuite) TestBootstrapArgs(c *gc.C) {
   584  	for i, t := range bootstrapArgTests {
   585  		c.Logf("test %d", i)
   586  		var args []string
   587  		args = append(args, t.input...)
   588  		_, cmd, err := s.initBootstrapCommand(c, nil, args...)
   589  		if t.err == "" {
   590  			c.Assert(cmd, gc.NotNil)
   591  			c.Assert(err, jc.ErrorIsNil)
   592  			c.Assert(cmd.BootstrapParamsFile, gc.Equals, t.expectedBootstrapParamsFile)
   593  		} else {
   594  			c.Assert(err, gc.ErrorMatches, t.err)
   595  		}
   596  	}
   597  }
   598  
   599  func (s *BootstrapSuite) TestInitializeStateArgs(c *gc.C) {
   600  	var called int
   601  	initializeState := func(_ names.UserTag, _ agent.ConfigSetter, args agentbootstrap.InitializeStateParams, dialOpts mongo.DialOpts, _ state.NewPolicyFunc) (_ *state.State, _ *state.Machine, resultErr error) {
   602  		called++
   603  		c.Assert(dialOpts.Direct, jc.IsTrue)
   604  		c.Assert(dialOpts.Timeout, gc.Equals, 30*time.Second)
   605  		c.Assert(dialOpts.SocketTimeout, gc.Equals, 123*time.Second)
   606  		c.Assert(args.HostedModelConfig, jc.DeepEquals, map[string]interface{}{
   607  			"name": "hosted-model",
   608  			"uuid": s.hostedModelUUID,
   609  		})
   610  		return nil, nil, errors.New("failed to initialize state")
   611  	}
   612  	s.PatchValue(&agentInitializeState, initializeState)
   613  	_, cmd, err := s.initBootstrapCommand(c, nil, "--timeout", "123s", s.bootstrapParamsFile)
   614  	c.Assert(err, jc.ErrorIsNil)
   615  	err = cmd.Run(nil)
   616  	c.Assert(err, gc.ErrorMatches, "failed to initialize state")
   617  	c.Assert(called, gc.Equals, 1)
   618  }
   619  
   620  func (s *BootstrapSuite) TestInitializeStateMinSocketTimeout(c *gc.C) {
   621  	var called int
   622  	initializeState := func(_ names.UserTag, _ agent.ConfigSetter, _ agentbootstrap.InitializeStateParams, dialOpts mongo.DialOpts, _ state.NewPolicyFunc) (_ *state.State, _ *state.Machine, resultErr error) {
   623  		called++
   624  		c.Assert(dialOpts.Direct, jc.IsTrue)
   625  		c.Assert(dialOpts.SocketTimeout, gc.Equals, 1*time.Minute)
   626  		return nil, nil, errors.New("failed to initialize state")
   627  	}
   628  
   629  	s.PatchValue(&agentInitializeState, initializeState)
   630  	_, cmd, err := s.initBootstrapCommand(c, nil, "--timeout", "13s", s.bootstrapParamsFile)
   631  	c.Assert(err, jc.ErrorIsNil)
   632  	err = cmd.Run(nil)
   633  	c.Assert(err, gc.ErrorMatches, "failed to initialize state")
   634  	c.Assert(called, gc.Equals, 1)
   635  }
   636  
   637  func (s *BootstrapSuite) TestSystemIdentityWritten(c *gc.C) {
   638  	_, err := os.Stat(filepath.Join(s.dataDir, agent.SystemIdentity))
   639  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   640  
   641  	_, cmd, err := s.initBootstrapCommand(c, nil)
   642  	c.Assert(err, jc.ErrorIsNil)
   643  	err = cmd.Run(nil)
   644  	c.Assert(err, jc.ErrorIsNil)
   645  
   646  	data, err := ioutil.ReadFile(filepath.Join(s.dataDir, agent.SystemIdentity))
   647  	c.Assert(err, jc.ErrorIsNil)
   648  	c.Assert(string(data), gc.Equals, "private-key")
   649  }
   650  
   651  func (s *BootstrapSuite) TestDownloadedToolsMetadata(c *gc.C) {
   652  	// Tools downloaded by cloud-init script.
   653  	s.testToolsMetadata(c, false)
   654  }
   655  
   656  func (s *BootstrapSuite) TestUploadedToolsMetadata(c *gc.C) {
   657  	// Tools uploaded over ssh.
   658  	s.writeDownloadedTools(c, &tools.Tools{
   659  		Version: version.Binary{
   660  			Number: jujuversion.Current,
   661  			Arch:   arch.HostArch(),
   662  			Series: series.HostSeries(),
   663  		},
   664  		URL: "file:///does/not/matter",
   665  	})
   666  	s.testToolsMetadata(c, true)
   667  }
   668  
   669  func (s *BootstrapSuite) testToolsMetadata(c *gc.C, exploded bool) {
   670  	envtesting.RemoveFakeToolsMetadata(c, s.toolsStorage)
   671  
   672  	_, cmd, err := s.initBootstrapCommand(c, nil)
   673  
   674  	c.Assert(err, jc.ErrorIsNil)
   675  	err = cmd.Run(nil)
   676  	c.Assert(err, jc.ErrorIsNil)
   677  
   678  	// We don't write metadata at bootstrap anymore.
   679  	simplestreamsMetadata, err := envtools.ReadMetadata(s.toolsStorage, "released")
   680  	c.Assert(err, jc.ErrorIsNil)
   681  	c.Assert(simplestreamsMetadata, gc.HasLen, 0)
   682  
   683  	// The tools should have been added to tools storage, and
   684  	// exploded into each of the supported series of
   685  	// the same operating system if the tools were uploaded.
   686  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   687  		Info: mongo.Info{
   688  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   689  			CACert: testing.CACert,
   690  		},
   691  		Password: testPassword,
   692  	}, mongotest.DialOpts(), nil)
   693  	c.Assert(err, jc.ErrorIsNil)
   694  	defer st.Close()
   695  	expectedSeries := make(set.Strings)
   696  	if exploded {
   697  		for _, ser := range series.SupportedSeries() {
   698  			os, err := series.GetOSFromSeries(ser)
   699  			c.Assert(err, jc.ErrorIsNil)
   700  			hostos, err := series.GetOSFromSeries(series.HostSeries())
   701  			c.Assert(err, jc.ErrorIsNil)
   702  			if os == hostos {
   703  				expectedSeries.Add(ser)
   704  			}
   705  		}
   706  	} else {
   707  		expectedSeries.Add(series.HostSeries())
   708  	}
   709  
   710  	storage, err := st.ToolsStorage()
   711  	c.Assert(err, jc.ErrorIsNil)
   712  	defer storage.Close()
   713  	metadata, err := storage.AllMetadata()
   714  	c.Assert(err, jc.ErrorIsNil)
   715  	c.Assert(metadata, gc.HasLen, expectedSeries.Size())
   716  	for _, m := range metadata {
   717  		v := version.MustParseBinary(m.Version)
   718  		c.Assert(expectedSeries.Contains(v.Series), jc.IsTrue)
   719  	}
   720  }
   721  
   722  func createImageMetadata() []*imagemetadata.ImageMetadata {
   723  	return []*imagemetadata.ImageMetadata{{
   724  		Id:         "imageId",
   725  		Storage:    "rootStore",
   726  		VirtType:   "virtType",
   727  		Arch:       "amd64",
   728  		Version:    "14.04",
   729  		Endpoint:   "endpoint",
   730  		RegionName: "region",
   731  	}}
   732  }
   733  
   734  func assertWrittenToState(c *gc.C, metadata cloudimagemetadata.Metadata) {
   735  	st, err := state.Open(testing.ModelTag, testing.ControllerTag, &mongo.MongoInfo{
   736  		Info: mongo.Info{
   737  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   738  			CACert: testing.CACert,
   739  		},
   740  		Password: testPassword,
   741  	}, mongotest.DialOpts(), nil)
   742  	c.Assert(err, jc.ErrorIsNil)
   743  	defer st.Close()
   744  
   745  	// find all image metadata in state
   746  	all, err := st.CloudImageMetadataStorage.FindMetadata(cloudimagemetadata.MetadataFilter{})
   747  	c.Assert(err, jc.ErrorIsNil)
   748  	// if there was no stream, it should have defaulted to "released"
   749  	if metadata.Stream == "" {
   750  		metadata.Stream = "released"
   751  	}
   752  	if metadata.DateCreated == 0 && len(all[metadata.Source]) > 0 {
   753  		metadata.DateCreated = all[metadata.Source][0].DateCreated
   754  	}
   755  	c.Assert(all, gc.DeepEquals, map[string][]cloudimagemetadata.Metadata{
   756  		metadata.Source: []cloudimagemetadata.Metadata{metadata},
   757  	})
   758  }
   759  
   760  func (s *BootstrapSuite) TestStructuredImageMetadataStored(c *gc.C) {
   761  	s.bootstrapParams.CustomImageMetadata = createImageMetadata()
   762  	s.writeBootstrapParamsFile(c)
   763  	_, cmd, err := s.initBootstrapCommand(c, nil)
   764  	c.Assert(err, jc.ErrorIsNil)
   765  	err = cmd.Run(nil)
   766  	c.Assert(err, jc.ErrorIsNil)
   767  
   768  	// This metadata should have also been written to state...
   769  	expect := cloudimagemetadata.Metadata{
   770  		MetadataAttributes: cloudimagemetadata.MetadataAttributes{
   771  			Region:          "region",
   772  			Arch:            "amd64",
   773  			Version:         "14.04",
   774  			Series:          "trusty",
   775  			RootStorageType: "rootStore",
   776  			VirtType:        "virtType",
   777  			Source:          "custom",
   778  		},
   779  		Priority: simplestreams.CUSTOM_CLOUD_DATA,
   780  		ImageId:  "imageId",
   781  	}
   782  	assertWrittenToState(c, expect)
   783  }
   784  
   785  func (s *BootstrapSuite) TestStructuredImageMetadataInvalidSeries(c *gc.C) {
   786  	s.bootstrapParams.CustomImageMetadata = createImageMetadata()
   787  	s.bootstrapParams.CustomImageMetadata[0].Version = "woat"
   788  	s.writeBootstrapParamsFile(c)
   789  
   790  	_, cmd, err := s.initBootstrapCommand(c, nil)
   791  	c.Assert(err, jc.ErrorIsNil)
   792  	err = cmd.Run(nil)
   793  	c.Assert(err, gc.ErrorMatches, `cannot determine series for version woat: unknown series for version: \"woat\"`)
   794  }
   795  
   796  func (s *BootstrapSuite) makeTestModel(c *gc.C) {
   797  	attrs := dummy.SampleConfig().Merge(
   798  		testing.Attrs{
   799  			"agent-version": jujuversion.Current.String(),
   800  		},
   801  	).Delete("admin-secret", "ca-private-key")
   802  	cfg, err := config.New(config.NoDefaults, attrs)
   803  	c.Assert(err, jc.ErrorIsNil)
   804  	provider, err := environs.Provider(cfg.Type())
   805  	c.Assert(err, jc.ErrorIsNil)
   806  	controllerCfg := testing.FakeControllerConfig()
   807  	cfg, err = provider.PrepareConfig(environs.PrepareConfigParams{
   808  		Config: cfg,
   809  	})
   810  	c.Assert(err, jc.ErrorIsNil)
   811  	env, err := provider.Open(environs.OpenParams{
   812  		Cloud:  dummy.SampleCloudSpec(),
   813  		Config: cfg,
   814  	})
   815  	c.Assert(err, jc.ErrorIsNil)
   816  	err = env.PrepareForBootstrap(nullContext())
   817  	c.Assert(err, jc.ErrorIsNil)
   818  	s.AddCleanup(func(c *gc.C) {
   819  		err := env.DestroyController(controllerCfg.ControllerUUID())
   820  		c.Assert(err, jc.ErrorIsNil)
   821  	})
   822  
   823  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
   824  	envtesting.MustUploadFakeTools(s.toolsStorage, cfg.AgentStream(), cfg.AgentStream())
   825  	inst, _, _, err := jujutesting.StartInstance(env, testing.FakeControllerConfig().ControllerUUID(), "0")
   826  	c.Assert(err, jc.ErrorIsNil)
   827  
   828  	addresses, err := inst.Addresses()
   829  	c.Assert(err, jc.ErrorIsNil)
   830  	addr, _ := network.SelectPublicAddress(addresses)
   831  	s.bootstrapName = addr.Value
   832  	s.hostedModelUUID = utils.MustNewUUID().String()
   833  
   834  	var args instancecfg.StateInitializationParams
   835  	args.ControllerConfig = controllerCfg
   836  	args.BootstrapMachineInstanceId = inst.Id()
   837  	args.ControllerModelConfig = env.Config()
   838  	hw := instance.MustParseHardware("arch=amd64 mem=8G")
   839  	args.BootstrapMachineHardwareCharacteristics = &hw
   840  	args.HostedModelConfig = map[string]interface{}{
   841  		"name": "hosted-model",
   842  		"uuid": s.hostedModelUUID,
   843  	}
   844  	args.ControllerCloudName = "dummy"
   845  	args.ControllerCloud = cloud.Cloud{
   846  		Type:      "dummy",
   847  		AuthTypes: []cloud.AuthType{cloud.EmptyAuthType},
   848  	}
   849  	s.bootstrapParams = args
   850  	s.writeBootstrapParamsFile(c)
   851  }
   852  
   853  func (s *BootstrapSuite) writeBootstrapParamsFile(c *gc.C) {
   854  	data, err := s.bootstrapParams.Marshal()
   855  	c.Assert(err, jc.ErrorIsNil)
   856  	err = ioutil.WriteFile(s.bootstrapParamsFile, data, 0600)
   857  	c.Assert(err, jc.ErrorIsNil)
   858  }
   859  
   860  func nullContext() environs.BootstrapContext {
   861  	ctx, _ := cmd.DefaultContext()
   862  	ctx.Stdin = io.LimitReader(nil, 0)
   863  	ctx.Stdout = ioutil.Discard
   864  	ctx.Stderr = ioutil.Discard
   865  	return modelcmd.BootstrapContext(ctx)
   866  }
   867  
   868  type b64yaml map[string]interface{}
   869  
   870  func (m b64yaml) encode() string {
   871  	data, err := goyaml.Marshal(m)
   872  	if err != nil {
   873  		panic(err)
   874  	}
   875  	return base64.StdEncoding.EncodeToString(data)
   876  }