Initial push
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/arescom/docker-migrate/internal/spec"
|
||||
)
|
||||
|
||||
func sample() *spec.Container {
|
||||
return &spec.Container{
|
||||
ID: "abc123", Name: "app", State: "running", Image: "app:1.0",
|
||||
Mounts: []spec.Mount{
|
||||
{Kind: spec.MountVolume, Name: "appdata", Destination: "/data", SizeBytes: 4096},
|
||||
{Kind: spec.MountBind, Source: "/srv/app/conf", Destination: "/etc/app", ReadOnly: true},
|
||||
{Kind: spec.MountAnonymous, Name: strings.Repeat("f", 64), Destination: "/tmp/cache"},
|
||||
{Kind: spec.MountTmpfs, Destination: "/run"},
|
||||
},
|
||||
Endpoints: []spec.Endpoint{{Network: "appnet"}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareDefaultsCopyEverything(t *testing.T) {
|
||||
c := sample()
|
||||
sel := spec.DefaultSelection(c)
|
||||
sel.Include = true
|
||||
|
||||
p, err := Prepare(c, sel,
|
||||
[]spec.Volume{{Name: "appdata", Driver: "local"}},
|
||||
[]spec.Network{{Name: "appnet", Driver: "bridge"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// tmpfs carries no data, so exactly the three real locations transfer.
|
||||
if len(p.Transfers) != 3 {
|
||||
t.Fatalf("expected 3 transfers, got %d: %+v", len(p.Transfers), p.Transfers)
|
||||
}
|
||||
if len(p.Volumes) != 1 || p.Volumes[0].Name != "appdata" {
|
||||
t.Errorf("named volume not scheduled for creation: %+v", p.Volumes)
|
||||
}
|
||||
if len(p.Networks) != 1 {
|
||||
t.Errorf("network not scheduled for creation: %+v", p.Networks)
|
||||
}
|
||||
|
||||
byDest := map[string]Transfer{}
|
||||
for _, tr := range p.Transfers {
|
||||
byDest[tr.Destination] = tr
|
||||
}
|
||||
if got := byDest["/data"].RestoreInto; got != "/" {
|
||||
t.Errorf("/data must be restored into /, got %q", got)
|
||||
}
|
||||
if got := byDest["/etc/app"].RestoreInto; got != "/etc" {
|
||||
t.Errorf("/etc/app must be restored into /etc, got %q", got)
|
||||
}
|
||||
if !byDest["/etc/app"].ReadOnly {
|
||||
t.Error("read-only bind must be flagged so it is seeded through a staging container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareSkipAndRelocate(t *testing.T) {
|
||||
c := sample()
|
||||
sel := spec.DefaultSelection(c)
|
||||
sel.Include = true
|
||||
sel.Mounts["/tmp/cache"] = spec.MountSelection{Action: spec.MountActionSkip}
|
||||
sel.Mounts["/etc/app"] = spec.MountSelection{Action: spec.MountActionCopy, TargetSource: "/opt/app/conf"}
|
||||
sel.Mounts["/data"] = spec.MountSelection{Action: spec.MountActionStructure, TargetName: "appdata2"}
|
||||
|
||||
p, err := Prepare(c, sel, []spec.Volume{{Name: "appdata", Driver: "local"}}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !p.Render.DropMounts["/tmp/cache"] {
|
||||
t.Error("skipped mount must be dropped from the create command")
|
||||
}
|
||||
// structure-only means the volume is created but no data is copied.
|
||||
for _, tr := range p.Transfers {
|
||||
if tr.Destination == "/data" {
|
||||
t.Error("a structure-only mount must not be transferred")
|
||||
}
|
||||
}
|
||||
if len(p.Volumes) != 1 || p.Volumes[0].Name != "appdata2" {
|
||||
t.Errorf("renamed volume not applied: %+v", p.Volumes)
|
||||
}
|
||||
|
||||
args := strings.Join(p.Target.CreateArgs(p.Render), " ")
|
||||
if !strings.Contains(args, "--volume /opt/app/conf:/etc/app:ro") {
|
||||
t.Errorf("relocated bind not applied: %s", args)
|
||||
}
|
||||
if !strings.Contains(args, "--volume appdata2:/data") {
|
||||
t.Errorf("renamed volume not applied to create args: %s", args)
|
||||
}
|
||||
if strings.Contains(args, "/tmp/cache") {
|
||||
t.Errorf("skipped mount still present: %s", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareAnonymousVolumeIsRecreatedFresh(t *testing.T) {
|
||||
c := sample()
|
||||
sel := spec.DefaultSelection(c)
|
||||
sel.Include = true
|
||||
|
||||
p, err := Prepare(c, sel, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
args := strings.Join(p.Target.CreateArgs(p.Render), " ")
|
||||
if strings.Contains(args, strings.Repeat("f", 64)) {
|
||||
t.Errorf("the generated volume name must not be pinned on the target: %s", args)
|
||||
}
|
||||
if !strings.Contains(args, "--volume /tmp/cache") {
|
||||
t.Errorf("anonymous volume must still be declared: %s", args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagingPathsLandArchiveOnTheMountPoint(t *testing.T) {
|
||||
// A tar produced from /var/lib/postgresql/data has entries rooted at
|
||||
// "data/", so the staging container must mount the volume at
|
||||
// <root>/data and extract into <root>.
|
||||
mountAt, into := StagingPaths("/var/lib/postgresql/data")
|
||||
if mountAt != into+"/data" {
|
||||
t.Fatalf("mount point %q is not directly under the extraction dir %q", mountAt, into)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareRefusesRootMount(t *testing.T) {
|
||||
c := &spec.Container{
|
||||
ID: "x", Name: "weird", Image: "img",
|
||||
Mounts: []spec.Mount{{Kind: spec.MountBind, Source: "/", Destination: "/"}},
|
||||
}
|
||||
sel := spec.DefaultSelection(c)
|
||||
sel.Include = true
|
||||
p, err := Prepare(c, sel, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(p.Transfers) != 0 {
|
||||
t.Errorf("a mount at / must not be copied: %+v", p.Transfers)
|
||||
}
|
||||
if len(p.Notes) == 0 {
|
||||
t.Error("refusing to copy / should be reported to the operator")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user