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 = ¶ms.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 }