github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/agent/agentbootstrap/bootstrap_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agentbootstrap_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"net"
     9  	"path/filepath"
    10  
    11  	gitjujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	"github.com/juju/utils/series"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/agent"
    19  	"github.com/juju/juju/agent/agentbootstrap"
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/cloud"
    22  	"github.com/juju/juju/cloudconfig/instancecfg"
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/controller"
    25  	"github.com/juju/juju/environs"
    26  	"github.com/juju/juju/environs/config"
    27  	"github.com/juju/juju/instance"
    28  	"github.com/juju/juju/mongo"
    29  	"github.com/juju/juju/mongo/mongotest"
    30  	"github.com/juju/juju/network"
    31  	"github.com/juju/juju/provider/dummy"
    32  	"github.com/juju/juju/state"
    33  	"github.com/juju/juju/state/multiwatcher"
    34  	"github.com/juju/juju/storage/provider"
    35  	"github.com/juju/juju/testing"
    36  	jujuversion "github.com/juju/juju/version"
    37  )
    38  
    39  type bootstrapSuite struct {
    40  	testing.BaseSuite
    41  	mgoInst gitjujutesting.MgoInstance
    42  }
    43  
    44  var _ = gc.Suite(&bootstrapSuite{})
    45  
    46  func (s *bootstrapSuite) SetUpTest(c *gc.C) {
    47  	s.BaseSuite.SetUpTest(c)
    48  	// Don't use MgoSuite, because we need to ensure
    49  	// we have a fresh mongo for each test case.
    50  	s.mgoInst.EnableAuth = true
    51  	err := s.mgoInst.Start(testing.Certs)
    52  	c.Assert(err, jc.ErrorIsNil)
    53  }
    54  
    55  func (s *bootstrapSuite) TearDownTest(c *gc.C) {
    56  	s.mgoInst.Destroy()
    57  	s.BaseSuite.TearDownTest(c)
    58  }
    59  
    60  func (s *bootstrapSuite) TestInitializeState(c *gc.C) {
    61  	dataDir := c.MkDir()
    62  
    63  	lxcFakeNetConfig := filepath.Join(c.MkDir(), "lxc-net")
    64  	netConf := []byte(`
    65    # comments ignored
    66  LXC_BR= ignored
    67  LXC_ADDR = "fooo"
    68  LXC_BRIDGE="foobar" # detected
    69  anything else ignored
    70  LXC_BRIDGE="ignored"`[1:])
    71  	err := ioutil.WriteFile(lxcFakeNetConfig, netConf, 0644)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	s.PatchValue(&network.InterfaceByNameAddrs, func(name string) ([]net.Addr, error) {
    75  		if name == "foobar" {
    76  			// The addresses on the LXC bridge
    77  			return []net.Addr{
    78  				&net.IPAddr{IP: net.IPv4(10, 0, 3, 1)},
    79  				&net.IPAddr{IP: net.IPv4(10, 0, 3, 4)},
    80  			}, nil
    81  		} else if name == network.DefaultLXDBridge {
    82  			// The addresses on the LXD bridge
    83  			return []net.Addr{
    84  				&net.IPAddr{IP: net.IPv4(10, 0, 4, 1)},
    85  				&net.IPAddr{IP: net.IPv4(10, 0, 4, 4)},
    86  			}, nil
    87  		}
    88  		c.Fatalf("unknown bridge in testing: %v", name)
    89  		return nil, nil
    90  	})
    91  	s.PatchValue(&network.LXCNetDefaultConfig, lxcFakeNetConfig)
    92  
    93  	configParams := agent.AgentConfigParams{
    94  		Paths:             agent.Paths{DataDir: dataDir},
    95  		Tag:               names.NewMachineTag("0"),
    96  		UpgradedToVersion: jujuversion.Current,
    97  		StateAddresses:    []string{s.mgoInst.Addr()},
    98  		CACert:            testing.CACert,
    99  		Password:          testing.DefaultMongoPassword,
   100  		Controller:        testing.ControllerTag,
   101  		Model:             testing.ModelTag,
   102  	}
   103  	servingInfo := params.StateServingInfo{
   104  		Cert:           testing.ServerCert,
   105  		PrivateKey:     testing.ServerKey,
   106  		CAPrivateKey:   testing.CAKey,
   107  		APIPort:        1234,
   108  		StatePort:      s.mgoInst.Port(),
   109  		SystemIdentity: "def456",
   110  	}
   111  
   112  	cfg, err := agent.NewStateMachineConfig(configParams, servingInfo)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	_, available := cfg.StateServingInfo()
   116  	c.Assert(available, jc.IsTrue)
   117  	expectBootstrapConstraints := constraints.MustParse("mem=1024M")
   118  	expectModelConstraints := constraints.MustParse("mem=512M")
   119  	expectHW := instance.MustParseHardware("mem=2048M")
   120  	initialAddrs := network.NewAddresses(
   121  		"zeroonetwothree",
   122  		"0.1.2.3",
   123  		"10.0.3.1", // lxc bridge address filtered.
   124  		"10.0.3.4", // lxc bridge address filtered (-"-).
   125  		"10.0.3.3", // not a lxc bridge address
   126  		"10.0.4.1", // lxd bridge address filtered.
   127  		"10.0.4.4", // lxd bridge address filtered.
   128  		"10.0.4.5", // not an lxd bridge address
   129  	)
   130  	filteredAddrs := network.NewAddresses(
   131  		"zeroonetwothree",
   132  		"0.1.2.3",
   133  		"10.0.3.3",
   134  		"10.0.4.5",
   135  	)
   136  
   137  	modelAttrs := testing.FakeConfig().Merge(testing.Attrs{
   138  		"agent-version":  jujuversion.Current.String(),
   139  		"not-for-hosted": "foo",
   140  	})
   141  	modelCfg, err := config.New(config.NoDefaults, modelAttrs)
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	controllerCfg := testing.FakeControllerConfig()
   144  
   145  	hostedModelUUID := utils.MustNewUUID().String()
   146  	hostedModelConfigAttrs := map[string]interface{}{
   147  		"name": "hosted",
   148  		"uuid": hostedModelUUID,
   149  	}
   150  	controllerInheritedConfig := map[string]interface{}{
   151  		"apt-mirror": "http://mirror",
   152  		"no-proxy":   "value",
   153  	}
   154  	regionConfig := cloud.RegionConfig{
   155  		"some-region": cloud.Attrs{
   156  			"no-proxy": "a-value",
   157  		},
   158  	}
   159  	var envProvider fakeProvider
   160  	args := agentbootstrap.InitializeStateParams{
   161  		StateInitializationParams: instancecfg.StateInitializationParams{
   162  			BootstrapMachineConstraints:             expectBootstrapConstraints,
   163  			BootstrapMachineInstanceId:              "i-bootstrap",
   164  			BootstrapMachineHardwareCharacteristics: &expectHW,
   165  			ControllerCloud: cloud.Cloud{
   166  				Type:         "dummy",
   167  				AuthTypes:    []cloud.AuthType{cloud.EmptyAuthType},
   168  				Regions:      []cloud.Region{{Name: "dummy-region"}},
   169  				RegionConfig: regionConfig,
   170  			},
   171  			ControllerCloudName:       "dummy",
   172  			ControllerCloudRegion:     "dummy-region",
   173  			ControllerConfig:          controllerCfg,
   174  			ControllerModelConfig:     modelCfg,
   175  			ModelConstraints:          expectModelConstraints,
   176  			ControllerInheritedConfig: controllerInheritedConfig,
   177  			HostedModelConfig:         hostedModelConfigAttrs,
   178  		},
   179  		BootstrapMachineAddresses: initialAddrs,
   180  		BootstrapMachineJobs:      []multiwatcher.MachineJob{multiwatcher.JobManageModel},
   181  		SharedSecret:              "abc123",
   182  		Provider: func(t string) (environs.EnvironProvider, error) {
   183  			c.Assert(t, gc.Equals, "dummy")
   184  			return &envProvider, nil
   185  		},
   186  		StorageProviderRegistry: provider.CommonStorageProviders(),
   187  	}
   188  
   189  	adminUser := names.NewLocalUserTag("agent-admin")
   190  	st, m, err := agentbootstrap.InitializeState(
   191  		adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil),
   192  	)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	defer st.Close()
   195  
   196  	err = cfg.Write()
   197  	c.Assert(err, jc.ErrorIsNil)
   198  
   199  	// Check that the environment has been set up.
   200  	model, err := st.Model()
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	c.Assert(model.UUID(), gc.Equals, modelCfg.UUID())
   203  
   204  	// Check that initial admin user has been set up correctly.
   205  	modelTag := model.Tag().(names.ModelTag)
   206  	controllerTag := names.NewControllerTag(controllerCfg.ControllerUUID())
   207  	s.assertCanLogInAsAdmin(c, modelTag, controllerTag, testing.DefaultMongoPassword)
   208  	user, err := st.User(model.Owner())
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	c.Assert(user.PasswordValid(testing.DefaultMongoPassword), jc.IsTrue)
   211  
   212  	// Check controller config
   213  	controllerCfg, err = st.ControllerConfig()
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	c.Assert(controllerCfg, jc.DeepEquals, controller.Config{
   216  		"controller-uuid":         testing.ControllerTag.Id(),
   217  		"ca-cert":                 testing.CACert,
   218  		"state-port":              1234,
   219  		"api-port":                17777,
   220  		"set-numa-control-policy": false,
   221  	})
   222  
   223  	// Check that controller model configuration has been added, and
   224  	// model constraints set.
   225  	newModelCfg, err := st.ModelConfig()
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	// Add in the cloud attributes.
   228  	expectedCfg, err := config.New(config.UseDefaults, modelAttrs)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	expectedAttrs := expectedCfg.AllAttrs()
   231  	expectedAttrs["apt-mirror"] = "http://mirror"
   232  	expectedAttrs["no-proxy"] = "value"
   233  	c.Assert(newModelCfg.AllAttrs(), jc.DeepEquals, expectedAttrs)
   234  
   235  	gotModelConstraints, err := st.ModelConstraints()
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints)
   238  
   239  	// Check that the hosted model has been added, model constraints
   240  	// set, and its config contains the same authorized-keys as the
   241  	// controller model.
   242  	hostedModelSt, err := st.ForModel(names.NewModelTag(hostedModelUUID))
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	defer hostedModelSt.Close()
   245  	gotModelConstraints, err = hostedModelSt.ModelConstraints()
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints)
   248  	hostedModel, err := hostedModelSt.Model()
   249  	c.Assert(err, jc.ErrorIsNil)
   250  	c.Assert(hostedModel.Name(), gc.Equals, "hosted")
   251  	c.Assert(hostedModel.CloudRegion(), gc.Equals, "dummy-region")
   252  	hostedCfg, err := hostedModelSt.ModelConfig()
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	_, hasUnexpected := hostedCfg.AllAttrs()["not-for-hosted"]
   255  	c.Assert(hasUnexpected, jc.IsFalse)
   256  	c.Assert(hostedCfg.AuthorizedKeys(), gc.Equals, newModelCfg.AuthorizedKeys())
   257  
   258  	// Check that the bootstrap machine looks correct.
   259  	c.Assert(m.Id(), gc.Equals, "0")
   260  	c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel})
   261  	c.Assert(m.Series(), gc.Equals, series.HostSeries())
   262  	c.Assert(m.CheckProvisioned(agent.BootstrapNonce), jc.IsTrue)
   263  	c.Assert(m.Addresses(), jc.DeepEquals, filteredAddrs)
   264  	gotBootstrapConstraints, err := m.Constraints()
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	c.Assert(gotBootstrapConstraints, gc.DeepEquals, expectBootstrapConstraints)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	gotHW, err := m.HardwareCharacteristics()
   269  	c.Assert(err, jc.ErrorIsNil)
   270  	c.Assert(*gotHW, gc.DeepEquals, expectHW)
   271  
   272  	// Check that the API host ports are initialised correctly.
   273  	apiHostPorts, err := st.APIHostPorts()
   274  	c.Assert(err, jc.ErrorIsNil)
   275  	c.Assert(apiHostPorts, jc.DeepEquals, [][]network.HostPort{
   276  		network.AddressesWithPort(filteredAddrs, 1234),
   277  	})
   278  
   279  	// Check that the state serving info is initialised correctly.
   280  	stateServingInfo, err := st.StateServingInfo()
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	c.Assert(stateServingInfo, jc.DeepEquals, state.StateServingInfo{
   283  		APIPort:        1234,
   284  		StatePort:      s.mgoInst.Port(),
   285  		Cert:           testing.ServerCert,
   286  		PrivateKey:     testing.ServerKey,
   287  		CAPrivateKey:   testing.CAKey,
   288  		SharedSecret:   "abc123",
   289  		SystemIdentity: "def456",
   290  	})
   291  
   292  	// Check that the machine agent's config has been written
   293  	// and that we can use it to connect to the state.
   294  	machine0 := names.NewMachineTag("0")
   295  	newCfg, err := agent.ReadConfig(agent.ConfigPath(dataDir, machine0))
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	c.Assert(newCfg.Tag(), gc.Equals, machine0)
   298  	info, ok := cfg.MongoInfo()
   299  	c.Assert(ok, jc.IsTrue)
   300  	c.Assert(info.Password, gc.Not(gc.Equals), testing.DefaultMongoPassword)
   301  	st1, err := state.Open(newCfg.Model(), newCfg.Controller(), info, mongotest.DialOpts(), nil)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	defer st1.Close()
   304  
   305  	// Make sure that the hosted model Environ's Create method is called.
   306  	envProvider.CheckCallNames(c,
   307  		"PrepareConfig",
   308  		"Validate",
   309  		"Open",
   310  		"Create",
   311  	)
   312  	envProvider.CheckCall(c, 2, "Open", environs.OpenParams{
   313  		Cloud: environs.CloudSpec{
   314  			Type:   "dummy",
   315  			Name:   "dummy",
   316  			Region: "dummy-region",
   317  		},
   318  		Config: hostedCfg,
   319  	})
   320  	envProvider.CheckCall(c, 3, "Create", environs.CreateParams{
   321  		ControllerUUID: controllerCfg.ControllerUUID(),
   322  	})
   323  }
   324  
   325  func (s *bootstrapSuite) TestInitializeStateWithStateServingInfoNotAvailable(c *gc.C) {
   326  	configParams := agent.AgentConfigParams{
   327  		Paths:             agent.Paths{DataDir: c.MkDir()},
   328  		Tag:               names.NewMachineTag("0"),
   329  		UpgradedToVersion: jujuversion.Current,
   330  		StateAddresses:    []string{s.mgoInst.Addr()},
   331  		CACert:            testing.CACert,
   332  		Password:          "fake",
   333  		Controller:        testing.ControllerTag,
   334  		Model:             testing.ModelTag,
   335  	}
   336  	cfg, err := agent.NewAgentConfig(configParams)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  
   339  	_, available := cfg.StateServingInfo()
   340  	c.Assert(available, jc.IsFalse)
   341  
   342  	args := agentbootstrap.InitializeStateParams{}
   343  
   344  	adminUser := names.NewLocalUserTag("agent-admin")
   345  	_, _, err = agentbootstrap.InitializeState(adminUser, cfg, args, mongotest.DialOpts(), nil)
   346  	// InitializeState will fail attempting to get the api port information
   347  	c.Assert(err, gc.ErrorMatches, "state serving information not available")
   348  }
   349  
   350  func (s *bootstrapSuite) TestInitializeStateFailsSecondTime(c *gc.C) {
   351  	dataDir := c.MkDir()
   352  
   353  	configParams := agent.AgentConfigParams{
   354  		Paths:             agent.Paths{DataDir: dataDir},
   355  		Tag:               names.NewMachineTag("0"),
   356  		UpgradedToVersion: jujuversion.Current,
   357  		StateAddresses:    []string{s.mgoInst.Addr()},
   358  		CACert:            testing.CACert,
   359  		Password:          testing.DefaultMongoPassword,
   360  		Controller:        testing.ControllerTag,
   361  		Model:             testing.ModelTag,
   362  	}
   363  	cfg, err := agent.NewAgentConfig(configParams)
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	cfg.SetStateServingInfo(params.StateServingInfo{
   366  		APIPort:        5555,
   367  		StatePort:      s.mgoInst.Port(),
   368  		Cert:           "foo",
   369  		PrivateKey:     "bar",
   370  		SharedSecret:   "baz",
   371  		SystemIdentity: "qux",
   372  	})
   373  	modelAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{
   374  		"agent-version": jujuversion.Current.String(),
   375  	})
   376  	modelCfg, err := config.New(config.NoDefaults, modelAttrs)
   377  	c.Assert(err, jc.ErrorIsNil)
   378  
   379  	hostedModelConfigAttrs := map[string]interface{}{
   380  		"name": "hosted",
   381  		"uuid": utils.MustNewUUID().String(),
   382  	}
   383  
   384  	args := agentbootstrap.InitializeStateParams{
   385  		StateInitializationParams: instancecfg.StateInitializationParams{
   386  			BootstrapMachineInstanceId: "i-bootstrap",
   387  			ControllerCloud: cloud.Cloud{
   388  				Type:      "dummy",
   389  				AuthTypes: []cloud.AuthType{cloud.EmptyAuthType},
   390  			},
   391  			ControllerCloudName:   "dummy",
   392  			ControllerConfig:      testing.FakeControllerConfig(),
   393  			ControllerModelConfig: modelCfg,
   394  			HostedModelConfig:     hostedModelConfigAttrs,
   395  		},
   396  		BootstrapMachineJobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel},
   397  		SharedSecret:         "abc123",
   398  		Provider: func(t string) (environs.EnvironProvider, error) {
   399  			return &fakeProvider{}, nil
   400  		},
   401  		StorageProviderRegistry: provider.CommonStorageProviders(),
   402  	}
   403  
   404  	adminUser := names.NewLocalUserTag("agent-admin")
   405  	st, _, err := agentbootstrap.InitializeState(
   406  		adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil),
   407  	)
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	st.Close()
   410  
   411  	st, _, err = agentbootstrap.InitializeState(
   412  		adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil),
   413  	)
   414  	if err == nil {
   415  		st.Close()
   416  	}
   417  	c.Assert(err, gc.ErrorMatches, "failed to initialize mongo admin user: cannot set admin password: not authorized .*")
   418  }
   419  
   420  func (s *bootstrapSuite) TestMachineJobFromParams(c *gc.C) {
   421  	var tests = []struct {
   422  		name multiwatcher.MachineJob
   423  		want state.MachineJob
   424  		err  string
   425  	}{{
   426  		name: multiwatcher.JobHostUnits,
   427  		want: state.JobHostUnits,
   428  	}, {
   429  		name: multiwatcher.JobManageModel,
   430  		want: state.JobManageModel,
   431  	}, {
   432  		name: "invalid",
   433  		want: -1,
   434  		err:  `invalid machine job "invalid"`,
   435  	}}
   436  	for _, test := range tests {
   437  		got, err := agentbootstrap.MachineJobFromParams(test.name)
   438  		if err != nil {
   439  			c.Check(err, gc.ErrorMatches, test.err)
   440  		}
   441  		c.Check(got, gc.Equals, test.want)
   442  	}
   443  }
   444  
   445  func (s *bootstrapSuite) assertCanLogInAsAdmin(c *gc.C, modelTag names.ModelTag, controllerTag names.ControllerTag, password string) {
   446  	info := &mongo.MongoInfo{
   447  		Info: mongo.Info{
   448  			Addrs:  []string{s.mgoInst.Addr()},
   449  			CACert: testing.CACert,
   450  		},
   451  		Tag:      nil, // admin user
   452  		Password: password,
   453  	}
   454  	st, err := state.Open(modelTag, controllerTag, info, mongotest.DialOpts(), state.NewPolicyFunc(nil))
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	defer st.Close()
   457  	_, err = st.Machine("0")
   458  	c.Assert(err, jc.ErrorIsNil)
   459  }
   460  
   461  type fakeProvider struct {
   462  	environs.EnvironProvider
   463  	gitjujutesting.Stub
   464  }
   465  
   466  func (p *fakeProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   467  	p.MethodCall(p, "PrepareConfig", args)
   468  	return args.Config, p.NextErr()
   469  }
   470  
   471  func (p *fakeProvider) Validate(newCfg, oldCfg *config.Config) (*config.Config, error) {
   472  	p.MethodCall(p, "Validate", newCfg, oldCfg)
   473  	return newCfg, p.NextErr()
   474  }
   475  
   476  func (p *fakeProvider) Open(args environs.OpenParams) (environs.Environ, error) {
   477  	p.MethodCall(p, "Open", args)
   478  	return &fakeEnviron{Stub: &p.Stub}, p.NextErr()
   479  }
   480  
   481  type fakeEnviron struct {
   482  	environs.Environ
   483  	*gitjujutesting.Stub
   484  }
   485  
   486  func (e *fakeEnviron) Create(args environs.CreateParams) error {
   487  	e.MethodCall(e, "Create", args)
   488  	return e.NextErr()
   489  }