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