github.com/bigcommerce/nomad@v0.9.3-bc/drivers/docker/coordinator_test.go (about)

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