make deleting idempotent

This commit is contained in:
Michael Eischer 2022-09-02 23:22:42 +02:00
parent b562edefd1
commit a8fdca3b9f
2 changed files with 93 additions and 4 deletions

View file

@ -165,8 +165,8 @@ func createOverwriteDeleteSeq(t testing.TB, path string, data string) []TestRequ
return req
}
// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticHandler(t *testing.T) {
// TestResticAppendOnlyHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticAppendOnlyHandler(t *testing.T) {
buf := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
@ -270,6 +270,87 @@ func TestResticHandler(t *testing.T) {
}
}
// createOverwriteDeleteSeq returns a sequence which will create a new file at
// path, and then deletes it twice.
func createIdempotentDeleteSeq(t testing.TB, path string, data string) []TestRequest {
return []TestRequest{
{
req: newRequest(t, "POST", path, strings.NewReader(data)),
want: []wantFunc{wantCode(http.StatusOK)},
},
{
req: newRequest(t, "DELETE", path, nil),
want: []wantFunc{wantCode(http.StatusOK)},
},
{
req: newRequest(t, "GET", path, nil),
want: []wantFunc{wantCode(http.StatusNotFound)},
},
{
req: newRequest(t, "DELETE", path, nil),
want: []wantFunc{wantCode(http.StatusOK)},
},
}
}
// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
func TestResticHandler(t *testing.T) {
buf := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, buf)
if err != nil {
t.Fatal(err)
}
data := "random data file " + hex.EncodeToString(buf)
dataHash := sha256.Sum256([]byte(data))
fileID := hex.EncodeToString(dataHash[:])
var tests = []struct {
seq []TestRequest
}{
{createIdempotentDeleteSeq(t, "/config", data)},
{createIdempotentDeleteSeq(t, "/data/"+fileID, data)},
}
// setup the server with a local backend in a temporary directory
tempdir, err := ioutil.TempDir("", "rest-server-test-")
if err != nil {
t.Fatal(err)
}
// make sure the tempdir is properly removed
defer func() {
err := os.RemoveAll(tempdir)
if err != nil {
t.Fatal(err)
}
}()
// set append-only mode and configure path
mux, err := NewHandler(&Server{
Path: tempdir,
NoAuth: true,
Debug: true,
PanicOnError: true,
})
if err != nil {
t.Fatalf("error from NewHandler: %v", err)
}
// create the repo
checkRequest(t, mux.ServeHTTP,
newRequest(t, "POST", "/?create=true", nil),
[]wantFunc{wantCode(http.StatusOK)})
for _, test := range tests {
t.Run("", func(t *testing.T) {
for i, seq := range test.seq {
t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
checkRequest(t, mux.ServeHTTP, seq.req, seq.want)
}
})
}
}
// TestResticErrorHandler runs tests on the restic handler error handling.
func TestResticErrorHandler(t *testing.T) {
var tests = []struct {

View file

@ -325,7 +325,11 @@ func (h *Handler) deleteConfig(w http.ResponseWriter, r *http.Request) {
cfg := h.getSubPath("config")
if err := os.Remove(cfg); err != nil {
h.fileAccessError(w, err)
// ignore not exist errors to make deleting idempotent, which is
// necessary to properly handle request retries
if !errors.Is(err, os.ErrNotExist) {
h.fileAccessError(w, err)
}
return
}
}
@ -690,7 +694,11 @@ func (h *Handler) deleteBlob(w http.ResponseWriter, r *http.Request) {
}
if err := os.Remove(path); err != nil {
h.fileAccessError(w, err)
// ignore not exist errors to make deleting idempotent, which is
// necessary to properly handle request retries
if !errors.Is(err, os.ErrNotExist) {
h.fileAccessError(w, err)
}
return
}