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 = &params.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  }