Merge pull request #827 from aaronlehmann/read-only-mode-2

Add a read-only mode as a configuration option
This commit is contained in:
Stephen Day 2015-10-15 11:50:31 -07:00
commit dfe60f4cb1
6 changed files with 113 additions and 25 deletions

View file

@ -118,6 +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:
enabled: false
auth: auth:
silly: silly:
realm: silly-realm realm: silly-realm
@ -644,14 +646,15 @@ This storage backend uses Amazon's Simple Storage Service (S3).
### Maintenance ### Maintenance
Currently the registry can perform one maintenance function: upload purging. This and future Currently upload purging and read-only mode are the only maintenance functions available.
maintenance functions which are related to storage can be configured under the maintenance section. These and future maintenance functions which are related to storage can be configured under
the maintenance section.
### Upload Purging ### Upload Purging
Upload purging is a background process that periodically removes orphaned files from the upload Upload purging is a background process that periodically removes orphaned files from the upload
directories of the registry. Upload purging is enabled by default. To directories of the registry. Upload purging is enabled by default. To
configure upload directory purging, the following parameters configure upload directory purging, the following parameters
must be set. must be set.
@ -664,6 +667,16 @@ must be set.
Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week). Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week).
### Read-only mode
If the `readonly` section under `maintenance` has `enabled` set to `true`,
clients will not be allowed to write to the registry. This mode is useful to
temporarily prevent writes to the backend storage so a garbage collection pass
can be run. Before running garbage collection, the registry should be
restarted with readonly's `enabled` set to true. After the garbage collection
pass finishes, the registry may be restarted again, this time with `readonly`
removed from the configuration (or set to false).
### Openstack Swift ### Openstack Swift
This storage backend uses Openstack Swift object storage. This storage backend uses Openstack Swift object storage.

View file

@ -633,6 +633,54 @@ func TestDeleteDisabled(t *testing.T) {
checkResponse(t, "deleting layer with delete disabled", resp, http.StatusMethodNotAllowed) checkResponse(t, "deleting layer with delete disabled", resp, http.StatusMethodNotAllowed)
} }
func TestDeleteReadOnly(t *testing.T) {
env := newTestEnv(t, true)
imageName := "foo/bar"
// "build" our layer file
layerFile, tarSumStr, err := testutil.CreateRandomTarFile()
if err != nil {
t.Fatalf("error creating random layer file: %v", err)
}
layerDigest := digest.Digest(tarSumStr)
layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
if err != nil {
t.Fatalf("Error building blob URL")
}
uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
env.app.readOnly = true
resp, err := httpDelete(layerURL)
if err != nil {
t.Fatalf("unexpected error deleting layer: %v", err)
}
checkResponse(t, "deleting layer in read-only mode", resp, http.StatusMethodNotAllowed)
}
func TestStartPushReadOnly(t *testing.T) {
env := newTestEnv(t, true)
env.app.readOnly = true
imageName := "foo/bar"
layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
if err != nil {
t.Fatalf("unexpected error building layer upload url: %v", err)
}
resp, err := http.Post(layerUploadURL, "", nil)
if err != nil {
t.Fatalf("unexpected error starting layer push: %v", err)
}
defer resp.Body.Close()
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) {
req, err := http.NewRequest("DELETE", url, nil) req, err := http.NewRequest("DELETE", url, nil)
if err != nil { if err != nil {

View file

@ -69,6 +69,9 @@ type App struct {
// true if this registry is configured as a pull through cache // true if this registry is configured as a pull through cache
isCache bool isCache bool
// true if the registry is in a read-only maintenance mode
readOnly bool
} }
// NewApp takes a configuration and returns a configured app, ready to serve // NewApp takes a configuration and returns a configured app, ready to serve
@ -104,13 +107,24 @@ func NewApp(ctx context.Context, configuration *configuration.Configuration) *Ap
purgeConfig := uploadPurgeDefaultConfig() purgeConfig := uploadPurgeDefaultConfig()
if mc, ok := configuration.Storage["maintenance"]; ok { if mc, ok := configuration.Storage["maintenance"]; ok {
for k, v := range mc { if v, ok := mc["uploadpurging"]; ok {
switch k { purgeConfig, ok = v.(map[interface{}]interface{})
case "uploadpurging": if !ok {
purgeConfig = v.(map[interface{}]interface{}) panic("uploadpurging config key must contain additional keys")
}
}
if v, ok := mc["readonly"]; ok {
readOnly, ok := v.(map[interface{}]interface{})
if !ok {
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")
}
} }
} }
} }
startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig) startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig)

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": http.HandlerFunc(blobHandler.DeleteBlob),
} }
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": http.HandlerFunc(buh.StartBlobUpload), "GET": http.HandlerFunc(buh.GetUploadStatus),
"GET": http.HandlerFunc(buh.GetUploadStatus), "HEAD": http.HandlerFunc(buh.GetUploadStatus),
"HEAD": http.HandlerFunc(buh.GetUploadStatus), }
"PATCH": http.HandlerFunc(buh.PatchBlobData),
"PUT": http.HandlerFunc(buh.PutBlobUploadComplete), if !ctx.readOnly {
"DELETE": http.HandlerFunc(buh.CancelBlobUpload), 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

@ -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": http.HandlerFunc(imageManifestHandler.PutImageManifest),
"DELETE": http.HandlerFunc(imageManifestHandler.DeleteImageManifest),
} }
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.