github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	gc "gopkg.in/check.v1"
    17  	goyaml "gopkg.in/yaml.v1"
    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/cert"
    23  	"github.com/juju/juju/cloudconfig"
    24  	"github.com/juju/juju/cloudconfig/cloudinit"
    25  	"github.com/juju/juju/cloudconfig/instancecfg"
    26  	"github.com/juju/juju/cloudconfig/providerinit"
    27  	"github.com/juju/juju/environs/config"
    28  	"github.com/juju/juju/juju/osenv"
    29  	"github.com/juju/juju/juju/paths"
    30  	"github.com/juju/juju/mongo"
    31  	"github.com/juju/juju/provider/dummy"
    32  	"github.com/juju/juju/provider/openstack"
    33  	"github.com/juju/juju/state/multiwatcher"
    34  	"github.com/juju/juju/testing"
    35  	"github.com/juju/juju/tools"
    36  	"github.com/juju/juju/version"
    37  )
    38  
    39  // dummySampleConfig returns the dummy sample config without
    40  // the state server 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  		"state-server": 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:                     true,
    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:                     true,
   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  		"state-server":    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 := utils.UserPasswordHash("lisboan-pork", utils.CompatSalt)
   156  	c.Check(icfg.APIInfo, gc.DeepEquals, &api.Info{
   157  		Password: password, CACert: testing.CACert,
   158  		EnvironTag: testing.EnvironmentTag,
   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, false)
   185  }
   186  
   187  func (s *CloudInitSuite) TestStateServerUserData(c *gc.C) {
   188  	s.testUserData(c, true)
   189  }
   190  
   191  func (*CloudInitSuite) testUserData(c *gc.C, bootstrap bool) {
   192  	testJujuHome := c.MkDir()
   193  	defer osenv.SetJujuHome(osenv.SetJujuHome(testJujuHome))
   194  	// Use actual series paths instead of local defaults
   195  	logDir := must(paths.LogDir("quantal"))
   196  	metricsSpoolDir := must(paths.MetricsSpoolDir("quantal"))
   197  	dataDir := must(paths.DataDir("quantal"))
   198  	tools := &tools.Tools{
   199  		URL:     "http://foo.com/tools/released/juju1.2.3-quantal-amd64.tgz",
   200  		Version: version.MustParseBinary("1.2.3-quantal-amd64"),
   201  	}
   202  	envConfig, err := config.New(config.NoDefaults, dummySampleConfig())
   203  	c.Assert(err, jc.ErrorIsNil)
   204  
   205  	series := "quantal"
   206  	allJobs := []multiwatcher.MachineJob{
   207  		multiwatcher.JobManageEnviron,
   208  		multiwatcher.JobHostUnits,
   209  		multiwatcher.JobManageNetworking,
   210  	}
   211  	cfg := &instancecfg.InstanceConfig{
   212  		MachineId:    "10",
   213  		MachineNonce: "5432",
   214  		Tools:        tools,
   215  		Series:       series,
   216  		MongoInfo: &mongo.MongoInfo{
   217  			Info: mongo.Info{
   218  				Addrs:  []string{"127.0.0.1:1234"},
   219  				CACert: "CA CERT\n" + testing.CACert,
   220  			},
   221  			Password: "pw1",
   222  			Tag:      names.NewMachineTag("10"),
   223  		},
   224  		APIInfo: &api.Info{
   225  			Addrs:      []string{"127.0.0.1:1234"},
   226  			Password:   "pw2",
   227  			CACert:     "CA CERT\n" + testing.CACert,
   228  			Tag:        names.NewMachineTag("10"),
   229  			EnvironTag: testing.EnvironmentTag,
   230  		},
   231  		DataDir:                 dataDir,
   232  		LogDir:                  path.Join(logDir, "juju"),
   233  		MetricsSpoolDir:         metricsSpoolDir,
   234  		Jobs:                    allJobs,
   235  		CloudInitOutputLog:      path.Join(logDir, "cloud-init-output.log"),
   236  		Config:                  envConfig,
   237  		AgentEnvironment:        map[string]string{agent.ProviderType: "dummy"},
   238  		AuthorizedKeys:          "wheredidileavemykeys",
   239  		MachineAgentServiceName: "jujud-machine-10",
   240  		EnableOSUpgrade:         true,
   241  	}
   242  	if bootstrap {
   243  		cfg.Bootstrap = true
   244  		cfg.StateServingInfo = &params.StateServingInfo{
   245  			StatePort:    envConfig.StatePort(),
   246  			APIPort:      envConfig.APIPort(),
   247  			Cert:         testing.ServerCert,
   248  			PrivateKey:   testing.ServerKey,
   249  			CAPrivateKey: testing.CAKey,
   250  		}
   251  	}
   252  	script1 := "script1"
   253  	script2 := "script2"
   254  	cloudcfg, err := cloudinit.New(series)
   255  	c.Assert(err, jc.ErrorIsNil)
   256  	cloudcfg.AddRunCmd(script1)
   257  	cloudcfg.AddRunCmd(script2)
   258  	result, err := providerinit.ComposeUserData(cfg, cloudcfg, &openstack.OpenstackRenderer{})
   259  	c.Assert(err, jc.ErrorIsNil)
   260  
   261  	unzipped, err := utils.Gunzip(result)
   262  	c.Assert(err, jc.ErrorIsNil)
   263  
   264  	config := make(map[interface{}]interface{})
   265  	err = goyaml.Unmarshal(unzipped, &config)
   266  	c.Assert(err, jc.ErrorIsNil)
   267  
   268  	// The scripts given to userData where added as the first
   269  	// commands to be run.
   270  	runCmd := config["runcmd"].([]interface{})
   271  	c.Check(runCmd[0], gc.Equals, script1)
   272  	c.Check(runCmd[1], gc.Equals, script2)
   273  
   274  	if bootstrap {
   275  		// The cloudinit config should have nothing but the basics:
   276  		// SSH authorized keys, the additional runcmds, and log output.
   277  		//
   278  		// Note: the additional runcmds *do* belong here, at least
   279  		// for MAAS. MAAS needs to configure and then bounce the
   280  		// network interfaces, which would sever the SSH connection
   281  		// in the synchronous bootstrap phase.
   282  		c.Check(config, jc.DeepEquals, map[interface{}]interface{}{
   283  			"output": map[interface{}]interface{}{
   284  				"all": "| tee -a /var/log/cloud-init-output.log",
   285  			},
   286  			"runcmd": []interface{}{
   287  				"script1", "script2",
   288  				"set -xe",
   289  				"install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown.conf'",
   290  				"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'",
   291  				"install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'",
   292  				"printf '%s\\n' '5432' > '/var/lib/juju/nonce.txt'",
   293  			},
   294  			"ssh_authorized_keys": []interface{}{"wheredidileavemykeys"},
   295  		})
   296  	} else {
   297  		// Just check that the cloudinit config looks good,
   298  		// and that there are more runcmds than the additional
   299  		// ones we passed into ComposeUserData.
   300  		c.Check(config["package_upgrade"], jc.IsTrue)
   301  		c.Check(len(runCmd) > 2, jc.IsTrue)
   302  	}
   303  }
   304  
   305  func (s *CloudInitSuite) TestWindowsUserdataEncoding(c *gc.C) {
   306  	series := "win8"
   307  	metricsSpoolDir := must(paths.MetricsSpoolDir("win8"))
   308  	tools := &tools.Tools{
   309  		URL:     "http://foo.com/tools/released/juju1.2.3-win8-amd64.tgz",
   310  		Version: version.MustParseBinary("1.2.3-win8-amd64"),
   311  		Size:    10,
   312  		SHA256:  "1234",
   313  	}
   314  	dataDir, err := paths.DataDir(series)
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	logDir, err := paths.LogDir(series)
   317  	c.Assert(err, jc.ErrorIsNil)
   318  
   319  	cfg := instancecfg.InstanceConfig{
   320  		MachineId:        "10",
   321  		AgentEnvironment: map[string]string{agent.ProviderType: "dummy"},
   322  		Tools:            tools,
   323  		Series:           series,
   324  		Bootstrap:        false,
   325  		Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   326  		MachineNonce:     "FAKE_NONCE",
   327  		MongoInfo: &mongo.MongoInfo{
   328  			Tag:      names.NewMachineTag("10"),
   329  			Password: "arble",
   330  			Info: mongo.Info{
   331  				CACert: "CA CERT\n" + testing.CACert,
   332  				Addrs:  []string{"state-addr.testing.invalid:12345"},
   333  			},
   334  		},
   335  		APIInfo: &api.Info{
   336  			Addrs:      []string{"state-addr.testing.invalid:54321"},
   337  			Password:   "bletch",
   338  			CACert:     "CA CERT\n" + testing.CACert,
   339  			Tag:        names.NewMachineTag("10"),
   340  			EnvironTag: testing.EnvironmentTag,
   341  		},
   342  		MachineAgentServiceName: "jujud-machine-10",
   343  		DataDir:                 dataDir,
   344  		LogDir:                  path.Join(logDir, "juju"),
   345  		MetricsSpoolDir:         metricsSpoolDir,
   346  		CloudInitOutputLog:      path.Join(logDir, "cloud-init-output.log"),
   347  	}
   348  
   349  	ci, err := cloudinit.New("win8")
   350  	c.Assert(err, jc.ErrorIsNil)
   351  
   352  	udata, err := cloudconfig.NewUserdataConfig(&cfg, ci)
   353  	c.Assert(err, jc.ErrorIsNil)
   354  	err = udata.Configure()
   355  	c.Assert(err, jc.ErrorIsNil)
   356  	data, err := ci.RenderYAML()
   357  	c.Assert(err, jc.ErrorIsNil)
   358  	base64Data := base64.StdEncoding.EncodeToString(utils.Gzip(data))
   359  	got := []byte(fmt.Sprintf(cloudconfig.UserdataScript, base64Data))
   360  
   361  	cicompose, err := cloudinit.New("win8")
   362  	c.Assert(err, jc.ErrorIsNil)
   363  	expected, err := providerinit.ComposeUserData(&cfg, cicompose, openstack.OpenstackRenderer{})
   364  	c.Assert(err, jc.ErrorIsNil)
   365  
   366  	c.Assert(string(expected), gc.Equals, string(got))
   367  }