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  }