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  }