Add an "enabled" parameter under "readonly", and make it as if the mutable handlers don't exist when read-only mode is enabled

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2015-08-06 18:02:43 -07:00
parent c9bb330b71
commit a601f92336
8 changed files with 49 additions and 52 deletions

View file

@ -118,7 +118,8 @@ information about each option that appears later in this page.
age: 168h age: 168h
interval: 24h interval: 24h
dryrun: false dryrun: false
readonly: false readonly:
enabled: false
auth: auth:
silly: silly:
realm: silly-realm realm: silly-realm
@ -667,12 +668,13 @@ Note: `age` and `interval` are strings containing a number with optional fractio
### Read-only mode ### Read-only mode
If the `readonly` parameter in the `maintenance` section is set to true, clients If the `readonly` section under `maintenance` has `enabled` set to `true`,
will not be allowed to write to the registry. This mode is useful to temporarily clients will not be allowed to write to the registry. This mode is useful to
prevent writes to the backend storage so a garbage collection pass can be run. temporarily prevent writes to the backend storage so a garbage collection pass
Before running garbage collection, the registry should be restarted with can be run. Before running garbage collection, the registry should be
`readonly` set to true. After the garbage collection pass finishes, the registry restarted with readonly's `enabled` set to true. After the garbage collection
may be restarted again, this time with `readonly` removed from the configuration. pass finishes, the registry may be restarted again, this time with `readonly`
removed from the configuration (or set to false).
### Openstack Swift ### Openstack Swift

View file

@ -133,14 +133,4 @@ var (
longer proceed.`, longer proceed.`,
HTTPStatusCode: http.StatusNotFound, HTTPStatusCode: http.StatusNotFound,
}) })
// ErrorCodeMaintenanceMode is returned when an upload can't be
// accepted because the registry is in maintenance mode.
ErrorCodeMaintenanceMode = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "MAINTENANCE_MODE",
Message: "registry in maintenance mode",
Description: `The upload cannot be accepted because the registry
is running read-only in maintenance mode.`,
HTTPStatusCode: http.StatusServiceUnavailable,
})
) )

View file

@ -658,7 +658,7 @@ func TestDeleteReadOnly(t *testing.T) {
t.Fatalf("unexpected error deleting layer: %v", err) t.Fatalf("unexpected error deleting layer: %v", err)
} }
checkResponse(t, "deleting layer in read-only mode", resp, http.StatusServiceUnavailable) checkResponse(t, "deleting layer in read-only mode", resp, http.StatusMethodNotAllowed)
} }
func TestStartPushReadOnly(t *testing.T) { func TestStartPushReadOnly(t *testing.T) {
@ -678,7 +678,7 @@ func TestStartPushReadOnly(t *testing.T) {
} }
defer resp.Body.Close() defer resp.Body.Close()
checkResponse(t, "starting push in read-only mode", resp, http.StatusServiceUnavailable) checkResponse(t, "starting push in read-only mode", resp, http.StatusMethodNotAllowed)
} }
func httpDelete(url string) (*http.Response, error) { func httpDelete(url string) (*http.Response, error) {

View file

@ -109,9 +109,15 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
} }
} }
if v, ok := mc["readonly"]; ok { if v, ok := mc["readonly"]; ok {
app.readOnly, ok = v.(bool) readOnly, ok := v.(map[interface{}]interface{})
if !ok { if !ok {
panic("readonly config key must have a boolean value") panic("readonly config key must contain additional keys")
}
if readOnlyEnabled, ok := readOnly["enabled"]; ok {
app.readOnly, ok = readOnlyEnabled.(bool)
if !ok {
panic("readonly's enabled config key must have a boolean value")
}
} }
} }
} }

View file

