github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cloudconfig/providerinit/providerinit_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package providerinit_test 6 7 import ( 8 "encoding/base64" 9 "fmt" 10 "path" 11 "time" 12 13 "github.com/juju/names" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 "github.com/juju/version" 17 gc "gopkg.in/check.v1" 18 goyaml "gopkg.in/yaml.v2" 19 20 "github.com/juju/juju/agent" 21 "github.com/juju/juju/api" 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/cert" 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/cloudconfig/providerinit" 28 "github.com/juju/juju/environs/config" 29 "github.com/juju/juju/juju/osenv" 30 "github.com/juju/juju/juju/paths" 31 "github.com/juju/juju/mongo" 32 "github.com/juju/juju/provider/dummy" 33 "github.com/juju/juju/provider/openstack" 34 "github.com/juju/juju/state/multiwatcher" 35 "github.com/juju/juju/testing" 36 "github.com/juju/juju/tools" 37 ) 38 39 // dummySampleConfig returns the dummy sample config without 40 // the controller configured. 41 // This function also exists in environs/config_test 42 // Maybe place it in dummy and export it? 43 func dummySampleConfig() testing.Attrs { 44 return dummy.SampleConfig().Merge(testing.Attrs{ 45 "controller": false, 46 }) 47 } 48 49 type CloudInitSuite struct { 50 testing.BaseSuite 51 } 52 53 var _ = gc.Suite(&CloudInitSuite{}) 54 55 // TODO: add this to the utils package 56 func must(s string, err error) string { 57 if err != nil { 58 panic(err) 59 } 60 return s 61 } 62 63 func (s *CloudInitSuite) TestFinishInstanceConfig(c *gc.C) { 64 65 userTag := names.NewLocalUserTag("not-touched") 66 67 expectedMcfg := &instancecfg.InstanceConfig{ 68 AuthorizedKeys: "we-are-the-keys", 69 AgentEnvironment: map[string]string{ 70 agent.ProviderType: "dummy", 71 agent.ContainerType: "", 72 }, 73 MongoInfo: &mongo.MongoInfo{Tag: userTag}, 74 APIInfo: &api.Info{Tag: userTag}, 75 DisableSSLHostnameVerification: false, 76 PreferIPv6: false, 77 EnableOSRefreshUpdate: true, 78 EnableOSUpgrade: true, 79 } 80 81 cfg, err := config.New(config.NoDefaults, dummySampleConfig().Merge(testing.Attrs{ 82 "authorized-keys": "we-are-the-keys", 83 })) 84 c.Assert(err, jc.ErrorIsNil) 85 86 icfg := &instancecfg.InstanceConfig{ 87 MongoInfo: &mongo.MongoInfo{Tag: userTag}, 88 APIInfo: &api.Info{Tag: userTag}, 89 } 90 err = instancecfg.FinishInstanceConfig(icfg, cfg) 91 92 c.Assert(err, jc.ErrorIsNil) 93 c.Assert(icfg, jc.DeepEquals, expectedMcfg) 94 95 // Test when updates/upgrades are set to false. 96 cfg, err = config.New(config.NoDefaults, dummySampleConfig().Merge(testing.Attrs{ 97 "authorized-keys": "we-are-the-keys", 98 "enable-os-refresh-update": false, 99 "enable-os-upgrade": false, 100 })) 101 c.Assert(err, jc.ErrorIsNil) 102 err = instancecfg.FinishInstanceConfig(icfg, cfg) 103 c.Assert(err, jc.ErrorIsNil) 104 expectedMcfg.EnableOSRefreshUpdate = false 105 expectedMcfg.EnableOSUpgrade = false 106 c.Assert(icfg, jc.DeepEquals, expectedMcfg) 107 } 108 109 func (s *CloudInitSuite) TestFinishInstanceConfigNonDefault(c *gc.C) { 110 userTag := names.NewLocalUserTag("not-touched") 111 attrs := dummySampleConfig().Merge(testing.Attrs{ 112 "authorized-keys": "we-are-the-keys", 113 "ssl-hostname-verification": false, 114 }) 115 cfg, err := config.New(config.NoDefaults, attrs) 116 c.Assert(err, jc.ErrorIsNil) 117 icfg := &instancecfg.InstanceConfig{ 118 MongoInfo: &mongo.MongoInfo{Tag: userTag}, 119 APIInfo: &api.Info{Tag: userTag}, 120 } 121 err = instancecfg.FinishInstanceConfig(icfg, cfg) 122 c.Assert(err, jc.ErrorIsNil) 123 c.Assert(icfg, jc.DeepEquals, &instancecfg.InstanceConfig{ 124 AuthorizedKeys: "we-are-the-keys", 125 AgentEnvironment: map[string]string{ 126 agent.ProviderType: "dummy", 127 agent.ContainerType: "", 128 }, 129 MongoInfo: &mongo.MongoInfo{Tag: userTag}, 130 APIInfo: &api.Info{Tag: userTag}, 131 DisableSSLHostnameVerification: true, 132 PreferIPv6: false, 133 EnableOSRefreshUpdate: true, 134 EnableOSUpgrade: true, 135 }) 136 } 137 138 func (s *CloudInitSuite) TestFinishBootstrapConfig(c *gc.C) { 139 attrs := dummySampleConfig().Merge(testing.Attrs{ 140 "authorized-keys": "we-are-the-keys", 141 "admin-secret": "lisboan-pork", 142 "agent-version": "1.2.3", 143 "controller": false, 144 }) 145 cfg, err := config.New(config.NoDefaults, attrs) 146 c.Assert(err, jc.ErrorIsNil) 147 oldAttrs := cfg.AllAttrs() 148 icfg := &instancecfg.InstanceConfig{ 149 Bootstrap: true, 150 } 151 err = instancecfg.FinishInstanceConfig(icfg, cfg) 152 c.Assert(err, jc.ErrorIsNil) 153 c.Check(icfg.AuthorizedKeys, gc.Equals, "we-are-the-keys") 154 c.Check(icfg.DisableSSLHostnameVerification, jc.IsFalse) 155 password := "lisboan-pork" 156 c.Check(icfg.APIInfo, gc.DeepEquals, &api.Info{ 157 Password: password, CACert: testing.CACert, 158 ModelTag: testing.ModelTag, 159 }) 160 c.Check(icfg.MongoInfo, gc.DeepEquals, &mongo.MongoInfo{ 161 Password: password, Info: mongo.Info{CACert: testing.CACert}, 162 }) 163 c.Check(icfg.StateServingInfo.StatePort, gc.Equals, cfg.StatePort()) 164 c.Check(icfg.StateServingInfo.APIPort, gc.Equals, cfg.APIPort()) 165 c.Check(icfg.StateServingInfo.CAPrivateKey, gc.Equals, oldAttrs["ca-private-key"]) 166 167 oldAttrs["ca-private-key"] = "" 168 oldAttrs["admin-secret"] = "" 169 c.Check(icfg.Config.AllAttrs(), gc.DeepEquals, oldAttrs) 170 srvCertPEM := icfg.StateServingInfo.Cert 171 srvKeyPEM := icfg.StateServingInfo.PrivateKey 172 _, _, err = cert.ParseCertAndKey(srvCertPEM, srvKeyPEM) 173 c.Check(err, jc.ErrorIsNil) 174 175 err = cert.Verify(srvCertPEM, testing.CACert, time.Now()) 176 c.Assert(err, jc.ErrorIsNil) 177 err = cert.Verify(srvCertPEM, testing.CACert, time.Now().AddDate(9, 0, 0)) 178 c.Assert(err, jc.ErrorIsNil) 179 err = cert.Verify(srvCertPEM, testing.CACert, time.Now().AddDate(10, 0, 1)) 180 c.Assert(err, gc.NotNil) 181 } 182 183 func (s *CloudInitSuite) TestUserData(c *gc.C) { 184 s.testUserData(c, "quantal", false) 185 } 186 187 func (s *CloudInitSuite) TestControllerUserData(c *gc.C) { 188 s.testUserData(c, "quantal", true) 189 } 190 191 func (s *CloudInitSuite) TestControllerUserDataPrecise(c *gc.C) { 192 s.testUserData(c, "precise", true) 193 } 194 195 func (*CloudInitSuite) testUserData(c *gc.C, series string, bootstrap bool) { 196 testJujuXDGDataHome := c.MkDir() 197 defer osenv.SetJujuXDGDataHome(osenv.SetJujuXDGDataHome(testJujuXDGDataHome)) 198 // Use actual series paths instead of local defaults 199 logDir := must(paths.LogDir(series)) 200 metricsSpoolDir := must(paths.MetricsSpoolDir(series)) 201 dataDir := must(paths.DataDir(series)) 202 toolsList := tools.List{ 203 &tools.Tools{ 204 URL: "http://tools.testing/tools/released/juju.tgz", 205 Version: version.Binary{version.MustParse("1.2.3"), "quantal", "amd64"}, 206 }, 207 } 208 envConfig, err := config.New(config.NoDefaults, dummySampleConfig()) 209 c.Assert(err, jc.ErrorIsNil) 210 211 allJobs := []multiwatcher.MachineJob{ 212 multiwatcher.JobManageModel, 213 multiwatcher.JobHostUnits, 214 multiwatcher.JobManageNetworking, 215 } 216 cfg := &instancecfg.InstanceConfig{ 217 MachineId: "10", 218 MachineNonce: "5432", 219 Series: series, 220 MongoInfo: &mongo.MongoInfo{ 221 Info: mongo.Info{ 222 Addrs: []string{"127.0.0.1:1234"}, 223 CACert: "CA CERT\n" + testing.CACert, 224 }, 225 Password: "pw1", 226 Tag: names.NewMachineTag("10"), 227 }, 228 APIInfo: &api.Info{ 229 Addrs: []string{"127.0.0.1:1234"}, 230 Password: "pw2", 231 CACert: "CA CERT\n" + testing.CACert, 232 Tag: names.NewMachineTag("10"), 233 ModelTag: testing.ModelTag, 234 }, 235 DataDir: dataDir, 236 LogDir: path.Join(logDir, "juju"), 237 MetricsSpoolDir: metricsSpoolDir, 238 Jobs: allJobs, 239 CloudInitOutputLog: path.Join(logDir, "cloud-init-output.log"), 240 Config: envConfig, 241 AgentEnvironment: map[string]string{agent.ProviderType: "dummy"}, 242 AuthorizedKeys: "wheredidileavemykeys", 243 MachineAgentServiceName: "jujud-machine-10", 244 EnableOSUpgrade: true, 245 } 246 err = cfg.SetTools(toolsList) 247 c.Assert(err, jc.ErrorIsNil) 248 if bootstrap { 249 cfg.Bootstrap = true 250 cfg.StateServingInfo = ¶ms.StateServingInfo{ 251 StatePort: envConfig.StatePort(), 252 APIPort: envConfig.APIPort(), 253 Cert: testing.ServerCert, 254 PrivateKey: testing.ServerKey, 255 CAPrivateKey: testing.CAKey, 256 } 257 } 258 script1 := "script1" 259 script2 := "script2" 260 cloudcfg, err := cloudinit.New(series) 261 c.Assert(err, jc.ErrorIsNil) 262 cloudcfg.AddRunCmd(script1) 263 cloudcfg.AddRunCmd(script2) 264 result, err := providerinit.ComposeUserData(cfg, cloudcfg, &openstack.OpenstackRenderer{}) 265 c.Assert(err, jc.ErrorIsNil) 266 267 unzipped, err := utils.Gunzip(result) 268 c.Assert(err, jc.ErrorIsNil) 269 270 config := make(map[interface{}]interface{}) 271 err = goyaml.Unmarshal(unzipped, &config) 272 c.Assert(err, jc.ErrorIsNil) 273 274 // The scripts given to userData where added as the first 275 // commands to be run. 276 runCmd := config["runcmd"].([]interface{}) 277 c.Check(runCmd[0], gc.Equals, script1) 278 c.Check(runCmd[1], gc.Equals, script2) 279 280 if bootstrap { 281 // The cloudinit config should have nothing but the basics: 282 // SSH authorized keys, the additional runcmds, and log output. 283 // 284 // Note: the additional runcmds *do* belong here, at least 285 // for MAAS. MAAS needs to configure and then bounce the 286 // network interfaces, which would sever the SSH connection 287 // in the synchronous bootstrap phase. 288 expected := map[interface{}]interface{}{ 289 "output": map[interface{}]interface{}{ 290 "all": "| tee -a /var/log/cloud-init-output.log", 291 }, 292 "runcmd": []interface{}{ 293 "script1", "script2", 294 "set -xe", 295 "install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown.conf'", 296 "printf '%s\\n' '\nauthor \"Juju Team <juju@lists.ubuntu.com>\"\ndescription \"Stop all network interfaces on shutdown\"\nstart on runlevel [016]\ntask\nconsole output\n\nexec /sbin/ifdown -a -v --force\n' > '/etc/init/juju-clean-shutdown.conf'", 297 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 298 "printf '%s\\n' '5432' > '/var/lib/juju/nonce.txt'", 299 }, 300 } 301 // Series with old cloudinit versions don't support adding 302 // users so need the old way to set SSH authorized keys. 303 if series == "precise" { 304 expected["ssh_authorized_keys"] = []interface{}{ 305 "wheredidileavemykeys", 306 } 307 } else { 308 expected["users"] = []interface{}{ 309 map[interface{}]interface{}{ 310 "name": "ubuntu", 311 "lock_passwd": true, 312 "groups": []interface{}{"adm", "audio", 313 "cdrom", "dialout", "dip", 314 "floppy", "netdev", "plugdev", 315 "sudo", "video"}, 316 "shell": "/bin/bash", 317 "sudo": []interface{}{"ALL=(ALL) NOPASSWD:ALL"}, 318 "ssh-authorized-keys": []interface{}{"wheredidileavemykeys"}, 319 }, 320 } 321 } 322 c.Check(config, jc.DeepEquals, expected) 323 } else { 324 // Just check that the cloudinit config looks good, 325 // and that there are more runcmds than the additional 326 // ones we passed into ComposeUserData. 327 c.Check(config["package_upgrade"], jc.IsTrue) 328 c.Check(len(runCmd) > 2, jc.IsTrue) 329 } 330 } 331 332 func (s *CloudInitSuite) TestWindowsUserdataEncoding(c *gc.C) { 333 series := "win8" 334 metricsSpoolDir := must(paths.MetricsSpoolDir("win8")) 335 toolsList := tools.List{ 336 &tools.Tools{ 337 URL: "http://foo.com/tools/released/juju1.2.3-win8-amd64.tgz", 338 Version: version.MustParseBinary("1.2.3-win8-amd64"), 339 Size: 10, 340 SHA256: "1234", 341 }, 342 } 343 dataDir, err := paths.DataDir(series) 344 c.Assert(err, jc.ErrorIsNil) 345 logDir, err := paths.LogDir(series) 346 c.Assert(err, jc.ErrorIsNil) 347 348 cfg := instancecfg.InstanceConfig{ 349 MachineId: "10", 350 AgentEnvironment: map[string]string{agent.ProviderType: "dummy"}, 351 Series: series, 352 Bootstrap: false, 353 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 354 MachineNonce: "FAKE_NONCE", 355 MongoInfo: &mongo.MongoInfo{ 356 Tag: names.NewMachineTag("10"), 357 Password: "arble", 358 Info: mongo.Info{ 359 CACert: "CA CERT\n" + testing.CACert, 360 Addrs: []string{"state-addr.testing.invalid:12345"}, 361 }, 362 }, 363 APIInfo: &api.Info{ 364 Addrs: []string{"state-addr.testing.invalid:54321"}, 365 Password: "bletch", 366 CACert: "CA CERT\n" + testing.CACert, 367 Tag: names.NewMachineTag("10"), 368 ModelTag: testing.ModelTag, 369 }, 370 MachineAgentServiceName: "jujud-machine-10", 371 DataDir: dataDir, 372 LogDir: path.Join(logDir, "juju"), 373 MetricsSpoolDir: metricsSpoolDir, 374 CloudInitOutputLog: path.Join(logDir, "cloud-init-output.log"), 375 } 376 err = cfg.SetTools(toolsList) 377 c.Assert(err, jc.ErrorIsNil) 378 379 ci, err := cloudinit.New("win8") 380 c.Assert(err, jc.ErrorIsNil) 381 382 udata, err := cloudconfig.NewUserdataConfig(&cfg, ci) 383 c.Assert(err, jc.ErrorIsNil) 384 err = udata.Configure() 385 c.Assert(err, jc.ErrorIsNil) 386 data, err := ci.RenderYAML() 387 c.Assert(err, jc.ErrorIsNil) 388 base64Data := base64.StdEncoding.EncodeToString(utils.Gzip(data)) 389 got := []byte(fmt.Sprintf(cloudconfig.UserdataScript, base64Data)) 390 391 cicompose, err := cloudinit.New("win8") 392 c.Assert(err, jc.ErrorIsNil) 393 expected, err := providerinit.ComposeUserData(&cfg, cicompose, openstack.OpenstackRenderer{}) 394 c.Assert(err, jc.ErrorIsNil) 395 396 c.Assert(string(expected), gc.Equals, string(got)) 397 }