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