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 }