|
@@ -6,6 +6,10 @@ import (
|
|
"io/ioutil"
|
|
"io/ioutil"
|
|
"os"
|
|
"os"
|
|
"path/filepath"
|
|
"path/filepath"
|
|
|
|
+ "runtime"
|
|
|
|
+ "strconv"
|
|
|
|
+ "sync"
|
|
|
|
+ "time"
|
|
|
|
|
|
"encoding/json"
|
|
"encoding/json"
|
|
|
|
|
|
@@ -19,7 +23,7 @@ import (
|
|
)
|
|
)
|
|
|
|
|
|
type graphIDRegistrar interface {
|
|
type graphIDRegistrar interface {
|
|
- RegisterByGraphID(string, layer.ChainID, string) (layer.Layer, error)
|
|
|
|
|
|
+ RegisterByGraphID(string, layer.ChainID, layer.DiffID, string, int64) (layer.Layer, error)
|
|
Release(layer.Layer) ([]layer.Metadata, error)
|
|
Release(layer.Layer) ([]layer.Metadata, error)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -27,11 +31,18 @@ type graphIDMounter interface {
|
|
CreateRWLayerByGraphID(string, string, layer.ChainID) error
|
|
CreateRWLayerByGraphID(string, string, layer.ChainID) error
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+type checksumCalculator interface {
|
|
|
|
+ ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID layer.DiffID, size int64, err error)
|
|
|
|
+}
|
|
|
|
+
|
|
const (
|
|
const (
|
|
graphDirName = "graph"
|
|
graphDirName = "graph"
|
|
tarDataFileName = "tar-data.json.gz"
|
|
tarDataFileName = "tar-data.json.gz"
|
|
migrationFileName = ".migration-v1-images.json"
|
|
migrationFileName = ".migration-v1-images.json"
|
|
migrationTagsFileName = ".migration-v1-tags"
|
|
migrationTagsFileName = ".migration-v1-tags"
|
|
|
|
+ migrationDiffIDFileName = ".migration-diffid"
|
|
|
|
+ migrationSizeFileName = ".migration-size"
|
|
|
|
+ migrationTarDataFileName = ".migration-tardata"
|
|
containersDirName = "containers"
|
|
containersDirName = "containers"
|
|
configFileNameLegacy = "config.json"
|
|
configFileNameLegacy = "config.json"
|
|
configFileName = "config.v2.json"
|
|
configFileName = "config.v2.json"
|
|
@@ -45,7 +56,19 @@ var (
|
|
// Migrate takes an old graph directory and transforms the metadata into the
|
|
// Migrate takes an old graph directory and transforms the metadata into the
|
|
// new format.
|
|
// new format.
|
|
func Migrate(root, driverName string, ls layer.Store, is image.Store, rs reference.Store, ms metadata.Store) error {
|
|
func Migrate(root, driverName string, ls layer.Store, is image.Store, rs reference.Store, ms metadata.Store) error {
|
|
- mappings := make(map[string]image.ID)
|
|
|
|
|
|
+ graphDir := filepath.Join(root, graphDirName)
|
|
|
|
+ if _, err := os.Lstat(graphDir); os.IsNotExist(err) {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mappings, err := restoreMappings(root)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if cc, ok := ls.(checksumCalculator); ok {
|
|
|
|
+ CalculateLayerChecksums(root, cc, mappings)
|
|
|
|
+ }
|
|
|
|
|
|
if registrar, ok := ls.(graphIDRegistrar); !ok {
|
|
if registrar, ok := ls.(graphIDRegistrar); !ok {
|
|
return errUnsupported
|
|
return errUnsupported
|
|
@@ -53,6 +76,11 @@ func Migrate(root, driverName string, ls layer.Store, is image.Store, rs referen
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ err = saveMappings(root, mappings)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
if mounter, ok := ls.(graphIDMounter); !ok {
|
|
if mounter, ok := ls.(graphIDMounter); !ok {
|
|
return errUnsupported
|
|
return errUnsupported
|
|
} else if err := migrateContainers(root, mounter, is, mappings); err != nil {
|
|
} else if err := migrateContainers(root, mounter, is, mappings); err != nil {
|
|
@@ -66,28 +94,115 @@ func Migrate(root, driverName string, ls layer.Store, is image.Store, rs referen
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error {
|
|
|
|
|
|
+// CalculateLayerChecksums walks an old graph directory and calculates checksums
|
|
|
|
+// for each layer. These checksums are later used for migration.
|
|
|
|
+func CalculateLayerChecksums(root string, ls checksumCalculator, mappings map[string]image.ID) {
|
|
graphDir := filepath.Join(root, graphDirName)
|
|
graphDir := filepath.Join(root, graphDirName)
|
|
- if _, err := os.Lstat(graphDir); err != nil {
|
|
|
|
- if os.IsNotExist(err) {
|
|
|
|
- return nil
|
|
|
|
|
|
+ // spawn some extra workers also for maximum performance because the process is bounded by both cpu and io
|
|
|
|
+ workers := runtime.NumCPU() * 3
|
|
|
|
+ workQueue := make(chan string, workers)
|
|
|
|
+
|
|
|
|
+ wg := sync.WaitGroup{}
|
|
|
|
+
|
|
|
|
+ for i := 0; i < workers; i++ {
|
|
|
|
+ wg.Add(1)
|
|
|
|
+ go func() {
|
|
|
|
+ for id := range workQueue {
|
|
|
|
+ start := time.Now()
|
|
|
|
+ if err := calculateLayerChecksum(graphDir, id, ls); err != nil {
|
|
|
|
+ logrus.Errorf("could not calculate checksum for %q, %q", id, err)
|
|
|
|
+ }
|
|
|
|
+ elapsed := time.Since(start)
|
|
|
|
+ logrus.Debugf("layer %s took %.2f seconds", id, elapsed.Seconds())
|
|
|
|
+ }
|
|
|
|
+ wg.Done()
|
|
|
|
+ }()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dir, err := ioutil.ReadDir(graphDir)
|
|
|
|
+ if err != nil {
|
|
|
|
+ logrus.Errorf("could not read directory %q", graphDir)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ for _, v := range dir {
|
|
|
|
+ v1ID := v.Name()
|
|
|
|
+ if err := imagev1.ValidateID(v1ID); err != nil {
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
|
|
+ if _, ok := mappings[v1ID]; ok { // support old migrations without helper files
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ workQueue <- v1ID
|
|
|
|
+ }
|
|
|
|
+ close(workQueue)
|
|
|
|
+ wg.Wait()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func calculateLayerChecksum(graphDir, id string, ls checksumCalculator) error {
|
|
|
|
+ diffIDFile := filepath.Join(graphDir, id, migrationDiffIDFileName)
|
|
|
|
+ if _, err := os.Lstat(diffIDFile); err == nil {
|
|
|
|
+ return nil
|
|
|
|
+ } else if !os.IsNotExist(err) {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ parent, err := getParent(filepath.Join(graphDir, id))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ diffID, size, err := ls.ChecksumForGraphID(id, parent, filepath.Join(graphDir, id, tarDataFileName), filepath.Join(graphDir, id, migrationTarDataFileName))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationSizeFileName), []byte(strconv.Itoa(int(size))), 0600); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationDiffIDFileName), []byte(diffID), 0600); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ logrus.Infof("calculated checksum for layer %s: %s", id, diffID)
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func restoreMappings(root string) (map[string]image.ID, error) {
|
|
|
|
+ mappings := make(map[string]image.ID)
|
|
|
|
+
|
|
mfile := filepath.Join(root, migrationFileName)
|
|
mfile := filepath.Join(root, migrationFileName)
|
|
f, err := os.Open(mfile)
|
|
f, err := os.Open(mfile)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
if err != nil && !os.IsNotExist(err) {
|
|
- return err
|
|
|
|
|
|
+ return nil, err
|
|
} else if err == nil {
|
|
} else if err == nil {
|
|
err := json.NewDecoder(f).Decode(&mappings)
|
|
err := json.NewDecoder(f).Decode(&mappings)
|
|
if err != nil {
|
|
if err != nil {
|
|
f.Close()
|
|
f.Close()
|
|
- return err
|
|
|
|
|
|
+ return nil, err
|
|
}
|
|
}
|
|
f.Close()
|
|
f.Close()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ return mappings, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func saveMappings(root string, mappings map[string]image.ID) error {
|
|
|
|
+ mfile := filepath.Join(root, migrationFileName)
|
|
|
|
+ f, err := os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ defer f.Close()
|
|
|
|
+ if err := json.NewEncoder(f).Encode(mappings); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error {
|
|
|
|
+ graphDir := filepath.Join(root, graphDirName)
|
|
|
|
+
|
|
dir, err := ioutil.ReadDir(graphDir)
|
|
dir, err := ioutil.ReadDir(graphDir)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
@@ -105,15 +220,6 @@ func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- f, err = os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
|
|
- if err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
- defer f.Close()
|
|
|
|
- if err := json.NewEncoder(f).Encode(mappings); err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -251,43 +357,51 @@ func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) (err error) {
|
|
|
|
- defer func() {
|
|
|
|
- if err != nil {
|
|
|
|
- logrus.Errorf("migration failed for %v, err: %v", id, err)
|
|
|
|
- }
|
|
|
|
- }()
|
|
|
|
-
|
|
|
|
- jsonFile := filepath.Join(root, graphDirName, id, "json")
|
|
|
|
|
|
+func getParent(confDir string) (string, error) {
|
|
|
|
+ jsonFile := filepath.Join(confDir, "json")
|
|
imageJSON, err := ioutil.ReadFile(jsonFile)
|
|
imageJSON, err := ioutil.ReadFile(jsonFile)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return err
|
|
|
|
|
|
+ return "", err
|
|
}
|
|
}
|
|
var parent struct {
|
|
var parent struct {
|
|
Parent string
|
|
Parent string
|
|
ParentID digest.Digest `json:"parent_id"`
|
|
ParentID digest.Digest `json:"parent_id"`
|
|
}
|
|
}
|
|
if err := json.Unmarshal(imageJSON, &parent); err != nil {
|
|
if err := json.Unmarshal(imageJSON, &parent); err != nil {
|
|
- return err
|
|
|
|
|
|
+ return "", err
|
|
}
|
|
}
|
|
if parent.Parent == "" && parent.ParentID != "" { // v1.9
|
|
if parent.Parent == "" && parent.ParentID != "" { // v1.9
|
|
parent.Parent = parent.ParentID.Hex()
|
|
parent.Parent = parent.ParentID.Hex()
|
|
}
|
|
}
|
|
// compatibilityID for parent
|
|
// compatibilityID for parent
|
|
- parentCompatibilityID, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, "parent"))
|
|
|
|
|
|
+ parentCompatibilityID, err := ioutil.ReadFile(filepath.Join(confDir, "parent"))
|
|
if err == nil && len(parentCompatibilityID) > 0 {
|
|
if err == nil && len(parentCompatibilityID) > 0 {
|
|
parent.Parent = string(parentCompatibilityID)
|
|
parent.Parent = string(parentCompatibilityID)
|
|
}
|
|
}
|
|
|
|
+ return parent.Parent, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) (err error) {
|
|
|
|
+ defer func() {
|
|
|
|
+ if err != nil {
|
|
|
|
+ logrus.Errorf("migration failed for %v, err: %v", id, err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ parent, err := getParent(filepath.Join(root, graphDirName, id))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
|
|
var parentID image.ID
|
|
var parentID image.ID
|
|
- if parent.Parent != "" {
|
|
|
|
|
|
+ if parent != "" {
|
|
var exists bool
|
|
var exists bool
|
|
- if parentID, exists = mappings[parent.Parent]; !exists {
|
|
|
|
- if err := migrateImage(parent.Parent, root, ls, is, ms, mappings); err != nil {
|
|
|
|
|
|
+ if parentID, exists = mappings[parent]; !exists {
|
|
|
|
+ if err := migrateImage(parent, root, ls, is, ms, mappings); err != nil {
|
|
// todo: fail or allow broken chains?
|
|
// todo: fail or allow broken chains?
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- parentID = mappings[parent.Parent]
|
|
|
|
|
|
+ parentID = mappings[parent]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -304,12 +418,32 @@ func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metad
|
|
history = parentImg.History
|
|
history = parentImg.History
|
|
}
|
|
}
|
|
|
|
|
|
- layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), filepath.Join(filepath.Join(root, graphDirName, id, tarDataFileName)))
|
|
|
|
|
|
+ diffID, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ sizeStr, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationSizeFileName))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ size, err := strconv.ParseInt(string(sizeStr), 10, 64)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), layer.DiffID(diffID), filepath.Join(root, graphDirName, id, migrationTarDataFileName), size)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
logrus.Infof("migrated layer %s to %s", id, layer.DiffID())
|
|
logrus.Infof("migrated layer %s to %s", id, layer.DiffID())
|
|
|
|
|
|
|
|
+ jsonFile := filepath.Join(root, graphDirName, id, "json")
|
|
|
|
+ imageJSON, err := ioutil.ReadFile(jsonFile)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
h, err := imagev1.HistoryFromConfig(imageJSON, false)
|
|
h, err := imagev1.HistoryFromConfig(imageJSON, false)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|