Specify and implement Docker-Upload-UUID

This changeset adds support for a header to identify docker upload uuids. This
id can be used as a key to manage local state for resumable uploads. The goal
is remove the necessity for a client to parse the url to get an upload uuid.
The restrictions for clients to use the location header are still strongly in
place.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day 2015-02-26 16:43:47 -08:00
parent 00ce453315
commit 32f5965c06
3 changed files with 48 additions and 11 deletions

View file

@ -72,6 +72,13 @@ var (
Format: "0",
}
dockerUploadUUIDHeader = ParameterDescriptor{
Name: "Docker-Upload-UUID",
Description: "Identifies the docker upload uuid for the current request.",
Type: "uuid",
Format: "<uuid>",
}
unauthorizedResponse = ResponseDescriptor{
Description: "The client does not have access to the repository.",
StatusCode: http.StatusUnauthorized,
@ -898,6 +905,7 @@ var routeDescriptors = []RouteDescriptor{
Format: "<blob location>",
},
contentLengthZeroHeader,
dockerUploadUUIDHeader,
},
},
},
@ -941,6 +949,7 @@ var routeDescriptors = []RouteDescriptor{
Format: "0-0",
Description: "Range header indicating the progress of the upload. When starting an upload, it will return an empty range, since no content has been received.",
},
dockerUploadUUIDHeader,
},
},
},
@ -994,6 +1003,7 @@ var routeDescriptors = []RouteDescriptor{
Description: "Range indicating the current progress of the upload.",
},
contentLengthZeroHeader,
dockerUploadUUIDHeader,
},
},
},
@ -1077,6 +1087,7 @@ var routeDescriptors = []RouteDescriptor{
Description: "Range indicating the current progress of the upload.",
},
contentLengthZeroHeader,
dockerUploadUUIDHeader,
},
},
},

View file

@ -11,6 +11,7 @@ import (
"net/http/httputil"
"net/url"
"os"
"path"
"reflect"
"testing"
@ -97,8 +98,20 @@ func TestLayerAPI(t *testing.T) {
checkResponse(t, "checking head on non-existent layer", resp, http.StatusNotFound)
// ------------------------------------------
// Start an upload and cancel
uploadURLBase := startPushLayer(t, env.builder, imageName)
// Start an upload, check the status then cancel
uploadURLBase, uploadUUID := startPushLayer(t, env.builder, imageName)
// A status check should work
resp, err = http.Get(uploadURLBase)
if err != nil {
t.Fatalf("unexpected error getting upload status: %v", err)
}
checkResponse(t, "status of deleted upload", resp, http.StatusNoContent)
checkHeaders(t, resp, http.Header{
"Location": []string{"*"},
"Range": []string{"0-0"},
"Docker-Upload-UUID": []string{uploadUUID},
})
req, err := http.NewRequest("DELETE", uploadURLBase, nil)
if err != nil {
@ -121,7 +134,7 @@ func TestLayerAPI(t *testing.T) {
// -----------------------------------------
// Do layer push with an empty body and different digest
uploadURLBase = startPushLayer(t, env.builder, imageName)
uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
resp, err = doPushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, bytes.NewReader([]byte{}))
if err != nil {
t.Fatalf("unexpected error doing bad layer push: %v", err)
@ -137,7 +150,7 @@ func TestLayerAPI(t *testing.T) {
t.Fatalf("unexpected error digesting empty buffer: %v", err)
}
uploadURLBase = startPushLayer(t, env.builder, imageName)
uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
pushLayer(t, env.builder, imageName, zeroDigest, uploadURLBase, bytes.NewReader([]byte{}))
// -----------------------------------------
@ -150,7 +163,7 @@ func TestLayerAPI(t *testing.T) {
t.Fatalf("unexpected error digesting empty tar: %v", err)
}
uploadURLBase = startPushLayer(t, env.builder, imageName)
uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
pushLayer(t, env.builder, imageName, emptyDigest, uploadURLBase, bytes.NewReader(emptyTar))
// ------------------------------------------
@ -158,7 +171,7 @@ func TestLayerAPI(t *testing.T) {
layerLength, _ := layerFile.Seek(0, os.SEEK_END)
layerFile.Seek(0, os.SEEK_SET)
uploadURLBase = startPushLayer(t, env.builder, imageName)
uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
// ------------------------
@ -284,7 +297,7 @@ func TestManifestAPI(t *testing.T) {
expectedLayers[dgst] = rs
unsignedManifest.FSLayers[i].BlobSum = dgst
uploadURLBase := startPushLayer(t, env.builder, imageName)
uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
}
@ -411,7 +424,7 @@ func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response {
return resp
}
func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) string {
func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) (location string, uuid string) {
layerUploadURL, err := ub.BuildBlobUploadURL(name)
if err != nil {
t.Fatalf("unexpected error building layer upload url: %v", err)
@ -424,12 +437,20 @@ func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) string {
defer resp.Body.Close()
checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name), resp, http.StatusAccepted)
u, err := url.Parse(resp.Header.Get("Location"))
if err != nil {
t.Fatalf("error parsing location header: %v", err)
}
uuid = path.Base(u.Path)
checkHeaders(t, resp, http.Header{
"Location": []string{"*"},
"Content-Length": []string{"0"},
"Location": []string{"*"},
"Content-Length": []string{"0"},
"Docker-Upload-UUID": []string{uuid},
})
return resp.Header.Get("Location")
return resp.Header.Get("Location"), uuid
}
// doPushLayer pushes the layer content returning the url on success returning

View file

@ -138,6 +138,8 @@ func (luh *layerUploadHandler) StartLayerUpload(w http.ResponseWriter, r *http.R
luh.Errors.Push(v2.ErrorCodeUnknown, err)
return
}
w.Header().Set("Docker-Upload-UUID", luh.Upload.UUID())
w.WriteHeader(http.StatusAccepted)
}
@ -155,6 +157,7 @@ func (luh *layerUploadHandler) GetUploadStatus(w http.ResponseWriter, r *http.Re
return
}
w.Header().Set("Docker-Upload-UUID", luh.UUID)
w.WriteHeader(http.StatusNoContent)
}
@ -235,6 +238,7 @@ func (luh *layerUploadHandler) CancelLayerUpload(w http.ResponseWriter, r *http.
return
}
w.Header().Set("Docker-Upload-UUID", luh.UUID)
if err := luh.Upload.Cancel(); err != nil {
ctxu.GetLogger(luh).Errorf("error encountered canceling upload: %v", err)
w.WriteHeader(http.StatusInternalServerError)
@ -277,6 +281,7 @@ func (luh *layerUploadHandler) layerUploadResponse(w http.ResponseWriter, r *htt
return err
}
w.Header().Set("Docker-Upload-UUID", luh.UUID)
w.Header().Set("Location", uploadURL)
w.Header().Set("Content-Length", "0")
w.Header().Set("Range", fmt.Sprintf("0-%d", luh.State.Offset))