github.com/Equinix-Metal/virtlet@v1.5.2-0.20210807010419-342346535dc5/tests/integration/container_create_test.go (about) 1 /* 2 Copyright 2017 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package integration 18 19 import ( 20 "fmt" 21 "os/exec" 22 "sort" 23 "strconv" 24 "strings" 25 "testing" 26 27 kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2" 28 ) 29 30 type containerFilterTestCase struct { 31 name string 32 nilFilter bool 33 filterByPodSandbox bool 34 filterByContainer bool 35 labelSelector map[string]string 36 expectedContainers []int 37 } 38 39 func (c *containerFilterTestCase) containerFilter(ct *containerTester) *kubeapi.ContainerFilter { 40 if c.nilFilter { 41 return nil 42 } 43 filter := &kubeapi.ContainerFilter{LabelSelector: c.labelSelector} 44 if c.filterByPodSandbox { 45 filter.PodSandboxId = ct.containers[0].SandboxID 46 } 47 if c.filterByContainer { 48 filter.Id = ct.containers[0].ContainerID 49 } 50 return filter 51 } 52 53 func (c *containerFilterTestCase) expectedIds(ct *containerTester) []string { 54 r := make([]string, len(c.expectedContainers)) 55 for n, idx := range c.expectedContainers { 56 r[n] = ct.containers[idx].ContainerID 57 } 58 return r 59 } 60 61 func runShellCommand(t *testing.T, format string, args ...interface{}) string { 62 command := fmt.Sprintf(format, args...) 63 out, err := exec.Command("bash", "-c", command).Output() 64 if err != nil { 65 t.Fatalf("Error executing command '%q': %v", command, err) 66 } 67 return strings.TrimSpace(string(out)) 68 } 69 70 func verifyUsingShell(t *testing.T, cmd, what, expected string) { 71 t.Logf("Command to verify %s: %s", what, cmd) 72 outStr := runShellCommand(t, "%s", cmd) 73 if outStr != expected { 74 t.Errorf("Verifying %s: expected %q, got %q", what, expected, outStr) 75 } 76 } 77 78 func checkAllCleaned(t *testing.T, id string) { 79 // Check domain is not defined 80 cmd := fmt.Sprintf("virsh list --all | grep '%s' | wc -l", id) 81 verifyUsingShell(t, cmd, "no domain defined", "0") 82 // Check root fs and ephemeral volumes are cleaned 83 cmd = fmt.Sprintf("virsh vol-list --pool volumes | grep '%s' | wc -l", id) 84 verifyUsingShell(t, cmd, "no volumes defined in 'volumes' pool", "0") 85 } 86 87 func TestContainerCleanup(t *testing.T) { 88 // Test checks cleanup after failure at 3 stages during domain running: 89 ct := newContainerTester(t) 90 sandbox := ct.sandboxes[0] 91 container := ct.containers[0] 92 defer ct.teardown() 93 ct.mountFlexvolume(ct.sandboxes[0].Metadata.Uid, "vol1", map[string]interface{}{ 94 "type": "qcow2", 95 }) 96 ct.mountFlexvolume(ct.sandboxes[0].Metadata.Uid, "vol2", map[string]interface{}{ 97 "type": "qcow2", 98 "capacity": "2MB", 99 }) 100 ct.mountFlexvolume(ct.sandboxes[0].Metadata.Uid, "vol3", map[string]interface{}{ 101 "type": "qcow2", 102 }) 103 ct.pullAllImages() 104 105 ct.runPodSandbox(sandbox) 106 mounts := []*kubeapi.Mount{} 107 108 uuid := getDomainUUID(sandbox.Metadata.Uid) 109 // 1. Failure during adding/processing ephemerial and flexolumes 110 // Define in advance the volume name of one of described to cause error on CreateContainer "Storage volume already exists". 111 volumeName := "virtlet-" + uuid + "-vol3" 112 rmDummyVolume, err := defineDummyVolume("volumes", volumeName) 113 if err != nil { 114 t.Fatalf("Failed to define dummy volume to test cleanup: %v", err) 115 } 116 defer rmDummyVolume() // it's ok to call this func twice 117 _, err = ct.callCreateContainer(sandbox, container, ct.imageSpecs[0], mounts) 118 if err == nil { 119 ct.removeContainer(uuid) 120 t.Fatalf("Failed to cause failure on ContainerCreate to check cleanup(stage 1, defined volume: '%s').", volumeName) 121 } 122 rmDummyVolume() 123 checkAllCleaned(t, uuid) 124 125 // 2. Failure on defining domain in libvirt 126 // Define dummy VM with the same name but other id to cause error on CreateContainer "Domain <name> already exists with uuid <uuid>". 127 domainName := "virtlet-" + uuid[:13] + "-" + container.Name 128 if err := defineDummyDomainWithName(domainName); err != nil { 129 t.Errorf("Failed to define dummy domain to test cleanup: %v", err) 130 } 131 if _, err := ct.callCreateContainer(sandbox, container, ct.imageSpecs[0], mounts); err == nil { 132 ct.removeContainer(uuid) 133 t.Fatalf("Failed to cause failure on ContainerCreate to check cleanup(stage 2, defined dummy domain: '%s').", domainName) 134 } 135 if err := undefDomain(domainName); err != nil { 136 t.Errorf("Failed to undefine dummy domain '%s' to test cleanup: %v", domainName, err) 137 } 138 checkAllCleaned(t, uuid) 139 } 140 141 func TestContainerVolumes(t *testing.T) { 142 ct := newContainerTester(t) 143 defer ct.teardown() 144 ct.mountFlexvolume(ct.sandboxes[0].Metadata.Uid, "vol1", map[string]interface{}{ 145 "type": "qcow2", 146 }) 147 ct.mountFlexvolume(ct.sandboxes[0].Metadata.Uid, "vol2", map[string]interface{}{ 148 "type": "qcow2", 149 "capacity": "2MB", 150 }) 151 ct.mountFlexvolume(ct.sandboxes[0].Metadata.Uid, "vol3", map[string]interface{}{ 152 "type": "qcow2", 153 }) 154 ct.mountFlexvolume(ct.sandboxes[1].Metadata.Uid, "vol1", map[string]interface{}{ 155 "type": "qcow2", 156 "capacity": "1024KB", 157 }) 158 ct.mountFlexvolume(ct.sandboxes[1].Metadata.Uid, "vol2", map[string]interface{}{ 159 "type": "qcow2", 160 "capacity": "2", 161 }) 162 ct.pullAllImages() 163 164 volumeCounts := []int{3, 2} 165 for idx, sandbox := range ct.sandboxes { 166 ct.runPodSandbox(sandbox) 167 mounts := []*kubeapi.Mount{} 168 createResp := ct.createContainer(sandbox, ct.containers[idx], ct.imageSpecs[idx], mounts) 169 ct.startContainer(createResp.ContainerId) 170 171 vmName := "virtlet-" + createResp.ContainerId[:13] + "-" + ct.containers[idx].Name 172 cmd := fmt.Sprintf("virsh domblklist '%s' | grep '%s-vol.*' | wc -l", vmName, createResp.ContainerId) 173 verifyUsingShell(t, cmd, "attached ephemeral volumes", strconv.Itoa(volumeCounts[idx])) 174 } 175 176 if len(ct.listContainers(nil).Containers) != 2 { 177 t.Errorf("expected 2 containers to be listed") 178 } 179 180 for _, container := range ct.containers { 181 ct.stopContainer(container.ContainerID) 182 ct.removeContainer(container.ContainerID) 183 checkAllCleaned(t, container.ContainerID) 184 } 185 } 186 187 func TestContainerCreateStartListRemove(t *testing.T) { 188 ct := newContainerTester(t) 189 defer ct.teardown() 190 ct.containers[0].Labels = map[string]string{"unique": "first", "common": "both"} 191 ct.containers[1].Labels = map[string]string{"unique": "second", "common": "both"} 192 ct.pullAllImages() 193 194 for idx, sandbox := range ct.sandboxes { 195 ct.runPodSandbox(sandbox) 196 createResp := ct.createContainer(sandbox, ct.containers[idx], ct.imageSpecs[idx], nil) 197 ct.startContainer(createResp.ContainerId) 198 } 199 200 // Define external domain, i.e. not registered in bolt, to control virtlet performs well in that case 201 if err := defineDummyDomain(); err != nil { 202 t.Errorf("failed to define dummy domain to test List function: %v", err) 203 } 204 205 for _, tc := range []*containerFilterTestCase{ 206 { 207 name: "by container id", 208 filterByContainer: true, 209 expectedContainers: []int{0}, 210 }, 211 { 212 name: "by sandbox id", 213 filterByPodSandbox: true, 214 expectedContainers: []int{0}, 215 }, 216 { 217 name: "by sandbox id and label selector", 218 filterByPodSandbox: true, 219 labelSelector: map[string]string{"unique": "first", "common": "both"}, 220 expectedContainers: []int{0}, 221 }, 222 { 223 name: "by sandbox id and non-matching label selector", 224 filterByPodSandbox: true, 225 labelSelector: map[string]string{"unique": "nomatch"}, 226 expectedContainers: []int{}, 227 }, 228 { 229 name: "by container id and sandbox id", 230 filterByContainer: true, 231 filterByPodSandbox: true, 232 expectedContainers: []int{0}, 233 }, 234 { 235 name: "by container id, sandbox id and label selector", 236 filterByContainer: true, 237 filterByPodSandbox: true, 238 labelSelector: map[string]string{"unique": "first", "common": "both"}, 239 expectedContainers: []int{0}, 240 }, 241 { 242 name: "by label selector", 243 labelSelector: map[string]string{"unique": "first", "common": "both"}, 244 expectedContainers: []int{0}, 245 }, 246 { 247 name: "by label selector matching 2 containers", 248 labelSelector: map[string]string{"common": "both"}, 249 expectedContainers: []int{0, 1}, 250 }, 251 { 252 name: "by empty filter", 253 expectedContainers: []int{0, 1}, 254 }, 255 { 256 name: "by nil filter", 257 nilFilter: true, 258 expectedContainers: []int{0, 1}, 259 }, 260 } { 261 t.Run(tc.name, func(t *testing.T) { 262 listResp := ct.listContainers(tc.containerFilter(ct)) 263 expectedIds := tc.expectedIds(ct) 264 actualIds := make([]string, len(listResp.Containers)) 265 for n, container := range listResp.Containers { 266 actualIds[n] = container.Id 267 } 268 sort.Strings(expectedIds) 269 sort.Strings(actualIds) 270 expectedIdStr := strings.Join(expectedIds, ",") 271 actualIdStr := strings.Join(actualIds, ",") 272 if expectedIdStr != actualIdStr { 273 t.Errorf("bad container list: %q instead of %q", actualIdStr, expectedIdStr) 274 } 275 }) 276 } 277 278 for _, container := range ct.containers { 279 ct.stopContainer(container.ContainerID) 280 ct.removeContainer(container.ContainerID) 281 checkAllCleaned(t, container.ContainerID) 282 } 283 284 if len(ct.listContainers(nil).Containers) != 0 { 285 t.Errorf("expected no containers to be listed after removing them") 286 } 287 }