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