github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cloudconfig/userdatacfg_test.go (about)

     1  // Copyright 2012, 2013, 2015 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package cloudconfig_test
     6  
     7  import (
     8  	"encoding/base64"
     9  	"fmt"
    10  	"path"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/names"
    16  	jc "github.com/juju/testing/checkers"
    17  	pacconf "github.com/juju/utils/packaging/config"
    18  	"github.com/juju/utils/set"
    19  	gc "gopkg.in/check.v1"
    20  	goyaml "gopkg.in/yaml.v1"
    21  
    22  	"github.com/juju/juju/agent"
    23  	"github.com/juju/juju/api"
    24  	"github.com/juju/juju/apiserver/params"
    25  	"github.com/juju/juju/cloudconfig"
    26  	"github.com/juju/juju/cloudconfig/cloudinit"
    27  	"github.com/juju/juju/cloudconfig/instancecfg"
    28  	"github.com/juju/juju/constraints"
    29  	"github.com/juju/juju/environs/config"
    30  	"github.com/juju/juju/environs/imagemetadata"
    31  	"github.com/juju/juju/juju/paths"
    32  	jujutesting "github.com/juju/juju/juju/testing"
    33  	"github.com/juju/juju/mongo"
    34  	"github.com/juju/juju/state/multiwatcher"
    35  	"github.com/juju/juju/testing"
    36  	"github.com/juju/juju/tools"
    37  	"github.com/juju/juju/version"
    38  )
    39  
    40  type cloudinitSuite struct {
    41  	testing.BaseSuite
    42  }
    43  
    44  var _ = gc.Suite(&cloudinitSuite{})
    45  
    46  var (
    47  	envConstraints = constraints.MustParse("mem=2G")
    48  
    49  	allMachineJobs = []multiwatcher.MachineJob{
    50  		multiwatcher.JobManageEnviron,
    51  		multiwatcher.JobHostUnits,
    52  		multiwatcher.JobManageNetworking,
    53  	}
    54  	normalMachineJobs = []multiwatcher.MachineJob{
    55  		multiwatcher.JobHostUnits,
    56  	}
    57  )
    58  
    59  func jujuLogDir(series string) string {
    60  	return path.Join(must(paths.LogDir(series)), "juju")
    61  }
    62  
    63  func jujuDataDir(series string) string {
    64  	return must(paths.DataDir(series))
    65  }
    66  
    67  func cloudInitOutputLog(logDir string) string {
    68  	return path.Join(logDir, "cloud-init-output.log")
    69  }
    70  
    71  func metricsSpoolDir(series string) string {
    72  	return must(paths.MetricsSpoolDir(series))
    73  }
    74  
    75  // TODO: add this to the utils package
    76  func must(s string, err error) string {
    77  	if err != nil {
    78  		panic(err)
    79  	}
    80  	return s
    81  }
    82  
    83  var stateServingInfo = &params.StateServingInfo{
    84  	Cert:         string(serverCert),
    85  	PrivateKey:   string(serverKey),
    86  	CAPrivateKey: "ca-private-key",
    87  	StatePort:    37017,
    88  	APIPort:      17070,
    89  }
    90  
    91  // testcfg wraps InstanceConfig and provides helpers to modify it as
    92  // needed for specific test cases before using it. Most methods return
    93  // the method receiver (cfg) after (possibly) modifying it to allow
    94  // chaining calls.
    95  type testInstanceConfig instancecfg.InstanceConfig
    96  
    97  // makeTestConfig returns a minimal instance config for a non state
    98  // server machine (unless bootstrap is true) for the given series.
    99  func makeTestConfig(series string, bootstrap bool) *testInstanceConfig {
   100  	const defaultMachineID = "99"
   101  
   102  	cfg := new(testInstanceConfig)
   103  	cfg.AuthorizedKeys = "sshkey1"
   104  	cfg.AgentEnvironment = map[string]string{
   105  		agent.ProviderType: "dummy",
   106  	}
   107  	cfg.MachineNonce = "FAKE_NONCE"
   108  	cfg.InstanceId = "i-machine"
   109  	cfg.Jobs = normalMachineJobs
   110  	cfg.MetricsSpoolDir = metricsSpoolDir(series)
   111  	// MongoInfo and APIInfo (sans Tag) must be initialized before
   112  	// calling setMachineID().
   113  	cfg.MongoInfo = &mongo.MongoInfo{
   114  		Password: "arble",
   115  		Info: mongo.Info{
   116  			Addrs:  []string{"state-addr.testing.invalid:12345"},
   117  			CACert: "CA CERT\n" + testing.CACert,
   118  		},
   119  	}
   120  	cfg.APIInfo = &api.Info{
   121  		Addrs:      []string{"state-addr.testing.invalid:54321"},
   122  		Password:   "bletch",
   123  		CACert:     "CA CERT\n" + testing.CACert,
   124  		EnvironTag: testing.EnvironmentTag,
   125  	}
   126  	cfg.setMachineID(defaultMachineID)
   127  	cfg.setSeries(series)
   128  	if bootstrap {
   129  		return cfg.setStateServer()
   130  	}
   131  	return cfg
   132  }
   133  
   134  // makeBootstrapConfig is a shortcut to call makeTestConfig(series, true).
   135  func makeBootstrapConfig(series string) *testInstanceConfig {
   136  	return makeTestConfig(series, true)
   137  }
   138  
   139  // makeNormalConfig is a shortcut to call makeTestConfig(series,
   140  // false).
   141  func makeNormalConfig(series string) *testInstanceConfig {
   142  	return makeTestConfig(series, false)
   143  }
   144  
   145  // setMachineID updates MachineId, MachineAgentServiceName,
   146  // MongoInfo.Tag, and APIInfo.Tag to match the given machine ID. If
   147  // MongoInfo or APIInfo are nil, they're not changed.
   148  func (cfg *testInstanceConfig) setMachineID(id string) *testInstanceConfig {
   149  	cfg.MachineId = id
   150  	cfg.MachineAgentServiceName = fmt.Sprintf("jujud-%s", names.NewMachineTag(id).String())
   151  	if cfg.MongoInfo != nil {
   152  		cfg.MongoInfo.Tag = names.NewMachineTag(id)
   153  	}
   154  	if cfg.APIInfo != nil {
   155  		cfg.APIInfo.Tag = names.NewMachineTag(id)
   156  	}
   157  	return cfg
   158  }
   159  
   160  // maybeSetEnvironConfig sets the Config field to the given envConfig, if not
   161  // nil.
   162  func (cfg *testInstanceConfig) maybeSetEnvironConfig(envConfig *config.Config) *testInstanceConfig {
   163  	if envConfig != nil {
   164  		cfg.Config = envConfig
   165  	}
   166  	return cfg
   167  }
   168  
   169  // setEnableOSUpdateAndUpgrade sets EnableOSRefreshUpdate and EnableOSUpgrade
   170  // fields to the given values.
   171  func (cfg *testInstanceConfig) setEnableOSUpdateAndUpgrade(updateEnabled, upgradeEnabled bool) *testInstanceConfig {
   172  	cfg.EnableOSRefreshUpdate = updateEnabled
   173  	cfg.EnableOSUpgrade = upgradeEnabled
   174  	return cfg
   175  }
   176  
   177  // setSeries sets the series-specific fields (Tools, Series, DataDir,
   178  // LogDir, and CloudInitOutputLog) to match the given series.
   179  func (cfg *testInstanceConfig) setSeries(series string) *testInstanceConfig {
   180  	cfg.Tools = newSimpleTools(fmt.Sprintf("1.2.3-%s-amd64", series))
   181  	cfg.Series = series
   182  	cfg.DataDir = jujuDataDir(series)
   183  	cfg.LogDir = jujuLogDir(series)
   184  	cfg.CloudInitOutputLog = cloudInitOutputLog(series)
   185  	return cfg
   186  }
   187  
   188  // setStateServer updates the config to be suitable for bootstrapping
   189  // a state server instance.
   190  func (cfg *testInstanceConfig) setStateServer() *testInstanceConfig {
   191  	cfg.setMachineID("0")
   192  	cfg.Constraints = envConstraints
   193  	cfg.Bootstrap = true
   194  	cfg.StateServingInfo = stateServingInfo
   195  	cfg.Jobs = allMachineJobs
   196  	cfg.InstanceId = "i-bootstrap"
   197  	cfg.MongoInfo.Tag = nil
   198  	cfg.APIInfo.Tag = nil
   199  	return cfg.setEnableOSUpdateAndUpgrade(true, false)
   200  }
   201  
   202  // mutate calls mutator passing cfg to it, and returns the (possibly)
   203  // modified cfg.
   204  func (cfg *testInstanceConfig) mutate(mutator func(*testInstanceConfig)) *testInstanceConfig {
   205  	if mutator == nil {
   206  		panic("mutator is nil!")
   207  	}
   208  	mutator(cfg)
   209  	return cfg
   210  }
   211  
   212  // render returns the config as InstanceConfig.
   213  func (cfg *testInstanceConfig) render() instancecfg.InstanceConfig {
   214  	return instancecfg.InstanceConfig(*cfg)
   215  }
   216  
   217  type cloudinitTest struct {
   218  	cfg           *testInstanceConfig
   219  	setEnvConfig  bool
   220  	expectScripts string
   221  	// inexactMatch signifies whether we allow extra lines
   222  	// in the actual scripts found. If it's true, the lines
   223  	// mentioned in expectScripts must appear in that
   224  	// order, but they can be arbitrarily interleaved with other
   225  	// script lines.
   226  	inexactMatch bool
   227  }
   228  
   229  func minimalEnvironConfig(c *gc.C) *config.Config {
   230  	cfg, err := config.New(config.NoDefaults, testing.FakeConfig())
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	c.Assert(cfg, gc.NotNil)
   233  	return cfg
   234  }
   235  
   236  // Each test gives a cloudinit config - we check the
   237  // output to see if it looks correct.
   238  var cloudinitTests = []cloudinitTest{
   239  	// Test that cloudinit respects update/upgrade settings.
   240  	{
   241  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(false, false),
   242  		inexactMatch: true,
   243  		// We're just checking for apt-flags. We don't much care if
   244  		// the script matches.
   245  		expectScripts: "",
   246  		setEnvConfig:  true,
   247  	},
   248  
   249  	// Test that cloudinit respects update/upgrade settings.
   250  	{
   251  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(true, false),
   252  		inexactMatch: true,
   253  		// We're just checking for apt-flags. We don't much care if
   254  		// the script matches.
   255  		expectScripts: "",
   256  		setEnvConfig:  true,
   257  	},
   258  
   259  	// Test that cloudinit respects update/upgrade settings.
   260  	{
   261  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(false, true),
   262  		inexactMatch: true,
   263  		// We're just checking for apt-flags. We don't much care if
   264  		// the script matches.
   265  		expectScripts: "",
   266  		setEnvConfig:  true,
   267  	},
   268  
   269  	// Test that cloudinit respects update/upgrade settings.
   270  	{
   271  		cfg:          makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(true, true),
   272  		inexactMatch: true,
   273  		// We're just checking for apt-flags. We don't much care if
   274  		// the script matches.
   275  		expectScripts: "",
   276  		setEnvConfig:  true,
   277  	},
   278  
   279  	// precise state server
   280  	{
   281  		cfg:          makeBootstrapConfig("precise"),
   282  		setEnvConfig: true,
   283  		expectScripts: `
   284  install -D -m 644 /dev/null '/etc/apt/preferences\.d/50-cloud-tools'
   285  printf '%s\\n' '.*' > '/etc/apt/preferences\.d/50-cloud-tools'
   286  set -xe
   287  install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf'
   288  printf '%s\\n' '.*"Stop all network interfaces.*' > '/etc/init/juju-clean-shutdown\.conf'
   289  install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'
   290  printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt'
   291  test -e /proc/self/fd/9 \|\| exec 9>&2
   292  \(\[ ! -e /home/ubuntu/.profile \] \|\| grep -q '.juju-proxy' /home/ubuntu/.profile\) \|\| printf .* >> /home/ubuntu/.profile
   293  mkdir -p /var/lib/juju/locks
   294  \(id ubuntu &> /dev/null\) && chown ubuntu:ubuntu /var/lib/juju/locks
   295  mkdir -p /var/log/juju
   296  chown syslog:adm /var/log/juju
   297  bin='/var/lib/juju/tools/1\.2\.3-precise-amd64'
   298  mkdir -p \$bin
   299  echo 'Fetching tools.*
   300  curl .* '.*' --retry 10 -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/released/juju1\.2\.3-precise-amd64\.tgz'
   301  sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-precise-amd64\.sha256
   302  grep '1234' \$bin/juju1\.2\.3-precise-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\)
   303  tar zxf \$bin/tools.tar.gz -C \$bin
   304  printf %s '{"version":"1\.2\.3-precise-amd64","url":"http://foo\.com/tools/released/juju1\.2\.3-precise-amd64\.tgz","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt
   305  mkdir -p '/var/lib/juju/agents/machine-0'
   306  cat > '/var/lib/juju/agents/machine-0/agent\.conf' << 'EOF'\\n.*\\nEOF
   307  chmod 0600 '/var/lib/juju/agents/machine-0/agent\.conf'
   308  echo 'Bootstrapping Juju machine agent'.*
   309  /var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --env-config '[^']*' --instance-id 'i-bootstrap' --constraints 'mem=2048M' --debug
   310  ln -s 1\.2\.3-precise-amd64 '/var/lib/juju/tools/machine-0'
   311  echo 'Starting Juju machine agent \(jujud-machine-0\)'.*
   312  cat > /etc/init/jujud-machine-0\.conf << 'EOF'\\ndescription "juju agent for machine-0"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit nofile 20000 20000\\n\\nscript\\n\\n\\n  # Ensure log files are properly protected\\n  touch /var/log/juju/machine-0\.log\\n  chown syslog:syslog /var/log/juju/machine-0\.log\\n  chmod 0600 /var/log/juju/machine-0\.log\\n\\n  exec '/var/lib/juju/tools/machine-0/jujud' machine --data-dir '/var/lib/juju' --machine-id 0 --debug >> /var/log/juju/machine-0\.log 2>&1\\nend script\\nEOF\\n
   313  start jujud-machine-0
   314  rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-precise-amd64\.sha256
   315  `,
   316  	},
   317  
   318  	// raring state server - we just test the raring-specific parts of the output.
   319  	{
   320  		cfg:          makeBootstrapConfig("raring"),
   321  		setEnvConfig: true,
   322  		inexactMatch: true,
   323  		expectScripts: `
   324  bin='/var/lib/juju/tools/1\.2\.3-raring-amd64'
   325  curl .* '.*' --retry 10 -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/released/juju1\.2\.3-raring-amd64\.tgz'
   326  sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-raring-amd64\.sha256
   327  grep '1234' \$bin/juju1\.2\.3-raring-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\)
   328  printf %s '{"version":"1\.2\.3-raring-amd64","url":"http://foo\.com/tools/released/juju1\.2\.3-raring-amd64\.tgz","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt
   329  /var/lib/juju/tools/1\.2\.3-raring-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --env-config '[^']*' --instance-id 'i-bootstrap' --constraints 'mem=2048M' --debug
   330  ln -s 1\.2\.3-raring-amd64 '/var/lib/juju/tools/machine-0'
   331  rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-raring-amd64\.sha256
   332  `,
   333  	},
   334  
   335  	// quantal non state server.
   336  	{
   337  		cfg: makeNormalConfig("quantal"),
   338  		expectScripts: `
   339  set -xe
   340  install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf'
   341  printf '%s\\n' '.*"Stop all network interfaces on shutdown".*' > '/etc/init/juju-clean-shutdown\.conf'
   342  install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'
   343  printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt'
   344  test -e /proc/self/fd/9 \|\| exec 9>&2
   345  \(\[ ! -e /home/ubuntu/\.profile \] \|\| grep -q '.juju-proxy' /home/ubuntu/.profile\) \|\| printf .* >> /home/ubuntu/.profile
   346  mkdir -p /var/lib/juju/locks
   347  \(id ubuntu &> /dev/null\) && chown ubuntu:ubuntu /var/lib/juju/locks
   348  mkdir -p /var/log/juju
   349  chown syslog:adm /var/log/juju
   350  bin='/var/lib/juju/tools/1\.2\.3-quantal-amd64'
   351  mkdir -p \$bin
   352  echo 'Fetching tools.*
   353  curl .* --noproxy "\*" --insecure -o \$bin/tools\.tar\.gz 'https://state-addr\.testing\.invalid:54321/tools/1\.2\.3-quantal-amd64'
   354  sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-quantal-amd64\.sha256
   355  grep '1234' \$bin/juju1\.2\.3-quantal-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\)
   356  tar zxf \$bin/tools.tar.gz -C \$bin
   357  printf %s '{"version":"1\.2\.3-quantal-amd64","url":"http://foo\.com/tools/released/juju1\.2\.3-quantal-amd64\.tgz","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt
   358  mkdir -p '/var/lib/juju/agents/machine-99'
   359  cat > '/var/lib/juju/agents/machine-99/agent\.conf' << 'EOF'\\n.*\\nEOF
   360  chmod 0600 '/var/lib/juju/agents/machine-99/agent\.conf'
   361  ln -s 1\.2\.3-quantal-amd64 '/var/lib/juju/tools/machine-99'
   362  echo 'Starting Juju machine agent \(jujud-machine-99\)'.*
   363  cat > /etc/init/jujud-machine-99\.conf << 'EOF'\\ndescription "juju agent for machine-99"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit nofile 20000 20000\\n\\nscript\\n\\n\\n  # Ensure log files are properly protected\\n  touch /var/log/juju/machine-99\.log\\n  chown syslog:syslog /var/log/juju/machine-99\.log\\n  chmod 0600 /var/log/juju/machine-99\.log\\n\\n  exec '/var/lib/juju/tools/machine-99/jujud' machine --data-dir '/var/lib/juju' --machine-id 99 --debug >> /var/log/juju/machine-99\.log 2>&1\\nend script\\nEOF\\n
   364  start jujud-machine-99
   365  rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-quantal-amd64\.sha256
   366  `,
   367  	},
   368  
   369  	// non state server with systemd (vivid)
   370  	{
   371  		cfg:          makeNormalConfig("vivid"),
   372  		inexactMatch: true,
   373  		expectScripts: `
   374  set -xe
   375  install -D -m 644 /dev/null '/etc/systemd/system/juju-clean-shutdown\.service'
   376  printf '%s\\n' '\\n\[Unit\]\\n.*Stop all network interfaces.*WantedBy=final\.target\\n' > '/etc/systemd.*'
   377  /bin/systemctl enable '/etc/systemd/system/juju-clean-shutdown\.service'
   378  install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'
   379  printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt'
   380  .*
   381  `,
   382  	},
   383  
   384  	// CentOS non state server with systemd
   385  	{
   386  		cfg:          makeNormalConfig("centos7"),
   387  		inexactMatch: true,
   388  		expectScripts: `
   389  systemctl is-enabled firewalld &> /dev/null && systemctl mask firewalld || true
   390  systemctl is-active firewalld &> /dev/null && systemctl stop firewalld || true
   391  sed -i "s/\^\.\*requiretty/#Defaults requiretty/" /etc/sudoers
   392  `,
   393  	},
   394  
   395  	// check that it works ok with compound machine ids.
   396  	{
   397  		cfg: makeNormalConfig("quantal").mutate(func(cfg *testInstanceConfig) {
   398  			cfg.MachineContainerType = "lxc"
   399  		}).setMachineID("2/lxc/1"),
   400  		inexactMatch: true,
   401  		expectScripts: `
   402  mkdir -p '/var/lib/juju/agents/machine-2-lxc-1'
   403  cat > '/var/lib/juju/agents/machine-2-lxc-1/agent\.conf' << 'EOF'\\n.*\\nEOF
   404  chmod 0600 '/var/lib/juju/agents/machine-2-lxc-1/agent\.conf'
   405  ln -s 1\.2\.3-quantal-amd64 '/var/lib/juju/tools/machine-2-lxc-1'
   406  cat > /etc/init/jujud-machine-2-lxc-1\.conf << 'EOF'\\ndescription "juju agent for machine-2-lxc-1"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit nofile 20000 20000\\n\\nscript\\n\\n\\n  # Ensure log files are properly protected\\n  touch /var/log/juju/machine-2-lxc-1\.log\\n  chown syslog:syslog /var/log/juju/machine-2-lxc-1\.log\\n  chmod 0600 /var/log/juju/machine-2-lxc-1\.log\\n\\n  exec '/var/lib/juju/tools/machine-2-lxc-1/jujud' machine --data-dir '/var/lib/juju' --machine-id 2/lxc/1 --debug >> /var/log/juju/machine-2-lxc-1\.log 2>&1\\nend script\\nEOF\\n
   407  start jujud-machine-2-lxc-1
   408  `,
   409  	},
   410  
   411  	// hostname verification disabled.
   412  	{
   413  		cfg: makeNormalConfig("quantal").mutate(func(cfg *testInstanceConfig) {
   414  			cfg.DisableSSLHostnameVerification = true
   415  		}),
   416  		inexactMatch: true,
   417  		expectScripts: `
   418  curl .* --noproxy "\*" --insecure -o \$bin/tools\.tar\.gz 'https://state-addr\.testing\.invalid:54321/tools/1\.2\.3-quantal-amd64'
   419  `,
   420  	},
   421  
   422  	// empty bootstrap contraints.
   423  	{
   424  		cfg: makeBootstrapConfig("precise").mutate(func(cfg *testInstanceConfig) {
   425  			cfg.Constraints = constraints.Value{}
   426  		}),
   427  		setEnvConfig: true,
   428  		inexactMatch: true,
   429  		expectScripts: `
   430  /var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --data-dir '/var/lib/juju' --env-config '[^']*' --instance-id 'i-bootstrap' --debug
   431  `,
   432  	},
   433  
   434  	// custom image metadata (at bootstrap).
   435  	{
   436  		cfg: makeBootstrapConfig("trusty").mutate(func(cfg *testInstanceConfig) {
   437  			cfg.CustomImageMetadata = []*imagemetadata.ImageMetadata{{
   438  				Id:         "image-id",
   439  				Storage:    "ebs",
   440  				VirtType:   "pv",
   441  				Arch:       "amd64",
   442  				Version:    "14.04",
   443  				RegionName: "us-east1",
   444  			}}
   445  		}),
   446  		setEnvConfig: true,
   447  		inexactMatch: true,
   448  		expectScripts: `
   449  printf '%s\\n' '.*' > '/var/lib/juju/simplestreams/images/streams/v1/index\.json'
   450  printf '%s\\n' '.*' > '/var/lib/juju/simplestreams/images/streams/v1/com.ubuntu.cloud-released-imagemetadata\.json'
   451  `,
   452  	},
   453  }
   454  
   455  func newSimpleTools(vers string) *tools.Tools {
   456  	return &tools.Tools{
   457  		URL:     "http://foo.com/tools/released/juju" + vers + ".tgz",
   458  		Version: version.MustParseBinary(vers),
   459  		Size:    10,
   460  		SHA256:  "1234",
   461  	}
   462  }
   463  
   464  func newFileTools(vers, path string) *tools.Tools {
   465  	tools := newSimpleTools(vers)
   466  	tools.URL = "file://" + path
   467  	return tools
   468  }
   469  
   470  func getAgentConfig(c *gc.C, tag string, scripts []string) (cfg string) {
   471  	c.Assert(scripts, gc.Not(gc.HasLen), 0)
   472  	re := regexp.MustCompile(`cat > .*agents/` + regexp.QuoteMeta(tag) + `/agent\.conf' << 'EOF'\n((\n|.)+)\nEOF`)
   473  	found := false
   474  	for _, s := range scripts {
   475  		m := re.FindStringSubmatch(s)
   476  		if m == nil {
   477  			continue
   478  		}
   479  		cfg = m[1]
   480  		found = true
   481  	}
   482  	c.Assert(found, jc.IsTrue)
   483  	return cfg
   484  }
   485  
   486  // check that any --env-config $base64 is valid and matches t.cfg.Config
   487  func checkEnvConfig(c *gc.C, cfg *config.Config, x map[interface{}]interface{}, scripts []string) {
   488  	c.Assert(scripts, gc.Not(gc.HasLen), 0)
   489  	re := regexp.MustCompile(`--env-config '([^']+)'`)
   490  	found := false
   491  	for _, s := range scripts {
   492  		m := re.FindStringSubmatch(s)
   493  		if m == nil {
   494  			continue
   495  		}
   496  		found = true
   497  		buf, err := base64.StdEncoding.DecodeString(m[1])
   498  		c.Assert(err, jc.ErrorIsNil)
   499  		var actual map[string]interface{}
   500  		err = goyaml.Unmarshal(buf, &actual)
   501  		c.Assert(err, jc.ErrorIsNil)
   502  		c.Assert(cfg.AllAttrs(), jc.DeepEquals, actual)
   503  	}
   504  	c.Assert(found, jc.IsTrue)
   505  }
   506  
   507  // TestCloudInit checks that the output from the various tests
   508  // in cloudinitTests is well formed.
   509  func (*cloudinitSuite) TestCloudInit(c *gc.C) {
   510  	for i, test := range cloudinitTests {
   511  
   512  		c.Logf("test %d", i)
   513  		var envConfig *config.Config
   514  		if test.setEnvConfig {
   515  			envConfig = minimalEnvironConfig(c)
   516  		}
   517  		testConfig := test.cfg.maybeSetEnvironConfig(envConfig).render()
   518  		ci, err := cloudinit.New(testConfig.Series)
   519  		c.Assert(err, jc.ErrorIsNil)
   520  		udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci)
   521  		c.Assert(err, jc.ErrorIsNil)
   522  		err = udata.Configure()
   523  
   524  		c.Assert(err, jc.ErrorIsNil)
   525  		c.Check(ci, gc.NotNil)
   526  		// render the cloudinit config to bytes, and then
   527  		// back to a map so we can introspect it without
   528  		// worrying about internal details of the cloudinit
   529  		// package.
   530  		data, err := ci.RenderYAML()
   531  		c.Assert(err, jc.ErrorIsNil)
   532  
   533  		configKeyValues := make(map[interface{}]interface{})
   534  		err = goyaml.Unmarshal(data, &configKeyValues)
   535  		c.Assert(err, jc.ErrorIsNil)
   536  
   537  		if testConfig.EnableOSRefreshUpdate {
   538  			c.Check(configKeyValues["package_update"], jc.IsTrue)
   539  		} else {
   540  			c.Check(configKeyValues["package_update"], jc.IsFalse)
   541  		}
   542  
   543  		if testConfig.EnableOSUpgrade {
   544  			c.Check(configKeyValues["package_upgrade"], jc.IsTrue)
   545  		} else {
   546  			c.Check(configKeyValues["package_upgrade"], jc.IsFalse)
   547  		}
   548  
   549  		scripts := getScripts(configKeyValues)
   550  		assertScriptMatch(c, scripts, test.expectScripts, !test.inexactMatch)
   551  		if testConfig.Config != nil {
   552  			checkEnvConfig(c, testConfig.Config, configKeyValues, scripts)
   553  		}
   554  
   555  		// curl should always be installed, since it's required by jujud.
   556  		checkPackage(c, configKeyValues, "curl", true)
   557  
   558  		tag := names.NewMachineTag(testConfig.MachineId).String()
   559  		acfg := getAgentConfig(c, tag, scripts)
   560  		c.Assert(acfg, jc.Contains, "AGENT_SERVICE_NAME: jujud-"+tag)
   561  		c.Assert(acfg, jc.Contains, "upgradedToVersion: 1.2.3\n")
   562  		source := "deb http://ubuntu-cloud.archive.canonical.com/ubuntu precise-updates/cloud-tools main"
   563  		needCloudArchive := testConfig.Series == "precise"
   564  		checkAptSource(c, configKeyValues, source, pacconf.UbuntuCloudArchiveSigningKey, needCloudArchive)
   565  	}
   566  }
   567  
   568  func (*cloudinitSuite) TestCloudInitConfigure(c *gc.C) {
   569  	for i, test := range cloudinitTests {
   570  		testConfig := test.cfg.maybeSetEnvironConfig(minimalEnvironConfig(c)).render()
   571  		c.Logf("test %d (Configure)", i)
   572  		cloudcfg, err := cloudinit.New(testConfig.Series)
   573  		c.Assert(err, jc.ErrorIsNil)
   574  		udata, err := cloudconfig.NewUserdataConfig(&testConfig, cloudcfg)
   575  		c.Assert(err, jc.ErrorIsNil)
   576  		err = udata.Configure()
   577  		c.Assert(err, jc.ErrorIsNil)
   578  	}
   579  }
   580  
   581  func (*cloudinitSuite) TestCloudInitConfigureBootstrapLogging(c *gc.C) {
   582  	loggo.GetLogger("").SetLogLevel(loggo.INFO)
   583  	envConfig := minimalEnvironConfig(c)
   584  	instConfig := makeBootstrapConfig("quantal").maybeSetEnvironConfig(envConfig)
   585  	rendered := instConfig.render()
   586  	cloudcfg, err := cloudinit.New(rendered.Series)
   587  	c.Assert(err, jc.ErrorIsNil)
   588  	udata, err := cloudconfig.NewUserdataConfig(&rendered, cloudcfg)
   589  
   590  	c.Assert(err, jc.ErrorIsNil)
   591  	err = udata.Configure()
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	data, err := cloudcfg.RenderYAML()
   594  	c.Assert(err, jc.ErrorIsNil)
   595  	configKeyValues := make(map[interface{}]interface{})
   596  	err = goyaml.Unmarshal(data, &configKeyValues)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  
   599  	scripts := getScripts(configKeyValues)
   600  	for i, script := range scripts {
   601  		if strings.Contains(script, "bootstrap") {
   602  			c.Logf("scripts[%d]: %q", i, script)
   603  		}
   604  	}
   605  	expected := "jujud bootstrap-state --data-dir '.*' --env-config '.*'" +
   606  		" --instance-id '.*' --constraints 'mem=2048M' --show-log"
   607  	assertScriptMatch(c, scripts, expected, false)
   608  }
   609  
   610  func (*cloudinitSuite) TestCloudInitConfigureUsesGivenConfig(c *gc.C) {
   611  	// Create a simple cloudinit config with a 'runcmd' statement.
   612  	cloudcfg, err := cloudinit.New("quantal")
   613  	c.Assert(err, jc.ErrorIsNil)
   614  	script := "test script"
   615  	cloudcfg.AddRunCmd(script)
   616  	envConfig := minimalEnvironConfig(c)
   617  	testConfig := cloudinitTests[0].cfg.maybeSetEnvironConfig(envConfig).render()
   618  	udata, err := cloudconfig.NewUserdataConfig(&testConfig, cloudcfg)
   619  	c.Assert(err, jc.ErrorIsNil)
   620  	err = udata.Configure()
   621  	c.Assert(err, jc.ErrorIsNil)
   622  	data, err := cloudcfg.RenderYAML()
   623  	c.Assert(err, jc.ErrorIsNil)
   624  
   625  	ciContent := make(map[interface{}]interface{})
   626  	err = goyaml.Unmarshal(data, &ciContent)
   627  	c.Assert(err, jc.ErrorIsNil)
   628  	// The 'runcmd' statement is at the beginning of the list
   629  	// of 'runcmd' statements.
   630  	runCmd := ciContent["runcmd"].([]interface{})
   631  	c.Check(runCmd[0], gc.Equals, script)
   632  }
   633  
   634  func getScripts(configKeyValue map[interface{}]interface{}) []string {
   635  	var scripts []string
   636  	if bootcmds, ok := configKeyValue["bootcmd"]; ok {
   637  		for _, s := range bootcmds.([]interface{}) {
   638  			scripts = append(scripts, s.(string))
   639  		}
   640  	}
   641  	for _, s := range configKeyValue["runcmd"].([]interface{}) {
   642  		scripts = append(scripts, s.(string))
   643  	}
   644  	return scripts
   645  }
   646  
   647  type line struct {
   648  	index int
   649  	line  string
   650  }
   651  
   652  func assertScriptMatch(c *gc.C, got []string, expect string, exact bool) {
   653  
   654  	// Convert string slice into line struct slice
   655  	assembleLines := func(lines []string, lineProcessor func(string) string) []line {
   656  		var assembledLines []line
   657  		for lineIdx, currLine := range lines {
   658  			if nil != lineProcessor {
   659  				currLine = lineProcessor(currLine)
   660  			}
   661  			assembledLines = append(assembledLines, line{
   662  				index: lineIdx,
   663  				line:  currLine,
   664  			})
   665  		}
   666  		return assembledLines
   667  	}
   668  
   669  	pats := assembleLines(strings.Split(strings.Trim(expect, "\n"), "\n"), nil)
   670  	scripts := assembleLines(got, func(line string) string {
   671  		return strings.Replace(line, "\n", "\\n", -1) // make .* work
   672  	})
   673  
   674  	// Pop patterns and scripts off the head as we find pairs
   675  	for {
   676  		switch {
   677  		case len(pats) == 0 && len(scripts) == 0:
   678  			return
   679  		case len(pats) == 0:
   680  			if exact {
   681  				c.Fatalf("too many scripts found (got %q at line %d)", scripts[0].line, scripts[0].index)
   682  			}
   683  			return
   684  		case len(scripts) == 0:
   685  			if exact {
   686  				c.Fatalf("too few scripts found (expected %q at line %d)", pats[0].line, pats[0].index)
   687  			}
   688  			c.Fatalf("could not find match for %q\ngot:\n%s", pats[0].line, strings.Join(got, "\n"))
   689  		default:
   690  			ok, err := regexp.MatchString(pats[0].line, scripts[0].line)
   691  			c.Assert(err, jc.ErrorIsNil, gc.Commentf("invalid regexp: %q", pats[0].line))
   692  			if ok {
   693  				pats = pats[1:]
   694  				scripts = scripts[1:]
   695  			} else if exact {
   696  				c.Assert(scripts[0].line, gc.Matches, pats[0].line, gc.Commentf("line %d; expected %q; got %q; paths: %#v", scripts[0].index, pats[0].line, scripts[0].line, pats))
   697  			} else {
   698  				scripts = scripts[1:]
   699  			}
   700  		}
   701  	}
   702  }
   703  
   704  // checkPackage checks that the cloudinit will or won't install the given
   705  // package, depending on the value of match.
   706  func checkPackage(c *gc.C, x map[interface{}]interface{}, pkg string, match bool) {
   707  	pkgs0 := x["packages"]
   708  	if pkgs0 == nil {
   709  		if match {
   710  			c.Errorf("cloudinit has no entry for packages")
   711  		}
   712  		return
   713  	}
   714  
   715  	pkgs := pkgs0.([]interface{})
   716  
   717  	found := false
   718  	for _, p0 := range pkgs {
   719  		p := p0.(string)
   720  		// p might be a space separate list of packages eg 'foo bar qed' so split them up
   721  		manyPkgs := set.NewStrings(strings.Split(p, " ")...)
   722  		hasPkg := manyPkgs.Contains(pkg)
   723  		if p == pkg || hasPkg {
   724  			found = true
   725  			break
   726  		}
   727  	}
   728  	switch {
   729  	case match && !found:
   730  		c.Errorf("package %q not found in %v", pkg, pkgs)
   731  	case !match && found:
   732  		c.Errorf("%q found but not expected in %v", pkg, pkgs)
   733  	}
   734  }
   735  
   736  // checkAptSource checks that the cloudinit will or won't install the given
   737  // source, depending on the value of match.
   738  func checkAptSource(c *gc.C, x map[interface{}]interface{}, source, key string, match bool) {
   739  	sources0 := x["apt_sources"]
   740  	if sources0 == nil {
   741  		if match {
   742  			c.Errorf("cloudinit has no entry for apt_sources")
   743  		}
   744  		return
   745  	}
   746  
   747  	sources := sources0.([]interface{})
   748  
   749  	found := false
   750  	for _, s0 := range sources {
   751  		s := s0.(map[interface{}]interface{})
   752  		if s["source"] == source && s["key"] == key {
   753  			found = true
   754  		}
   755  	}
   756  	switch {
   757  	case match && !found:
   758  		c.Errorf("source %q not found in %v", source, sources)
   759  	case !match && found:
   760  		c.Errorf("%q found but not expected in %v", source, sources)
   761  	}
   762  }
   763  
   764  // When mutate is called on a known-good InstanceConfig,
   765  // there should be an error complaining about the missing
   766  // field named by the adjacent err.
   767  var verifyTests = []struct {
   768  	err    string
   769  	mutate func(*instancecfg.InstanceConfig)
   770  }{
   771  	{"invalid machine id", func(cfg *instancecfg.InstanceConfig) {
   772  		cfg.MachineId = "-1"
   773  	}},
   774  	{"missing environment configuration", func(cfg *instancecfg.InstanceConfig) {
   775  		cfg.Config = nil
   776  	}},
   777  	{"missing state info", func(cfg *instancecfg.InstanceConfig) {
   778  		cfg.MongoInfo = nil
   779  	}},
   780  	{"missing API info", func(cfg *instancecfg.InstanceConfig) {
   781  		cfg.APIInfo = nil
   782  	}},
   783  	{"missing environment tag", func(cfg *instancecfg.InstanceConfig) {
   784  		cfg.APIInfo = &api.Info{
   785  			Addrs:  []string{"foo:35"},
   786  			Tag:    names.NewMachineTag("99"),
   787  			CACert: testing.CACert,
   788  		}
   789  	}},
   790  	{"missing state hosts", func(cfg *instancecfg.InstanceConfig) {
   791  		cfg.Bootstrap = false
   792  		cfg.MongoInfo = &mongo.MongoInfo{
   793  			Tag: names.NewMachineTag("99"),
   794  			Info: mongo.Info{
   795  				CACert: testing.CACert,
   796  			},
   797  		}
   798  		cfg.APIInfo = &api.Info{
   799  			Addrs:      []string{"foo:35"},
   800  			Tag:        names.NewMachineTag("99"),
   801  			CACert:     testing.CACert,
   802  			EnvironTag: testing.EnvironmentTag,
   803  		}
   804  	}},
   805  	{"missing API hosts", func(cfg *instancecfg.InstanceConfig) {
   806  		cfg.Bootstrap = false
   807  		cfg.MongoInfo = &mongo.MongoInfo{
   808  			Info: mongo.Info{
   809  				Addrs:  []string{"foo:35"},
   810  				CACert: testing.CACert,
   811  			},
   812  			Tag: names.NewMachineTag("99"),
   813  		}
   814  		cfg.APIInfo = &api.Info{
   815  			Tag:        names.NewMachineTag("99"),
   816  			CACert:     testing.CACert,
   817  			EnvironTag: testing.EnvironmentTag,
   818  		}
   819  	}},
   820  	{"missing CA certificate", func(cfg *instancecfg.InstanceConfig) {
   821  		cfg.MongoInfo = &mongo.MongoInfo{Info: mongo.Info{Addrs: []string{"host:98765"}}}
   822  	}},
   823  	{"missing CA certificate", func(cfg *instancecfg.InstanceConfig) {
   824  		cfg.Bootstrap = false
   825  		cfg.MongoInfo = &mongo.MongoInfo{
   826  			Tag: names.NewMachineTag("99"),
   827  			Info: mongo.Info{
   828  				Addrs: []string{"host:98765"},
   829  			},
   830  		}
   831  	}},
   832  	{"missing state server certificate", func(cfg *instancecfg.InstanceConfig) {
   833  		info := *cfg.StateServingInfo
   834  		info.Cert = ""
   835  		cfg.StateServingInfo = &info
   836  	}},
   837  	{"missing state server private key", func(cfg *instancecfg.InstanceConfig) {
   838  		info := *cfg.StateServingInfo
   839  		info.PrivateKey = ""
   840  		cfg.StateServingInfo = &info
   841  	}},
   842  	{"missing ca cert private key", func(cfg *instancecfg.InstanceConfig) {
   843  		info := *cfg.StateServingInfo
   844  		info.CAPrivateKey = ""
   845  		cfg.StateServingInfo = &info
   846  	}},
   847  	{"missing state port", func(cfg *instancecfg.InstanceConfig) {
   848  		info := *cfg.StateServingInfo
   849  		info.StatePort = 0
   850  		cfg.StateServingInfo = &info
   851  	}},
   852  	{"missing API port", func(cfg *instancecfg.InstanceConfig) {
   853  		info := *cfg.StateServingInfo
   854  		info.APIPort = 0
   855  		cfg.StateServingInfo = &info
   856  	}},
   857  	{"missing var directory", func(cfg *instancecfg.InstanceConfig) {
   858  		cfg.DataDir = ""
   859  	}},
   860  	{"missing log directory", func(cfg *instancecfg.InstanceConfig) {
   861  		cfg.LogDir = ""
   862  	}},
   863  	{"missing cloud-init output log path", func(cfg *instancecfg.InstanceConfig) {
   864  		cfg.CloudInitOutputLog = ""
   865  	}},
   866  	{"missing tools", func(cfg *instancecfg.InstanceConfig) {
   867  		cfg.Tools = nil
   868  	}},
   869  	{"missing tools URL", func(cfg *instancecfg.InstanceConfig) {
   870  		cfg.Tools = &tools.Tools{}
   871  	}},
   872  	{"entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
   873  		cfg.Bootstrap = false
   874  		info := *cfg.MongoInfo
   875  		info.Tag = names.NewMachineTag("0")
   876  		cfg.MongoInfo = &info
   877  	}},
   878  	{"entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
   879  		cfg.Bootstrap = false
   880  		info := *cfg.MongoInfo
   881  		info.Tag = nil // admin user
   882  		cfg.MongoInfo = &info
   883  	}},
   884  	{"entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
   885  		cfg.Bootstrap = false
   886  		info := *cfg.APIInfo
   887  		info.Tag = names.NewMachineTag("0")
   888  		cfg.APIInfo = &info
   889  	}},
   890  	{"entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) {
   891  		cfg.Bootstrap = false
   892  		info := *cfg.APIInfo
   893  		info.Tag = nil
   894  		cfg.APIInfo = &info
   895  	}},
   896  	{"entity tag must be nil when starting a state server", func(cfg *instancecfg.InstanceConfig) {
   897  		info := *cfg.MongoInfo
   898  		info.Tag = names.NewMachineTag("0")
   899  		cfg.MongoInfo = &info
   900  	}},
   901  	{"entity tag must be nil when starting a state server", func(cfg *instancecfg.InstanceConfig) {
   902  		info := *cfg.APIInfo
   903  		info.Tag = names.NewMachineTag("0")
   904  		cfg.APIInfo = &info
   905  	}},
   906  	{"missing machine nonce", func(cfg *instancecfg.InstanceConfig) {
   907  		cfg.MachineNonce = ""
   908  	}},
   909  	{"missing machine agent service name", func(cfg *instancecfg.InstanceConfig) {
   910  		cfg.MachineAgentServiceName = ""
   911  	}},
   912  	{"missing instance-id", func(cfg *instancecfg.InstanceConfig) {
   913  		cfg.InstanceId = ""
   914  	}},
   915  	{"state serving info unexpectedly present", func(cfg *instancecfg.InstanceConfig) {
   916  		cfg.Bootstrap = false
   917  		apiInfo := *cfg.APIInfo
   918  		apiInfo.Tag = names.NewMachineTag("99")
   919  		cfg.APIInfo = &apiInfo
   920  		stateInfo := *cfg.MongoInfo
   921  		stateInfo.Tag = names.NewMachineTag("99")
   922  		cfg.MongoInfo = &stateInfo
   923  	}},
   924  }
   925  
   926  // TestCloudInitVerify checks that required fields are appropriately
   927  // checked for by NewCloudInit.
   928  func (*cloudinitSuite) TestCloudInitVerify(c *gc.C) {
   929  	cfg := &instancecfg.InstanceConfig{
   930  		Bootstrap:        true,
   931  		StateServingInfo: stateServingInfo,
   932  		MachineId:        "99",
   933  		Tools:            newSimpleTools("9.9.9-quantal-arble"),
   934  		AuthorizedKeys:   "sshkey1",
   935  		Series:           "quantal",
   936  		AgentEnvironment: map[string]string{agent.ProviderType: "dummy"},
   937  		MongoInfo: &mongo.MongoInfo{
   938  			Info: mongo.Info{
   939  				Addrs:  []string{"host:98765"},
   940  				CACert: testing.CACert,
   941  			},
   942  			Password: "password",
   943  		},
   944  		APIInfo: &api.Info{
   945  			Addrs:      []string{"host:9999"},
   946  			CACert:     testing.CACert,
   947  			EnvironTag: testing.EnvironmentTag,
   948  		},
   949  		Config:                  minimalEnvironConfig(c),
   950  		DataDir:                 jujuDataDir("quantal"),
   951  		LogDir:                  jujuLogDir("quantal"),
   952  		MetricsSpoolDir:         metricsSpoolDir("quantal"),
   953  		Jobs:                    normalMachineJobs,
   954  		CloudInitOutputLog:      cloudInitOutputLog("quantal"),
   955  		InstanceId:              "i-bootstrap",
   956  		MachineNonce:            "FAKE_NONCE",
   957  		MachineAgentServiceName: "jujud-machine-99",
   958  	}
   959  	// check that the base configuration does not give an error
   960  	ci, err := cloudinit.New("quantal")
   961  	c.Assert(err, jc.ErrorIsNil)
   962  
   963  	for i, test := range verifyTests {
   964  		// check that the base configuration does not give an error
   965  		// and that a previous test hasn't mutated it accidentially.
   966  		udata, err := cloudconfig.NewUserdataConfig(cfg, ci)
   967  		c.Assert(err, jc.ErrorIsNil)
   968  		err = udata.Configure()
   969  		c.Assert(err, jc.ErrorIsNil)
   970  
   971  		c.Logf("test %d. %s", i, test.err)
   972  
   973  		cfg1 := *cfg
   974  		test.mutate(&cfg1)
   975  
   976  		udata, err = cloudconfig.NewUserdataConfig(&cfg1, ci)
   977  		c.Assert(err, jc.ErrorIsNil)
   978  		err = udata.Configure()
   979  		c.Check(err, gc.ErrorMatches, "invalid machine configuration: "+test.err)
   980  	}
   981  }
   982  
   983  func (*cloudinitSuite) createInstanceConfig(c *gc.C, environConfig *config.Config) *instancecfg.InstanceConfig {
   984  	machineId := "42"
   985  	machineNonce := "fake-nonce"
   986  	stateInfo := jujutesting.FakeStateInfo(machineId)
   987  	apiInfo := jujutesting.FakeAPIInfo(machineId)
   988  	instanceConfig, err := instancecfg.NewInstanceConfig(machineId, machineNonce, imagemetadata.ReleasedStream, "quantal", true, nil, stateInfo, apiInfo)
   989  	c.Assert(err, jc.ErrorIsNil)
   990  	instanceConfig.Tools = &tools.Tools{
   991  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   992  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   993  	}
   994  	err = instancecfg.FinishInstanceConfig(instanceConfig, environConfig)
   995  	c.Assert(err, jc.ErrorIsNil)
   996  	return instanceConfig
   997  }
   998  
   999  func (s *cloudinitSuite) TestAptProxyNotWrittenIfNotSet(c *gc.C) {
  1000  	environConfig := minimalEnvironConfig(c)
  1001  	instanceCfg := s.createInstanceConfig(c, environConfig)
  1002  	cloudcfg, err := cloudinit.New("quantal")
  1003  	c.Assert(err, jc.ErrorIsNil)
  1004  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1005  	c.Assert(err, jc.ErrorIsNil)
  1006  	err = udata.Configure()
  1007  	c.Assert(err, jc.ErrorIsNil)
  1008  
  1009  	cmds := cloudcfg.BootCmds()
  1010  	c.Assert(cmds, gc.IsNil)
  1011  }
  1012  
  1013  func (s *cloudinitSuite) TestAptProxyWritten(c *gc.C) {
  1014  	environConfig := minimalEnvironConfig(c)
  1015  	environConfig, err := environConfig.Apply(map[string]interface{}{
  1016  		"apt-http-proxy": "http://user@10.0.0.1",
  1017  	})
  1018  	c.Assert(err, jc.ErrorIsNil)
  1019  	instanceCfg := s.createInstanceConfig(c, environConfig)
  1020  	cloudcfg, err := cloudinit.New("quantal")
  1021  	c.Assert(err, jc.ErrorIsNil)
  1022  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1023  	c.Assert(err, jc.ErrorIsNil)
  1024  	err = udata.Configure()
  1025  	c.Assert(err, jc.ErrorIsNil)
  1026  
  1027  	cmds := cloudcfg.BootCmds()
  1028  	expected := "printf '%s\\n' 'Acquire::http::Proxy \"http://user@10.0.0.1\";' > /etc/apt/apt.conf.d/42-juju-proxy-settings"
  1029  	c.Assert(cmds, jc.DeepEquals, []string{expected})
  1030  }
  1031  
  1032  func (s *cloudinitSuite) TestProxyWritten(c *gc.C) {
  1033  	environConfig := minimalEnvironConfig(c)
  1034  	environConfig, err := environConfig.Apply(map[string]interface{}{
  1035  		"http-proxy": "http://user@10.0.0.1",
  1036  		"no-proxy":   "localhost,10.0.3.1",
  1037  	})
  1038  	c.Assert(err, jc.ErrorIsNil)
  1039  	instanceCfg := s.createInstanceConfig(c, environConfig)
  1040  	cloudcfg, err := cloudinit.New("quantal")
  1041  	c.Assert(err, jc.ErrorIsNil)
  1042  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1043  	c.Assert(err, jc.ErrorIsNil)
  1044  	err = udata.Configure()
  1045  	c.Assert(err, jc.ErrorIsNil)
  1046  
  1047  	cmds := cloudcfg.RunCmds()
  1048  	first := `([ ! -e /home/ubuntu/.profile ] || grep -q '.juju-proxy' /home/ubuntu/.profile) || printf '\n# Added by juju\n[ -f "$HOME/.juju-proxy" ] && . "$HOME/.juju-proxy"\n' >> /home/ubuntu/.profile`
  1049  	expected := []string{
  1050  		`export http_proxy=http://user@10.0.0.1`,
  1051  		`export HTTP_PROXY=http://user@10.0.0.1`,
  1052  		`export no_proxy=localhost,10.0.3.1`,
  1053  		`export NO_PROXY=localhost,10.0.3.1`,
  1054  		`(id ubuntu &> /dev/null) && (printf '%s\n' 'export http_proxy=http://user@10.0.0.1
  1055  export HTTP_PROXY=http://user@10.0.0.1
  1056  export no_proxy=localhost,10.0.3.1
  1057  export NO_PROXY=localhost,10.0.3.1' > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`,
  1058  	}
  1059  	found := false
  1060  	for i, cmd := range cmds {
  1061  		if cmd == first {
  1062  			c.Assert(cmds[i+1:i+6], jc.DeepEquals, expected)
  1063  			found = true
  1064  			break
  1065  		}
  1066  	}
  1067  	c.Assert(found, jc.IsTrue)
  1068  }
  1069  
  1070  func (s *cloudinitSuite) TestAptMirror(c *gc.C) {
  1071  	environConfig := minimalEnvironConfig(c)
  1072  	environConfig, err := environConfig.Apply(map[string]interface{}{
  1073  		"apt-mirror": "http://my.archive.ubuntu.com/ubuntu",
  1074  	})
  1075  	c.Assert(err, jc.ErrorIsNil)
  1076  	s.testAptMirror(c, environConfig, "http://my.archive.ubuntu.com/ubuntu")
  1077  }
  1078  
  1079  func (s *cloudinitSuite) TestAptMirrorNotSet(c *gc.C) {
  1080  	environConfig := minimalEnvironConfig(c)
  1081  	s.testAptMirror(c, environConfig, "")
  1082  }
  1083  
  1084  func (s *cloudinitSuite) testAptMirror(c *gc.C, cfg *config.Config, expect string) {
  1085  	instanceCfg := s.createInstanceConfig(c, cfg)
  1086  	cloudcfg, err := cloudinit.New("quantal")
  1087  	c.Assert(err, jc.ErrorIsNil)
  1088  	udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg)
  1089  	c.Assert(err, jc.ErrorIsNil)
  1090  	err = udata.Configure()
  1091  	c.Assert(err, jc.ErrorIsNil)
  1092  	//mirror, ok := cloudcfg.AptMirror()
  1093  	mirror := cloudcfg.PackageMirror()
  1094  	c.Assert(mirror, gc.Equals, expect)
  1095  	//c.Assert(ok, gc.Equals, expect != "")
  1096  }
  1097  
  1098  var serverCert = []byte(`
  1099  SERVER CERT
  1100  -----BEGIN CERTIFICATE-----
  1101  MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwHjENMAsGA1UEChMEanVqdTEN
  1102  MAsGA1UEAxMEcm9vdDAeFw0xMjExMDgxNjIyMzRaFw0xMzExMDgxNjI3MzRaMBwx
  1103  DDAKBgNVBAoTA2htbTEMMAoGA1UEAxMDYW55MFowCwYJKoZIhvcNAQEBA0sAMEgC
  1104  QQCACqz6JPwM7nbxAWub+APpnNB7myckWJ6nnsPKi9SipP1hyhfzkp8RGMJ5Uv7y
  1105  8CSTtJ8kg/ibka1VV8LvP9tnAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIAsDAdBgNV
  1106  HQ4EFgQU6G1ERaHCgfAv+yoDMFVpDbLOmIQwHwYDVR0jBBgwFoAUP/mfUdwOlHfk
  1107  fR+gLQjslxf64w0wCwYJKoZIhvcNAQEFA0EAbn0MaxWVgGYBomeLYfDdb8vCq/5/
  1108  G/2iCUQCXsVrBparMLFnor/iKOkJB5n3z3rtu70rFt+DpX6L8uBR3LB3+A==
  1109  -----END CERTIFICATE-----
  1110  `[1:])
  1111  
  1112  var serverKey = []byte(`
  1113  SERVER KEY
  1114  -----BEGIN RSA PRIVATE KEY-----
  1115  MIIBPAIBAAJBAIAKrPok/AzudvEBa5v4A+mc0HubJyRYnqeew8qL1KKk/WHKF/OS
  1116  nxEYwnlS/vLwJJO0nySD+JuRrVVXwu8/22cCAwEAAQJBAJsk1F0wTRuaIhJ5xxqw
  1117  FIWPFep/n5jhrDOsIs6cSaRbfIBy3rAl956pf/MHKvf/IXh7KlG9p36IW49hjQHK
  1118  7HkCIQD2CqyV1ppNPFSoCI8mSwO8IZppU3i2V4MhpwnqHz3H0wIhAIU5XIlhLJW8
  1119  TNOaFMEia/TuYofdwJnYvi9t0v4UKBWdAiEA76AtvjEoTpi3in/ri0v78zp2/KXD
  1120  JzPMDvZ0fYS30ukCIA1stlJxpFiCXQuFn0nG+jH4Q52FTv8xxBhrbLOFvHRRAiEA
  1121  2Vc9NN09ty+HZgxpwqIA1fHVuYJY9GMPG1LnTnZ9INg=
  1122  -----END RSA PRIVATE KEY-----
  1123  `[1:])
  1124  
  1125  var windowsCloudinitTests = []cloudinitTest{{
  1126  	cfg: makeNormalConfig("win8").setMachineID("10").mutate(func(cfg *testInstanceConfig) {
  1127  		cfg.MongoInfo.Info.CACert = "CA CERT\n" + string(serverCert)
  1128  		cfg.APIInfo.CACert = "CA CERT\n" + string(serverCert)
  1129  	}),
  1130  	setEnvConfig:  false,
  1131  	expectScripts: WindowsUserdata,
  1132  }}
  1133  
  1134  func (*cloudinitSuite) TestWindowsCloudInit(c *gc.C) {
  1135  	for i, test := range windowsCloudinitTests {
  1136  		testConfig := test.cfg.render()
  1137  		c.Logf("test %d", i)
  1138  		ci, err := cloudinit.New("win8")
  1139  		c.Assert(err, jc.ErrorIsNil)
  1140  		udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci)
  1141  
  1142  		c.Assert(err, jc.ErrorIsNil)
  1143  		err = udata.Configure()
  1144  
  1145  		c.Assert(err, jc.ErrorIsNil)
  1146  		c.Check(ci, gc.NotNil)
  1147  		data, err := ci.RenderYAML()
  1148  		c.Assert(err, jc.ErrorIsNil)
  1149  
  1150  		stringData := strings.Replace(string(data), "\r\n", "\n", -1)
  1151  		stringData = strings.Replace(stringData, "\t", " ", -1)
  1152  		stringData = strings.TrimSpace(stringData)
  1153  
  1154  		compareString := strings.Replace(string(test.expectScripts), "\r\n", "\n", -1)
  1155  		compareString = strings.Replace(compareString, "\t", " ", -1)
  1156  		compareString = strings.TrimSpace(compareString)
  1157  
  1158  		testing.CheckString(c, stringData, compareString)
  1159  	}
  1160  }
  1161  
  1162  func (*cloudinitSuite) TestToolsDownloadCommand(c *gc.C) {
  1163  	command := cloudconfig.ToolsDownloadCommand("download", []string{"a", "b", "c"})
  1164  
  1165  	expected := `
  1166  for n in $(seq 5); do
  1167  
  1168      printf "Attempt $n to download tools from %s...\n" 'a'
  1169      download 'a' && echo "Tools downloaded successfully." && break
  1170  
  1171      printf "Attempt $n to download tools from %s...\n" 'b'
  1172      download 'b' && echo "Tools downloaded successfully." && break
  1173  
  1174      printf "Attempt $n to download tools from %s...\n" 'c'
  1175      download 'c' && echo "Tools downloaded successfully." && break
  1176  
  1177      if [ $n -lt 5 ]; then
  1178          echo "Download failed..... wait 15s"
  1179      fi
  1180      sleep 15
  1181  done`
  1182  	c.Assert(command, gc.Equals, expected)
  1183  }