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 <cpuguy83@gmail.com>
This commit is contained in:
parent
0f9f99500c
commit
2600777469
3 changed files with 171 additions and 0 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
101
daemon/links_test.go
Normal file
101
daemon/links_test.go
Normal file
|
@ -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])
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue