github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/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/nomad/structs"
    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  	image := "foo"
    48  	imageID := structs.GenerateUUID()
    49  	mapping := map[string]string{imageID: image}
    50  
    51  	// Add a delay so we can get multiple queued up
    52  	mock := newMockImageClient(mapping, 10*time.Millisecond)
    53  	config := &dockerCoordinatorConfig{
    54  		logger:      testLogger(),
    55  		cleanup:     true,
    56  		client:      mock,
    57  		removeDelay: 100 * time.Millisecond,
    58  	}
    59  
    60  	// Create a coordinator
    61  	coordinator := NewDockerCoordinator(config)
    62  
    63  	id := ""
    64  	for i := 0; i < 10; i++ {
    65  		go func() {
    66  			id, _ = coordinator.PullImage(image, nil, structs.GenerateUUID())
    67  		}()
    68  	}
    69  
    70  	testutil.WaitForResult(func() (bool, error) {
    71  		p := mock.pulled[image]
    72  		if p != 1 {
    73  			return false, fmt.Errorf("Wrong number of pulls: %d", p)
    74  		}
    75  
    76  		// Check the reference count
    77  		if references := coordinator.imageRefCount[id]; len(references) != 10 {
    78  			return false, fmt.Errorf("Got reference count %d; want %d", len(references), 10)
    79  		}
    80  
    81  		// Ensure there is no pull future
    82  		if len(coordinator.pullFutures) != 0 {
    83  			return false, fmt.Errorf("Pull future exists after pull finished")
    84  		}
    85  
    86  		return true, nil
    87  	}, func(err error) {
    88  		t.Fatalf("err: %v", err)
    89  	})
    90  }
    91  
    92  func TestDockerCoordinator_Pull_Remove(t *testing.T) {
    93  	image := "foo"
    94  	imageID := structs.GenerateUUID()
    95  	mapping := map[string]string{imageID: image}
    96  
    97  	// Add a delay so we can get multiple queued up
    98  	mock := newMockImageClient(mapping, 10*time.Millisecond)
    99  	config := &dockerCoordinatorConfig{
   100  		logger:      testLogger(),
   101  		cleanup:     true,
   102  		client:      mock,
   103  		removeDelay: 1 * time.Millisecond,
   104  	}
   105  
   106  	// Create a coordinator
   107  	coordinator := NewDockerCoordinator(config)
   108  
   109  	id := ""
   110  	callerIDs := make([]string, 10, 10)
   111  	for i := 0; i < 10; i++ {
   112  		callerIDs[i] = structs.GenerateUUID()
   113  		id, _ = coordinator.PullImage(image, nil, callerIDs[i])
   114  	}
   115  
   116  	// Check the reference count
   117  	if references := coordinator.imageRefCount[id]; len(references) != 10 {
   118  		t.Fatalf("Got reference count %d; want %d", len(references), 10)
   119  	}
   120  
   121  	// Remove some
   122  	for i := 0; i < 8; i++ {
   123  		coordinator.RemoveImage(id, callerIDs[i])
   124  	}
   125  
   126  	// Check the reference count
   127  	if references := coordinator.imageRefCount[id]; len(references) != 2 {
   128  		t.Fatalf("Got reference count %d; want %d", len(references), 2)
   129  	}
   130  
   131  	// Remove all
   132  	for i := 8; i < 10; i++ {
   133  		coordinator.RemoveImage(id, callerIDs[i])
   134  	}
   135  
   136  	// Check the reference count
   137  	if references := coordinator.imageRefCount[id]; len(references) != 0 {
   138  		t.Fatalf("Got reference count %d; want %d", len(references), 0)
   139  	}
   140  
   141  	// Check that only one delete happened
   142  	testutil.WaitForResult(func() (bool, error) {
   143  		removes := mock.removed[id]
   144  		return removes == 1, fmt.Errorf("Wrong number of removes: %d", removes)
   145  	}, func(err error) {
   146  		t.Fatalf("err: %v", err)
   147  	})
   148  
   149  	// Make sure there is no future still
   150  	if _, ok := coordinator.deleteFuture[id]; ok {
   151  		t.Fatal("Got delete future")
   152  	}
   153  }
   154  
   155  func TestDockerCoordinator_Remove_Cancel(t *testing.T) {
   156  	image := "foo"
   157  	imageID := structs.GenerateUUID()
   158  	mapping := map[string]string{imageID: image}
   159  
   160  	mock := newMockImageClient(mapping, 1*time.Millisecond)
   161  	config := &dockerCoordinatorConfig{
   162  		logger:      testLogger(),
   163  		cleanup:     true,
   164  		client:      mock,
   165  		removeDelay: 100 * time.Millisecond,
   166  	}
   167  
   168  	// Create a coordinator
   169  	coordinator := NewDockerCoordinator(config)
   170  	callerID := structs.GenerateUUID()
   171  
   172  	// Pull image
   173  	id, _ := coordinator.PullImage(image, nil, callerID)
   174  
   175  	// Check the reference count
   176  	if references := coordinator.imageRefCount[id]; len(references) != 1 {
   177  		t.Fatalf("Got reference count %d; want %d", len(references), 1)
   178  	}
   179  
   180  	// Remove image
   181  	coordinator.RemoveImage(id, callerID)
   182  
   183  	// Check the reference count
   184  	if references := coordinator.imageRefCount[id]; len(references) != 0 {
   185  		t.Fatalf("Got reference count %d; want %d", len(references), 0)
   186  	}
   187  
   188  	// Pull image again within delay
   189  	id, _ = coordinator.PullImage(image, nil, callerID)
   190  
   191  	// Check the reference count
   192  	if references := coordinator.imageRefCount[id]; len(references) != 1 {
   193  		t.Fatalf("Got reference count %d; want %d", len(references), 1)
   194  	}
   195  
   196  	// Check that only no delete happened
   197  	if removes := mock.removed[id]; removes != 0 {
   198  		t.Fatalf("Image deleted when it shouldn't have")
   199  	}
   200  }
   201  
   202  func TestDockerCoordinator_No_Cleanup(t *testing.T) {
   203  	image := "foo"
   204  	imageID := structs.GenerateUUID()
   205  	mapping := map[string]string{imageID: image}
   206  
   207  	mock := newMockImageClient(mapping, 1*time.Millisecond)
   208  	config := &dockerCoordinatorConfig{
   209  		logger:      testLogger(),
   210  		cleanup:     false,
   211  		client:      mock,
   212  		removeDelay: 1 * time.Millisecond,
   213  	}
   214  
   215  	// Create a coordinator
   216  	coordinator := NewDockerCoordinator(config)
   217  	callerID := structs.GenerateUUID()
   218  
   219  	// Pull image
   220  	id, _ := coordinator.PullImage(image, nil, callerID)
   221  
   222  	// Check the reference count
   223  	if references := coordinator.imageRefCount[id]; len(references) != 0 {
   224  		t.Fatalf("Got reference count %d; want %d", len(references), 0)
   225  	}
   226  
   227  	// Remove image
   228  	coordinator.RemoveImage(id, callerID)
   229  
   230  	// Check that only no delete happened
   231  	if removes := mock.removed[id]; removes != 0 {
   232  		t.Fatalf("Image deleted when it shouldn't have")
   233  	}
   234  }