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: ¶ms.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 }