github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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 t.Parallel() 48 image := "foo" 49 imageID := structs.GenerateUUID() 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, structs.GenerateUUID()) 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 := structs.GenerateUUID() 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] = structs.GenerateUUID() 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 := structs.GenerateUUID() 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 := structs.GenerateUUID() 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 := structs.GenerateUUID() 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 := structs.GenerateUUID() 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 }