github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/provisioner/broker_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"net"
     9  	"path/filepath"
    10  
    11  	"github.com/juju/loggo"
    12  	gitjujutesting "github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/arch"
    15  	"github.com/juju/version"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/charm.v6"
    18  	"gopkg.in/juju/names.v2"
    19  
    20  	apiprovisioner "github.com/juju/juju/api/provisioner"
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/cloudconfig/instancecfg"
    23  	"github.com/juju/juju/container"
    24  	"github.com/juju/juju/core/constraints"
    25  	"github.com/juju/juju/core/instance"
    26  	"github.com/juju/juju/core/status"
    27  	"github.com/juju/juju/environs"
    28  	"github.com/juju/juju/environs/context"
    29  	"github.com/juju/juju/environs/instances"
    30  	"github.com/juju/juju/environs/instances/instancetest"
    31  	jujutesting "github.com/juju/juju/juju/testing"
    32  	"github.com/juju/juju/network"
    33  	coretesting "github.com/juju/juju/testing"
    34  	coretools "github.com/juju/juju/tools"
    35  	"github.com/juju/juju/worker/provisioner"
    36  )
    37  
    38  type brokerSuite struct {
    39  	coretesting.BaseSuite
    40  }
    41  
    42  var _ = gc.Suite(&brokerSuite{})
    43  
    44  func (s *brokerSuite) SetUpSuite(c *gc.C) {
    45  	s.BaseSuite.SetUpSuite(c)
    46  	s.PatchValue(&provisioner.GetMachineCloudInitData, func(_ string) (map[string]interface{}, error) {
    47  		return map[string]interface{}{
    48  			"packages":   []interface{}{"python-novaclient"},
    49  			"fake-entry": []interface{}{"testing-garbage"},
    50  			"apt": map[interface{}]interface{}{
    51  				"primary": []interface{}{
    52  					map[interface{}]interface{}{
    53  						"arches": []interface{}{"default"},
    54  						"uri":    "http://archive.ubuntu.com/ubuntu",
    55  					},
    56  				},
    57  				"security": []interface{}{
    58  					map[interface{}]interface{}{
    59  						"arches": []interface{}{"default"},
    60  						"uri":    "http://archive.ubuntu.com/ubuntu",
    61  					},
    62  				},
    63  			},
    64  			"ca-certs": map[interface{}]interface{}{
    65  				"remove-defaults": true,
    66  				"trusted":         []interface{}{"-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT-HERE\n-----END CERTIFICATE-----\n"},
    67  			},
    68  		}, nil
    69  	})
    70  }
    71  
    72  func (s *brokerSuite) TestCombinedCloudInitDataNoCloudInitUserData(c *gc.C) {
    73  	obtained, err := provisioner.CombinedCloudInitData(nil, "ca-certs,apt-primary", "xenial", loggo.Logger{})
    74  	c.Assert(err, jc.ErrorIsNil)
    75  
    76  	assertCloudInitUserData(obtained, map[string]interface{}{
    77  		"apt": map[string]interface{}{
    78  			"primary": []interface{}{
    79  				map[interface{}]interface{}{
    80  					"arches": []interface{}{"default"},
    81  					"uri":    "http://archive.ubuntu.com/ubuntu",
    82  				},
    83  			},
    84  		},
    85  		"ca-certs": map[interface{}]interface{}{
    86  			"remove-defaults": true,
    87  			"trusted":         []interface{}{"-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT-HERE\n-----END CERTIFICATE-----\n"},
    88  		},
    89  	}, c)
    90  }
    91  
    92  func (s *brokerSuite) TestCombinedCloudInitDataNoContainerInheritProperties(c *gc.C) {
    93  	containerConfig := fakeContainerConfig()
    94  	obtained, err := provisioner.CombinedCloudInitData(containerConfig.CloudInitUserData, "", "xenial", loggo.Logger{})
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	assertCloudInitUserData(obtained, containerConfig.CloudInitUserData, c)
    97  }
    98  
    99  type fakeAddr struct{ value string }
   100  
   101  func (f *fakeAddr) Network() string { return "net" }
   102  func (f *fakeAddr) String() string {
   103  	if f.value != "" {
   104  		return f.value
   105  	}
   106  	return "fakeAddr"
   107  }
   108  
   109  var _ net.Addr = (*fakeAddr)(nil)
   110  
   111  type fakeAPI struct {
   112  	*gitjujutesting.Stub
   113  
   114  	fakeContainerConfig params.ContainerConfig
   115  	fakeInterfaceInfo   network.InterfaceInfo
   116  	fakeDeviceToBridge  network.DeviceToBridge
   117  	fakeBridger         network.Bridger
   118  	fakePreparer        provisioner.PrepareHostFunc
   119  }
   120  
   121  var _ provisioner.APICalls = (*fakeAPI)(nil)
   122  
   123  var fakeInterfaceInfo network.InterfaceInfo = network.InterfaceInfo{
   124  	DeviceIndex:    0,
   125  	MACAddress:     "aa:bb:cc:dd:ee:ff",
   126  	CIDR:           "0.1.2.0/24",
   127  	InterfaceName:  "dummy0",
   128  	Address:        network.NewAddress("0.1.2.3"),
   129  	GatewayAddress: network.NewAddress("0.1.2.1"),
   130  	// Explicitly set only DNSServers, but not DNSSearchDomains to test this is
   131  	// detected and the latter populated by parsing the fake resolv.conf created
   132  	// by patchResolvConf(). See LP bug http://pad.lv/1575940 for more info.
   133  	DNSServers:       network.NewAddresses("ns1.dummy"),
   134  	DNSSearchDomains: nil,
   135  }
   136  
   137  var fakeDeviceToBridge network.DeviceToBridge = network.DeviceToBridge{
   138  	DeviceName: "dummy0",
   139  	BridgeName: "br-dummy0",
   140  }
   141  
   142  func fakeContainerConfig() params.ContainerConfig {
   143  	return params.ContainerConfig{
   144  		UpdateBehavior:          &params.UpdateBehavior{true, true},
   145  		ProviderType:            "fake",
   146  		AuthorizedKeys:          coretesting.FakeAuthKeys,
   147  		SSLHostnameVerification: true,
   148  		CloudInitUserData: map[string]interface{}{
   149  			"packages":        []interface{}{"python-keystoneclient", "python-glanceclient"},
   150  			"preruncmd":       []interface{}{"mkdir /tmp/preruncmd", "mkdir /tmp/preruncmd2"},
   151  			"postruncmd":      []interface{}{"mkdir /tmp/postruncmd", "mkdir /tmp/postruncmd2"},
   152  			"package_upgrade": false,
   153  		},
   154  	}
   155  }
   156  
   157  func NewFakeAPI() *fakeAPI {
   158  	return &fakeAPI{
   159  		Stub:                &gitjujutesting.Stub{},
   160  		fakeContainerConfig: fakeContainerConfig(),
   161  		fakeInterfaceInfo:   fakeInterfaceInfo,
   162  	}
   163  }
   164  
   165  func (f *fakeAPI) ContainerConfig() (params.ContainerConfig, error) {
   166  	f.MethodCall(f, "ContainerConfig")
   167  	if err := f.NextErr(); err != nil {
   168  		return params.ContainerConfig{}, err
   169  	}
   170  	return f.fakeContainerConfig, nil
   171  }
   172  
   173  func (f *fakeAPI) PrepareContainerInterfaceInfo(tag names.MachineTag) ([]network.InterfaceInfo, error) {
   174  	f.MethodCall(f, "PrepareContainerInterfaceInfo", tag)
   175  	if err := f.NextErr(); err != nil {
   176  		return nil, err
   177  	}
   178  	return []network.InterfaceInfo{f.fakeInterfaceInfo}, nil
   179  }
   180  
   181  func (f *fakeAPI) GetContainerInterfaceInfo(tag names.MachineTag) ([]network.InterfaceInfo, error) {
   182  	f.MethodCall(f, "GetContainerInterfaceInfo", tag)
   183  	if err := f.NextErr(); err != nil {
   184  		return nil, err
   185  	}
   186  	return []network.InterfaceInfo{f.fakeInterfaceInfo}, nil
   187  }
   188  
   189  func (f *fakeAPI) ReleaseContainerAddresses(tag names.MachineTag) error {
   190  	f.MethodCall(f, "ReleaseContainerAddresses", tag)
   191  	if err := f.NextErr(); err != nil {
   192  		return err
   193  	}
   194  	return nil
   195  }
   196  
   197  func (f *fakeAPI) SetHostMachineNetworkConfig(hostMachineTag names.MachineTag, netConfig []params.NetworkConfig) error {
   198  	f.MethodCall(f, "SetHostMachineNetworkConfig", hostMachineTag.String(), netConfig)
   199  	if err := f.NextErr(); err != nil {
   200  		return err
   201  	}
   202  	return nil
   203  }
   204  
   205  func (f *fakeAPI) HostChangesForContainer(machineTag names.MachineTag) ([]network.DeviceToBridge, int, error) {
   206  	f.MethodCall(f, "HostChangesForContainer", machineTag)
   207  	if err := f.NextErr(); err != nil {
   208  		return nil, 0, err
   209  	}
   210  	return []network.DeviceToBridge{f.fakeDeviceToBridge}, 0, nil
   211  }
   212  
   213  func (f *fakeAPI) PrepareHost(containerTag names.MachineTag, log loggo.Logger, abort <-chan struct{}) error {
   214  	// This is not actually part of the API, however it is something that the
   215  	// Brokers should be calling, and putting it here means we get a wholistic
   216  	// view of when what function is getting called.
   217  	f.MethodCall(f, "PrepareHost", containerTag)
   218  	if err := f.NextErr(); err != nil {
   219  		return err
   220  	}
   221  	if f.fakePreparer != nil {
   222  		return f.fakePreparer(containerTag, log, abort)
   223  	}
   224  	return nil
   225  }
   226  
   227  func (f *fakeAPI) GetContainerProfileInfo(containerTag names.MachineTag) ([]*apiprovisioner.LXDProfileResult, error) {
   228  	f.MethodCall(f, "GetContainerProfileInfo", containerTag)
   229  	if err := f.NextErr(); err != nil {
   230  		return nil, err
   231  	}
   232  	return []*apiprovisioner.LXDProfileResult{}, nil
   233  }
   234  
   235  type fakeContainerManager struct {
   236  	gitjujutesting.Stub
   237  }
   238  
   239  func (m *fakeContainerManager) CreateContainer(instanceConfig *instancecfg.InstanceConfig,
   240  	cons constraints.Value,
   241  	series string,
   242  	network *container.NetworkConfig,
   243  	storage *container.StorageConfig,
   244  	callback environs.StatusCallbackFunc,
   245  ) (instances.Instance, *instance.HardwareCharacteristics, error) {
   246  	m.MethodCall(m, "CreateContainer", instanceConfig, cons, series, network, storage, callback)
   247  	inst := mockInstance{id: "testinst"}
   248  	arch := "testarch"
   249  	hw := instance.HardwareCharacteristics{Arch: &arch}
   250  	return &inst, &hw, m.NextErr()
   251  }
   252  
   253  func (m *fakeContainerManager) DestroyContainer(id instance.Id) error {
   254  	m.MethodCall(m, "DestroyContainer", id)
   255  	return m.NextErr()
   256  }
   257  
   258  func (m *fakeContainerManager) ListContainers() ([]instances.Instance, error) {
   259  	m.MethodCall(m, "ListContainers")
   260  	return nil, m.NextErr()
   261  }
   262  
   263  func (m *fakeContainerManager) Namespace() instance.Namespace {
   264  	ns, _ := instance.NewNamespace(coretesting.ModelTag.Id())
   265  	return ns
   266  }
   267  
   268  func (m *fakeContainerManager) IsInitialized() bool {
   269  	m.MethodCall(m, "IsInitialized")
   270  	m.PopNoErr()
   271  	return true
   272  }
   273  
   274  func (m *fakeContainerManager) MaybeWriteLXDProfile(pName string, put *charm.LXDProfile) error {
   275  	m.MethodCall(m, "MaybeWriteLXDProfile")
   276  	return m.NextErr()
   277  }
   278  
   279  type mockInstance struct {
   280  	id string
   281  }
   282  
   283  var _ instances.Instance = (*mockInstance)(nil)
   284  
   285  // Id implements instances.instance.Id.
   286  func (m *mockInstance) Id() instance.Id {
   287  	return instance.Id(m.id)
   288  }
   289  
   290  // Status implements instances.Instance.Status.
   291  func (m *mockInstance) Status(context.ProviderCallContext) instance.Status {
   292  	return instance.Status{}
   293  }
   294  
   295  // Addresses implements instances.Instance.Addresses.
   296  func (m *mockInstance) Addresses(context.ProviderCallContext) ([]network.Address, error) {
   297  	return nil, nil
   298  }
   299  
   300  type patcher interface {
   301  	PatchValue(destination, source interface{})
   302  }
   303  
   304  func patchResolvConf(s patcher, c *gc.C) {
   305  	const fakeConf = `
   306  nameserver ns1.dummy
   307  search dummy invalid
   308  nameserver ns2.dummy
   309  `
   310  
   311  	fakeResolvConf := filepath.Join(c.MkDir(), "fakeresolv.conf")
   312  	err := ioutil.WriteFile(fakeResolvConf, []byte(fakeConf), 0644)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	s.PatchValue(provisioner.ResolvConfFiles, []string{fakeResolvConf})
   315  }
   316  
   317  func instancesFromResults(results ...*environs.StartInstanceResult) []instances.Instance {
   318  	instances := make([]instances.Instance, len(results))
   319  	for i := range results {
   320  		instances[i] = results[i].Instance
   321  	}
   322  	return instances
   323  }
   324  
   325  func assertInstancesStarted(c *gc.C, broker environs.InstanceBroker, results ...*environs.StartInstanceResult) {
   326  	allInstances, err := broker.AllInstances(context.NewCloudCallContext())
   327  	c.Assert(err, jc.ErrorIsNil)
   328  	instancetest.MatchInstances(c, allInstances, instancesFromResults(results...)...)
   329  }
   330  
   331  func makeInstanceConfig(c *gc.C, s patcher, machineId string) *instancecfg.InstanceConfig {
   332  	machineNonce := "fake-nonce"
   333  	// To isolate the tests from the host's architecture, we override it here.
   334  	s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 })
   335  	apiInfo := jujutesting.FakeAPIInfo(machineId)
   336  	instanceConfig, err := instancecfg.NewInstanceConfig(coretesting.ControllerTag, machineId, machineNonce, "released", "quantal", apiInfo)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	return instanceConfig
   339  }
   340  
   341  func makePossibleTools() coretools.List {
   342  	return coretools.List{&coretools.Tools{
   343  		Version: version.MustParseBinary("2.3.4-quantal-amd64"),
   344  		URL:     "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz",
   345  	}, {
   346  		// non-host-arch tools should be filtered out by StartInstance
   347  		Version: version.MustParseBinary("2.3.4-quantal-arm64"),
   348  		URL:     "http://tools.testing.invalid/2.3.4-quantal-arm64.tgz",
   349  	}}
   350  }
   351  
   352  func makeNoOpStatusCallback() func(settableStatus status.Status, info string, data map[string]interface{}) error {
   353  	return func(_ status.Status, _ string, _ map[string]interface{}) error {
   354  		return nil
   355  	}
   356  }
   357  
   358  func callStartInstance(c *gc.C, s patcher, broker environs.InstanceBroker, machineId string) (*environs.StartInstanceResult, error) {
   359  	return broker.StartInstance(context.NewCloudCallContext(), environs.StartInstanceParams{
   360  		Constraints:    constraints.Value{},
   361  		Tools:          makePossibleTools(),
   362  		InstanceConfig: makeInstanceConfig(c, s, machineId),
   363  		StatusCallback: makeNoOpStatusCallback(),
   364  	})
   365  }
   366  
   367  func callMaintainInstance(c *gc.C, s patcher, broker environs.InstanceBroker, machineId string) {
   368  	err := broker.MaintainInstance(context.NewCloudCallContext(), environs.StartInstanceParams{
   369  		Constraints:    constraints.Value{},
   370  		Tools:          makePossibleTools(),
   371  		InstanceConfig: makeInstanceConfig(c, s, machineId),
   372  		StatusCallback: makeNoOpStatusCallback(),
   373  	})
   374  	c.Assert(err, jc.ErrorIsNil)
   375  }
   376  
   377  func assertCloudInitUserData(obtained, expected map[string]interface{}, c *gc.C) {
   378  	c.Assert(obtained, gc.HasLen, len(expected))
   379  	for obtainedK, obtainedV := range obtained {
   380  		expectedV, ok := expected[obtainedK]
   381  		c.Assert(ok, jc.IsTrue)
   382  		switch obtainedK {
   383  		case "package_upgrade":
   384  			c.Assert(obtainedV, gc.Equals, expectedV)
   385  		case "apt", "ca-certs":
   386  			c.Assert(obtainedV, jc.DeepEquals, expectedV)
   387  		default:
   388  			c.Assert(obtainedV, jc.SameContents, expectedV)
   389  		}
   390  	}
   391  }