github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "path" 13 "path/filepath" 14 "regexp" 15 "strings" 16 "time" 17 18 "github.com/juju/collections/set" 19 "github.com/juju/loggo" 20 pacconf "github.com/juju/packaging/config" 21 "github.com/juju/proxy" 22 jc "github.com/juju/testing/checkers" 23 "github.com/juju/version" 24 gc "gopkg.in/check.v1" 25 "gopkg.in/juju/names.v2" 26 goyaml "gopkg.in/yaml.v2" 27 28 "github.com/juju/juju/agent" 29 "github.com/juju/juju/api" 30 "github.com/juju/juju/apiserver/params" 31 "github.com/juju/juju/cloudconfig" 32 "github.com/juju/juju/cloudconfig/cloudinit" 33 "github.com/juju/juju/cloudconfig/instancecfg" 34 "github.com/juju/juju/core/constraints" 35 "github.com/juju/juju/environs/config" 36 "github.com/juju/juju/environs/imagemetadata" 37 "github.com/juju/juju/juju/paths" 38 jujutesting "github.com/juju/juju/juju/testing" 39 "github.com/juju/juju/mongo" 40 "github.com/juju/juju/state/multiwatcher" 41 "github.com/juju/juju/testing" 42 "github.com/juju/juju/tools" 43 ) 44 45 type cloudinitSuite struct { 46 testing.BaseSuite 47 } 48 49 var _ = gc.Suite(&cloudinitSuite{}) 50 51 var ( 52 envConstraints = constraints.MustParse("mem=2G") 53 bootstrapConstraints = constraints.MustParse("mem=4G") 54 55 allMachineJobs = []multiwatcher.MachineJob{ 56 multiwatcher.JobManageModel, 57 multiwatcher.JobHostUnits, 58 } 59 normalMachineJobs = []multiwatcher.MachineJob{ 60 multiwatcher.JobHostUnits, 61 } 62 ) 63 64 func jujuLogDir(series string) string { 65 return path.Join(must(paths.LogDir(series)), "juju") 66 } 67 68 func jujuDataDir(series string) string { 69 return must(paths.DataDir(series)) 70 } 71 72 func cloudInitOutputLog(logDir string) string { 73 return path.Join(logDir, "cloud-init-output.log") 74 } 75 76 func metricsSpoolDir(series string) string { 77 return must(paths.MetricsSpoolDir(series)) 78 } 79 80 // TODO: add this to the utils package 81 func must(s string, err error) string { 82 if err != nil { 83 panic(err) 84 } 85 return s 86 } 87 88 var stateServingInfo = params.StateServingInfo{ 89 Cert: string(serverCert), 90 PrivateKey: string(serverKey), 91 CAPrivateKey: "ca-private-key", 92 StatePort: 37017, 93 APIPort: 17070, 94 } 95 96 // testcfg wraps InstanceConfig and provides helpers to modify it as 97 // needed for specific test cases before using it. Most methods return 98 // the method receiver (cfg) after (possibly) modifying it to allow 99 // chaining calls. 100 type testInstanceConfig instancecfg.InstanceConfig 101 102 // makeTestConfig returns a minimal instance config for a non state 103 // server machine (unless bootstrap is true) for the given series. 104 func makeTestConfig(series string, bootstrap bool) *testInstanceConfig { 105 const defaultMachineID = "99" 106 107 cfg := new(testInstanceConfig) 108 cfg.ControllerTag = testing.ControllerTag 109 cfg.AuthorizedKeys = "sshkey1" 110 cfg.AgentEnvironment = map[string]string{ 111 agent.ProviderType: "dummy", 112 } 113 cfg.MachineNonce = "FAKE_NONCE" 114 cfg.Jobs = normalMachineJobs 115 cfg.MetricsSpoolDir = metricsSpoolDir(series) 116 // APIInfo (sans Tag) must be initialized before calling setMachineID(). 117 cfg.APIInfo = &api.Info{ 118 Addrs: []string{"state-addr.testing.invalid:54321"}, 119 Password: "bletch", 120 CACert: "CA CERT\n" + testing.CACert, 121 ModelTag: testing.ModelTag, 122 } 123 cfg.setMachineID(defaultMachineID) 124 cfg.setSeries(series) 125 if bootstrap { 126 return cfg.setController() 127 } else { 128 // Non-controller machines fetch their tools from 129 // the controller. 130 icfg := (*instancecfg.InstanceConfig)(cfg) 131 toolsList := icfg.ToolsList() 132 for i, tools := range toolsList { 133 tools.URL = fmt.Sprintf( 134 "https://%s/%s/tools/%s", 135 cfg.APIInfo.Addrs[0], 136 testing.ModelTag.Id(), 137 tools.Version, 138 ) 139 toolsList[i] = tools 140 } 141 if err := icfg.SetTools(toolsList); err != nil { 142 panic(err) 143 } 144 } 145 146 return cfg 147 } 148 149 // makeBootstrapConfig is a shortcut to call makeTestConfig(series, true). 150 func makeBootstrapConfig(series string) *testInstanceConfig { 151 return makeTestConfig(series, true) 152 } 153 154 // makeNormalConfig is a shortcut to call makeTestConfig(series, 155 // false). 156 func makeNormalConfig(series string) *testInstanceConfig { 157 return makeTestConfig(series, false) 158 } 159 160 // setMachineID updates MachineId, MachineAgentServiceName, 161 // MongoInfo.Tag, and APIInfo.Tag to match the given machine ID. If 162 // MongoInfo or APIInfo are nil, they're not changed. 163 func (cfg *testInstanceConfig) setMachineID(id string) *testInstanceConfig { 164 cfg.MachineId = id 165 cfg.MachineAgentServiceName = fmt.Sprintf("jujud-%s", names.NewMachineTag(id).String()) 166 if cfg.APIInfo != nil { 167 cfg.APIInfo.Tag = names.NewMachineTag(id) 168 } 169 return cfg 170 } 171 172 // setGUI populates the configuration with the Juju GUI tools. 173 func (cfg *testInstanceConfig) setGUI(url string) *testInstanceConfig { 174 cfg.Bootstrap.GUI = &tools.GUIArchive{ 175 URL: url, 176 Version: version.MustParse("1.2.3"), 177 Size: 42, 178 SHA256: "1234", 179 } 180 return cfg 181 } 182 183 // maybeSetModelConfig sets the Config field to the given envConfig, if not 184 // nil, and the instance config is for a bootstrap machine. 185 func (cfg *testInstanceConfig) maybeSetModelConfig(envConfig *config.Config) *testInstanceConfig { 186 if envConfig != nil && cfg.Bootstrap != nil { 187 cfg.Bootstrap.ControllerModelConfig = envConfig 188 cfg.Bootstrap.HostedModelConfig = map[string]interface{}{"name": "hosted-model"} 189 } 190 return cfg 191 } 192 193 // setEnableOSUpdateAndUpgrade sets EnableOSRefreshUpdate and EnableOSUpgrade 194 // fields to the given values. 195 func (cfg *testInstanceConfig) setEnableOSUpdateAndUpgrade(updateEnabled, upgradeEnabled bool) *testInstanceConfig { 196 cfg.EnableOSRefreshUpdate = updateEnabled 197 cfg.EnableOSUpgrade = upgradeEnabled 198 return cfg 199 } 200 201 // setSeries sets the series-specific fields (Tools, Series, DataDir, 202 // LogDir, and CloudInitOutputLog) to match the given series. 203 func (cfg *testInstanceConfig) setSeries(series string) *testInstanceConfig { 204 err := ((*instancecfg.InstanceConfig)(cfg)).SetTools(tools.List{ 205 newSimpleTools(fmt.Sprintf("1.2.3-%s-amd64", series)), 206 }) 207 if err != nil { 208 panic(err) 209 } 210 cfg.Series = series 211 cfg.DataDir = jujuDataDir(series) 212 cfg.LogDir = jujuLogDir(series) 213 cfg.CloudInitOutputLog = cloudInitOutputLog(series) 214 return cfg 215 } 216 217 // setController updates the config to be suitable for bootstrapping 218 // a controller instance. 219 func (cfg *testInstanceConfig) setController() *testInstanceConfig { 220 cfg.setMachineID("0") 221 cfg.Controller = &instancecfg.ControllerConfig{ 222 MongoInfo: &mongo.MongoInfo{ 223 Password: "arble", 224 Info: mongo.Info{ 225 Addrs: []string{"state-addr.testing.invalid:12345"}, 226 CACert: "CA CERT\n" + testing.CACert, 227 }, 228 }, 229 } 230 cfg.Bootstrap = &instancecfg.BootstrapConfig{ 231 StateInitializationParams: instancecfg.StateInitializationParams{ 232 BootstrapMachineInstanceId: "i-bootstrap", 233 BootstrapMachineConstraints: bootstrapConstraints, 234 ModelConstraints: envConstraints, 235 }, 236 StateServingInfo: stateServingInfo, 237 Timeout: time.Minute * 10, 238 } 239 cfg.Jobs = allMachineJobs 240 cfg.APIInfo.Tag = nil 241 return cfg.setEnableOSUpdateAndUpgrade(true, false) 242 } 243 244 // mutate calls mutator passing cfg to it, and returns the (possibly) 245 // modified cfg. 246 func (cfg *testInstanceConfig) mutate(mutator func(*testInstanceConfig)) *testInstanceConfig { 247 if mutator == nil { 248 panic("mutator is nil!") 249 } 250 mutator(cfg) 251 return cfg 252 } 253 254 // render returns the config as InstanceConfig. 255 func (cfg *testInstanceConfig) render() instancecfg.InstanceConfig { 256 return instancecfg.InstanceConfig(*cfg) 257 } 258 259 type cloudinitTest struct { 260 cfg *testInstanceConfig 261 setEnvConfig bool 262 expectScripts string 263 // inexactMatch signifies whether we allow extra lines 264 // in the actual scripts found. If it's true, the lines 265 // mentioned in expectScripts must appear in that 266 // order, but they can be arbitrarily interleaved with other 267 // script lines. 268 inexactMatch bool 269 } 270 271 func minimalModelConfig(c *gc.C) *config.Config { 272 cfg, err := config.New(config.NoDefaults, testing.FakeConfig().Delete("authorized-keys")) 273 c.Assert(err, jc.ErrorIsNil) 274 c.Assert(cfg, gc.NotNil) 275 return cfg 276 } 277 278 // Each test gives a cloudinit config - we check the 279 // output to see if it looks correct. 280 var cloudinitTests = []cloudinitTest{ 281 // Test that cloudinit respects update/upgrade settings. 282 { 283 cfg: makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(false, false), 284 inexactMatch: true, 285 // We're just checking for apt-flags. We don't much care if 286 // the script matches. 287 expectScripts: "", 288 setEnvConfig: true, 289 }, 290 291 // Test that cloudinit respects update/upgrade settings. 292 { 293 cfg: makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(true, false), 294 inexactMatch: true, 295 // We're just checking for apt-flags. We don't much care if 296 // the script matches. 297 expectScripts: "", 298 setEnvConfig: true, 299 }, 300 301 // Test that cloudinit respects update/upgrade settings. 302 { 303 cfg: makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(false, true), 304 inexactMatch: true, 305 // We're just checking for apt-flags. We don't much care if 306 // the script matches. 307 expectScripts: "", 308 setEnvConfig: true, 309 }, 310 311 // Test that cloudinit respects update/upgrade settings. 312 { 313 cfg: makeBootstrapConfig("quantal").setEnableOSUpdateAndUpgrade(true, true), 314 inexactMatch: true, 315 // We're just checking for apt-flags. We don't much care if 316 // the script matches. 317 expectScripts: "", 318 setEnvConfig: true, 319 }, 320 321 // precise controller 322 { 323 cfg: makeBootstrapConfig("precise"), 324 setEnvConfig: true, 325 expectScripts: ` 326 install -D -m 644 /dev/null '/etc/apt/preferences\.d/50-cloud-tools' 327 printf '%s\\n' '.*' > '/etc/apt/preferences\.d/50-cloud-tools' 328 set -xe 329 install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf' 330 printf '%s\\n' '.*"Stop all network interfaces.*' > '/etc/init/juju-clean-shutdown\.conf' 331 install -D -m 644 /dev/null '/var/lib/juju/nonce.txt' 332 printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt' 333 test -n "\$JUJU_PROGRESS_FD" \|\| \(exec \{JUJU_PROGRESS_FD\}>&2\) 2>/dev/null && exec \{JUJU_PROGRESS_FD\}>&2 \|\| JUJU_PROGRESS_FD=2 334 \[ -e /etc/profile.d/juju-proxy.sh \] \|\| printf .* >> /etc/profile.d/juju-proxy.sh 335 mkdir -p /var/lib/juju/locks 336 \(id ubuntu &> /dev/null\) && chown ubuntu:ubuntu /var/lib/juju/locks 337 mkdir -p /var/log/juju 338 chown syslog:adm /var/log/juju 339 bin='/var/lib/juju/tools/1\.2\.3-precise-amd64' 340 mkdir -p \$bin 341 echo 'Fetching Juju agent version.* 342 curl .* '.*' --retry 10 -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/released/juju1\.2\.3-precise-amd64\.tgz' 343 sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-precise-amd64\.sha256 344 grep '1234' \$bin/juju1\.2\.3-precise-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) 345 tar zxf \$bin/tools.tar.gz -C \$bin 346 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 347 mkdir -p '/var/lib/juju/agents/machine-0' 348 cat > '/var/lib/juju/agents/machine-0/agent\.conf' << 'EOF'\\n.*\\nEOF 349 chmod 0600 '/var/lib/juju/agents/machine-0/agent\.conf' 350 install -D -m 600 /dev/null '/var/lib/juju/bootstrap-params' 351 printf '%s\\n' '.*' > '/var/lib/juju/bootstrap-params' 352 echo 'Installing Juju machine agent'.* 353 /var/lib/juju/tools/1\.2\.3-precise-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug '/var/lib/juju/bootstrap-params' 354 ln -s 1\.2\.3-precise-amd64 '/var/lib/juju/tools/machine-0' 355 echo 'Starting Juju machine agent \(service jujud-machine-0\)'.* 356 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 .*\\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 357 start jujud-machine-0 358 rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-precise-amd64\.sha256 359 `, 360 }, 361 362 // raring controller - we just test the raring-specific parts of the output. 363 { 364 cfg: makeBootstrapConfig("raring"), 365 setEnvConfig: true, 366 inexactMatch: true, 367 expectScripts: ` 368 bin='/var/lib/juju/tools/1\.2\.3-raring-amd64' 369 curl .* '.*' --retry 10 -o \$bin/tools\.tar\.gz 'http://foo\.com/tools/released/juju1\.2\.3-raring-amd64\.tgz' 370 sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-raring-amd64\.sha256 371 grep '1234' \$bin/juju1\.2\.3-raring-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) 372 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 373 install -D -m 600 /dev/null '/var/lib/juju/bootstrap-params' 374 printf '%s\\n' '.*' > '/var/lib/juju/bootstrap-params' 375 /var/lib/juju/tools/1\.2\.3-raring-amd64/jujud bootstrap-state --timeout 10m0s --data-dir '/var/lib/juju' --debug '/var/lib/juju/bootstrap-params' 376 ln -s 1\.2\.3-raring-amd64 '/var/lib/juju/tools/machine-0' 377 rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-raring-amd64\.sha256 378 `, 379 }, 380 381 // quantal non controller. 382 { 383 cfg: makeNormalConfig("quantal"), 384 expectScripts: ` 385 set -xe 386 install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown\.conf' 387 printf '%s\\n' '.*"Stop all network interfaces on shutdown".*' > '/etc/init/juju-clean-shutdown\.conf' 388 install -D -m 644 /dev/null '/var/lib/juju/nonce.txt' 389 printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt' 390 test -n "\$JUJU_PROGRESS_FD" \|\| \(exec \{JUJU_PROGRESS_FD\}>&2\) 2>/dev/null && exec \{JUJU_PROGRESS_FD\}>&2 \|\| JUJU_PROGRESS_FD=2 391 \[ -e /etc/profile.d/juju-proxy.sh \] \|\| printf .* >> /etc/profile.d/juju-proxy.sh 392 mkdir -p /var/lib/juju/locks 393 \(id ubuntu &> /dev/null\) && chown ubuntu:ubuntu /var/lib/juju/locks 394 mkdir -p /var/log/juju 395 chown syslog:adm /var/log/juju 396 bin='/var/lib/juju/tools/1\.2\.3-quantal-amd64' 397 mkdir -p \$bin 398 echo 'Fetching Juju agent version.* 399 curl -sSfw '.*' --connect-timeout 20 --noproxy "\*" --insecure -o \$bin/tools\.tar\.gz 'https://state-addr\.testing\.invalid:54321/deadbeef-0bad-400d-8000-4b1d0d06f00d/tools/1\.2\.3-quantal-amd64' 400 sha256sum \$bin/tools\.tar\.gz > \$bin/juju1\.2\.3-quantal-amd64\.sha256 401 grep '1234' \$bin/juju1\.2\.3-quantal-amd64.sha256 \|\| \(echo "Tools checksum mismatch"; exit 1\) 402 tar zxf \$bin/tools.tar.gz -C \$bin 403 printf %s '{"version":"1\.2\.3-quantal-amd64","url":"https://state-addr\.testing\.invalid:54321/deadbeef-0bad-400d-8000-4b1d0d06f00d/tools/1\.2\.3-quantal-amd64","sha256":"1234","size":10}' > \$bin/downloaded-tools\.txt 404 mkdir -p '/var/lib/juju/agents/machine-99' 405 cat > '/var/lib/juju/agents/machine-99/agent\.conf' << 'EOF'\\n.*\\nEOF 406 chmod 0600 '/var/lib/juju/agents/machine-99/agent\.conf' 407 ln -s 1\.2\.3-quantal-amd64 '/var/lib/juju/tools/machine-99' 408 echo 'Starting Juju machine agent \(service jujud-machine-99\)'.* 409 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 .*\\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 410 start jujud-machine-99 411 rm \$bin/tools\.tar\.gz && rm \$bin/juju1\.2\.3-quantal-amd64\.sha256 412 `, 413 }, 414 415 // non controller with systemd (vivid) 416 { 417 cfg: makeNormalConfig("vivid"), 418 inexactMatch: true, 419 expectScripts: ` 420 set -xe 421 install -D -m 644 /dev/null '/etc/systemd/system/juju-clean-shutdown\.service' 422 printf '%s\\n' '\\n\[Unit\]\\n.*Stop all network interfaces.*WantedBy=final\.target\\n' > '/etc/systemd.*' 423 /bin/systemctl enable '/etc/systemd/system/juju-clean-shutdown\.service' 424 install -D -m 644 /dev/null '/var/lib/juju/nonce.txt' 425 printf '%s\\n' 'FAKE_NONCE' > '/var/lib/juju/nonce.txt' 426 .* 427 `, 428 }, 429 430 // CentOS non controller with systemd 431 { 432 cfg: makeNormalConfig("centos7"), 433 inexactMatch: true, 434 expectScripts: ` 435 systemctl is-enabled firewalld &> /dev/null && systemctl mask firewalld || true 436 systemctl is-active firewalld &> /dev/null && systemctl stop firewalld || true 437 sed -i "s/\^\.\*requiretty/#Defaults requiretty/" /etc/sudoers 438 `, 439 }, 440 // OpenSUSE non controller with systemd 441 { 442 cfg: makeNormalConfig("opensuseleap"), 443 inexactMatch: true, 444 expectScripts: ` 445 systemctl is-enabled firewalld &> /dev/null && systemctl mask firewalld || true 446 systemctl is-active firewalld &> /dev/null && systemctl stop firewalld || true 447 sed -i "s/\^\.\*requiretty/#Defaults requiretty/" /etc/sudoers 448 `, 449 }, 450 451 // check that it works ok with compound machine ids. 452 { 453 cfg: makeNormalConfig("quantal").mutate(func(cfg *testInstanceConfig) { 454 cfg.MachineContainerType = "lxd" 455 }).setMachineID("2/lxd/1"), 456 inexactMatch: true, 457 expectScripts: ` 458 mkdir -p '/var/lib/juju/agents/machine-2-lxd-1' 459 cat > '/var/lib/juju/agents/machine-2-lxd-1/agent\.conf' << 'EOF'\\n.*\\nEOF 460 chmod 0600 '/var/lib/juju/agents/machine-2-lxd-1/agent\.conf' 461 ln -s 1\.2\.3-quantal-amd64 '/var/lib/juju/tools/machine-2-lxd-1' 462 cat > /etc/init/jujud-machine-2-lxd-1\.conf << 'EOF'\\ndescription "juju agent for machine-2-lxd-1"\\nauthor "Juju Team <juju@lists\.ubuntu\.com>"\\nstart on runlevel \[2345\]\\nstop on runlevel \[!2345\]\\nrespawn\\nnormal exit 0\\n\\nlimit .*\\n\\nscript\\n\\n\\n # Ensure log files are properly protected\\n touch /var/log/juju/machine-2-lxd-1\.log\\n chown syslog:syslog /var/log/juju/machine-2-lxd-1\.log\\n chmod 0600 /var/log/juju/machine-2-lxd-1\.log\\n\\n exec '/var/lib/juju/tools/machine-2-lxd-1/jujud' machine --data-dir '/var/lib/juju' --machine-id 2/lxd/1 --debug >> /var/log/juju/machine-2-lxd-1\.log 2>&1\\nend script\\nEOF\\n 463 start jujud-machine-2-lxd-1 464 `, 465 }, 466 467 // hostname verification disabled. 468 { 469 cfg: makeNormalConfig("quantal").mutate(func(cfg *testInstanceConfig) { 470 cfg.DisableSSLHostnameVerification = true 471 }), 472 inexactMatch: true, 473 expectScripts: ` 474 curl .* --noproxy "\*" --insecure -o \$bin/tools\.tar\.gz 'https://state-addr\.testing\.invalid:54321/deadbeef-0bad-400d-8000-4b1d0d06f00d/tools/1\.2\.3-quantal-amd64' 475 `, 476 }, 477 478 // empty bootstrap contraints. 479 { 480 cfg: makeBootstrapConfig("precise").mutate(func(cfg *testInstanceConfig) { 481 cfg.Bootstrap.BootstrapMachineConstraints = constraints.Value{} 482 }), 483 setEnvConfig: true, 484 inexactMatch: true, 485 expectScripts: ` 486 printf '%s\\n' '.*bootstrap-machine-constraints: {}.*' > '/var/lib/juju/bootstrap-params' 487 `, 488 }, 489 490 // empty environ contraints. 491 { 492 cfg: makeBootstrapConfig("precise").mutate(func(cfg *testInstanceConfig) { 493 cfg.Bootstrap.ModelConstraints = constraints.Value{} 494 }), 495 setEnvConfig: true, 496 inexactMatch: true, 497 expectScripts: ` 498 printf '%s\\n' '.*model-constraints: {}.*' > '/var/lib/juju/bootstrap-params' 499 `, 500 }, 501 502 // custom image metadata (at bootstrap). 503 { 504 cfg: makeBootstrapConfig("trusty").mutate(func(cfg *testInstanceConfig) { 505 cfg.Bootstrap.CustomImageMetadata = []*imagemetadata.ImageMetadata{{ 506 Id: "image-id", 507 Storage: "ebs", 508 VirtType: "pv", 509 Arch: "amd64", 510 Version: "14.04", 511 RegionName: "us-east1", 512 }} 513 }), 514 setEnvConfig: true, 515 inexactMatch: true, 516 expectScripts: ` 517 printf '%s\\n' '.*custom-image-metadata:.*us-east1.*.*' > '/var/lib/juju/bootstrap-params' 518 `, 519 }, 520 521 // custom image metadata signing key. 522 { 523 cfg: makeBootstrapConfig("trusty").mutate(func(cfg *testInstanceConfig) { 524 cfg.Controller.PublicImageSigningKey = "publickey" 525 }), 526 setEnvConfig: true, 527 inexactMatch: true, 528 expectScripts: ` 529 install -D -m 644 /dev/null '.*publicsimplestreamskey' 530 printf '%s\\n' 'publickey' > '.*publicsimplestreamskey' 531 `, 532 }, 533 } 534 535 func newSimpleTools(vers string) *tools.Tools { 536 return &tools.Tools{ 537 URL: "http://foo.com/tools/released/juju" + vers + ".tgz", 538 Version: version.MustParseBinary(vers), 539 Size: 10, 540 SHA256: "1234", 541 } 542 } 543 544 func getAgentConfig(c *gc.C, tag string, scripts []string) (cfg string) { 545 c.Assert(scripts, gc.Not(gc.HasLen), 0) 546 re := regexp.MustCompile(`cat > .*agents/` + regexp.QuoteMeta(tag) + `/agent\.conf' << 'EOF'\n((\n|.)+)\nEOF`) 547 found := false 548 for _, s := range scripts { 549 m := re.FindStringSubmatch(s) 550 if m == nil { 551 continue 552 } 553 cfg = m[1] 554 found = true 555 } 556 c.Assert(found, jc.IsTrue) 557 return cfg 558 } 559 560 // check that any --model-config $base64 is valid and matches t.cfg.Config 561 func checkEnvConfig(c *gc.C, cfg *config.Config, scripts []string) { 562 args := getStateInitializationParams(c, scripts) 563 c.Assert(cfg.AllAttrs(), jc.DeepEquals, args.ControllerModelConfig.AllAttrs()) 564 } 565 566 func getStateInitializationParams(c *gc.C, scripts []string) instancecfg.StateInitializationParams { 567 var args instancecfg.StateInitializationParams 568 c.Assert(scripts, gc.Not(gc.HasLen), 0) 569 re := regexp.MustCompile(`printf '%s\\n' '(?s:(.+))' > '/var/lib/juju/bootstrap-params'`) 570 for _, s := range scripts { 571 m := re.FindStringSubmatch(s) 572 if m == nil { 573 continue 574 } 575 str := strings.Replace(m[1], "'\"'\"'", "'", -1) 576 err := args.Unmarshal([]byte(str)) 577 c.Assert(err, jc.ErrorIsNil) 578 return args 579 } 580 c.Fatal("could not find state initialization params") 581 panic("unreachable") 582 } 583 584 // TestCloudInit checks that the output from the various tests 585 // in cloudinitTests is well formed. 586 func (*cloudinitSuite) TestCloudInit(c *gc.C) { 587 for i, test := range cloudinitTests { 588 589 c.Logf("test %d", i) 590 var envConfig *config.Config 591 if test.setEnvConfig { 592 envConfig = minimalModelConfig(c) 593 } 594 testConfig := test.cfg.maybeSetModelConfig(envConfig).render() 595 ci, err := cloudinit.New(testConfig.Series) 596 c.Assert(err, jc.ErrorIsNil) 597 udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci) 598 c.Assert(err, jc.ErrorIsNil) 599 err = udata.Configure() 600 601 c.Assert(err, jc.ErrorIsNil) 602 c.Check(ci, gc.NotNil) 603 // render the cloudinit config to bytes, and then 604 // back to a map so we can introspect it without 605 // worrying about internal details of the cloudinit 606 // package. 607 data, err := ci.RenderYAML() 608 c.Assert(err, jc.ErrorIsNil) 609 610 configKeyValues := make(map[interface{}]interface{}) 611 err = goyaml.Unmarshal(data, &configKeyValues) 612 c.Assert(err, jc.ErrorIsNil) 613 614 if testConfig.EnableOSRefreshUpdate { 615 c.Check(configKeyValues["package_update"], jc.IsTrue) 616 } else { 617 c.Check(configKeyValues["package_update"], jc.IsFalse) 618 } 619 620 if testConfig.EnableOSUpgrade { 621 c.Check(configKeyValues["package_upgrade"], jc.IsTrue) 622 } else { 623 c.Check(configKeyValues["package_upgrade"], jc.IsFalse) 624 } 625 626 scripts := getScripts(configKeyValues) 627 assertScriptMatch(c, scripts, test.expectScripts, !test.inexactMatch) 628 if testConfig.Bootstrap != nil { 629 checkEnvConfig(c, testConfig.Bootstrap.ControllerModelConfig, scripts) 630 } 631 632 // curl should always be installed, since it's required by jujud. 633 checkPackage(c, configKeyValues, "curl", true) 634 635 tag := names.NewMachineTag(testConfig.MachineId).String() 636 acfg := getAgentConfig(c, tag, scripts) 637 c.Assert(acfg, jc.Contains, "AGENT_SERVICE_NAME: jujud-"+tag) 638 c.Assert(acfg, jc.Contains, "upgradedToVersion: 1.2.3\n") 639 source := "deb http://ubuntu-cloud.archive.canonical.com/ubuntu precise-updates/cloud-tools main" 640 needCloudArchive := testConfig.Series == "precise" 641 checkAptSource(c, configKeyValues, source, pacconf.UbuntuCloudArchiveSigningKey, needCloudArchive) 642 } 643 } 644 645 func (*cloudinitSuite) TestCloudInitWithLocalGUI(c *gc.C) { 646 guiPath := path.Join(c.MkDir(), "gui.tar.bz2") 647 content := []byte("content") 648 err := ioutil.WriteFile(guiPath, content, 0644) 649 c.Assert(err, jc.ErrorIsNil) 650 cfg := makeBootstrapConfig("precise").setGUI("file://" + filepath.ToSlash(guiPath)) 651 guiJson, err := json.Marshal(cfg.Bootstrap.GUI) 652 c.Assert(err, jc.ErrorIsNil) 653 base64Content := base64.StdEncoding.EncodeToString(content) 654 expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`gui='/var/lib/juju/gui' 655 mkdir -p $gui 656 install -D -m 644 /dev/null '/var/lib/juju/gui/gui.tar.bz2' 657 printf %%s %s | base64 -d > '/var/lib/juju/gui/gui.tar.bz2' 658 [ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256 659 [ -f $gui/jujugui.sha256 ] && (grep '1234' $gui/jujugui.sha256 && printf %%s '%s' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch) 660 rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt 661 `, base64Content, guiJson)) 662 checkCloudInitWithGUI(c, cfg, expectedScripts, "") 663 } 664 665 func (*cloudinitSuite) TestCloudInitWithRemoteGUI(c *gc.C) { 666 cfg := makeBootstrapConfig("precise").setGUI("https://1.2.3.4/gui.tar.bz2") 667 guiJson, err := json.Marshal(cfg.Bootstrap.GUI) 668 c.Assert(err, jc.ErrorIsNil) 669 expectedScripts := regexp.QuoteMeta(fmt.Sprintf(`gui='/var/lib/juju/gui' 670 mkdir -p $gui 671 curl -sSf -o $gui/gui.tar.bz2 --retry 10 'https://1.2.3.4/gui.tar.bz2' || echo Unable to retrieve Juju GUI 672 [ -f $gui/gui.tar.bz2 ] && sha256sum $gui/gui.tar.bz2 > $gui/jujugui.sha256 673 [ -f $gui/jujugui.sha256 ] && (grep '1234' $gui/jujugui.sha256 && printf %%s '%s' > $gui/downloaded-gui.txt || echo Juju GUI checksum mismatch) 674 rm -f $gui/gui.tar.bz2 $gui/jujugui.sha256 $gui/downloaded-gui.txt 675 `, guiJson)) 676 checkCloudInitWithGUI(c, cfg, expectedScripts, "") 677 } 678 679 func (*cloudinitSuite) TestCloudInitWithGUIReadError(c *gc.C) { 680 cfg := makeBootstrapConfig("precise").setGUI("file:///no/such/gui.tar.bz2") 681 expectedError := "cannot set up Juju GUI: cannot read Juju GUI archive: .*" 682 checkCloudInitWithGUI(c, cfg, "", expectedError) 683 } 684 685 func (*cloudinitSuite) TestCloudInitWithGUIURLError(c *gc.C) { 686 cfg := makeBootstrapConfig("precise").setGUI(":") 687 expectedError := "cannot set up Juju GUI: cannot parse Juju GUI URL: .*" 688 checkCloudInitWithGUI(c, cfg, "", expectedError) 689 } 690 691 func checkCloudInitWithGUI(c *gc.C, cfg *testInstanceConfig, expectedScripts string, expectedError string) { 692 envConfig := minimalModelConfig(c) 693 testConfig := cfg.maybeSetModelConfig(envConfig).render() 694 ci, err := cloudinit.New(testConfig.Series) 695 c.Assert(err, jc.ErrorIsNil) 696 udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci) 697 c.Assert(err, jc.ErrorIsNil) 698 err = udata.Configure() 699 if expectedError != "" { 700 c.Assert(err, gc.ErrorMatches, expectedError) 701 return 702 } 703 c.Assert(err, jc.ErrorIsNil) 704 c.Check(ci, gc.NotNil) 705 data, err := ci.RenderYAML() 706 c.Assert(err, jc.ErrorIsNil) 707 708 configKeyValues := make(map[interface{}]interface{}) 709 err = goyaml.Unmarshal(data, &configKeyValues) 710 c.Assert(err, jc.ErrorIsNil) 711 712 scripts := getScripts(configKeyValues) 713 assertScriptMatch(c, scripts, expectedScripts, false) 714 } 715 716 func (*cloudinitSuite) TestCloudInitConfigure(c *gc.C) { 717 for i, test := range cloudinitTests { 718 testConfig := test.cfg.maybeSetModelConfig(minimalModelConfig(c)).render() 719 c.Logf("test %d (Configure)", i) 720 cloudcfg, err := cloudinit.New(testConfig.Series) 721 c.Assert(err, jc.ErrorIsNil) 722 udata, err := cloudconfig.NewUserdataConfig(&testConfig, cloudcfg) 723 c.Assert(err, jc.ErrorIsNil) 724 err = udata.Configure() 725 c.Assert(err, jc.ErrorIsNil) 726 } 727 } 728 729 func (s *cloudinitSuite) TestCloudInitConfigCloudInitUserData(c *gc.C) { 730 environConfig := minimalModelConfig(c) 731 environConfig, err := environConfig.Apply(map[string]interface{}{ 732 config.CloudInitUserDataKey: validCloudInitUserData, 733 }) 734 c.Assert(err, jc.ErrorIsNil) 735 instanceCfg := s.createInstanceConfig(c, environConfig) 736 cloudcfg, err := cloudinit.New("xenial") 737 c.Assert(err, jc.ErrorIsNil) 738 udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg) 739 c.Assert(err, jc.ErrorIsNil) 740 err = udata.Configure() 741 c.Assert(err, jc.ErrorIsNil) 742 743 // Verify the settings against cloudinit-userdata 744 cfgPackages := cloudcfg.Packages() 745 expectedPackages := []string{ 746 `ubuntu-fan`, // last juju specified package 747 `python-keystoneclient`, 748 `python-glanceclient`, 749 } 750 c.Assert(len(cfgPackages), jc.GreaterThan, 2) 751 c.Assert(cfgPackages[len(cfgPackages)-3:], gc.DeepEquals, expectedPackages) 752 753 cmds := cloudcfg.RunCmds() 754 beginning := []string{ 755 `mkdir /tmp/preruncmd`, 756 `mkdir /tmp/preruncmd2`, 757 `set -xe`, // first line of juju specified cmds 758 } 759 ending := []string{ 760 `rm $bin/tools.tar.gz && rm $bin/juju2.3.4-quantal-amd64.sha256`, // last line of juju specified cmds 761 `mkdir /tmp/postruncmd`, 762 `mkdir /tmp/postruncmd2`, 763 } 764 c.Assert(len(cmds), jc.GreaterThan, 6) 765 c.Assert(cmds[:3], gc.DeepEquals, beginning) 766 c.Assert(cmds[len(cmds)-3:], gc.DeepEquals, ending) 767 768 c.Assert(cloudcfg.SystemUpgrade(), gc.Equals, false) 769 770 // Render to check for the "unexpected" cloudinit text. 771 // cloudconfig doesn't have public access to all attrs. 772 data, err := cloudcfg.RenderYAML() 773 c.Assert(err, jc.ErrorIsNil) 774 ciContent := make(map[interface{}]interface{}) 775 err = goyaml.Unmarshal(data, &ciContent) 776 c.Assert(err, jc.ErrorIsNil) 777 testCmd, ok := ciContent["test-key"].([]interface{}) 778 c.Assert(ok, jc.IsTrue) 779 c.Check(testCmd, gc.DeepEquals, []interface{}{"test line one"}) 780 } 781 782 var validCloudInitUserData = ` 783 packages: 784 - 'python-keystoneclient' 785 - 'python-glanceclient' 786 preruncmd: 787 - mkdir /tmp/preruncmd 788 - mkdir /tmp/preruncmd2 789 postruncmd: 790 - mkdir /tmp/postruncmd 791 - mkdir /tmp/postruncmd2 792 package_upgrade: false 793 test-key: 794 - test line one 795 `[1:] 796 797 func (*cloudinitSuite) bootstrapConfigScripts(c *gc.C) []string { 798 loggo.GetLogger("").SetLogLevel(loggo.INFO) 799 envConfig := minimalModelConfig(c) 800 instConfig := makeBootstrapConfig("quantal").maybeSetModelConfig(envConfig) 801 rendered := instConfig.render() 802 cloudcfg, err := cloudinit.New(rendered.Series) 803 c.Assert(err, jc.ErrorIsNil) 804 udata, err := cloudconfig.NewUserdataConfig(&rendered, cloudcfg) 805 806 c.Assert(err, jc.ErrorIsNil) 807 err = udata.Configure() 808 c.Assert(err, jc.ErrorIsNil) 809 data, err := cloudcfg.RenderYAML() 810 c.Assert(err, jc.ErrorIsNil) 811 configKeyValues := make(map[interface{}]interface{}) 812 err = goyaml.Unmarshal(data, &configKeyValues) 813 c.Assert(err, jc.ErrorIsNil) 814 815 scripts := getScripts(configKeyValues) 816 for i, script := range scripts { 817 if strings.Contains(script, "bootstrap") { 818 c.Logf("scripts[%d]: %q", i, script) 819 } 820 } 821 return scripts 822 } 823 824 func (s *cloudinitSuite) TestCloudInitConfigureBootstrapLogging(c *gc.C) { 825 scripts := s.bootstrapConfigScripts(c) 826 expected := "jujud bootstrap-state .* --show-log .*" 827 assertScriptMatch(c, scripts, expected, false) 828 } 829 830 func (s *cloudinitSuite) TestCloudInitConfigureBootstrapFeatureFlags(c *gc.C) { 831 s.SetFeatureFlags("special", "foo") 832 scripts := s.bootstrapConfigScripts(c) 833 expected := "JUJU_DEV_FEATURE_FLAGS=foo,special .*/jujud bootstrap-state .*" 834 assertScriptMatch(c, scripts, expected, false) 835 } 836 837 func (*cloudinitSuite) TestCloudInitConfigureUsesGivenConfig(c *gc.C) { 838 // Create a simple cloudinit config with a 'runcmd' statement. 839 cloudcfg, err := cloudinit.New("quantal") 840 c.Assert(err, jc.ErrorIsNil) 841 script := "test script" 842 cloudcfg.AddRunCmd(script) 843 envConfig := minimalModelConfig(c) 844 testConfig := cloudinitTests[0].cfg.maybeSetModelConfig(envConfig).render() 845 udata, err := cloudconfig.NewUserdataConfig(&testConfig, cloudcfg) 846 c.Assert(err, jc.ErrorIsNil) 847 err = udata.Configure() 848 c.Assert(err, jc.ErrorIsNil) 849 data, err := cloudcfg.RenderYAML() 850 c.Assert(err, jc.ErrorIsNil) 851 852 ciContent := make(map[interface{}]interface{}) 853 err = goyaml.Unmarshal(data, &ciContent) 854 c.Assert(err, jc.ErrorIsNil) 855 // The 'runcmd' statement is at the beginning of the list 856 // of 'runcmd' statements. 857 runCmd := ciContent["runcmd"].([]interface{}) 858 c.Check(runCmd[0], gc.Equals, script) 859 } 860 861 func getScripts(configKeyValue map[interface{}]interface{}) []string { 862 var scripts []string 863 if bootcmds, ok := configKeyValue["bootcmd"]; ok { 864 for _, s := range bootcmds.([]interface{}) { 865 scripts = append(scripts, s.(string)) 866 } 867 } 868 for _, s := range configKeyValue["runcmd"].([]interface{}) { 869 scripts = append(scripts, s.(string)) 870 } 871 return scripts 872 } 873 874 type line struct { 875 index int 876 line string 877 } 878 879 func assertScriptMatch(c *gc.C, got []string, expect string, exact bool) { 880 881 // Convert string slice into line struct slice 882 assembleLines := func(lines []string, lineProcessor func(string) string) []line { 883 var assembledLines []line 884 for lineIdx, currLine := range lines { 885 if nil != lineProcessor { 886 currLine = lineProcessor(currLine) 887 } 888 assembledLines = append(assembledLines, line{ 889 index: lineIdx, 890 line: currLine, 891 }) 892 } 893 return assembledLines 894 } 895 896 pats := assembleLines(strings.Split(strings.Trim(expect, "\n"), "\n"), nil) 897 scripts := assembleLines(got, func(line string) string { 898 return strings.Replace(line, "\n", "\\n", -1) // make .* work 899 }) 900 901 // Pop patterns and scripts off the head as we find pairs 902 for { 903 switch { 904 case len(pats) == 0 && len(scripts) == 0: 905 return 906 case len(pats) == 0: 907 if exact { 908 c.Fatalf("too many scripts found (got %q at line %d)", scripts[0].line, scripts[0].index) 909 } 910 return 911 case len(scripts) == 0: 912 if exact { 913 c.Fatalf("too few scripts found (expected %q at line %d)", pats[0].line, pats[0].index) 914 } 915 c.Fatalf("could not find match for %q\ngot:\n%s", pats[0].line, strings.Join(got, "\n")) 916 default: 917 ok, err := regexp.MatchString(pats[0].line, scripts[0].line) 918 c.Assert(err, jc.ErrorIsNil, gc.Commentf("invalid regexp: %q", pats[0].line)) 919 if ok { 920 pats = pats[1:] 921 scripts = scripts[1:] 922 } else if exact { 923 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)) 924 } else { 925 scripts = scripts[1:] 926 } 927 } 928 } 929 } 930 931 // checkPackage checks that the cloudinit will or won't install the given 932 // package, depending on the value of match. 933 func checkPackage(c *gc.C, x map[interface{}]interface{}, pkg string, match bool) { 934 pkgs0 := x["packages"] 935 if pkgs0 == nil { 936 if match { 937 c.Errorf("cloudinit has no entry for packages") 938 } 939 return 940 } 941 942 pkgs := pkgs0.([]interface{}) 943 944 found := false 945 for _, p0 := range pkgs { 946 p := p0.(string) 947 // p might be a space separate list of packages eg 'foo bar qed' so split them up 948 manyPkgs := set.NewStrings(strings.Split(p, " ")...) 949 hasPkg := manyPkgs.Contains(pkg) 950 if p == pkg || hasPkg { 951 found = true 952 break 953 } 954 } 955 switch { 956 case match && !found: 957 c.Errorf("package %q not found in %v", pkg, pkgs) 958 case !match && found: 959 c.Errorf("%q found but not expected in %v", pkg, pkgs) 960 } 961 } 962 963 // checkAptSource checks that the cloudinit will or won't install the given 964 // source, depending on the value of match. 965 func checkAptSource(c *gc.C, x map[interface{}]interface{}, source, key string, match bool) { 966 sources0 := x["apt_sources"] 967 if sources0 == nil { 968 if match { 969 c.Errorf("cloudinit has no entry for apt_sources") 970 } 971 return 972 } 973 974 sources := sources0.([]interface{}) 975 976 found := false 977 for _, s0 := range sources { 978 s := s0.(map[interface{}]interface{}) 979 if s["source"] == source && s["key"] == key { 980 found = true 981 } 982 } 983 switch { 984 case match && !found: 985 c.Errorf("source %q not found in %v", source, sources) 986 case !match && found: 987 c.Errorf("%q found but not expected in %v", source, sources) 988 } 989 } 990 991 // When mutate is called on a known-good InstanceConfig, 992 // there should be an error complaining about the missing 993 // field named by the adjacent err. 994 var verifyTests = []struct { 995 err string 996 mutate func(*instancecfg.InstanceConfig) 997 }{ 998 {"invalid machine id", func(cfg *instancecfg.InstanceConfig) { 999 cfg.MachineId = "-1" 1000 }}, 1001 {"invalid bootstrap configuration: missing model configuration", func(cfg *instancecfg.InstanceConfig) { 1002 cfg.Bootstrap.ControllerModelConfig = nil 1003 }}, 1004 {"invalid controller configuration: missing state info", func(cfg *instancecfg.InstanceConfig) { 1005 cfg.Controller.MongoInfo = nil 1006 }}, 1007 {"missing API info", func(cfg *instancecfg.InstanceConfig) { 1008 cfg.APIInfo = nil 1009 }}, 1010 {"missing model tag", func(cfg *instancecfg.InstanceConfig) { 1011 cfg.APIInfo = &api.Info{ 1012 Addrs: []string{"foo:35"}, 1013 Tag: names.NewMachineTag("99"), 1014 CACert: testing.CACert, 1015 } 1016 }}, 1017 {"invalid controller configuration: missing state hosts", func(cfg *instancecfg.InstanceConfig) { 1018 cfg.Bootstrap = nil 1019 cfg.Controller.MongoInfo = &mongo.MongoInfo{ 1020 Tag: names.NewMachineTag("99"), 1021 Info: mongo.Info{ 1022 CACert: testing.CACert, 1023 }, 1024 } 1025 }}, 1026 {"missing API hosts", func(cfg *instancecfg.InstanceConfig) { 1027 cfg.Bootstrap = nil 1028 cfg.Controller.MongoInfo.Tag = names.NewMachineTag("99") 1029 cfg.APIInfo = &api.Info{ 1030 Tag: names.NewMachineTag("99"), 1031 CACert: testing.CACert, 1032 ModelTag: testing.ModelTag, 1033 } 1034 }}, 1035 {"invalid controller configuration: missing CA certificate", func(cfg *instancecfg.InstanceConfig) { 1036 cfg.Controller.MongoInfo.CACert = "" 1037 }}, 1038 {"invalid bootstrap configuration: missing controller certificate", func(cfg *instancecfg.InstanceConfig) { 1039 cfg.Bootstrap.StateServingInfo.Cert = "" 1040 }}, 1041 {"invalid bootstrap configuration: missing controller private key", func(cfg *instancecfg.InstanceConfig) { 1042 cfg.Bootstrap.StateServingInfo.PrivateKey = "" 1043 }}, 1044 {"invalid bootstrap configuration: missing ca cert private key", func(cfg *instancecfg.InstanceConfig) { 1045 cfg.Bootstrap.StateServingInfo.CAPrivateKey = "" 1046 }}, 1047 {"invalid bootstrap configuration: missing state port", func(cfg *instancecfg.InstanceConfig) { 1048 cfg.Bootstrap.StateServingInfo.StatePort = 0 1049 }}, 1050 {"invalid bootstrap configuration: missing API port", func(cfg *instancecfg.InstanceConfig) { 1051 cfg.Bootstrap.StateServingInfo.APIPort = 0 1052 }}, 1053 {"missing var directory", func(cfg *instancecfg.InstanceConfig) { 1054 cfg.DataDir = "" 1055 }}, 1056 {"missing log directory", func(cfg *instancecfg.InstanceConfig) { 1057 cfg.LogDir = "" 1058 }}, 1059 {"missing cloud-init output log path", func(cfg *instancecfg.InstanceConfig) { 1060 cfg.CloudInitOutputLog = "" 1061 }}, 1062 {"invalid controller configuration: entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) { 1063 cfg.Bootstrap = nil 1064 cfg.Controller.MongoInfo.Tag = names.NewMachineTag("0") 1065 }}, 1066 {"invalid controller configuration: entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) { 1067 cfg.Bootstrap = nil 1068 cfg.Controller.MongoInfo.Tag = nil // admin user 1069 }}, 1070 {"API entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) { 1071 cfg.Bootstrap = nil 1072 cfg.Controller.MongoInfo.Tag = names.NewMachineTag("99") 1073 cfg.APIInfo.Tag = names.NewMachineTag("0") 1074 }}, 1075 {"API entity tag must match started machine", func(cfg *instancecfg.InstanceConfig) { 1076 cfg.Bootstrap = nil 1077 cfg.Controller.MongoInfo.Tag = names.NewMachineTag("99") 1078 cfg.APIInfo.Tag = nil 1079 }}, 1080 {"invalid bootstrap configuration: entity tag must be nil when bootstrapping", func(cfg *instancecfg.InstanceConfig) { 1081 cfg.Controller.MongoInfo.Tag = names.NewMachineTag("0") 1082 }}, 1083 {"invalid bootstrap configuration: entity tag must be nil when bootstrapping", func(cfg *instancecfg.InstanceConfig) { 1084 cfg.APIInfo.Tag = names.NewMachineTag("0") 1085 }}, 1086 {"missing machine nonce", func(cfg *instancecfg.InstanceConfig) { 1087 cfg.MachineNonce = "" 1088 }}, 1089 {"missing machine agent service name", func(cfg *instancecfg.InstanceConfig) { 1090 cfg.MachineAgentServiceName = "" 1091 }}, 1092 {"invalid bootstrap configuration: missing bootstrap machine instance ID", func(cfg *instancecfg.InstanceConfig) { 1093 cfg.Bootstrap.BootstrapMachineInstanceId = "" 1094 }}, 1095 } 1096 1097 // TestCloudInitVerify checks that required fields are appropriately 1098 // checked for by NewCloudInit. 1099 func (*cloudinitSuite) TestCloudInitVerify(c *gc.C) { 1100 toolsList := tools.List{ 1101 newSimpleTools("9.9.9-quantal-arble"), 1102 } 1103 1104 makeCfgWithoutTools := func() instancecfg.InstanceConfig { 1105 return instancecfg.InstanceConfig{ 1106 Bootstrap: &instancecfg.BootstrapConfig{ 1107 StateInitializationParams: instancecfg.StateInitializationParams{ 1108 BootstrapMachineInstanceId: "i-bootstrap", 1109 ControllerModelConfig: minimalModelConfig(c), 1110 HostedModelConfig: map[string]interface{}{"name": "hosted-model"}, 1111 }, 1112 StateServingInfo: stateServingInfo, 1113 }, 1114 Controller: &instancecfg.ControllerConfig{ 1115 MongoInfo: &mongo.MongoInfo{ 1116 Info: mongo.Info{ 1117 Addrs: []string{"host:98765"}, 1118 CACert: testing.CACert, 1119 }, 1120 Password: "password", 1121 }, 1122 }, 1123 ControllerTag: testing.ControllerTag, 1124 MachineId: "99", 1125 AuthorizedKeys: "sshkey1", 1126 Series: "quantal", 1127 AgentEnvironment: map[string]string{agent.ProviderType: "dummy"}, 1128 APIInfo: &api.Info{ 1129 Addrs: []string{"host:9999"}, 1130 CACert: testing.CACert, 1131 ModelTag: testing.ModelTag, 1132 }, 1133 DataDir: jujuDataDir("quantal"), 1134 LogDir: jujuLogDir("quantal"), 1135 MetricsSpoolDir: metricsSpoolDir("quantal"), 1136 Jobs: normalMachineJobs, 1137 CloudInitOutputLog: cloudInitOutputLog("quantal"), 1138 MachineNonce: "FAKE_NONCE", 1139 MachineAgentServiceName: "jujud-machine-99", 1140 } 1141 } 1142 1143 // check that the base configuration does not give an error 1144 ci, err := cloudinit.New("quantal") 1145 c.Assert(err, jc.ErrorIsNil) 1146 1147 // check that missing tools causes an error. 1148 cfg := makeCfgWithoutTools() 1149 udata, err := cloudconfig.NewUserdataConfig(&cfg, ci) 1150 c.Assert(err, jc.ErrorIsNil) 1151 err = udata.Configure() 1152 c.Assert(err, gc.ErrorMatches, "invalid machine configuration: missing agent binaries") 1153 1154 for i, test := range verifyTests { 1155 c.Logf("test %d. %s", i, test.err) 1156 cfg := makeCfgWithoutTools() 1157 err := cfg.SetTools(toolsList) 1158 c.Assert(err, jc.ErrorIsNil) 1159 1160 // check that the base configuration does not give an error 1161 udata, err := cloudconfig.NewUserdataConfig(&cfg, ci) 1162 c.Assert(err, jc.ErrorIsNil) 1163 err = udata.Configure() 1164 c.Assert(err, jc.ErrorIsNil) 1165 1166 test.mutate(&cfg) 1167 udata, err = cloudconfig.NewUserdataConfig(&cfg, ci) 1168 c.Assert(err, jc.ErrorIsNil) 1169 err = udata.Configure() 1170 c.Check(err, gc.ErrorMatches, "invalid machine configuration: "+test.err) 1171 } 1172 } 1173 1174 func (*cloudinitSuite) createInstanceConfig(c *gc.C, environConfig *config.Config) *instancecfg.InstanceConfig { 1175 machineId := "42" 1176 machineNonce := "fake-nonce" 1177 apiInfo := jujutesting.FakeAPIInfo(machineId) 1178 instanceConfig, err := instancecfg.NewInstanceConfig(testing.ControllerTag, machineId, machineNonce, imagemetadata.ReleasedStream, "quantal", apiInfo) 1179 c.Assert(err, jc.ErrorIsNil) 1180 instanceConfig.SetTools(tools.List{ 1181 &tools.Tools{ 1182 Version: version.MustParseBinary("2.3.4-quantal-amd64"), 1183 URL: "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz", 1184 }, 1185 }) 1186 err = instancecfg.FinishInstanceConfig(instanceConfig, environConfig) 1187 c.Assert(err, jc.ErrorIsNil) 1188 return instanceConfig 1189 } 1190 1191 func (s *cloudinitSuite) TestAptProxyNotWrittenIfNotSet(c *gc.C) { 1192 environConfig := minimalModelConfig(c) 1193 instanceCfg := s.createInstanceConfig(c, environConfig) 1194 cloudcfg, err := cloudinit.New("quantal") 1195 c.Assert(err, jc.ErrorIsNil) 1196 udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg) 1197 c.Assert(err, jc.ErrorIsNil) 1198 err = udata.Configure() 1199 c.Assert(err, jc.ErrorIsNil) 1200 1201 cmds := cloudcfg.BootCmds() 1202 c.Assert(cmds, gc.IsNil) 1203 } 1204 1205 func (s *cloudinitSuite) TestAptProxyWritten(c *gc.C) { 1206 environConfig := minimalModelConfig(c) 1207 environConfig, err := environConfig.Apply(map[string]interface{}{ 1208 "apt-http-proxy": "http://user@10.0.0.1", 1209 }) 1210 c.Assert(err, jc.ErrorIsNil) 1211 instanceCfg := s.createInstanceConfig(c, environConfig) 1212 cloudcfg, err := cloudinit.New("quantal") 1213 c.Assert(err, jc.ErrorIsNil) 1214 udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg) 1215 c.Assert(err, jc.ErrorIsNil) 1216 err = udata.Configure() 1217 c.Assert(err, jc.ErrorIsNil) 1218 1219 cmds := cloudcfg.BootCmds() 1220 expected := "printf '%s\\n' 'Acquire::http::Proxy \"http://user@10.0.0.1\";' > /etc/apt/apt.conf.d/95-juju-proxy-settings" 1221 c.Assert(cmds, jc.DeepEquals, []string{expected}) 1222 } 1223 1224 func (s *cloudinitSuite) TestProxyWritten(c *gc.C) { 1225 environConfig := minimalModelConfig(c) 1226 environConfig, err := environConfig.Apply(map[string]interface{}{ 1227 "http-proxy": "http://user@10.0.0.1", 1228 "no-proxy": "localhost,10.0.3.1", 1229 }) 1230 c.Assert(err, jc.ErrorIsNil) 1231 instanceCfg := s.createInstanceConfig(c, environConfig) 1232 cloudcfg, err := cloudinit.New("quantal") 1233 c.Assert(err, jc.ErrorIsNil) 1234 udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg) 1235 c.Assert(err, jc.ErrorIsNil) 1236 err = udata.Configure() 1237 c.Assert(err, jc.ErrorIsNil) 1238 1239 cmds := cloudcfg.RunCmds() 1240 first := `[ -e /etc/profile.d/juju-proxy.sh ] || printf '\n# Added by juju\n[ -f "/etc/juju-proxy.conf" ] && . "/etc/juju-proxy.conf"\n' >> /etc/profile.d/juju-proxy.sh` 1241 expected := []string{ 1242 `export http_proxy=http://user@10.0.0.1`, 1243 `export HTTP_PROXY=http://user@10.0.0.1`, 1244 `export no_proxy=0.1.2.3,10.0.3.1,localhost`, 1245 `export NO_PROXY=0.1.2.3,10.0.3.1,localhost`, 1246 `(printf '%s\n' 'export http_proxy=http://user@10.0.0.1 1247 export HTTP_PROXY=http://user@10.0.0.1 1248 export no_proxy=0.1.2.3,10.0.3.1,localhost 1249 export NO_PROXY=0.1.2.3,10.0.3.1,localhost' > /etc/juju-proxy.conf && chmod 0644 /etc/juju-proxy.conf)`, 1250 `printf '%s\n' '# To allow juju to control the global systemd proxy settings, 1251 # create symbolic links to this file from within /etc/systemd/system.conf.d/ 1252 # and /etc/systemd/users.conf.d/. 1253 [Manager] 1254 DefaultEnvironment="http_proxy=http://user@10.0.0.1" "HTTP_PROXY=http://user@10.0.0.1" "no_proxy=0.1.2.3,10.0.3.1,localhost" "NO_PROXY=0.1.2.3,10.0.3.1,localhost" 1255 ' > /etc/juju-proxy-systemd.conf`, 1256 } 1257 found := false 1258 for i, cmd := range cmds { 1259 if cmd == first { 1260 c.Assert(cmds[i+1:i+7], jc.DeepEquals, expected) 1261 found = true 1262 break 1263 } 1264 } 1265 c.Assert(found, jc.IsTrue) 1266 } 1267 1268 // Ensure the bootstrap curl which fetch tools respects the proxy settings 1269 func (s *cloudinitSuite) TestProxyArgsAddedToCurlCommand(c *gc.C) { 1270 series := "bionic" 1271 instcfg := makeBootstrapConfig("bionic").maybeSetModelConfig( 1272 minimalModelConfig(c), 1273 ).render() 1274 instcfg.JujuProxySettings = proxy.Settings{ 1275 Http: "0.1.2.3", 1276 } 1277 1278 // create the cloud configuration 1279 cldcfg, err := cloudinit.New(series) 1280 c.Assert(err, jc.ErrorIsNil) 1281 1282 // create the user data configuration setup 1283 udata, err := cloudconfig.NewUserdataConfig(&instcfg, cldcfg) 1284 c.Assert(err, jc.ErrorIsNil) 1285 1286 // configure the user data 1287 err = udata.Configure() 1288 c.Assert(err, jc.ErrorIsNil) 1289 1290 // check to see that the first boot curl command to download tools 1291 // respects the configured proxy settings. 1292 cmds := cldcfg.RunCmds() 1293 expectedCurlCommand := "curl -sSfw 'agent binaries from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s ' --retry 10 --proxy 0.1.2.3 -o $bin/tools.tar.gz" 1294 assertCommandsContain(c, cmds, expectedCurlCommand) 1295 } 1296 1297 // search a list of first boot commands to ensure it contains the specified curl 1298 // command. 1299 func assertCommandsContain(c *gc.C, runCmds []string, str string) { 1300 found := false 1301 for _, runCmd := range runCmds { 1302 if strings.Contains(runCmd, str) { 1303 found = true 1304 } 1305 } 1306 c.Logf("expecting to find %q in %#v", str, runCmds) 1307 c.Assert(found, jc.IsTrue) 1308 } 1309 1310 func (s *cloudinitSuite) TestAptMirror(c *gc.C) { 1311 environConfig := minimalModelConfig(c) 1312 environConfig, err := environConfig.Apply(map[string]interface{}{ 1313 "apt-mirror": "http://my.archive.ubuntu.com/ubuntu", 1314 }) 1315 c.Assert(err, jc.ErrorIsNil) 1316 s.testAptMirror(c, environConfig, "http://my.archive.ubuntu.com/ubuntu") 1317 } 1318 1319 func (s *cloudinitSuite) TestAptMirrorNotSet(c *gc.C) { 1320 environConfig := minimalModelConfig(c) 1321 s.testAptMirror(c, environConfig, "") 1322 } 1323 1324 func (s *cloudinitSuite) testAptMirror(c *gc.C, cfg *config.Config, expect string) { 1325 instanceCfg := s.createInstanceConfig(c, cfg) 1326 cloudcfg, err := cloudinit.New("quantal") 1327 c.Assert(err, jc.ErrorIsNil) 1328 udata, err := cloudconfig.NewUserdataConfig(instanceCfg, cloudcfg) 1329 c.Assert(err, jc.ErrorIsNil) 1330 err = udata.Configure() 1331 c.Assert(err, jc.ErrorIsNil) 1332 //mirror, ok := cloudcfg.AptMirror() 1333 mirror := cloudcfg.PackageMirror() 1334 c.Assert(mirror, gc.Equals, expect) 1335 //c.Assert(ok, gc.Equals, expect != "") 1336 } 1337 1338 var serverCert = []byte(` 1339 SERVER CERT 1340 -----BEGIN CERTIFICATE----- 1341 MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwHjENMAsGA1UEChMEanVqdTEN 1342 MAsGA1UEAxMEcm9vdDAeFw0xMjExMDgxNjIyMzRaFw0xMzExMDgxNjI3MzRaMBwx 1343 DDAKBgNVBAoTA2htbTEMMAoGA1UEAxMDYW55MFowCwYJKoZIhvcNAQEBA0sAMEgC 1344 QQCACqz6JPwM7nbxAWub+APpnNB7myckWJ6nnsPKi9SipP1hyhfzkp8RGMJ5Uv7y 1345 8CSTtJ8kg/ibka1VV8LvP9tnAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIAsDAdBgNV 1346 HQ4EFgQU6G1ERaHCgfAv+yoDMFVpDbLOmIQwHwYDVR0jBBgwFoAUP/mfUdwOlHfk 1347 fR+gLQjslxf64w0wCwYJKoZIhvcNAQEFA0EAbn0MaxWVgGYBomeLYfDdb8vCq/5/ 1348 G/2iCUQCXsVrBparMLFnor/iKOkJB5n3z3rtu70rFt+DpX6L8uBR3LB3+A== 1349 -----END CERTIFICATE----- 1350 `[1:]) 1351 1352 var serverKey = []byte(` 1353 SERVER KEY 1354 -----BEGIN RSA PRIVATE KEY----- 1355 MIIBPAIBAAJBAIAKrPok/AzudvEBa5v4A+mc0HubJyRYnqeew8qL1KKk/WHKF/OS 1356 nxEYwnlS/vLwJJO0nySD+JuRrVVXwu8/22cCAwEAAQJBAJsk1F0wTRuaIhJ5xxqw 1357 FIWPFep/n5jhrDOsIs6cSaRbfIBy3rAl956pf/MHKvf/IXh7KlG9p36IW49hjQHK 1358 7HkCIQD2CqyV1ppNPFSoCI8mSwO8IZppU3i2V4MhpwnqHz3H0wIhAIU5XIlhLJW8 1359 TNOaFMEia/TuYofdwJnYvi9t0v4UKBWdAiEA76AtvjEoTpi3in/ri0v78zp2/KXD 1360 JzPMDvZ0fYS30ukCIA1stlJxpFiCXQuFn0nG+jH4Q52FTv8xxBhrbLOFvHRRAiEA 1361 2Vc9NN09ty+HZgxpwqIA1fHVuYJY9GMPG1LnTnZ9INg= 1362 -----END RSA PRIVATE KEY----- 1363 `[1:]) 1364 1365 var windowsCloudinitTests = []cloudinitTest{{ 1366 cfg: makeNormalConfig("win8").setMachineID("10").mutate(func(cfg *testInstanceConfig) { 1367 cfg.APIInfo.CACert = "CA CERT\n" + string(serverCert) 1368 }), 1369 setEnvConfig: false, 1370 expectScripts: WindowsUserdata, 1371 }} 1372 1373 func (*cloudinitSuite) TestWindowsCloudInit(c *gc.C) { 1374 for i, test := range windowsCloudinitTests { 1375 testConfig := test.cfg.render() 1376 c.Logf("test %d", i) 1377 ci, err := cloudinit.New("win8") 1378 c.Assert(err, jc.ErrorIsNil) 1379 udata, err := cloudconfig.NewUserdataConfig(&testConfig, ci) 1380 1381 c.Assert(err, jc.ErrorIsNil) 1382 err = udata.Configure() 1383 1384 c.Assert(err, jc.ErrorIsNil) 1385 c.Check(ci, gc.NotNil) 1386 data, err := ci.RenderYAML() 1387 c.Assert(err, jc.ErrorIsNil) 1388 1389 stringData := strings.Replace(string(data), "\r\n", "\n", -1) 1390 stringData = strings.Replace(stringData, "\t", " ", -1) 1391 stringData = strings.TrimSpace(stringData) 1392 1393 compareString := strings.Replace(string(test.expectScripts), "\r\n", "\n", -1) 1394 compareString = strings.Replace(compareString, "\t", " ", -1) 1395 compareString = strings.TrimSpace(compareString) 1396 1397 testing.CheckString(c, stringData, compareString) 1398 } 1399 } 1400 1401 func (*cloudinitSuite) TestToolsDownloadCommand(c *gc.C) { 1402 command := cloudconfig.ToolsDownloadCommand("download", []string{"a", "b", "c"}) 1403 1404 expected := ` 1405 n=1 1406 while true; do 1407 1408 printf "Attempt $n to download agent binaries from %s...\n" 'a' 1409 download 'a' && echo "Agent binaries downloaded successfully." && break 1410 1411 printf "Attempt $n to download agent binaries from %s...\n" 'b' 1412 download 'b' && echo "Agent binaries downloaded successfully." && break 1413 1414 printf "Attempt $n to download agent binaries from %s...\n" 'c' 1415 download 'c' && echo "Agent binaries downloaded successfully." && break 1416 1417 echo "Download failed, retrying in 15s" 1418 sleep 15 1419 n=$((n+1)) 1420 done` 1421 c.Assert(command, gc.Equals, expected) 1422 } 1423 1424 func expectedUbuntuUser(groups, keys []string) map[string]interface{} { 1425 user := map[string]interface{}{ 1426 "name": "ubuntu", 1427 "lock_passwd": true, 1428 "shell": "/bin/bash", 1429 "sudo": []interface{}{"ALL=(ALL) NOPASSWD:ALL"}, 1430 } 1431 if groups != nil { 1432 user["groups"] = groups 1433 } 1434 if keys != nil { 1435 user["ssh-authorized-keys"] = keys 1436 } 1437 return map[string]interface{}{ 1438 "users": []map[string]interface{}{user}, 1439 } 1440 } 1441 1442 func (*cloudinitSuite) TestSetUbuntuUserPrecise(c *gc.C) { 1443 ci, err := cloudinit.New("precise") 1444 c.Assert(err, jc.ErrorIsNil) 1445 cloudconfig.SetUbuntuUser(ci, "akey") 1446 data, err := ci.RenderYAML() 1447 c.Assert(err, jc.ErrorIsNil) 1448 expected := map[string]interface{}{"ssh_authorized_keys": []string{ 1449 "akey", 1450 }} 1451 c.Assert(string(data), jc.YAMLEquals, expected) 1452 } 1453 1454 func (*cloudinitSuite) TestSetUbuntuUserPreciseNoKeys(c *gc.C) { 1455 ci, err := cloudinit.New("precise") 1456 c.Assert(err, jc.ErrorIsNil) 1457 cloudconfig.SetUbuntuUser(ci, "") 1458 data, err := ci.RenderYAML() 1459 c.Assert(err, jc.ErrorIsNil) 1460 c.Assert(string(data), jc.YAMLEquals, map[string]interface{}{}) 1461 } 1462 1463 func (*cloudinitSuite) TestSetUbuntuUserQuantal(c *gc.C) { 1464 ci, err := cloudinit.New("quantal") 1465 c.Assert(err, jc.ErrorIsNil) 1466 cloudconfig.SetUbuntuUser(ci, "akey") 1467 data, err := ci.RenderYAML() 1468 c.Assert(err, jc.ErrorIsNil) 1469 keys := []string{"akey"} 1470 expected := expectedUbuntuUser(cloudconfig.UbuntuGroups, keys) 1471 c.Assert(string(data), jc.YAMLEquals, expected) 1472 } 1473 1474 func (*cloudinitSuite) TestSetUbuntuUserCentOS(c *gc.C) { 1475 ci, err := cloudinit.New("centos7") 1476 c.Assert(err, jc.ErrorIsNil) 1477 cloudconfig.SetUbuntuUser(ci, "akey\n#also\nbkey") 1478 data, err := ci.RenderYAML() 1479 c.Assert(err, jc.ErrorIsNil) 1480 keys := []string{"akey", "bkey"} 1481 expected := expectedUbuntuUser(cloudconfig.CentOSGroups, keys) 1482 c.Assert(string(data), jc.YAMLEquals, expected) 1483 } 1484 1485 func (*cloudinitSuite) TestCloudInitBootstrapInitialSSHKeys(c *gc.C) { 1486 instConfig := makeBootstrapConfig("quantal").maybeSetModelConfig( 1487 minimalModelConfig(c), 1488 ).render() 1489 instConfig.Bootstrap.InitialSSHHostKeys.RSA = &instancecfg.SSHKeyPair{ 1490 Private: "private", 1491 Public: "public", 1492 } 1493 cloudcfg, err := cloudinit.New(instConfig.Series) 1494 c.Assert(err, jc.ErrorIsNil) 1495 1496 udata, err := cloudconfig.NewUserdataConfig(&instConfig, cloudcfg) 1497 c.Assert(err, jc.ErrorIsNil) 1498 err = udata.Configure() 1499 c.Assert(err, jc.ErrorIsNil) 1500 data, err := cloudcfg.RenderYAML() 1501 c.Assert(err, jc.ErrorIsNil) 1502 1503 configKeyValues := make(map[interface{}]interface{}) 1504 err = goyaml.Unmarshal(data, &configKeyValues) 1505 c.Assert(err, jc.ErrorIsNil) 1506 c.Assert(configKeyValues["ssh_keys"], jc.DeepEquals, map[interface{}]interface{}{ 1507 "rsa_private": "private", 1508 "rsa_public": "public", 1509 }) 1510 1511 cmds := cloudcfg.BootCmds() 1512 c.Assert(cmds, jc.DeepEquals, []string{ 1513 `echo 'Regenerating SSH RSA host key' >&$JUJU_PROGRESS_FD`, 1514 `rm /etc/ssh/ssh_host_rsa_key*`, 1515 `ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key`, 1516 `ssh-keygen -t dsa -N "" -f /etc/ssh/ssh_host_dsa_key`, 1517 `ssh-keygen -t ecdsa -N "" -f /etc/ssh/ssh_host_ecdsa_key`, 1518 }) 1519 } 1520 1521 func (*cloudinitSuite) TestSetUbuntuUserOpenSUSE(c *gc.C) { 1522 ci, err := cloudinit.New("opensuseleap") 1523 c.Assert(err, jc.ErrorIsNil) 1524 cloudconfig.SetUbuntuUser(ci, "akey\n#also\nbkey") 1525 data, err := ci.RenderYAML() 1526 c.Assert(err, jc.ErrorIsNil) 1527 keys := []string{"akey", "bkey"} 1528 expected := expectedUbuntuUser(cloudconfig.OpenSUSEGroups, keys) 1529 c.Assert(string(data), jc.YAMLEquals, expected) 1530 }