mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
add client timeout tests
This commit is contained in:
parent
6c1dfecea3
commit
a762ec39aa
3 changed files with 161 additions and 5 deletions
35
internal/testing/delayhttp/delayhttp.go
Normal file
35
internal/testing/delayhttp/delayhttp.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Package delayhttp creates http.HandlerFunc's that delays the response.
|
||||
// Useful for testing timeout scenarios.
|
||||
package delayhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WithChannel returns a handler that delays until it recieves something on returnChan
|
||||
func WithChannel(returnChan chan struct{}) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Wait until channel sends return code
|
||||
<-returnChan
|
||||
}
|
||||
}
|
||||
|
||||
// WithCancel returns a handler that delays until the cancel func is called.
|
||||
// Useful together with defer to clean up tests.
|
||||
func WithCancel() (http.HandlerFunc, func()) {
|
||||
returnChan := make(chan struct{}, 1)
|
||||
return WithChannel(returnChan), func() {
|
||||
returnChan <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout returns a handler that delays until the passed duration has elapsed
|
||||
func WithTimeout(delay time.Duration) http.HandlerFunc {
|
||||
returnChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
returnChan <- struct{}{}
|
||||
}()
|
||||
return WithChannel(returnChan)
|
||||
}
|
|
@ -3,9 +3,12 @@ package container
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/containrrr/watchtower/internal/testing/delayhttp"
|
||||
"github.com/containrrr/watchtower/pkg/container/mocks"
|
||||
"github.com/containrrr/watchtower/pkg/filters"
|
||||
"github.com/containrrr/watchtower/pkg/registry"
|
||||
t "github.com/containrrr/watchtower/pkg/types"
|
||||
wt "github.com/containrrr/watchtower/pkg/types"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
|
@ -25,6 +28,7 @@ import (
|
|||
var _ = Describe("the client", func() {
|
||||
var docker *cli.Client
|
||||
var mockServer *ghttp.Server
|
||||
mockRegServer := ghttp.NewTLSServer()
|
||||
BeforeEach(func() {
|
||||
mockServer = ghttp.NewServer()
|
||||
docker, _ = cli.NewClientWithOpts(
|
||||
|
@ -33,6 +37,7 @@ var _ = Describe("the client", func() {
|
|||
})
|
||||
AfterEach(func() {
|
||||
mockServer.Close()
|
||||
mockRegServer.Reset()
|
||||
})
|
||||
Describe("WarnOnHeadPullFailed", func() {
|
||||
containerUnknown := *MockContainer(WithImageName("unknown.repo/prefix/imagename:latest"))
|
||||
|
@ -103,6 +108,97 @@ var _ = Describe("the client", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
When("checking if container is stale", func() {
|
||||
|
||||
testRegPullFallback := func(client *dockerClient, cnt *Container, regResponses int) (bool, error) {
|
||||
delayedResponseHandler, cancel := delayhttp.WithCancel()
|
||||
defer cancel()
|
||||
|
||||
// TODO: Add mock handlers for repository requests
|
||||
regRequestHandlers := [][]http.HandlerFunc{
|
||||
{
|
||||
ghttp.VerifyRequest("GET", HaveSuffix("v2/")),
|
||||
func(_ http.ResponseWriter, _ *http.Request) {
|
||||
Fail("registry request is not implemented")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, regRequestPair := range regRequestHandlers {
|
||||
|
||||
verifyHandler := regRequestPair[0]
|
||||
responseHandler := regRequestPair[1]
|
||||
|
||||
if i >= regResponses {
|
||||
// Registry should not be responding
|
||||
responseHandler = delayedResponseHandler
|
||||
}
|
||||
|
||||
mockRegServer.AppendHandlers(ghttp.CombineHandlers(
|
||||
verifyHandler,
|
||||
responseHandler,
|
||||
))
|
||||
|
||||
if i >= regResponses {
|
||||
// No need to add further handlers since the last one is blocking
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newImage := types.ImageInspect{ID: "newer_id"}
|
||||
|
||||
// Docker should respond normally
|
||||
mockServer.AppendHandlers(
|
||||
mocks.PullImageHandlerOK(),
|
||||
mocks.GetImageHandlerOK(cnt.ImageName(), &newImage),
|
||||
)
|
||||
|
||||
stale, latest, err := client.IsContainerStale(*cnt)
|
||||
|
||||
Expect(stale).To(Equal(latest == wt.ImageID(newImage.ID)))
|
||||
|
||||
return stale, err
|
||||
|
||||
}
|
||||
|
||||
When("head request times out", func() {
|
||||
It("should gracefully fail and continue using pull", func() {
|
||||
mockContainer := MockContainer(WithImageName(mockRegServer.Addr() + "/prefix/imagename:latest"))
|
||||
|
||||
regClient := registry.NewClientWithHTTPClient(mockRegServer.HTTPTestServer.Client())
|
||||
regClient.Timeout = time.Second * 2
|
||||
client := dockerClient{
|
||||
api: docker,
|
||||
ClientOptions: ClientOptions{PullImages: true},
|
||||
reg: regClient,
|
||||
}
|
||||
|
||||
stale, err := testRegPullFallback(&client, mockContainer, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(stale).To(BeTrue())
|
||||
|
||||
})
|
||||
})
|
||||
When("client request times out", func() {
|
||||
It("should fail with a useful message", func() {
|
||||
mockContainer := MockContainer(WithImageName(mockRegServer.Addr() + "/prefix/imagename:latest"))
|
||||
|
||||
regClient := registry.NewClientWithHTTPClient(mockRegServer.HTTPTestServer.Client())
|
||||
client := dockerClient{
|
||||
api: docker,
|
||||
ClientOptions: ClientOptions{
|
||||
Timeout: time.Second * 2,
|
||||
PullImages: true,
|
||||
},
|
||||
reg: regClient,
|
||||
}
|
||||
|
||||
_, err := testRegPullFallback(&client, mockContainer, 0)
|
||||
Expect(err).To(MatchError(context.DeadlineExceeded))
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
When("listing containers", func() {
|
||||
When("no filter is provided", func() {
|
||||
It("should return all available containers", func() {
|
||||
|
|
|
@ -92,7 +92,10 @@ func getContainerHandler(containerId string, responseHandler http.HandlerFunc) h
|
|||
)
|
||||
}
|
||||
|
||||
// GetContainerHandler mocks the GET containers/{id}/json endpoint
|
||||
// GetContainerHandler mocks the GET containers/{containerID}/json endpoint.
|
||||
//
|
||||
// If containerInfo is nil, it returns an 200 OK http result with the containerInfo as the body.
|
||||
// Otherwise, it returns the appropriate 404 NotFound result for the containerID.
|
||||
func GetContainerHandler(containerID string, containerInfo *types.ContainerJSON) http.HandlerFunc {
|
||||
responseHandler := containerNotFoundResponse(containerID)
|
||||
if containerInfo != nil {
|
||||
|
@ -101,9 +104,29 @@ func GetContainerHandler(containerID string, containerInfo *types.ContainerJSON)
|
|||
return getContainerHandler(containerID, responseHandler)
|
||||
}
|
||||
|
||||
// GetImageHandler mocks the GET images/{id}/json endpoint
|
||||
func GetImageHandler(imageInfo *types.ImageInspect) http.HandlerFunc {
|
||||
return getImageHandler(imageInfo.ID, ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo))
|
||||
// GetContainerHandlerOK mocks the GET containers/{containerInfo.ID}/json endpoint, returning the containerInfo
|
||||
func GetContainerHandlerOK(containerInfo *types.ContainerJSON) http.HandlerFunc {
|
||||
O.ExpectWithOffset(1, containerInfo).ToNot(O.BeNil())
|
||||
return GetContainerHandler(containerInfo.ID, containerInfo)
|
||||
}
|
||||
|
||||
// GetImageHandler mocks the GET images/{imageName}/json endpoint, returns a 200 OK response with the imageInfo
|
||||
func GetImageHandlerOK(imageName string, imageInfo *types.ImageInspect) http.HandlerFunc {
|
||||
return getImageHandler(imageName, ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo))
|
||||
}
|
||||
|
||||
// GetImageHandlerNotFound mocks the GET images/{imageName}/json endpoint, returning a 404 NotFound response
|
||||
// with the appropriate error message for imageName
|
||||
func GetImageHandlerNotFound(imageName string) http.HandlerFunc {
|
||||
body := errorMessage{"no such image: " + imageName}
|
||||
return getImageHandler(imageName, ghttp.RespondWithJSONEncoded(http.StatusNotFound, body))
|
||||
}
|
||||
|
||||
func PullImageHandlerOK() http.HandlerFunc {
|
||||
return ghttp.CombineHandlers(
|
||||
ghttp.VerifyRequest("POST", O.HaveSuffix("images/create")),
|
||||
ghttp.RespondWith(http.StatusNoContent, nil),
|
||||
)
|
||||
}
|
||||
|
||||
// ListContainersHandler mocks the GET containers/json endpoint, filtering the returned containers based on statuses
|
||||
|
@ -179,8 +202,10 @@ func RemoveContainerHandler(containerID string, found FoundStatus) http.HandlerF
|
|||
)
|
||||
}
|
||||
|
||||
type errorMessage struct{ message string }
|
||||
|
||||
func containerNotFoundResponse(containerID string) http.HandlerFunc {
|
||||
return ghttp.RespondWithJSONEncoded(http.StatusNotFound, struct{ message string }{message: "No such container: " + containerID})
|
||||
return ghttp.RespondWithJSONEncoded(http.StatusNotFound, errorMessage{"No such container: " + containerID})
|
||||
}
|
||||
|
||||
var noContentStatusResponse = ghttp.RespondWith(http.StatusNoContent, nil)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue