github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	}
    75  
    76  	cfg, err := config.New(config.NoDefaults, dummySampleConfig().Merge(testing.Attrs{
    77  		"authorized-keys": "we-are-the-keys",
    78  	}))
    79  	c.Assert(err, jc.ErrorIsNil)
    80  
    81  	icfg := &instancecfg.InstanceConfig{
    82  		APIInfo: &api.Info{Tag: userTag},
    83  	}
    84  	err = instancecfg.FinishInstanceConfig(icfg, cfg)
    85  
    86  	c.Assert(err, jc.ErrorIsNil)
    87  	c.Assert(icfg, jc.DeepEquals, expectedMcfg)
    88  
    89  	// Test when updates/upgrades are set to false.
    90  	cfg, err = config.New(config.NoDefaults, dummySampleConfig().Merge(testing.Attrs{
    91  		"authorized-keys":          "we-are-the-keys",
    92  		"enable-os-refresh-update": false,
    93  		"enable-os-upgrade":        false,
    94  	}))
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	err = instancecfg.FinishInstanceConfig(icfg, cfg)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	expectedMcfg.EnableOSRefreshUpdate = false
    99  	expectedMcfg.EnableOSUpgrade = false
   100  	c.Assert(icfg, jc.DeepEquals, expectedMcfg)
   101  }
   102  
   103  func (s *CloudInitSuite) TestFinishInstanceConfigNonDefault(c *gc.C) {
   104  	userTag := names.NewLocalUserTag("not-touched")
   105  	attrs := dummySampleConfig().Merge(testing.Attrs{
   106  		"authorized-keys":           "we-are-the-keys",
   107  		"ssl-hostname-verification": false,
   108  	})
   109  	cfg, err := config.New(config.NoDefaults, attrs)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	icfg := &instancecfg.InstanceConfig{
   112  		APIInfo: &api.Info{Tag: userTag},
   113  	}
   114  	err = instancecfg.FinishInstanceConfig(icfg, cfg)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	c.Assert(icfg, jc.DeepEquals, &instancecfg.InstanceConfig{
   117  		AuthorizedKeys: "we-are-the-keys",
   118  		AgentEnvironment: map[string]string{
   119  			agent.ProviderType:  "dummy",
   120  			agent.ContainerType: "",
   121  		},
   122  		APIInfo: &api.Info{Tag: userTag},
   123  		DisableSSLHostnameVerification: true,
   124  		EnableOSRefreshUpdate:          true,
   125  		EnableOSUpgrade:                true,
   126  	})
   127  }
   128  
   129  func (s *CloudInitSuite) TestUserData(c *gc.C) {
   130  	s.testUserData(c, "quantal", false)
   131  }
   132  
   133  func (s *CloudInitSuite) TestControllerUserData(c *gc.C) {
   134  	s.testUserData(c, "quantal", true)
   135  }
   136  
   137  func (s *CloudInitSuite) TestControllerUserDataPrecise(c *gc.C) {
   138  	s.testUserData(c, "precise", true)
   139  }
   140  
   141  func (*CloudInitSuite) testUserData(c *gc.C, series string, bootstrap bool) {
   142  	// Use actual series paths instead of local defaults
   143  	logDir := must(paths.LogDir(series))
   144  	metricsSpoolDir := must(paths.MetricsSpoolDir(series))
   145  	dataDir := must(paths.DataDir(series))
   146  	toolsList := tools.List{
   147  		&tools.Tools{
   148  			URL:     "http://tools.testing/tools/released/juju.tgz",
   149  			Version: version.Binary{version.MustParse("1.2.3"), "quantal", "amd64"},
   150  		},
   151  	}
   152  	envConfig, err := config.New(config.NoDefaults, dummySampleConfig())
   153  	c.Assert(err, jc.ErrorIsNil)
   154  
   155  	allJobs := []multiwatcher.MachineJob{
   156  		multiwatcher.JobManageModel,
   157  		multiwatcher.JobHostUnits,
   158  	}
   159  	cfg := &instancecfg.InstanceConfig{
   160  		ControllerTag: testing.ControllerTag,
   161  		MachineId:     "10",
   162  		MachineNonce:  "5432",
   163  		Series:        series,
   164  		APIInfo: &api.Info{
   165  			Addrs:    []string{"127.0.0.1:1234"},
   166  			Password: "pw2",
   167  			CACert:   "CA CERT\n" + testing.CACert,
   168  			Tag:      names.NewMachineTag("10"),
   169  			ModelTag: testing.ModelTag,
   170  		},
   171  		DataDir:                 dataDir,
   172  		LogDir:                  path.Join(logDir, "juju"),
   173  		MetricsSpoolDir:         metricsSpoolDir,
   174  		Jobs:                    allJobs,
   175  		CloudInitOutputLog:      path.Join(logDir, "cloud-init-output.log"),
   176  		AgentEnvironment:        map[string]string{agent.ProviderType: "dummy"},
   177  		AuthorizedKeys:          "wheredidileavemykeys",
   178  		MachineAgentServiceName: "jujud-machine-10",
   179  		EnableOSUpgrade:         true,
   180  	}
   181  	err = cfg.SetTools(toolsList)
   182  	c.Assert(err, jc.ErrorIsNil)
   183  	if bootstrap {
   184  		controllerCfg := testing.FakeControllerConfig()
   185  		cfg.Bootstrap = &instancecfg.BootstrapConfig{
   186  			StateInitializationParams: instancecfg.StateInitializationParams{
   187  				ControllerConfig:      controllerCfg,
   188  				ControllerModelConfig: envConfig,
   189  			},
   190  			StateServingInfo: params.StateServingInfo{
   191  				StatePort:    controllerCfg.StatePort(),
   192  				APIPort:      controllerCfg.APIPort(),
   193  				Cert:         testing.ServerCert,
   194  				PrivateKey:   testing.ServerKey,
   195  				CAPrivateKey: testing.CAKey,
   196  			},
   197  		}
   198  		cfg.Controller = &instancecfg.ControllerConfig{
   199  			MongoInfo: &mongo.MongoInfo{
   200  				Info: mongo.Info{
   201  					Addrs:  []string{"127.0.0.1:1234"},
   202  					CACert: "CA CERT\n" + testing.CACert,
   203  				},
   204  				Password: "pw1",
   205  				Tag:      names.NewMachineTag("10"),
   206  			},
   207  		}
   208  	}
   209  	script1 := "script1"
   210  	script2 := "script2"
   211  	cloudcfg, err := cloudinit.New(series)
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	cloudcfg.AddRunCmd(script1)
   214  	cloudcfg.AddRunCmd(script2)
   215  	result, err := providerinit.ComposeUserData(cfg, cloudcfg, &openstack.OpenstackRenderer{})
   216  	c.Assert(err, jc.ErrorIsNil)
   217  
   218  	unzipped, err := utils.Gunzip(result)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  
   221  	config := make(map[interface{}]interface{})
   222  	err = goyaml.Unmarshal(unzipped, &config)
   223  	c.Assert(err, jc.ErrorIsNil)
   224  
   225  	// The scripts given to userData where added as the first
   226  	// commands to be run.
   227  	runCmd := config["runcmd"].([]interface{})
   228  	c.Check(runCmd[0], gc.Equals, script1)
   229  	c.Check(runCmd[1], gc.Equals, script2)
   230  
   231  	if bootstrap {
   232  		// The cloudinit config should have nothing but the basics:
   233  		// SSH authorized keys, the additional runcmds, and log output.
   234  		//
   235  		// Note: the additional runcmds *do* belong here, at least
   236  		// for MAAS. MAAS needs to configure and then bounce the
   237  		// network interfaces, which would sever the SSH connection
   238  		// in the synchronous bootstrap phase.
   239  		expected := map[interface{}]interface{}{
   240  			"output": map[interface{}]interface{}{
   241  				"all": "| tee -a /var/log/cloud-init-output.log",
   242  			},
   243  			"runcmd": []interface{}{
   244  				"script1", "script2",
   245  				"set -xe",
   246  				"install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown.conf'",
   247  				"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'",
   248  				"install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'",
   249  				"printf '%s\\n' '5432' > '/var/lib/juju/nonce.txt'",
   250  			},
   251  		}
   252  		// Series with old cloudinit versions don't support adding
   253  		// users so need the old way to set SSH authorized keys.
   254  		if series == "precise" {
   255  			expected["ssh_authorized_keys"] = []interface{}{
   256  				"wheredidileavemykeys",
   257  			}
   258  		} else {
   259  			expected["users"] = []interface{}{
   260  				map[interface{}]interface{}{
   261  					"name":        "ubuntu",
   262  					"lock_passwd": true,
   263  					"groups": []interface{}{"adm", "audio",
   264  						"cdrom", "dialout", "dip",
   265  						"floppy", "netdev", "plugdev",
   266  						"sudo", "video"},
   267  					"shell":               "/bin/bash",
   268  					"sudo":                []interface{}{"ALL=(ALL) NOPASSWD:ALL"},
   269  					"ssh-authorized-keys": []interface{}{"wheredidileavemykeys"},
   270  				},
   271  			}
   272  		}
   273  		c.Check(config, jc.DeepEquals, expected)
   274  	} else {
   275  		// Just check that the cloudinit config looks good,
   276  		// and that there are more runcmds than the additional
   277  		// ones we passed into ComposeUserData.
   278  		c.Check(config["package_upgrade"], jc.IsTrue)
   279  		c.Check(len(runCmd) > 2, jc.IsTrue)
   280  	}
   281  }
   282  
   283  func (s *CloudInitSuite) TestWindowsUserdataEncoding(c *gc.C) {
   284  	series := "win8"
   285  	metricsSpoolDir := must(paths.MetricsSpoolDir("win8"))
   286  	toolsList := tools.List{
   287  		&tools.Tools{
   288  			URL:     "http://foo.com/tools/released/juju1.2.3-win8-amd64.tgz",
   289  			Version: version.MustParseBinary("1.2.3-win8-amd64"),
   290  			Size:    10,
   291  			SHA256:  "1234",
   292  		},
   293  	}
   294  	dataDir, err := paths.DataDir(series)
   295  	c.Assert(err, jc.ErrorIsNil)
   296  	logDir, err := paths.LogDir(series)
   297  	c.Assert(err, jc.ErrorIsNil)
   298  
   299  	cfg := instancecfg.InstanceConfig{
   300  		ControllerTag:    testing.ControllerTag,
   301  		MachineId:        "10",
   302  		AgentEnvironment: map[string]string{agent.ProviderType: "dummy"},
   303  		Series:           series,
   304  		Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   305  		MachineNonce:     "FAKE_NONCE",
   306  		APIInfo: &api.Info{
   307  			Addrs:    []string{"state-addr.testing.invalid:54321"},
   308  			Password: "bletch",
   309  			CACert:   "CA CERT\n" + testing.CACert,
   310  			Tag:      names.NewMachineTag("10"),
   311  			ModelTag: testing.ModelTag,
   312  		},
   313  		MachineAgentServiceName: "jujud-machine-10",
   314  		DataDir:                 dataDir,
   315  		LogDir:                  path.Join(logDir, "juju"),
   316  		MetricsSpoolDir:         metricsSpoolDir,
   317  		CloudInitOutputLog:      path.Join(logDir, "cloud-init-output.log"),
   318  	}
   319  	err = cfg.SetTools(toolsList)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  
   322  	ci, err := cloudinit.New("win8")
   323  	c.Assert(err, jc.ErrorIsNil)
   324  
   325  	udata, err := cloudconfig.NewUserdataConfig(&cfg, ci)
   326  	c.Assert(err, jc.ErrorIsNil)
   327  
   328  	err = udata.Configure()
   329  	c.Assert(err, jc.ErrorIsNil)
   330  
   331  	data, err := ci.RenderYAML()
   332  	c.Assert(err, jc.ErrorIsNil)
   333  
   334  	cicompose, err := cloudinit.New("win8")
   335  	c.Assert(err, jc.ErrorIsNil)
   336  
   337  	base64Data := base64.StdEncoding.EncodeToString(utils.Gzip(data))
   338  	got := []byte(fmt.Sprintf(cloudconfig.UserDataScript, base64Data))
   339  	expected, err := providerinit.ComposeUserData(&cfg, cicompose, openstack.OpenstackRenderer{})
   340  	c.Assert(err, jc.ErrorIsNil)
   341  	c.Assert(string(got), gc.Equals, string(expected))
   342  }