mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
098a44c07f
Finish the refactor which was partially completed with commit
34536c498d
, passing around IdentityMapping structs instead of pairs of
[]IDMap slices.
Existing code which uses []IDMap relies on zero-valued fields to be
valid, empty mappings. So in order to successfully finish the
refactoring without introducing bugs, their replacement therefore also
needs to have a useful zero value which represents an empty mapping.
Change IdentityMapping to be a pass-by-value type so that there are no
nil pointers to worry about.
The functionality provided by the deprecated NewIDMappingsFromMaps
function is required by unit tests to to construct arbitrary
IdentityMapping values. And the daemon will always need to access the
mappings to pass them to the Linux kernel. Accommodate these use cases
by exporting the struct fields instead. BuildKit currently depends on
the UIDs and GIDs methods so we cannot get rid of them yet.
Signed-off-by: Cory Snider <csnider@mirantis.com>
463 lines
12 KiB
Go
463 lines
12 KiB
Go
package graphdriver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/daemon/graphdriver"
|
|
"github.com/docker/docker/daemon/graphdriver/vfs"
|
|
"github.com/docker/docker/integration/internal/container"
|
|
"github.com/docker/docker/integration/internal/requirement"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/docker/docker/pkg/plugins"
|
|
"github.com/docker/docker/testutil/daemon"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
"gotest.tools/v3/skip"
|
|
)
|
|
|
|
type graphEventsCounter struct {
|
|
activations int
|
|
creations int
|
|
removals int
|
|
gets int
|
|
puts int
|
|
stats int
|
|
cleanups int
|
|
exists int
|
|
init int
|
|
metadata int
|
|
diff int
|
|
applydiff int
|
|
changes int
|
|
diffsize int
|
|
}
|
|
|
|
func TestExternalGraphDriver(t *testing.T) {
|
|
skip.If(t, runtime.GOOS == "windows")
|
|
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
|
|
skip.If(t, !requirement.HasHubConnectivity(t))
|
|
skip.If(t, testEnv.IsRootless, "rootless mode doesn't support external graph driver")
|
|
|
|
// Setup plugin(s)
|
|
ec := make(map[string]*graphEventsCounter)
|
|
sserver := setupPluginViaSpecFile(t, ec)
|
|
jserver := setupPluginViaJSONFile(t, ec)
|
|
// Create daemon
|
|
d := daemon.New(t, daemon.WithExperimental())
|
|
c := d.NewClientT(t)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
test func(client.APIClient, *daemon.Daemon) func(*testing.T)
|
|
}{
|
|
{
|
|
name: "json",
|
|
test: testExternalGraphDriver("json", ec),
|
|
},
|
|
{
|
|
name: "spec",
|
|
test: testExternalGraphDriver("spec", ec),
|
|
},
|
|
{
|
|
name: "pull",
|
|
test: testGraphDriverPull,
|
|
},
|
|
} {
|
|
t.Run(tc.name, tc.test(c, d))
|
|
}
|
|
|
|
sserver.Close()
|
|
jserver.Close()
|
|
err := os.RemoveAll("/etc/docker/plugins")
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
|
|
mux := http.NewServeMux()
|
|
server := httptest.NewServer(mux)
|
|
|
|
setupPlugin(t, ec, "spec", mux, []byte(server.URL))
|
|
|
|
return server
|
|
}
|
|
|
|
func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
|
|
mux := http.NewServeMux()
|
|
server := httptest.NewServer(mux)
|
|
|
|
p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL)
|
|
b, err := json.Marshal(p)
|
|
assert.NilError(t, err)
|
|
|
|
setupPlugin(t, ec, "json", mux, b)
|
|
|
|
return server
|
|
}
|
|
|
|
func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) {
|
|
name := fmt.Sprintf("%s-external-graph-driver", ext)
|
|
type graphDriverRequest struct {
|
|
ID string `json:",omitempty"`
|
|
Parent string `json:",omitempty"`
|
|
MountLabel string `json:",omitempty"`
|
|
ReadOnly bool `json:",omitempty"`
|
|
}
|
|
|
|
type graphDriverResponse struct {
|
|
Err error `json:",omitempty"`
|
|
Dir string `json:",omitempty"`
|
|
Exists bool `json:",omitempty"`
|
|
Status [][2]string `json:",omitempty"`
|
|
Metadata map[string]string `json:",omitempty"`
|
|
Changes []archive.Change `json:",omitempty"`
|
|
Size int64 `json:",omitempty"`
|
|
}
|
|
|
|
respond := func(w http.ResponseWriter, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
|
switch t := data.(type) {
|
|
case error:
|
|
fmt.Fprintf(w, "{\"Err\": %q}\n", t.Error())
|
|
case string:
|
|
fmt.Fprintln(w, t)
|
|
default:
|
|
json.NewEncoder(w).Encode(&data)
|
|
}
|
|
}
|
|
|
|
decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error {
|
|
defer b.Close()
|
|
if err := json.NewDecoder(b).Decode(&out); err != nil {
|
|
http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
base, err := os.MkdirTemp("", name)
|
|
assert.NilError(t, err)
|
|
vfsProto, err := vfs.Init(base, []string{}, idtools.IdentityMapping{})
|
|
assert.NilError(t, err, "error initializing graph driver")
|
|
driver := graphdriver.NewNaiveDiffDriver(vfsProto, idtools.IdentityMapping{})
|
|
|
|
ec[ext] = &graphEventsCounter{}
|
|
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].activations++
|
|
respond(w, `{"Implements": ["GraphDriver"]}`)
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].init++
|
|
respond(w, "{}")
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].creations++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, "{}")
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].creations++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
if err := driver.Create(req.ID, req.Parent, nil); err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, "{}")
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].removals++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
if err := driver.Remove(req.ID); err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, "{}")
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].gets++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
// TODO @gupta-ak: Figure out what to do here.
|
|
dir, err := driver.Get(req.ID, req.MountLabel)
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, &graphDriverResponse{Dir: dir.Path()})
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].puts++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
if err := driver.Put(req.ID); err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, "{}")
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].exists++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)})
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].stats++
|
|
respond(w, &graphDriverResponse{Status: driver.Status()})
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].cleanups++
|
|
err := driver.Cleanup()
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, `{}`)
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].metadata++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
data, err := driver.GetMetadata(req.ID)
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, &graphDriverResponse{Metadata: data})
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].diff++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
diff, err := driver.Diff(req.ID, req.Parent)
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
io.Copy(w, diff)
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].changes++
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
changes, err := driver.Changes(req.ID, req.Parent)
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, &graphDriverResponse{Changes: changes})
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].applydiff++
|
|
diff := r.Body
|
|
defer r.Body.Close()
|
|
|
|
id := r.URL.Query().Get("id")
|
|
parent := r.URL.Query().Get("parent")
|
|
|
|
if id == "" {
|
|
http.Error(w, "missing id", 409)
|
|
}
|
|
|
|
size, err := driver.ApplyDiff(id, parent, diff)
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, &graphDriverResponse{Size: size})
|
|
})
|
|
|
|
mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) {
|
|
ec[ext].diffsize++
|
|
|
|
var req graphDriverRequest
|
|
if err := decReq(r.Body, &req, w); err != nil {
|
|
return
|
|
}
|
|
|
|
size, err := driver.DiffSize(req.ID, req.Parent)
|
|
if err != nil {
|
|
respond(w, err)
|
|
return
|
|
}
|
|
respond(w, &graphDriverResponse{Size: size})
|
|
})
|
|
|
|
err = os.MkdirAll("/etc/docker/plugins", 0755)
|
|
assert.NilError(t, err)
|
|
|
|
specFile := "/etc/docker/plugins/" + name + "." + ext
|
|
err = os.WriteFile(specFile, b, 0644)
|
|
assert.NilError(t, err)
|
|
}
|
|
|
|
func testExternalGraphDriver(ext string, ec map[string]*graphEventsCounter) func(client.APIClient, *daemon.Daemon) func(*testing.T) {
|
|
return func(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
|
|
return func(t *testing.T) {
|
|
driverName := fmt.Sprintf("%s-external-graph-driver", ext)
|
|
d.StartWithBusybox(t, "-s", driverName)
|
|
|
|
ctx := context.Background()
|
|
|
|
testGraphDriver(ctx, t, c, driverName, func(t *testing.T) {
|
|
d.Restart(t, "-s", driverName)
|
|
})
|
|
|
|
_, err := c.Info(ctx)
|
|
assert.NilError(t, err)
|
|
|
|
d.Stop(t)
|
|
|
|
// Don't check ec.exists, because the daemon no longer calls the
|
|
// Exists function.
|
|
assert.Check(t, is.Equal(ec[ext].activations, 2))
|
|
assert.Check(t, is.Equal(ec[ext].init, 2))
|
|
assert.Check(t, ec[ext].creations >= 1)
|
|
assert.Check(t, ec[ext].removals >= 1)
|
|
assert.Check(t, ec[ext].gets >= 1)
|
|
assert.Check(t, ec[ext].puts >= 1)
|
|
assert.Check(t, is.Equal(ec[ext].stats, 5))
|
|
assert.Check(t, is.Equal(ec[ext].cleanups, 2))
|
|
assert.Check(t, ec[ext].applydiff >= 1)
|
|
assert.Check(t, is.Equal(ec[ext].changes, 1))
|
|
assert.Check(t, is.Equal(ec[ext].diffsize, 0))
|
|
assert.Check(t, is.Equal(ec[ext].diff, 0))
|
|
assert.Check(t, is.Equal(ec[ext].metadata, 1))
|
|
}
|
|
}
|
|
}
|
|
|
|
func testGraphDriverPull(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
|
|
return func(t *testing.T) {
|
|
d.Start(t)
|
|
defer d.Stop(t)
|
|
ctx := context.Background()
|
|
|
|
r, err := c.ImagePull(ctx, "busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209", types.ImagePullOptions{})
|
|
assert.NilError(t, err)
|
|
_, err = io.Copy(io.Discard, r)
|
|
assert.NilError(t, err)
|
|
|
|
container.Run(ctx, t, c, container.WithImage("busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209"))
|
|
}
|
|
}
|
|
|
|
func TestGraphdriverPluginV2(t *testing.T) {
|
|
skip.If(t, runtime.GOOS == "windows")
|
|
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
|
|
skip.If(t, !requirement.HasHubConnectivity(t))
|
|
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
|
|
skip.If(t, !requirement.Overlay2Supported(testEnv.DaemonInfo.KernelVersion))
|
|
|
|
d := daemon.New(t, daemon.WithExperimental())
|
|
d.Start(t)
|
|
defer d.Stop(t)
|
|
|
|
client := d.NewClientT(t)
|
|
defer client.Close()
|
|
ctx := context.Background()
|
|
|
|
// install the plugin
|
|
plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
|
|
responseReader, err := client.PluginInstall(ctx, plugin, types.PluginInstallOptions{
|
|
RemoteRef: plugin,
|
|
AcceptAllPermissions: true,
|
|
})
|
|
assert.NilError(t, err)
|
|
defer responseReader.Close()
|
|
// ensure it's done by waiting for EOF on the response
|
|
_, err = io.Copy(io.Discard, responseReader)
|
|
assert.NilError(t, err)
|
|
|
|
// restart the daemon with the plugin set as the storage driver
|
|
d.Stop(t)
|
|
d.StartWithBusybox(t, "-s", plugin, "--storage-opt", "overlay2.override_kernel_check=1")
|
|
|
|
testGraphDriver(ctx, t, client, plugin, nil)
|
|
}
|
|
|
|
func testGraphDriver(ctx context.Context, t *testing.T, c client.APIClient, driverName string, afterContainerRunFn func(*testing.T)) {
|
|
id := container.Run(ctx, t, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
|
|
|
|
if afterContainerRunFn != nil {
|
|
afterContainerRunFn(t)
|
|
}
|
|
|
|
i, err := c.ContainerInspect(ctx, id)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
|
|
|
|
diffs, err := c.ContainerDiff(ctx, id)
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Contains(diffs, containertypes.ContainerChangeResponseItem{
|
|
Kind: archive.ChangeAdd,
|
|
Path: "/hello",
|
|
}), "diffs: %v", diffs)
|
|
|
|
err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
|
|
Force: true,
|
|
})
|
|
assert.NilError(t, err)
|
|
}
|