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