github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/docker_coordinator_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	docker "github.com/fsouza/go-dockerclient"
     9  	"github.com/hashicorp/nomad/helper/uuid"
    10  	"github.com/hashicorp/nomad/testutil"
    11  )
    12  
    13  type mockImageClient struct {
    14  	pulled    map[string]int
    15  	idToName  map[string]string
    16  	removed   map[string]int
    17  	pullDelay time.Duration
    18  }
    19  
    20  func newMockImageClient(idToName map[string]string, pullDelay time.Duration) *mockImageClient {
    21  	return &mockImageClient{
    22  		pulled:    make(map[string]int),
    23  		removed:   make(map[string]int),
    24  		idToName:  idToName,
    25  		pullDelay: pullDelay,
    26  	}
    27  }
    28  
    29  func (m *mockImageClient) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error {
    30  	time.Sleep(m.pullDelay)
    31  	m.pulled[opts.Repository]++
    32  	return nil
    33  }
    34  
    35  func (m *mockImageClient) InspectImage(id string) (*docker.Image, error) {
    36  	return &docker.Image{
    37  		ID: m.idToName[id],
    38  	}, nil
    39  }
    40  
    41  func (m *mockImageClient) RemoveImage(id string) error {
    42  	m.removed[id]++
    43  	return nil
    44  }
    45  
    46  func TestDockerCoordinator_ConcurrentPulls(t *testing.T) {
    47  	t.Parallel()
    48  	image := "foo"
    49  	imageID := uuid.Generate()
    50  	mapping := map[string]string{imageID: image}
    51  
    52  	// Add a delay so we can get multiple queued up
    53  	mock := newMockImageClient(mapping, 10*time.Millisecond)
    54  	config := &dockerCoordinatorConfig{
    55  		logger:      testLogger(),
    56  		cleanup:     true,
    57  		client:      mock,
    58  		removeDelay: 100 * time.Millisecond,
    59  	}
    60  
    61  	// Create a coordinator
    62  	coordinator := NewDockerCoordinator(config)
    63  
    64  	id := ""
    65  	for i := 0; i < 10; i++ {
    66  		go func() {
    67  			id, _ = coordinator.PullImage(image, nil, uuid.Generate())
    68  		}()
    69  	}
    70  
    71  	testutil.WaitForResult(func() (bool, error) {
    72  		p := mock.pulled[image]
    73  		if p >= 10 {
    74  			return false, fmt.Errorf("Wrong number of pulls: %d", p)
    75  		}
    76  
    77  		// Check the reference count
    78  		if references := coordinator.imageRefCount[id]; len(references) != 10 {
    79  			return false, fmt.Errorf("Got reference count %d; want %d", len(references), 10)
    80  		}
    81  
    82  		// Ensure there is no pull future
    83  		if len(coordinator.pullFutures) != 0 {
    84  			return false, fmt.Errorf("Pull future exists after pull finished")
    85  		}
    86  
    87  		return true, nil
    88  	}, func(err error) {
    89  		t.Fatalf("err: %v", err)
    90  	})
    91  }
    92  
    93  func TestDockerCoordinator_Pull_Remove(t *testing.T) {
    94  	t.Parallel()
    95  	image := "foo"
    96  	imageID := uuid.Generate()
    97  	mapping := map[string]string{imageID: image}
    98  
    99  	// Add a delay so we can get multiple queued up
   100  	mock := newMockImageClient(mapping, 10*time.Millisecond)
   101  	config := &dockerCoordinatorConfig{
   102  		logger:      testLogger(),
   103  		cleanup:     true,
   104  		client:      mock,
   105  		removeDelay: 1 * time.Millisecond,
   106  	}
   107  
   108  	// Create a coordinator
   109  	coordinator := NewDockerCoordinator(config)
   110  
   111  	id := ""
   112  	callerIDs := make([]string, 10, 10)
   113  	for i := 0; i < 10; i++ {
   114  		callerIDs[i] = uuid.Generate()
   115  		id, _ = coordinator.PullImage(image, nil, callerIDs[i])
   116  	}
   117  
   118  	// Check the reference count
   119  	if references := coordinator.imageRefCount[id]; len(references) != 10 {
   120  		t.Fatalf("Got reference count %d; want %d", len(references), 10)
   121  	}
   122  
   123  	// Remove some
   124  	for i := 0; i < 8; i++ {
   125  		coordinator.RemoveImage(id, callerIDs[i])
   126  	}
   127  
   128  	// Check the reference count
   129  	if references := coordinator.imageRefCount[id]; len(references) != 2 {
   130  		t.Fatalf("Got reference count %d; want %d", len(references), 2)
   131  	}
   132  
   133  	// Remove all
   134  	for i := 8; i < 10; i++ {
   135  		coordinator.RemoveImage(id, callerIDs[i])
   136  	}
   137  
   138  	// Check the reference count
   139  	if references := coordinator.imageRefCount[id]; len(references) != 0 {
   140  		t.Fatalf("Got reference count %d; want %d", len(references), 0)
   141  	}
   142  
   143  	// Check that only one delete happened
   144  	testutil.WaitForResult(func() (bool, error) {
   145  		removes := mock.removed[id]
   146  		return removes == 1, fmt.Errorf("Wrong number of removes: %d", removes)
   147  	}, func(err error) {
   148  		t.Fatalf("err: %v", err)
   149  	})
   150  
   151  	// Make sure there is no future still
   152  	if _, ok := coordinator.deleteFuture[id]; ok {
   153  		t.Fatal("Got delete future")
   154  	}
   155  }
   156  
   157  func TestDockerCoordinator_Remove_Cancel(t *testing.T) {
   158  	t.Parallel()
   159  	image := "foo"
   160  	imageID := uuid.Generate()
   161  	mapping := map[string]string{imageID: image}
   162  
   163  	mock := newMockImageClient(mapping, 1*time.Millisecond)
   164  	config := &dockerCoordinatorConfig{
   165  		logger:      testLogger(),
   166  		cleanup:     true,
   167  		client:      mock,
   168  		removeDelay: 100 * time.Millisecond,
   169  	}
   170  
   171  	// Create a coordinator
   172  	coordinator := NewDockerCoordinator(config)
   173  	callerID := uuid.Generate()
   174  
   175  	// Pull image
   176  	id, _ := coordinator.PullImage(image, nil, callerID)
   177  
   178  	// Check the reference count
   179  	if references := coordinator.imageRefCount[id]; len(references) != 1 {
   180  		t.Fatalf("Got reference count %d; want %d", len(references), 1)
   181  	}
   182  
   183  	// Remove image
   184  	coordinator.RemoveImage(id, callerID)
   185  
   186  	// Check the reference count
   187  	if references := coordinator.imageRefCount[id]; len(references) != 0 {
   188  		t.Fatalf("Got reference count %d; want %d", len(references), 0)
   189  	}
   190  
   191  	// Pull image again within delay
   192  	id, _ = coordinator.PullImage(image, nil, callerID)
   193  
   194  	// Check the reference count
   195  	if references := coordinator.imageRefCount[id]; len(references) != 1 {
   196  		t.Fatalf("Got reference count %d; want %d", len(references), 1)
   197  	}
   198  
   199  	// Check that only no delete happened
   200  	if removes := mock.removed[id]; removes != 0 {
   201  		t.Fatalf("Image deleted when it shouldn't have")
   202  	}
   203  }
   204  
   205  func TestDockerCoordinator_No_Cleanup(t *testing.T) {
   206  	t.Parallel()
   207  	image := "foo"
   208  	imageID := uuid.Generate()
   209  	mapping := map[string]string{imageID: image}
   210  
   211  	mock := newMockImageClient(mapping, 1*time.Millisecond)
   212  	config := &dockerCoordinatorConfig{
   213  		logger:      testLogger(),
   214  		cleanup:     false,
   215  		client:      mock,
   216  		removeDelay: 1 * time.Millisecond,
   217  	}
   218  
   219  	// Create a coordinator
   220  	coordinator := NewDockerCoordinator(config)
   221  	callerID := uuid.Generate()
   222  
   223  	// Pull image
   224  	id, _ := coordinator.PullImage(image, nil, callerID)
   225  
   226  	// Check the reference count
   227  	if references := coordinator.imageRefCount[id]; len(references) != 0 {
   228  		t.Fatalf("Got reference count %d; want %d", len(references), 0)
   229  	}
   230  
   231  	// Remove image
   232  	coordinator.RemoveImage(id, callerID)
   233  
   234  	// Check that only no delete happened
   235  	if removes := mock.removed[id]; removes != 0 {
   236  		t.Fatalf("Image deleted when it shouldn't have")
   237  	}
   238  }