github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"github.com/juju/names"
    21  	gitjujutesting "github.com/juju/testing"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/utils"
    24  	"github.com/juju/utils/arch"
    25  	"github.com/juju/utils/series"
    26  	"github.com/juju/utils/set"
    27  	"github.com/juju/version"
    28  	gc "gopkg.in/check.v1"
    29  	"gopkg.in/mgo.v2"
    30  	goyaml "gopkg.in/yaml.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  	agenttesting "github.com/juju/juju/cmd/jujud/agent/testing"
    37  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    38  	"github.com/juju/juju/cmd/modelcmd"
    39  	"github.com/juju/juju/constraints"
    40  	"github.com/juju/juju/environs"
    41  	"github.com/juju/juju/environs/config"
    42  	"github.com/juju/juju/environs/filestorage"
    43  	"github.com/juju/juju/environs/simplestreams"
    44  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    45  	"github.com/juju/juju/environs/storage"
    46  	envtesting "github.com/juju/juju/environs/testing"
    47  	envtools "github.com/juju/juju/environs/tools"
    48  	"github.com/juju/juju/instance"
    49  	"github.com/juju/juju/juju"
    50  	jujutesting "github.com/juju/juju/juju/testing"
    51  	"github.com/juju/juju/mongo"
    52  	"github.com/juju/juju/network"
    53  	"github.com/juju/juju/provider/dummy"
    54  	"github.com/juju/juju/state"
    55  	"github.com/juju/juju/state/cloudimagemetadata"
    56  	"github.com/juju/juju/state/multiwatcher"
    57  	statestorage "github.com/juju/juju/state/storage"
    58  	statetesting "github.com/juju/juju/state/testing"
    59  	"github.com/juju/juju/storage/poolmanager"
    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  	envcfg                       *config.Config
    71  	b64yamlControllerModelConfig string
    72  	b64yamlHostedModelConfig     string
    73  	instanceId                   instance.Id
    74  	dataDir                      string
    75  	logDir                       string
    76  	mongoOplogSize               string
    77  	fakeEnsureMongo              *agenttesting.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  	s.makeTestEnv(c)
   100  }
   101  
   102  func (s *BootstrapSuite) TearDownSuite(c *gc.C) {
   103  	s.MgoSuite.TearDownSuite(c)
   104  	s.BaseSuite.TearDownSuite(c)
   105  	dummy.Reset(c)
   106  }
   107  
   108  func (s *BootstrapSuite) SetUpTest(c *gc.C) {
   109  	s.BaseSuite.SetUpTest(c)
   110  	s.PatchValue(&sshGenerateKey, func(name string) (string, string, error) {
   111  		return "private-key", "public-key", nil
   112  	})
   113  
   114  	s.MgoSuite.SetUpTest(c)
   115  	s.dataDir = c.MkDir()
   116  	s.logDir = c.MkDir()
   117  	s.mongoOplogSize = "1234"
   118  	s.fakeEnsureMongo = agenttesting.InstallFakeEnsureMongo(s)
   119  	s.PatchValue(&initiateMongoServer, s.fakeEnsureMongo.InitiateMongo)
   120  
   121  	// Create fake tools.tar.gz and downloaded-tools.txt.
   122  	current := version.Binary{
   123  		Number: jujuversion.Current,
   124  		Arch:   arch.HostArch(),
   125  		Series: series.HostSeries(),
   126  	}
   127  	toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, current))
   128  	err := os.MkdirAll(toolsDir, 0755)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	err = ioutil.WriteFile(filepath.Join(toolsDir, "tools.tar.gz"), nil, 0644)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	s.writeDownloadedTools(c, &tools.Tools{Version: current})
   133  
   134  	// Create fake gui.tar.bz2 and downloaded-gui.txt.
   135  	guiDir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   136  	err = os.MkdirAll(guiDir, 0755)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	err = ioutil.WriteFile(filepath.Join(guiDir, "gui.tar.bz2"), nil, 0644)
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	s.writeDownloadedGUI(c, &tools.GUIArchive{
   141  		Version: version.MustParse("2.0.42"),
   142  	})
   143  }
   144  
   145  func (s *BootstrapSuite) TearDownTest(c *gc.C) {
   146  	s.MgoSuite.TearDownTest(c)
   147  	s.BaseSuite.TearDownTest(c)
   148  }
   149  
   150  func (s *BootstrapSuite) writeDownloadedTools(c *gc.C, tools *tools.Tools) {
   151  	toolsDir := filepath.FromSlash(agenttools.SharedToolsDir(s.dataDir, tools.Version))
   152  	err := os.MkdirAll(toolsDir, 0755)
   153  	c.Assert(err, jc.ErrorIsNil)
   154  	data, err := json.Marshal(tools)
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	err = ioutil.WriteFile(filepath.Join(toolsDir, "downloaded-tools.txt"), data, 0644)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  }
   159  
   160  func (s *BootstrapSuite) writeDownloadedGUI(c *gc.C, gui *tools.GUIArchive) {
   161  	guiDir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   162  	err := os.MkdirAll(guiDir, 0755)
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	data, err := json.Marshal(gui)
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	err = ioutil.WriteFile(filepath.Join(guiDir, "downloaded-gui.txt"), data, 0644)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  }
   169  
   170  func (s *BootstrapSuite) TestGUIArchiveInfoNotFound(c *gc.C) {
   171  	dir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   172  	info := filepath.Join(dir, "downloaded-gui.txt")
   173  	err := os.Remove(info)
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	_, cmd, err := s.initBootstrapCommand(
   176  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
   177  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   178  		"--instance-id", string(s.instanceId))
   179  	c.Assert(err, jc.ErrorIsNil)
   180  
   181  	var tw loggo.TestWriter
   182  	err = loggo.RegisterWriter("bootstrap-test", &tw, loggo.DEBUG)
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	defer loggo.RemoveWriter("bootstrap-test")
   185  
   186  	err = cmd.Run(nil)
   187  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   188  		loggo.WARNING,
   189  		`cannot set up Juju GUI: cannot fetch GUI info: GUI metadata not found`,
   190  	}})
   191  }
   192  
   193  func (s *BootstrapSuite) TestGUIArchiveInfoError(c *gc.C) {
   194  	if runtime.GOOS == "windows" {
   195  		// TODO frankban: skipping for now due to chmod problems with mode 0000
   196  		// on Windows. We will re-enable this test after further investigation:
   197  		// "jujud bootstrap" is never run on Windows anyway.
   198  		c.Skip("needs chmod investigation")
   199  	}
   200  	dir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   201  	info := filepath.Join(dir, "downloaded-gui.txt")
   202  	err := os.Chmod(info, 0000)
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	defer os.Chmod(info, 0600)
   205  	_, cmd, err := s.initBootstrapCommand(
   206  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
   207  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   208  		"--instance-id", string(s.instanceId))
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	var tw loggo.TestWriter
   212  	err = loggo.RegisterWriter("bootstrap-test", &tw, loggo.DEBUG)
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	defer loggo.RemoveWriter("bootstrap-test")
   215  
   216  	err = cmd.Run(nil)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   219  		loggo.WARNING,
   220  		`cannot set up Juju GUI: cannot fetch GUI info: cannot read GUI metadata in tools directory: .*`,
   221  	}})
   222  }
   223  
   224  func (s *BootstrapSuite) TestGUIArchiveError(c *gc.C) {
   225  	dir := filepath.FromSlash(agenttools.SharedGUIDir(s.dataDir))
   226  	archive := filepath.Join(dir, "gui.tar.bz2")
   227  	err := os.Remove(archive)
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	_, cmd, err := s.initBootstrapCommand(
   230  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
   231  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   232  		"--instance-id", string(s.instanceId))
   233  	c.Assert(err, jc.ErrorIsNil)
   234  
   235  	var tw loggo.TestWriter
   236  	err = loggo.RegisterWriter("bootstrap-test", &tw, loggo.DEBUG)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	defer loggo.RemoveWriter("bootstrap-test")
   239  
   240  	err = cmd.Run(nil)
   241  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   242  		loggo.WARNING,
   243  		`cannot set up Juju GUI: cannot read GUI archive: .*`,
   244  	}})
   245  }
   246  
   247  func (s *BootstrapSuite) TestGUIArchiveSuccess(c *gc.C) {
   248  	_, cmd, err := s.initBootstrapCommand(
   249  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
   250  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   251  		"--instance-id", string(s.instanceId))
   252  	c.Assert(err, jc.ErrorIsNil)
   253  
   254  	var tw loggo.TestWriter
   255  	err = loggo.RegisterWriter("bootstrap-test", &tw, loggo.DEBUG)
   256  	c.Assert(err, jc.ErrorIsNil)
   257  	defer loggo.RemoveWriter("bootstrap-test")
   258  
   259  	err = cmd.Run(nil)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	c.Assert(tw.Log(), jc.LogMatches, jc.SimpleMessages{{
   262  		loggo.DEBUG,
   263  		`Juju GUI successfully set up`,
   264  	}})
   265  
   266  	// Retrieve the state so that it is possible to access the GUI storage.
   267  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   268  		Info: mongo.Info{
   269  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   270  			CACert: testing.CACert,
   271  		},
   272  		Password: testPassword,
   273  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   274  	c.Assert(err, jc.ErrorIsNil)
   275  	defer st.Close()
   276  
   277  	// The GUI archive has been uploaded to the GUI storage.
   278  	storage, err := st.GUIStorage()
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	defer storage.Close()
   281  	allMeta, err := storage.AllMetadata()
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	c.Assert(allMeta, gc.HasLen, 1)
   284  	c.Assert(allMeta[0].Version, gc.Equals, "2.0.42")
   285  
   286  	// The current GUI version has been set.
   287  	vers, err := st.GUIVersion()
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	c.Assert(vers.String(), gc.Equals, "2.0.42")
   290  }
   291  
   292  var testPassword = "my-admin-secret"
   293  
   294  func (s *BootstrapSuite) initBootstrapCommand(c *gc.C, jobs []multiwatcher.MachineJob, args ...string) (machineConf agent.ConfigSetterWriter, cmd *BootstrapCommand, err error) {
   295  	if len(jobs) == 0 {
   296  		// Add default jobs.
   297  		jobs = []multiwatcher.MachineJob{
   298  			multiwatcher.JobManageModel,
   299  			multiwatcher.JobHostUnits,
   300  			multiwatcher.JobManageNetworking,
   301  		}
   302  	}
   303  	// NOTE: the old test used an equivalent of the NewAgentConfig, but it
   304  	// really should be using NewStateMachineConfig.
   305  	agentParams := agent.AgentConfigParams{
   306  		Paths: agent.Paths{
   307  			LogDir:  s.logDir,
   308  			DataDir: s.dataDir,
   309  		},
   310  		Jobs:              jobs,
   311  		Tag:               names.NewMachineTag("0"),
   312  		UpgradedToVersion: jujuversion.Current,
   313  		Password:          testPassword,
   314  		Nonce:             agent.BootstrapNonce,
   315  		Model:             testing.ModelTag,
   316  		StateAddresses:    []string{gitjujutesting.MgoServer.Addr()},
   317  		APIAddresses:      []string{"0.1.2.3:1234"},
   318  		CACert:            testing.CACert,
   319  		Values: map[string]string{
   320  			agent.Namespace:      "foobar",
   321  			agent.MongoOplogSize: s.mongoOplogSize,
   322  		},
   323  	}
   324  	servingInfo := params.StateServingInfo{
   325  		Cert:         "some cert",
   326  		PrivateKey:   "some key",
   327  		CAPrivateKey: "another key",
   328  		APIPort:      3737,
   329  		StatePort:    gitjujutesting.MgoServer.Port(),
   330  	}
   331  
   332  	machineConf, err = agent.NewStateMachineConfig(agentParams, servingInfo)
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	err = machineConf.Write()
   335  	c.Assert(err, jc.ErrorIsNil)
   336  
   337  	cmd = NewBootstrapCommand()
   338  
   339  	err = testing.InitCommand(cmd, append([]string{"--data-dir", s.dataDir}, args...))
   340  	return machineConf, cmd, err
   341  }
   342  
   343  func (s *BootstrapSuite) TestInitializeEnvironment(c *gc.C) {
   344  	hw := instance.MustParseHardware("arch=amd64 mem=8G")
   345  	machConf, cmd, err := s.initBootstrapCommand(
   346  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
   347  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   348  		"--instance-id", string(s.instanceId), "--hardware", hw.String(),
   349  	)
   350  	c.Assert(err, jc.ErrorIsNil)
   351  	err = cmd.Run(nil)
   352  	c.Assert(err, jc.ErrorIsNil)
   353  
   354  	c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
   355  	c.Assert(s.fakeEnsureMongo.InitiateCount, gc.Equals, 1)
   356  	c.Assert(s.fakeEnsureMongo.EnsureCount, gc.Equals, 1)
   357  	c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
   358  	c.Assert(s.fakeEnsureMongo.OplogSize, gc.Equals, 1234)
   359  
   360  	expectInfo, exists := machConf.StateServingInfo()
   361  	c.Assert(exists, jc.IsTrue)
   362  	c.Assert(expectInfo.SharedSecret, gc.Equals, "")
   363  	c.Assert(expectInfo.SystemIdentity, gc.Equals, "")
   364  
   365  	servingInfo := s.fakeEnsureMongo.Info
   366  	c.Assert(len(servingInfo.SharedSecret), gc.Not(gc.Equals), 0)
   367  	c.Assert(len(servingInfo.SystemIdentity), gc.Not(gc.Equals), 0)
   368  	servingInfo.SharedSecret = ""
   369  	servingInfo.SystemIdentity = ""
   370  	expect := cmdutil.ParamsStateServingInfoToStateStateServingInfo(expectInfo)
   371  	c.Assert(servingInfo, jc.DeepEquals, expect)
   372  	expectDialAddrs := []string{fmt.Sprintf("127.0.0.1:%d", expectInfo.StatePort)}
   373  	gotDialAddrs := s.fakeEnsureMongo.InitiateParams.DialInfo.Addrs
   374  	c.Assert(gotDialAddrs, gc.DeepEquals, expectDialAddrs)
   375  
   376  	c.Assert(s.fakeEnsureMongo.InitiateParams.MemberHostPort, gc.Equals, expectDialAddrs[0])
   377  	c.Assert(s.fakeEnsureMongo.InitiateParams.User, gc.Equals, "")
   378  	c.Assert(s.fakeEnsureMongo.InitiateParams.Password, gc.Equals, "")
   379  
   380  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   381  		Info: mongo.Info{
   382  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   383  			CACert: testing.CACert,
   384  		},
   385  		Password: testPassword,
   386  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	defer st.Close()
   389  	machines, err := st.AllMachines()
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	c.Assert(machines, gc.HasLen, 1)
   392  
   393  	instid, err := machines[0].InstanceId()
   394  	c.Assert(err, jc.ErrorIsNil)
   395  	c.Assert(instid, gc.Equals, instance.Id(string(s.instanceId)))
   396  
   397  	stateHw, err := machines[0].HardwareCharacteristics()
   398  	c.Assert(err, jc.ErrorIsNil)
   399  	c.Assert(stateHw, gc.NotNil)
   400  	c.Assert(*stateHw, gc.DeepEquals, hw)
   401  
   402  	cons, err := st.ModelConstraints()
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	c.Assert(&cons, jc.Satisfies, constraints.IsEmpty)
   405  
   406  	cfg, err := st.ModelConfig()
   407  	c.Assert(err, jc.ErrorIsNil)
   408  	c.Assert(cfg.AuthorizedKeys(), gc.Equals, s.envcfg.AuthorizedKeys()+"\npublic-key")
   409  }
   410  
   411  func (s *BootstrapSuite) TestInitializeEnvironmentInvalidOplogSize(c *gc.C) {
   412  	s.mongoOplogSize = "NaN"
   413  	hw := instance.MustParseHardware("arch=amd64 mem=8G")
   414  	_, cmd, err := s.initBootstrapCommand(
   415  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
   416  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   417  		"--instance-id", string(s.instanceId), "--hardware", hw.String(),
   418  	)
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	err = cmd.Run(nil)
   421  	c.Assert(err, gc.ErrorMatches, `invalid oplog size: "NaN"`)
   422  }
   423  
   424  func (s *BootstrapSuite) TestInitializeEnvironmentToolsNotFound(c *gc.C) {
   425  	// bootstrap with 1.99.1 but there will be no tools so version will be reset.
   426  	envcfg, err := s.envcfg.Apply(map[string]interface{}{
   427  		"agent-version": "1.99.1",
   428  	})
   429  	c.Assert(err, jc.ErrorIsNil)
   430  	b64yamlControllerModelConfig := b64yaml(envcfg.AllAttrs()).encode()
   431  
   432  	hw := instance.MustParseHardware("arch=amd64 mem=8G")
   433  	_, cmd, err := s.initBootstrapCommand(
   434  		c, nil, "--model-config", b64yamlControllerModelConfig,
   435  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   436  		"--instance-id", string(s.instanceId), "--hardware", hw.String(),
   437  	)
   438  	c.Assert(err, jc.ErrorIsNil)
   439  	err = cmd.Run(nil)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  
   442  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   443  		Info: mongo.Info{
   444  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   445  			CACert: testing.CACert,
   446  		},
   447  		Password: testPassword,
   448  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   449  	c.Assert(err, jc.ErrorIsNil)
   450  	defer st.Close()
   451  
   452  	cfg, err := st.ModelConfig()
   453  	c.Assert(err, jc.ErrorIsNil)
   454  	vers, ok := cfg.AgentVersion()
   455  	c.Assert(ok, jc.IsTrue)
   456  	c.Assert(vers.String(), gc.Equals, "1.99.0")
   457  }
   458  
   459  func (s *BootstrapSuite) TestSetConstraints(c *gc.C) {
   460  	bootstrapCons := constraints.Value{Mem: uint64p(4096), CpuCores: uint64p(4)}
   461  	modelCons := constraints.Value{Mem: uint64p(2048), CpuCores: uint64p(2)}
   462  	_, cmd, err := s.initBootstrapCommand(c, nil,
   463  		"--model-config", s.b64yamlControllerModelConfig,
   464  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   465  		"--instance-id", string(s.instanceId),
   466  		"--bootstrap-constraints", bootstrapCons.String(),
   467  		"--constraints", modelCons.String(),
   468  	)
   469  	c.Assert(err, jc.ErrorIsNil)
   470  	err = cmd.Run(nil)
   471  	c.Assert(err, jc.ErrorIsNil)
   472  
   473  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   474  		Info: mongo.Info{
   475  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   476  			CACert: testing.CACert,
   477  		},
   478  		Password: testPassword,
   479  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   480  	c.Assert(err, jc.ErrorIsNil)
   481  	defer st.Close()
   482  
   483  	cons, err := st.ModelConstraints()
   484  	c.Assert(err, jc.ErrorIsNil)
   485  	c.Assert(cons, gc.DeepEquals, modelCons)
   486  
   487  	machines, err := st.AllMachines()
   488  	c.Assert(err, jc.ErrorIsNil)
   489  	c.Assert(machines, gc.HasLen, 1)
   490  	cons, err = machines[0].Constraints()
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	c.Assert(cons, gc.DeepEquals, bootstrapCons)
   493  }
   494  
   495  func uint64p(v uint64) *uint64 {
   496  	return &v
   497  }
   498  
   499  func (s *BootstrapSuite) TestDefaultMachineJobs(c *gc.C) {
   500  	expectedJobs := []state.MachineJob{
   501  		state.JobManageModel,
   502  		state.JobHostUnits,
   503  		state.JobManageNetworking,
   504  	}
   505  	_, cmd, err := s.initBootstrapCommand(c, nil,
   506  		"--model-config", s.b64yamlControllerModelConfig,
   507  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   508  		"--instance-id", string(s.instanceId),
   509  	)
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	err = cmd.Run(nil)
   512  	c.Assert(err, jc.ErrorIsNil)
   513  
   514  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   515  		Info: mongo.Info{
   516  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   517  			CACert: testing.CACert,
   518  		},
   519  		Password: testPassword,
   520  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   521  	c.Assert(err, jc.ErrorIsNil)
   522  	defer st.Close()
   523  	m, err := st.Machine("0")
   524  	c.Assert(err, jc.ErrorIsNil)
   525  	c.Assert(m.Jobs(), gc.DeepEquals, expectedJobs)
   526  }
   527  
   528  func (s *BootstrapSuite) TestConfiguredMachineJobs(c *gc.C) {
   529  	jobs := []multiwatcher.MachineJob{multiwatcher.JobManageModel}
   530  	_, cmd, err := s.initBootstrapCommand(c, jobs,
   531  		"--model-config", s.b64yamlControllerModelConfig,
   532  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   533  		"--instance-id", string(s.instanceId),
   534  	)
   535  	c.Assert(err, jc.ErrorIsNil)
   536  	err = cmd.Run(nil)
   537  	c.Assert(err, jc.ErrorIsNil)
   538  
   539  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   540  		Info: mongo.Info{
   541  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   542  			CACert: testing.CACert,
   543  		},
   544  		Password: testPassword,
   545  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   546  	c.Assert(err, jc.ErrorIsNil)
   547  	defer st.Close()
   548  	m, err := st.Machine("0")
   549  	c.Assert(err, jc.ErrorIsNil)
   550  	c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel})
   551  }
   552  
   553  func testOpenState(c *gc.C, info *mongo.MongoInfo, expectErrType error) {
   554  	st, err := state.Open(testing.ModelTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   555  	if st != nil {
   556  		st.Close()
   557  	}
   558  	if expectErrType != nil {
   559  		c.Assert(err, gc.FitsTypeOf, expectErrType)
   560  	} else {
   561  		c.Assert(err, jc.ErrorIsNil)
   562  	}
   563  }
   564  
   565  func (s *BootstrapSuite) TestInitialPassword(c *gc.C) {
   566  	machineConf, cmd, err := s.initBootstrapCommand(c, nil,
   567  		"--model-config", s.b64yamlControllerModelConfig,
   568  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   569  		"--instance-id", string(s.instanceId),
   570  	)
   571  	c.Assert(err, jc.ErrorIsNil)
   572  
   573  	err = cmd.Run(nil)
   574  	c.Assert(err, jc.ErrorIsNil)
   575  
   576  	info := &mongo.MongoInfo{
   577  		Info: mongo.Info{
   578  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   579  			CACert: testing.CACert,
   580  		},
   581  	}
   582  
   583  	// Check we can log in to mongo as admin.
   584  	// TODO(dfc) does passing nil for the admin user name make your skin crawl ? mine too.
   585  	info.Tag, info.Password = nil, testPassword
   586  	st, err := state.Open(testing.ModelTag, info, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   587  	c.Assert(err, jc.ErrorIsNil)
   588  	defer st.Close()
   589  
   590  	// We're running Mongo with --noauth; let's explicitly verify
   591  	// that we can login as that user. Even with --noauth, an
   592  	// explicit Login will still be verified.
   593  	adminDB := st.MongoSession().DB("admin")
   594  	err = adminDB.Login("admin", "invalid-password")
   595  	c.Assert(err, gc.ErrorMatches, "auth fail(s|ed)")
   596  	err = adminDB.Login("admin", info.Password)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  
   599  	// Check that the admin user has been given an appropriate
   600  	// password
   601  	u, err := st.User(names.NewLocalUserTag("admin"))
   602  	c.Assert(err, jc.ErrorIsNil)
   603  	c.Assert(u.PasswordValid(testPassword), jc.IsTrue)
   604  
   605  	// Check that the machine configuration has been given a new
   606  	// password and that we can connect to mongo as that machine
   607  	// and that the in-mongo password also verifies correctly.
   608  	machineConf1, err := agent.ReadConfig(agent.ConfigPath(machineConf.DataDir(), names.NewMachineTag("0")))
   609  	c.Assert(err, jc.ErrorIsNil)
   610  
   611  	stateinfo, ok := machineConf1.MongoInfo()
   612  	c.Assert(ok, jc.IsTrue)
   613  	st, err = state.Open(testing.ModelTag, stateinfo, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   614  	c.Assert(err, jc.ErrorIsNil)
   615  	defer st.Close()
   616  
   617  	m, err := st.Machine("0")
   618  	c.Assert(err, jc.ErrorIsNil)
   619  	c.Assert(m.HasVote(), jc.IsTrue)
   620  }
   621  
   622  var bootstrapArgTests = []struct {
   623  	input              []string
   624  	err                string
   625  	expectedInstanceId string
   626  	expectedHardware   instance.HardwareCharacteristics
   627  	expectedConfig     map[string]interface{}
   628  }{
   629  	{
   630  		// no value supplied for model-config
   631  		err: "--model-config option must be set",
   632  	}, {
   633  		// empty model-config
   634  		input: []string{"--model-config", ""},
   635  		err:   "--model-config option must be set",
   636  	}, {
   637  		// wrong, should be base64
   638  		input: []string{"--model-config", "name: banana\n"},
   639  		err:   ".*illegal base64 data at input byte.*",
   640  	}, {
   641  		// no value supplied for hosted-model-config
   642  		input: []string{
   643  			"--model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   644  		},
   645  		err: "--hosted-model-config option must be set",
   646  	}, {
   647  		// no value supplied for instance-id
   648  		input: []string{
   649  			"--model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   650  			"--hosted-model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   651  		},
   652  		err: "--instance-id option must be set",
   653  	}, {
   654  		// empty instance-id
   655  		input: []string{
   656  			"--model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   657  			"--hosted-model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   658  			"--instance-id", "",
   659  		},
   660  		err: "--instance-id option must be set",
   661  	}, {
   662  		input: []string{
   663  			"--model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   664  			"--hosted-model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   665  			"--instance-id", "anything",
   666  		},
   667  		expectedInstanceId: "anything",
   668  		expectedConfig:     map[string]interface{}{"name": "banana"},
   669  	}, {
   670  		input: []string{
   671  			"--model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   672  			"--hosted-model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   673  			"--instance-id", "anything",
   674  			"--hardware", "nonsense",
   675  		},
   676  		err: `invalid value "nonsense" for flag --hardware: malformed characteristic "nonsense"`,
   677  	}, {
   678  		input: []string{
   679  			"--model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   680  			"--hosted-model-config", base64.StdEncoding.EncodeToString([]byte("name: banana\n")),
   681  			"--instance-id", "anything",
   682  			"--hardware", "arch=amd64 cpu-cores=4 root-disk=2T",
   683  		},
   684  		expectedInstanceId: "anything",
   685  		expectedHardware:   instance.MustParseHardware("arch=amd64 cpu-cores=4 root-disk=2T"),
   686  		expectedConfig:     map[string]interface{}{"name": "banana"},
   687  	},
   688  }
   689  
   690  func (s *BootstrapSuite) TestBootstrapArgs(c *gc.C) {
   691  	for i, t := range bootstrapArgTests {
   692  		c.Logf("test %d", i)
   693  		var args []string
   694  		args = append(args, t.input...)
   695  		_, cmd, err := s.initBootstrapCommand(c, nil, args...)
   696  		if t.err == "" {
   697  			c.Assert(cmd, gc.NotNil)
   698  			c.Assert(err, jc.ErrorIsNil)
   699  			c.Assert(cmd.ControllerModelConfig, gc.DeepEquals, t.expectedConfig)
   700  			c.Assert(cmd.InstanceId, gc.Equals, t.expectedInstanceId)
   701  			c.Assert(cmd.Hardware, gc.DeepEquals, t.expectedHardware)
   702  		} else {
   703  			c.Assert(err, gc.ErrorMatches, t.err)
   704  		}
   705  	}
   706  }
   707  
   708  func (s *BootstrapSuite) TestInitializeStateArgs(c *gc.C) {
   709  	var called int
   710  	initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, hostedModelConfig map[string]interface{}, machineCfg agentbootstrap.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) {
   711  		called++
   712  		c.Assert(dialOpts.Direct, jc.IsTrue)
   713  		c.Assert(dialOpts.Timeout, gc.Equals, 30*time.Second)
   714  		c.Assert(dialOpts.SocketTimeout, gc.Equals, 123*time.Second)
   715  		c.Assert(hostedModelConfig, jc.DeepEquals, map[string]interface{}{
   716  			"name": "hosted-model",
   717  			"uuid": s.hostedModelUUID,
   718  		})
   719  		return nil, nil, errors.New("failed to initialize state")
   720  	}
   721  	s.PatchValue(&agentInitializeState, initializeState)
   722  	_, cmd, err := s.initBootstrapCommand(c, nil,
   723  		"--model-config", s.b64yamlControllerModelConfig,
   724  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   725  		"--instance-id", string(s.instanceId),
   726  	)
   727  	c.Assert(err, jc.ErrorIsNil)
   728  	err = cmd.Run(nil)
   729  	c.Assert(err, gc.ErrorMatches, "failed to initialize state")
   730  	c.Assert(called, gc.Equals, 1)
   731  }
   732  
   733  func (s *BootstrapSuite) TestInitializeStateMinSocketTimeout(c *gc.C) {
   734  	var called int
   735  	initializeState := func(_ names.UserTag, _ agent.ConfigSetter, envCfg *config.Config, hostedModelConfig map[string]interface{}, machineCfg agentbootstrap.BootstrapMachineConfig, dialOpts mongo.DialOpts, policy state.Policy) (_ *state.State, _ *state.Machine, resultErr error) {
   736  		called++
   737  		c.Assert(dialOpts.Direct, jc.IsTrue)
   738  		c.Assert(dialOpts.SocketTimeout, gc.Equals, 1*time.Minute)
   739  		return nil, nil, errors.New("failed to initialize state")
   740  	}
   741  
   742  	envcfg, err := s.envcfg.Apply(map[string]interface{}{
   743  		"bootstrap-timeout": "13",
   744  	})
   745  	c.Assert(err, jc.ErrorIsNil)
   746  	b64yamlControllerModelConfig := b64yaml(envcfg.AllAttrs()).encode()
   747  
   748  	s.PatchValue(&agentInitializeState, initializeState)
   749  	_, cmd, err := s.initBootstrapCommand(c, nil,
   750  		"--model-config", b64yamlControllerModelConfig,
   751  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   752  		"--instance-id", string(s.instanceId),
   753  	)
   754  	c.Assert(err, jc.ErrorIsNil)
   755  	err = cmd.Run(nil)
   756  	c.Assert(err, gc.ErrorMatches, "failed to initialize state")
   757  	c.Assert(called, gc.Equals, 1)
   758  }
   759  
   760  func (s *BootstrapSuite) TestSystemIdentityWritten(c *gc.C) {
   761  	_, err := os.Stat(filepath.Join(s.dataDir, agent.SystemIdentity))
   762  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   763  
   764  	_, cmd, err := s.initBootstrapCommand(c, nil,
   765  		"--model-config", s.b64yamlControllerModelConfig,
   766  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   767  		"--instance-id", string(s.instanceId),
   768  	)
   769  	c.Assert(err, jc.ErrorIsNil)
   770  	err = cmd.Run(nil)
   771  	c.Assert(err, jc.ErrorIsNil)
   772  
   773  	data, err := ioutil.ReadFile(filepath.Join(s.dataDir, agent.SystemIdentity))
   774  	c.Assert(err, jc.ErrorIsNil)
   775  	c.Assert(string(data), gc.Equals, "private-key")
   776  }
   777  
   778  func (s *BootstrapSuite) TestDownloadedToolsMetadata(c *gc.C) {
   779  	// Tools downloaded by cloud-init script.
   780  	s.testToolsMetadata(c, false)
   781  }
   782  
   783  func (s *BootstrapSuite) TestUploadedToolsMetadata(c *gc.C) {
   784  	// Tools uploaded over ssh.
   785  	s.writeDownloadedTools(c, &tools.Tools{
   786  		Version: version.Binary{
   787  			Number: jujuversion.Current,
   788  			Arch:   arch.HostArch(),
   789  			Series: series.HostSeries(),
   790  		},
   791  		URL: "file:///does/not/matter",
   792  	})
   793  	s.testToolsMetadata(c, true)
   794  }
   795  
   796  func (s *BootstrapSuite) testToolsMetadata(c *gc.C, exploded bool) {
   797  	envtesting.RemoveFakeToolsMetadata(c, s.toolsStorage)
   798  
   799  	_, cmd, err := s.initBootstrapCommand(c, nil,
   800  		"--model-config", s.b64yamlControllerModelConfig,
   801  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   802  		"--instance-id", string(s.instanceId),
   803  	)
   804  	c.Assert(err, jc.ErrorIsNil)
   805  	err = cmd.Run(nil)
   806  	c.Assert(err, jc.ErrorIsNil)
   807  
   808  	// We don't write metadata at bootstrap anymore.
   809  	simplestreamsMetadata, err := envtools.ReadMetadata(s.toolsStorage, "released")
   810  	c.Assert(err, jc.ErrorIsNil)
   811  	c.Assert(simplestreamsMetadata, gc.HasLen, 0)
   812  
   813  	// The tools should have been added to tools storage, and
   814  	// exploded into each of the supported series of
   815  	// the same operating system if the tools were uploaded.
   816  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   817  		Info: mongo.Info{
   818  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   819  			CACert: testing.CACert,
   820  		},
   821  		Password: testPassword,
   822  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   823  	c.Assert(err, jc.ErrorIsNil)
   824  	defer st.Close()
   825  	expectedSeries := make(set.Strings)
   826  	if exploded {
   827  		for _, ser := range series.SupportedSeries() {
   828  			os, err := series.GetOSFromSeries(ser)
   829  			c.Assert(err, jc.ErrorIsNil)
   830  			hostos, err := series.GetOSFromSeries(series.HostSeries())
   831  			c.Assert(err, jc.ErrorIsNil)
   832  			if os == hostos {
   833  				expectedSeries.Add(ser)
   834  			}
   835  		}
   836  	} else {
   837  		expectedSeries.Add(series.HostSeries())
   838  	}
   839  
   840  	storage, err := st.ToolsStorage()
   841  	c.Assert(err, jc.ErrorIsNil)
   842  	defer storage.Close()
   843  	metadata, err := storage.AllMetadata()
   844  	c.Assert(err, jc.ErrorIsNil)
   845  	c.Assert(metadata, gc.HasLen, expectedSeries.Size())
   846  	for _, m := range metadata {
   847  		v := version.MustParseBinary(m.Version)
   848  		c.Assert(expectedSeries.Contains(v.Series), jc.IsTrue)
   849  	}
   850  }
   851  
   852  const (
   853  	indexContent = `{
   854      "index": {
   855          "com.ubuntu.cloud:%v": {
   856              "updated": "Fri, 17 Jul 2015 13:42:48 +1000",
   857              "format": "products:1.0",
   858              "datatype": "image-ids",
   859              "cloudname": "custom",
   860              "clouds": [
   861                  {
   862                      "region": "%v",
   863                      "endpoint": "endpoint"
   864                  }
   865              ],
   866              "path": "streams/v1/products.json",
   867              "products": [
   868                  "com.ubuntu.cloud:server:14.04:%v"
   869              ]
   870          }
   871      },
   872      "updated": "Fri, 17 Jul 2015 13:42:48 +1000",
   873      "format": "index:1.0"
   874  }`
   875  
   876  	productContent = `{
   877      "products": {
   878          "com.ubuntu.cloud:server:14.04:%v": {
   879              "version": "14.04",
   880              "arch": "%v",
   881              "versions": {
   882                  "20151707": {
   883                      "items": {
   884                          "%v": {
   885                              "id": "%v",
   886                              "root_store": "%v",
   887                              "virt": "%v",
   888                              "region": "%v",
   889                              "endpoint": "endpoint"
   890                          }
   891                      }
   892                  }
   893              }
   894          }
   895       },
   896      "updated": "Fri, 17 Jul 2015 13:42:48 +1000",
   897      "format": "products:1.0",
   898      "content_id": "com.ubuntu.cloud:%v"
   899  }`
   900  )
   901  
   902  func writeTempFiles(c *gc.C, metadataDir string, expected []struct{ path, content string }) {
   903  	for _, pair := range expected {
   904  		path := filepath.Join(metadataDir, pair.path)
   905  		err := os.MkdirAll(filepath.Dir(path), 0755)
   906  		c.Assert(err, jc.ErrorIsNil)
   907  		err = ioutil.WriteFile(path, []byte(pair.content), 0644)
   908  		c.Assert(err, jc.ErrorIsNil)
   909  	}
   910  }
   911  
   912  func createImageMetadata(c *gc.C) (string, cloudimagemetadata.Metadata, []struct{ path, content string }) {
   913  	// setup data for this test
   914  	metadata := cloudimagemetadata.Metadata{
   915  		MetadataAttributes: cloudimagemetadata.MetadataAttributes{
   916  			Region:          "region",
   917  			Series:          "trusty",
   918  			Arch:            "amd64",
   919  			VirtType:        "virtType",
   920  			RootStorageType: "rootStore",
   921  			Source:          "custom"},
   922  		Priority: simplestreams.CUSTOM_CLOUD_DATA,
   923  		ImageId:  "imageId"}
   924  
   925  	// setup files containing test's data
   926  	metadataDir := c.MkDir()
   927  	expected := []struct{ path, content string }{{
   928  		path:    "streams/v1/index.json",
   929  		content: fmt.Sprintf(indexContent, metadata.Source, metadata.Region, metadata.Arch),
   930  	}, {
   931  		path:    "streams/v1/products.json",
   932  		content: fmt.Sprintf(productContent, metadata.Arch, metadata.Arch, metadata.ImageId, metadata.ImageId, metadata.RootStorageType, metadata.VirtType, metadata.Region, metadata.Source),
   933  	}, {
   934  		path:    "wayward/file.txt",
   935  		content: "ghi",
   936  	}}
   937  	writeTempFiles(c, metadataDir, expected)
   938  	return metadataDir, metadata, expected
   939  }
   940  
   941  func assertWrittenToState(c *gc.C, metadata cloudimagemetadata.Metadata) {
   942  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
   943  		Info: mongo.Info{
   944  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
   945  			CACert: testing.CACert,
   946  		},
   947  		Password: testPassword,
   948  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
   949  	c.Assert(err, jc.ErrorIsNil)
   950  	defer st.Close()
   951  
   952  	// find all image metadata in state
   953  	all, err := st.CloudImageMetadataStorage.FindMetadata(cloudimagemetadata.MetadataFilter{})
   954  	c.Assert(err, jc.ErrorIsNil)
   955  	c.Assert(all, gc.DeepEquals, map[string][]cloudimagemetadata.Metadata{
   956  		metadata.Source: []cloudimagemetadata.Metadata{metadata},
   957  	})
   958  }
   959  
   960  func (s *BootstrapSuite) TestStructuredImageMetadataStored(c *gc.C) {
   961  	dir, m, _ := createImageMetadata(c)
   962  	_, cmd, err := s.initBootstrapCommand(
   963  		c, nil,
   964  		"--model-config", s.b64yamlControllerModelConfig,
   965  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   966  		"--instance-id", string(s.instanceId),
   967  		"--image-metadata", dir,
   968  	)
   969  	c.Assert(err, jc.ErrorIsNil)
   970  	err = cmd.Run(nil)
   971  	c.Assert(err, jc.ErrorIsNil)
   972  
   973  	// This metadata should have also been written to state...
   974  	// m.Version would be deduced from m.Series
   975  	m.Version = "14.04"
   976  	assertWrittenToState(c, m)
   977  
   978  }
   979  
   980  func (s *BootstrapSuite) TestCustomDataSourceHasKey(c *gc.C) {
   981  	dir, _, _ := createImageMetadata(c)
   982  	_, cmd, err := s.initBootstrapCommand(
   983  		c, nil,
   984  		"--model-config", s.b64yamlControllerModelConfig,
   985  		"--hosted-model-config", s.b64yamlHostedModelConfig,
   986  		"--instance-id", string(s.instanceId),
   987  		"--image-metadata", dir,
   988  	)
   989  	c.Assert(err, jc.ErrorIsNil)
   990  
   991  	called := false
   992  	s.PatchValue(&storeImageMetadataFromFiles, func(st *state.State, env environs.Environ, source simplestreams.DataSource) error {
   993  		called = true
   994  		// This data source does not require to contain signed data.
   995  		// However, it may still contain it.
   996  		// Since we will always try to read signed data first,
   997  		// we want to be able to try to read this signed data
   998  		// with a user provided public key. For this test, none is provided.
   999  		// Bugs #1542127, #1542131
  1000  		c.Assert(source.PublicSigningKey(), gc.Equals, "")
  1001  		return nil
  1002  	})
  1003  	err = cmd.Run(nil)
  1004  	c.Assert(err, jc.ErrorIsNil)
  1005  	c.Assert(called, jc.IsTrue)
  1006  }
  1007  
  1008  func (s *BootstrapSuite) TestStructuredImageMetadataInvalidSeries(c *gc.C) {
  1009  	dir, _, _ := createImageMetadata(c)
  1010  
  1011  	msg := "my test error"
  1012  	s.PatchValue(&seriesFromVersion, func(string) (string, error) {
  1013  		return "", errors.New(msg)
  1014  	})
  1015  
  1016  	_, cmd, err := s.initBootstrapCommand(
  1017  		c, nil,
  1018  		"--model-config", s.b64yamlControllerModelConfig,
  1019  		"--hosted-model-config", s.b64yamlHostedModelConfig,
  1020  		"--instance-id", string(s.instanceId),
  1021  		"--image-metadata", dir,
  1022  	)
  1023  	c.Assert(err, jc.ErrorIsNil)
  1024  	err = cmd.Run(nil)
  1025  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf(".*%v.*", msg))
  1026  }
  1027  
  1028  func (s *BootstrapSuite) TestImageMetadata(c *gc.C) {
  1029  	metadataDir, _, expected := createImageMetadata(c)
  1030  
  1031  	var stor statetesting.MapStorage
  1032  	s.PatchValue(&newStateStorage, func(string, *mgo.Session) statestorage.Storage {
  1033  		return &stor
  1034  	})
  1035  
  1036  	_, cmd, err := s.initBootstrapCommand(
  1037  		c, nil,
  1038  		"--model-config", s.b64yamlControllerModelConfig,
  1039  		"--hosted-model-config", s.b64yamlHostedModelConfig,
  1040  		"--instance-id", string(s.instanceId),
  1041  		"--image-metadata", metadataDir,
  1042  	)
  1043  	c.Assert(err, jc.ErrorIsNil)
  1044  	err = cmd.Run(nil)
  1045  	c.Assert(err, jc.ErrorIsNil)
  1046  
  1047  	// The contents of the directory should have been added to
  1048  	// environment storage.
  1049  	for _, pair := range expected {
  1050  		r, length, err := stor.Get(pair.path)
  1051  		c.Assert(err, jc.ErrorIsNil)
  1052  		data, err := ioutil.ReadAll(r)
  1053  		r.Close()
  1054  		c.Assert(err, jc.ErrorIsNil)
  1055  		c.Assert(length, gc.Equals, int64(len(pair.content)))
  1056  		c.Assert(data, gc.HasLen, int(length))
  1057  		c.Assert(string(data), gc.Equals, pair.content)
  1058  	}
  1059  }
  1060  
  1061  func (s *BootstrapSuite) makeTestEnv(c *gc.C) {
  1062  	attrs := dummy.SampleConfig().Merge(
  1063  		testing.Attrs{
  1064  			"agent-version":     jujuversion.Current.String(),
  1065  			"bootstrap-timeout": "123",
  1066  		},
  1067  	).Delete("admin-secret", "ca-private-key")
  1068  	cfg, err := config.New(config.NoDefaults, attrs)
  1069  	c.Assert(err, jc.ErrorIsNil)
  1070  	provider, err := environs.Provider(cfg.Type())
  1071  	c.Assert(err, jc.ErrorIsNil)
  1072  	cfg, err = provider.BootstrapConfig(environs.BootstrapConfigParams{Config: cfg})
  1073  	c.Assert(err, jc.ErrorIsNil)
  1074  	env, err := provider.PrepareForBootstrap(nullContext(), cfg)
  1075  	c.Assert(err, jc.ErrorIsNil)
  1076  
  1077  	s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey)
  1078  	envtesting.MustUploadFakeTools(s.toolsStorage, cfg.AgentStream(), cfg.AgentStream())
  1079  	inst, _, _, err := jujutesting.StartInstance(env, "0")
  1080  	c.Assert(err, jc.ErrorIsNil)
  1081  	s.instanceId = inst.Id()
  1082  
  1083  	addresses, err := inst.Addresses()
  1084  	c.Assert(err, jc.ErrorIsNil)
  1085  	addr, _ := network.SelectPublicAddress(addresses)
  1086  	s.bootstrapName = addr.Value
  1087  	s.envcfg = env.Config()
  1088  	s.b64yamlControllerModelConfig = b64yaml(s.envcfg.AllAttrs()).encode()
  1089  
  1090  	s.hostedModelUUID = utils.MustNewUUID().String()
  1091  	hostedModelConfigAttrs := map[string]interface{}{
  1092  		"name": "hosted-model",
  1093  		"uuid": s.hostedModelUUID,
  1094  	}
  1095  	s.b64yamlHostedModelConfig = b64yaml(hostedModelConfigAttrs).encode()
  1096  }
  1097  
  1098  func nullContext() environs.BootstrapContext {
  1099  	ctx, _ := cmd.DefaultContext()
  1100  	ctx.Stdin = io.LimitReader(nil, 0)
  1101  	ctx.Stdout = ioutil.Discard
  1102  	ctx.Stderr = ioutil.Discard
  1103  	return modelcmd.BootstrapContext(ctx)
  1104  }
  1105  
  1106  type b64yaml map[string]interface{}
  1107  
  1108  func (m b64yaml) encode() string {
  1109  	data, err := goyaml.Marshal(m)
  1110  	if err != nil {
  1111  		panic(err)
  1112  	}
  1113  	return base64.StdEncoding.EncodeToString(data)
  1114  }
  1115  
  1116  func (s *BootstrapSuite) TestDefaultStoragePools(c *gc.C) {
  1117  	_, cmd, err := s.initBootstrapCommand(
  1118  		c, nil, "--model-config", s.b64yamlControllerModelConfig,
  1119  		"--hosted-model-config", s.b64yamlHostedModelConfig,
  1120  		"--instance-id", string(s.instanceId),
  1121  	)
  1122  	c.Assert(err, jc.ErrorIsNil)
  1123  	err = cmd.Run(nil)
  1124  	c.Assert(err, jc.ErrorIsNil)
  1125  
  1126  	st, err := state.Open(testing.ModelTag, &mongo.MongoInfo{
  1127  		Info: mongo.Info{
  1128  			Addrs:  []string{gitjujutesting.MgoServer.Addr()},
  1129  			CACert: testing.CACert,
  1130  		},
  1131  		Password: testPassword,
  1132  	}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
  1133  	c.Assert(err, jc.ErrorIsNil)
  1134  	defer st.Close()
  1135  
  1136  	settings := state.NewStateSettings(st)
  1137  	pm := poolmanager.New(settings)
  1138  	for _, p := range []string{"ebs-ssd"} {
  1139  		_, err = pm.Get(p)
  1140  		c.Assert(err, jc.ErrorIsNil)
  1141  	}
  1142  }