github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/drivers/docker/reconcile_dangling_test.go (about)

     1  package docker
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"testing"
     7  	"time"
     8  
     9  	docker "github.com/fsouza/go-dockerclient"
    10  	"github.com/hashicorp/nomad/ci"
    11  	"github.com/hashicorp/nomad/client/testutil"
    12  	"github.com/hashicorp/nomad/helper/freeport"
    13  	"github.com/hashicorp/nomad/helper/uuid"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func fakeContainerList(t *testing.T) (nomadContainer, nonNomadContainer docker.APIContainers) {
    18  	path := "./test-resources/docker/reconciler_containers_list.json"
    19  
    20  	f, err := os.Open(path)
    21  	if err != nil {
    22  		t.Fatalf("failed to open file: %v", err)
    23  	}
    24  
    25  	var sampleContainerList []docker.APIContainers
    26  	err = json.NewDecoder(f).Decode(&sampleContainerList)
    27  	if err != nil {
    28  		t.Fatalf("failed to decode container list: %v", err)
    29  	}
    30  
    31  	return sampleContainerList[0], sampleContainerList[1]
    32  }
    33  
    34  func Test_HasMount(t *testing.T) {
    35  	ci.Parallel(t)
    36  
    37  	nomadContainer, nonNomadContainer := fakeContainerList(t)
    38  
    39  	require.True(t, hasMount(nomadContainer, "/alloc"))
    40  	require.True(t, hasMount(nomadContainer, "/data"))
    41  	require.True(t, hasMount(nomadContainer, "/secrets"))
    42  	require.False(t, hasMount(nomadContainer, "/random"))
    43  
    44  	require.False(t, hasMount(nonNomadContainer, "/alloc"))
    45  	require.False(t, hasMount(nonNomadContainer, "/data"))
    46  	require.False(t, hasMount(nonNomadContainer, "/secrets"))
    47  	require.False(t, hasMount(nonNomadContainer, "/random"))
    48  }
    49  
    50  func Test_HasNomadName(t *testing.T) {
    51  	ci.Parallel(t)
    52  
    53  	nomadContainer, nonNomadContainer := fakeContainerList(t)
    54  
    55  	require.True(t, hasNomadName(nomadContainer))
    56  	require.False(t, hasNomadName(nonNomadContainer))
    57  }
    58  
    59  // TestDanglingContainerRemoval asserts containers without corresponding tasks
    60  // are removed after the creation grace period.
    61  func TestDanglingContainerRemoval(t *testing.T) {
    62  	ci.Parallel(t)
    63  	testutil.DockerCompatible(t)
    64  
    65  	// start two containers: one tracked nomad container, and one unrelated container
    66  	task, cfg, ports := dockerTask(t)
    67  	defer freeport.Return(ports)
    68  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
    69  
    70  	client, d, handle, cleanup := dockerSetup(t, task, nil)
    71  	defer cleanup()
    72  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
    73  
    74  	nonNomadContainer, err := client.CreateContainer(docker.CreateContainerOptions{
    75  		Name: "mytest-image-" + uuid.Generate(),
    76  		Config: &docker.Config{
    77  			Image: cfg.Image,
    78  			Cmd:   append([]string{cfg.Command}, cfg.Args...),
    79  		},
    80  	})
    81  	require.NoError(t, err)
    82  	defer client.RemoveContainer(docker.RemoveContainerOptions{
    83  		ID:    nonNomadContainer.ID,
    84  		Force: true,
    85  	})
    86  
    87  	err = client.StartContainer(nonNomadContainer.ID, nil)
    88  	require.NoError(t, err)
    89  
    90  	untrackedNomadContainer, err := client.CreateContainer(docker.CreateContainerOptions{
    91  		Name: "mytest-image-" + uuid.Generate(),
    92  		Config: &docker.Config{
    93  			Image: cfg.Image,
    94  			Cmd:   append([]string{cfg.Command}, cfg.Args...),
    95  			Labels: map[string]string{
    96  				dockerLabelAllocID: uuid.Generate(),
    97  			},
    98  		},
    99  	})
   100  	require.NoError(t, err)
   101  	defer client.RemoveContainer(docker.RemoveContainerOptions{
   102  		ID:    untrackedNomadContainer.ID,
   103  		Force: true,
   104  	})
   105  
   106  	err = client.StartContainer(untrackedNomadContainer.ID, nil)
   107  	require.NoError(t, err)
   108  
   109  	dd := d.Impl().(*Driver)
   110  
   111  	reconciler := newReconciler(dd)
   112  	trackedContainers := map[string]bool{handle.containerID: true}
   113  
   114  	tf := reconciler.trackedContainers()
   115  	require.Contains(t, tf, handle.containerID)
   116  	require.NotContains(t, tf, untrackedNomadContainer)
   117  	require.NotContains(t, tf, nonNomadContainer.ID)
   118  
   119  	// assert tracked containers should never be untracked
   120  	untracked, err := reconciler.untrackedContainers(trackedContainers, time.Now())
   121  	require.NoError(t, err)
   122  	require.NotContains(t, untracked, handle.containerID)
   123  	require.NotContains(t, untracked, nonNomadContainer.ID)
   124  	require.Contains(t, untracked, untrackedNomadContainer.ID)
   125  
   126  	// assert we recognize nomad containers with appropriate cutoff
   127  	untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now())
   128  	require.NoError(t, err)
   129  	require.Contains(t, untracked, handle.containerID)
   130  	require.Contains(t, untracked, untrackedNomadContainer.ID)
   131  	require.NotContains(t, untracked, nonNomadContainer.ID)
   132  
   133  	// but ignore if creation happened before cutoff
   134  	untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now().Add(-1*time.Minute))
   135  	require.NoError(t, err)
   136  	require.NotContains(t, untracked, handle.containerID)
   137  	require.NotContains(t, untracked, untrackedNomadContainer.ID)
   138  	require.NotContains(t, untracked, nonNomadContainer.ID)
   139  
   140  	// a full integration tests to assert that containers are removed
   141  	prestineDriver := dockerDriverHarness(t, nil).Impl().(*Driver)
   142  	prestineDriver.config.GC.DanglingContainers = ContainerGCConfig{
   143  		Enabled:       true,
   144  		period:        1 * time.Second,
   145  		CreationGrace: 0 * time.Second,
   146  	}
   147  	nReconciler := newReconciler(prestineDriver)
   148  
   149  	require.NoError(t, nReconciler.removeDanglingContainersIteration())
   150  
   151  	_, err = client.InspectContainer(nonNomadContainer.ID)
   152  	require.NoError(t, err)
   153  
   154  	_, err = client.InspectContainer(handle.containerID)
   155  	require.Error(t, err)
   156  	require.Contains(t, err.Error(), NoSuchContainerError)
   157  
   158  	_, err = client.InspectContainer(untrackedNomadContainer.ID)
   159  	require.Error(t, err)
   160  	require.Contains(t, err.Error(), NoSuchContainerError)
   161  }
   162  
   163  // TestDanglingContainerRemoval_Stopped asserts stopped containers without
   164  // corresponding tasks are not removed even if after creation grace period.
   165  func TestDanglingContainerRemoval_Stopped(t *testing.T) {
   166  	ci.Parallel(t)
   167  	testutil.DockerCompatible(t)
   168  
   169  	_, cfg, ports := dockerTask(t)
   170  	defer freeport.Return(ports)
   171  
   172  	client := newTestDockerClient(t)
   173  	container, err := client.CreateContainer(docker.CreateContainerOptions{
   174  		Name: "mytest-image-" + uuid.Generate(),
   175  		Config: &docker.Config{
   176  			Image: cfg.Image,
   177  			Cmd:   append([]string{cfg.Command}, cfg.Args...),
   178  			Labels: map[string]string{
   179  				dockerLabelAllocID: uuid.Generate(),
   180  			},
   181  		},
   182  	})
   183  	require.NoError(t, err)
   184  	defer client.RemoveContainer(docker.RemoveContainerOptions{
   185  		ID:    container.ID,
   186  		Force: true,
   187  	})
   188  
   189  	err = client.StartContainer(container.ID, nil)
   190  	require.NoError(t, err)
   191  
   192  	err = client.StopContainer(container.ID, 60)
   193  	require.NoError(t, err)
   194  
   195  	dd := dockerDriverHarness(t, nil).Impl().(*Driver)
   196  	reconciler := newReconciler(dd)
   197  
   198  	// assert nomad container is tracked, and we ignore stopped one
   199  	tf := reconciler.trackedContainers()
   200  	require.NotContains(t, tf, container.ID)
   201  
   202  	untracked, err := reconciler.untrackedContainers(map[string]bool{}, time.Now())
   203  	require.NoError(t, err)
   204  	require.NotContains(t, untracked, container.ID)
   205  
   206  	// if we start container again, it'll be marked as untracked
   207  	require.NoError(t, client.StartContainer(container.ID, nil))
   208  
   209  	untracked, err = reconciler.untrackedContainers(map[string]bool{}, time.Now())
   210  	require.NoError(t, err)
   211  	require.Contains(t, untracked, container.ID)
   212  }