@ -32,11 +32,16 @@ func blobDispatcher(ctx *Context, r *http.Request) http.Handler {
Digest: dgst, Digest: dgst,
} }
return handlers.MethodHandler{ mhandler := handlers.MethodHandler{
"GET": http.HandlerFunc(blobHandler.GetBlob), "GET": http.HandlerFunc(blobHandler.GetBlob),
"HEAD": http.HandlerFunc(blobHandler.GetBlob), "HEAD": http.HandlerFunc(blobHandler.GetBlob),
"DELETE": mutableHandler(blobHandler.DeleteBlob, ctx),
} }
if !ctx.readOnly {
mhandler["DELETE"] = http.HandlerFunc(blobHandler.DeleteBlob)
}
return mhandler
} }
// blobHandler serves http blob requests. // blobHandler serves http blob requests.

View file

@ -22,14 +22,17 @@ func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
UUID: getUploadUUID(ctx), UUID: getUploadUUID(ctx),
} }
handler := http.Handler(handlers.MethodHandler{ handler := handlers.MethodHandler{
"POST": mutableHandler(buh.StartBlobUpload, ctx),
"GET": http.HandlerFunc(buh.GetUploadStatus), "GET": http.HandlerFunc(buh.GetUploadStatus),
"HEAD": http.HandlerFunc(buh.GetUploadStatus), "HEAD": http.HandlerFunc(buh.GetUploadStatus),
"PATCH": mutableHandler(buh.PatchBlobData, ctx), }
"PUT": mutableHandler(buh.PutBlobUploadComplete, ctx),
"DELETE": mutableHandler(buh.CancelBlobUpload, ctx), if !ctx.readOnly {
}) handler["POST"] = http.HandlerFunc(buh.StartBlobUpload)
handler["PATCH"] = http.HandlerFunc(buh.PatchBlobData)
handler["PUT"] = http.HandlerFunc(buh.PutBlobUploadComplete)
handler["DELETE"] = http.HandlerFunc(buh.CancelBlobUpload)
}
if buh.UUID != "" { if buh.UUID != "" {
state, err := hmacKey(ctx.Config.HTTP.Secret).unpackUploadState(r.FormValue("_state")) state, err := hmacKey(ctx.Config.HTTP.Secret).unpackUploadState(r.FormValue("_state"))
@ -93,7 +96,7 @@ func blobUploadDispatcher(ctx *Context, r *http.Request) http.Handler {
} }
} }
handler = closeResources(handler, buh.Upload) return closeResources(handler, buh.Upload)
} }
return handler return handler

View file

@ -7,7 +7,6 @@ import (
ctxu "github.com/docker/distribution/context" ctxu "github.com/docker/distribution/context"
"github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
) )
// closeResources closes all the provided resources after running the target // closeResources closes all the provided resources after running the target
@ -61,16 +60,3 @@ func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWr
return nil return nil
} }
// mutableHandler wraps a http.HandlerFunc with a check that the registry is
// not in read-only mode. If it is in read-only mode, the wrapper returns
// v2.ErrorCodeMaintenanceMode to the client.
func mutableHandler(handler http.HandlerFunc, ctx *Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if ctx.App.readOnly {
ctx.Errors = append(ctx.Errors, v2.ErrorCodeMaintenanceMode)
return
}
handler(w, r)
}
}

View file

@ -32,11 +32,16 @@ func imageManifestDispatcher(ctx *Context, r *http.Request) http.Handler {
imageManifestHandler.Digest = dgst imageManifestHandler.Digest = dgst
} }
return handlers.MethodHandler{ mhandler := handlers.MethodHandler{
"GET": http.HandlerFunc(imageManifestHandler.GetImageManifest), "GET": http.HandlerFunc(imageManifestHandler.GetImageManifest),
"PUT": mutableHandler(imageManifestHandler.PutImageManifest, ctx),
"DELETE": mutableHandler(imageManifestHandler.DeleteImageManifest, ctx),
} }
if !ctx.readOnly {
mhandler["PUT"] = http.HandlerFunc(imageManifestHandler.PutImageManifest)
mhandler["DELETE"] = http.HandlerFunc(imageManifestHandler.DeleteImageManifest)
}
return mhandler
} }
// imageManifestHandler handles http operations on image manifests. // imageManifestHandler handles http operations on image manifests.