From 2600777469b18f7133fc4d6c6c99698d6aa700fe Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Sat, 21 Nov 2015 13:45:34 -0500 Subject: [PATCH] Add migration from sqlite links back to hostConfig Before #16032, once links were setup in the sqlite db, hostConfig.Links was cleared out. This means that we need to migrate data back out of the sqlite db and put it back into hostConfig.Links so that links specified on older daemons can be used. Signed-off-by: Brian Goff --- daemon/daemon.go | 29 +++++++++++++ daemon/links.go | 41 ++++++++++++++++++ daemon/links_test.go | 101 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 daemon/links_test.go diff --git a/daemon/daemon.go b/daemon/daemon.go index f6a9d3871..cd4dffe53 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -48,6 +48,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/discovery" "github.com/docker/docker/pkg/fileutils" + "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/namesgenerator" @@ -331,6 +332,7 @@ func (daemon *Daemon) restore() error { } } + var migrateLegacyLinks bool restartContainers := make(map[*container.Container]chan struct{}) for _, c := range containers { if err := daemon.registerName(c); err != nil { @@ -346,10 +348,31 @@ func (daemon *Daemon) restore() error { if daemon.configStore.AutoRestart && c.ShouldRestart() { restartContainers[c] = make(chan struct{}) } + + // if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated + if c.HostConfig != nil && c.HostConfig.Links == nil { + migrateLegacyLinks = true + } + } + + // migrate any legacy links from sqlite + linkdbFile := filepath.Join(daemon.root, "linkgraph.db") + var legacyLinkDB *graphdb.Database + if migrateLegacyLinks { + legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile) + if err != nil { + return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err) + } + defer legacyLinkDB.Close() } // Now that all the containers are registered, register the links for _, c := range containers { + if migrateLegacyLinks { + if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil { + return err + } + } if err := daemon.registerLinks(c, c.HostConfig); err != nil { logrus.Errorf("failed to register link for container %s: %v", c.ID, err) } @@ -1359,6 +1382,12 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig * return err } + // make sure links is not nil + // this ensures that on the next daemon restart we don't try to migrate from legacy sqlite links + if hostConfig.Links == nil { + hostConfig.Links = []string{} + } + container.HostConfig = hostConfig return container.ToDisk() } diff --git a/daemon/links.go b/daemon/links.go index 7f691d4f1..aaf1917d7 100644 --- a/daemon/links.go +++ b/daemon/links.go @@ -1,9 +1,12 @@ package daemon import ( + "strings" "sync" + "github.com/Sirupsen/logrus" "github.com/docker/docker/container" + "github.com/docker/docker/pkg/graphdb" ) // linkIndex stores link relationships between containers, including their specified alias @@ -85,3 +88,41 @@ func (l *linkIndex) delete(container *container.Container) { delete(l.childIdx, container) l.mu.Unlock() } + +// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig +// when sqlite links were used, hostConfig.Links was set to nil +func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error { + // if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped + if container.HostConfig == nil || container.HostConfig.Links != nil { + return nil + } + + logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID) + + fullName := container.Name + if fullName[0] != '/' { + fullName = "/" + fullName + } + + // don't use a nil slice, this ensures that the check above will skip once the migration has completed + links := []string{} + children, err := db.Children(fullName, 0) + if err != nil { + if !strings.Contains(err.Error(), "Cannot find child for") { + return err + } + // else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration + } + + for _, child := range children { + c, err := daemon.GetContainer(child.Entity.ID()) + if err != nil { + return err + } + + links = append(links, c.Name+":"+child.Edge.Name) + } + + container.HostConfig.Links = links + return container.WriteHostConfig() +} diff --git a/daemon/links_test.go b/daemon/links_test.go new file mode 100644 index 000000000..79a641563 --- /dev/null +++ b/daemon/links_test.go @@ -0,0 +1,101 @@ +package daemon + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + "path/filepath" + "testing" + + "github.com/docker/docker/container" + "github.com/docker/docker/pkg/graphdb" + "github.com/docker/docker/pkg/stringid" + containertypes "github.com/docker/engine-api/types/container" +) + +func TestMigrateLegacySqliteLinks(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + name1 := "test1" + c1 := &container.Container{ + CommonContainer: container.CommonContainer{ + ID: stringid.GenerateNonCryptoID(), + Name: name1, + HostConfig: &containertypes.HostConfig{}, + }, + } + c1.Root = tmpDir + + name2 := "test2" + c2 := &container.Container{ + CommonContainer: container.CommonContainer{ + ID: stringid.GenerateNonCryptoID(), + Name: name2, + }, + } + + store := &contStore{ + s: map[string]*container.Container{ + c1.ID: c1, + c2.ID: c2, + }, + } + + d := &Daemon{root: tmpDir, containers: store} + db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db")) + if err != nil { + t.Fatal(err) + } + + if _, err := db.Set("/"+name1, c1.ID); err != nil { + t.Fatal(err) + } + + if _, err := db.Set("/"+name2, c2.ID); err != nil { + t.Fatal(err) + } + + alias := "hello" + if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil { + t.Fatal(err) + } + + if err := d.migrateLegacySqliteLinks(db, c1); err != nil { + t.Fatal(err) + } + + if len(c1.HostConfig.Links) != 1 { + t.Fatal("expected links to be populated but is empty") + } + + expected := name2 + ":" + alias + actual := c1.HostConfig.Links[0] + if actual != expected { + t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual) + } + + // ensure this is persisted + b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json")) + if err != nil { + t.Fatal(err) + } + type hc struct { + Links []string + } + var cfg hc + if err := json.Unmarshal(b, &cfg); err != nil { + t.Fatal(err) + } + + if len(cfg.Links) != 1 { + t.Fatalf("expected one entry in links, got: %d", len(cfg.Links)) + } + if cfg.Links[0] != expected { // same expected as above + t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0]) + } +}