github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/container_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 "errors" 8 9 "github.com/golang/mock/gomock" 10 jc "github.com/juju/testing/checkers" 11 "github.com/juju/utils/arch" 12 "github.com/lxc/lxd/shared/api" 13 "github.com/lxc/lxd/shared/osarch" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/container/lxd" 17 lxdtesting "github.com/juju/juju/container/lxd/testing" 18 "github.com/juju/juju/core/constraints" 19 "github.com/juju/juju/environs/tags" 20 "github.com/juju/juju/network" 21 ) 22 23 type containerSuite struct { 24 lxdtesting.BaseSuite 25 } 26 27 var _ = gc.Suite(&containerSuite{}) 28 29 func (s *containerSuite) TestContainerMetadata(c *gc.C) { 30 container := lxd.Container{} 31 container.Config = map[string]string{"user.juju-controller-uuid": "something"} 32 c.Check(container.Metadata(tags.JujuController), gc.Equals, "something") 33 } 34 35 func (s *containerSuite) TestContainerArch(c *gc.C) { 36 lxdArch, _ := osarch.ArchitectureName(osarch.ARCH_64BIT_INTEL_X86) 37 container := lxd.Container{} 38 container.Architecture = lxdArch 39 c.Check(container.Arch(), gc.Equals, arch.AMD64) 40 } 41 42 func (s *containerSuite) TestContainerCPUs(c *gc.C) { 43 container := lxd.Container{} 44 container.Config = map[string]string{"limits.cpu": "2"} 45 c.Check(container.CPUs(), gc.Equals, uint(2)) 46 } 47 48 func (s *containerSuite) TestContainerMem(c *gc.C) { 49 container := lxd.Container{} 50 51 container.Config = map[string]string{"limits.memory": "1MiB"} 52 c.Check(int(container.Mem()), gc.Equals, 1) 53 54 container.Config = map[string]string{"limits.memory": "2GiB"} 55 c.Check(int(container.Mem()), gc.Equals, 2048) 56 } 57 58 func (s *containerSuite) TestContainerAddDiskNoDevices(c *gc.C) { 59 container := lxd.Container{} 60 err := container.AddDisk("root", "/", "source", "default", true) 61 c.Assert(err, jc.ErrorIsNil) 62 63 expected := map[string]string{ 64 "path": "/", 65 "source": "source", 66 "pool": "default", 67 "readonly": "true", 68 } 69 c.Check(container.Devices["root"], gc.DeepEquals, expected) 70 } 71 72 func (s *containerSuite) TestContainerAddDiskDevicePresentError(c *gc.C) { 73 container := lxd.Container{} 74 container.Name = "seeyounexttuesday" 75 container.Devices = map[string]map[string]string{"root": {}} 76 77 err := container.AddDisk("root", "/", "source", "default", true) 78 c.Check(err, gc.ErrorMatches, `container "seeyounexttuesday" already has a device "root"`) 79 } 80 81 func (s *containerSuite) TestFilterContainers(c *gc.C) { 82 ctrl := gomock.NewController(c) 83 defer ctrl.Finish() 84 cSvr := s.NewMockServer(ctrl) 85 86 matching := []api.Container{ 87 { 88 Name: "prefix-c1", 89 StatusCode: api.Starting, 90 }, 91 { 92 Name: "prefix-c2", 93 StatusCode: api.Stopped, 94 }, 95 } 96 ret := append(matching, []api.Container{ 97 { 98 Name: "prefix-c3", 99 StatusCode: api.Started, 100 }, 101 { 102 Name: "not-prefix-c4", 103 StatusCode: api.Stopped, 104 }, 105 }...) 106 cSvr.EXPECT().GetContainers().Return(ret, nil) 107 108 jujuSvr, err := lxd.NewServer(cSvr) 109 c.Assert(err, jc.ErrorIsNil) 110 111 filtered, err := jujuSvr.FilterContainers("prefix", "Starting", "Stopped") 112 c.Assert(err, jc.ErrorIsNil) 113 114 expected := make([]lxd.Container, len(matching)) 115 for i, v := range matching { 116 expected[i] = lxd.Container{v} 117 } 118 119 c.Check(filtered, gc.DeepEquals, expected) 120 } 121 122 func (s *containerSuite) TestAliveContainers(c *gc.C) { 123 ctrl := gomock.NewController(c) 124 defer ctrl.Finish() 125 cSvr := s.NewMockServer(ctrl) 126 127 matching := []api.Container{ 128 { 129 Name: "c1", 130 StatusCode: api.Starting, 131 }, 132 { 133 Name: "c2", 134 StatusCode: api.Stopped, 135 }, 136 { 137 Name: "c3", 138 StatusCode: api.Running, 139 }, 140 } 141 ret := append(matching, api.Container{ 142 Name: "c4", 143 StatusCode: api.Frozen, 144 }) 145 cSvr.EXPECT().GetContainers().Return(ret, nil) 146 147 jujuSvr, err := lxd.NewServer(cSvr) 148 c.Assert(err, jc.ErrorIsNil) 149 150 filtered, err := jujuSvr.AliveContainers("") 151 c.Assert(err, jc.ErrorIsNil) 152 153 expected := make([]lxd.Container, len(matching)) 154 for i, v := range matching { 155 expected[i] = lxd.Container{v} 156 } 157 c.Check(filtered, gc.DeepEquals, expected) 158 } 159 160 func (s *containerSuite) TestContainerAddresses(c *gc.C) { 161 ctrl := gomock.NewController(c) 162 defer ctrl.Finish() 163 cSvr := s.NewMockServer(ctrl) 164 165 state := api.ContainerState{ 166 Network: map[string]api.ContainerStateNetwork{ 167 "eth0": { 168 Addresses: []api.ContainerStateNetworkAddress{ 169 { 170 Family: "inet", 171 Address: "10.0.8.173", 172 Netmask: "24", 173 Scope: "global", 174 }, 175 { 176 Family: "inet6", 177 Address: "fe80::216:3eff:fe3b:e582", 178 Netmask: "64", 179 Scope: "link", 180 }, 181 }, 182 }, 183 "lo": { 184 Addresses: []api.ContainerStateNetworkAddress{ 185 { 186 Family: "inet", 187 Address: "127.0.0.1", 188 Netmask: "8", 189 Scope: "local", 190 }, 191 { 192 Family: "inet6", 193 Address: "::1", 194 Netmask: "128", 195 Scope: "local", 196 }, 197 }, 198 }, 199 "lxcbr0": { 200 Addresses: []api.ContainerStateNetworkAddress{ 201 { 202 Family: "inet", 203 Address: "10.0.5.12", 204 Netmask: "24", 205 Scope: "global", 206 }, 207 { 208 Family: "inet6", 209 Address: "fe80::216:3eff:fe3b:e432", 210 Netmask: "64", 211 Scope: "link", 212 }, 213 }, 214 }, 215 "lxdbr0": { 216 Addresses: []api.ContainerStateNetworkAddress{ 217 { 218 Family: "inet", 219 Address: "10.0.6.17", 220 Netmask: "24", 221 Scope: "global", 222 }, 223 { 224 Family: "inet6", 225 Address: "fe80::5c9b:b2ff:feaf:4cf2", 226 Netmask: "64", 227 Scope: "link", 228 }, 229 }, 230 }, 231 }, 232 } 233 cSvr.EXPECT().GetContainerState("c1").Return(&state, lxdtesting.ETag, nil) 234 235 jujuSvr, err := lxd.NewServer(cSvr) 236 c.Assert(err, jc.ErrorIsNil) 237 238 addrs, err := jujuSvr.ContainerAddresses("c1") 239 c.Assert(err, jc.ErrorIsNil) 240 241 expected := []network.Address{ 242 { 243 Value: "10.0.8.173", 244 Type: network.IPv4Address, 245 Scope: network.ScopeCloudLocal, 246 }, 247 } 248 c.Check(addrs, gc.DeepEquals, expected) 249 } 250 251 func (s *containerSuite) TestCreateContainerFromSpecSuccess(c *gc.C) { 252 ctrl := gomock.NewController(c) 253 defer ctrl.Finish() 254 cSvr := s.NewMockServer(ctrl) 255 256 // Operation arrangements. 257 createOp := lxdtesting.NewMockRemoteOperation(ctrl) 258 createOp.EXPECT().Wait().Return(nil) 259 createOp.EXPECT().GetTarget().Return(&api.Operation{StatusCode: api.Success}, nil) 260 261 startOp := lxdtesting.NewMockOperation(ctrl) 262 startOp.EXPECT().Wait().Return(nil) 263 264 // Request data. 265 image := api.Image{Filename: "container-image"} 266 spec := lxd.ContainerSpec{ 267 Name: "c1", 268 Image: lxd.SourcedImage{ 269 Image: &image, 270 LXDServer: cSvr, 271 }, 272 Profiles: []string{"default"}, 273 Devices: map[string]map[string]string{ 274 "eth0": { 275 "parent": network.DefaultLXDBridge, 276 "type": "nic", 277 "nictype": "bridged", 278 }, 279 }, 280 Config: map[string]string{ 281 "limits.cpu": "2", 282 }, 283 } 284 285 createReq := api.ContainersPost{ 286 Name: spec.Name, 287 ContainerPut: api.ContainerPut{ 288 Profiles: spec.Profiles, 289 Devices: spec.Devices, 290 Config: spec.Config, 291 Ephemeral: false, 292 }, 293 } 294 295 startReq := api.ContainerStatePut{ 296 Action: "start", 297 Timeout: -1, 298 Force: false, 299 Stateful: false, 300 } 301 302 // Container created, started and returned. 303 exp := cSvr.EXPECT() 304 gomock.InOrder( 305 exp.CreateContainerFromImage(cSvr, image, createReq).Return(createOp, nil), 306 exp.UpdateContainerState(spec.Name, startReq, "").Return(startOp, nil), 307 exp.GetContainer(spec.Name).Return(&api.Container{}, lxdtesting.ETag, nil), 308 ) 309 310 jujuSvr, err := lxd.NewServer(cSvr) 311 c.Assert(err, jc.ErrorIsNil) 312 313 container, err := jujuSvr.CreateContainerFromSpec(spec) 314 c.Assert(err, jc.ErrorIsNil) 315 c.Check(container, gc.NotNil) 316 } 317 318 func (s *containerSuite) TestCreateContainerFromSpecStartFailed(c *gc.C) { 319 ctrl := gomock.NewController(c) 320 defer ctrl.Finish() 321 cSvr := s.NewMockServer(ctrl) 322 323 // Operation arrangements. 324 createOp := lxdtesting.NewMockRemoteOperation(ctrl) 325 createOp.EXPECT().Wait().Return(nil) 326 createOp.EXPECT().GetTarget().Return(&api.Operation{StatusCode: api.Success}, nil) 327 328 deleteOp := lxdtesting.NewMockOperation(ctrl) 329 deleteOp.EXPECT().Wait().Return(nil) 330 331 // Request data. 332 image := api.Image{Filename: "container-image"} 333 spec := lxd.ContainerSpec{ 334 Name: "c1", 335 Image: lxd.SourcedImage{ 336 Image: &image, 337 LXDServer: cSvr, 338 }, 339 Profiles: []string{"default"}, 340 Devices: map[string]map[string]string{ 341 "eth0": { 342 "parent": network.DefaultLXDBridge, 343 "type": "nic", 344 "nictype": "bridged", 345 }, 346 }, 347 Config: map[string]string{ 348 "limits.cpu": "2", 349 }, 350 } 351 352 createReq := api.ContainersPost{ 353 Name: spec.Name, 354 ContainerPut: api.ContainerPut{ 355 Profiles: spec.Profiles, 356 Devices: spec.Devices, 357 Config: spec.Config, 358 Ephemeral: false, 359 }, 360 } 361 362 startReq := api.ContainerStatePut{ 363 Action: "start", 364 Timeout: -1, 365 Force: false, 366 Stateful: false, 367 } 368 369 // Container created, starting fails, container state checked, container deleted. 370 exp := cSvr.EXPECT() 371 gomock.InOrder( 372 exp.CreateContainerFromImage(cSvr, image, createReq).Return(createOp, nil), 373 exp.UpdateContainerState(spec.Name, startReq, "").Return(nil, errors.New("start failed")), 374 exp.GetContainerState(spec.Name).Return( 375 &api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil), 376 exp.DeleteContainer(spec.Name).Return(deleteOp, nil), 377 ) 378 379 jujuSvr, err := lxd.NewServer(cSvr) 380 c.Assert(err, jc.ErrorIsNil) 381 382 container, err := jujuSvr.CreateContainerFromSpec(spec) 383 c.Assert(err, gc.ErrorMatches, "start failed") 384 c.Check(container, gc.IsNil) 385 } 386 387 func (s *containerSuite) TestRemoveContainersSuccess(c *gc.C) { 388 ctrl := gomock.NewController(c) 389 defer ctrl.Finish() 390 cSvr := s.NewMockServer(ctrl) 391 392 stopOp := lxdtesting.NewMockOperation(ctrl) 393 stopOp.EXPECT().Wait().Return(nil) 394 395 deleteOp := lxdtesting.NewMockOperation(ctrl) 396 deleteOp.EXPECT().Wait().Return(nil).Times(2) 397 398 stopReq := api.ContainerStatePut{ 399 Action: "stop", 400 Timeout: -1, 401 Force: true, 402 Stateful: false, 403 } 404 405 // Container c1 is already stopped. Container c2 is started and stopped before deletion. 406 exp := cSvr.EXPECT() 407 exp.GetContainerState("c1").Return(&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil) 408 exp.DeleteContainer("c1").Return(deleteOp, nil) 409 exp.GetContainerState("c2").Return(&api.ContainerState{StatusCode: api.Started}, lxdtesting.ETag, nil) 410 exp.UpdateContainerState("c2", stopReq, lxdtesting.ETag).Return(stopOp, nil) 411 exp.DeleteContainer("c2").Return(deleteOp, nil) 412 413 jujuSvr, err := lxd.NewServer(cSvr) 414 c.Assert(err, jc.ErrorIsNil) 415 416 err = jujuSvr.RemoveContainers([]string{"c1", "c2"}) 417 c.Assert(err, jc.ErrorIsNil) 418 } 419 420 func (s *containerSuite) TestRemoveContainersPartialFailure(c *gc.C) { 421 ctrl := gomock.NewController(c) 422 defer ctrl.Finish() 423 cSvr := s.NewMockServer(ctrl) 424 425 stopOp := lxdtesting.NewMockOperation(ctrl) 426 stopOp.EXPECT().Wait().Return(nil) 427 428 deleteOp := lxdtesting.NewMockOperation(ctrl) 429 deleteOp.EXPECT().Wait().Return(nil) 430 431 stopReq := api.ContainerStatePut{ 432 Action: "stop", 433 Timeout: -1, 434 Force: true, 435 Stateful: false, 436 } 437 438 // Container c1, c2 already stopped, but delete fails. Container c2 is started and stopped before deletion. 439 exp := cSvr.EXPECT() 440 exp.GetContainerState("c1").Return(&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil) 441 exp.DeleteContainer("c1").Return(nil, errors.New("deletion failed")) 442 exp.GetContainerState("c2").Return(&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil) 443 exp.DeleteContainer("c2").Return(nil, errors.New("deletion failed")) 444 exp.GetContainerState("c3").Return(&api.ContainerState{StatusCode: api.Started}, lxdtesting.ETag, nil) 445 exp.UpdateContainerState("c3", stopReq, lxdtesting.ETag).Return(stopOp, nil) 446 exp.DeleteContainer("c3").Return(deleteOp, nil) 447 448 jujuSvr, err := lxd.NewServer(cSvr) 449 c.Assert(err, jc.ErrorIsNil) 450 451 err = jujuSvr.RemoveContainers([]string{"c1", "c2", "c3"}) 452 c.Assert(err, gc.ErrorMatches, "failed to remove containers: c1, c2") 453 } 454 455 func (s *managerSuite) TestSpecApplyConstraints(c *gc.C) { 456 mem := uint64(2046) 457 cores := uint64(4) 458 instType := "t2.micro" 459 460 cons := constraints.Value{ 461 Mem: &mem, 462 CpuCores: &cores, 463 InstanceType: &instType, 464 } 465 466 spec := lxd.ContainerSpec{ 467 Config: map[string]string{lxd.AutoStartKey: "true"}, 468 } 469 spec.ApplyConstraints(cons) 470 471 exp := map[string]string{ 472 lxd.AutoStartKey: "true", 473 "limits.memory": "2046MiB", 474 "limits.cpu": "4", 475 } 476 c.Check(spec.Config, gc.DeepEquals, exp) 477 c.Check(spec.InstanceType, gc.Equals, instType) 478 }