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