migratev1.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. package v1
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. "runtime"
  9. "strconv"
  10. "sync"
  11. "time"
  12. "encoding/json"
  13. "github.com/Sirupsen/logrus"
  14. "github.com/docker/distribution/digest"
  15. "github.com/docker/docker/distribution/metadata"
  16. "github.com/docker/docker/image"
  17. imagev1 "github.com/docker/docker/image/v1"
  18. "github.com/docker/docker/layer"
  19. "github.com/docker/docker/pkg/ioutils"
  20. "github.com/docker/docker/reference"
  21. )
  22. type graphIDRegistrar interface {
  23. RegisterByGraphID(string, layer.ChainID, layer.DiffID, string, int64) (layer.Layer, error)
  24. Release(layer.Layer) ([]layer.Metadata, error)
  25. }
  26. type graphIDMounter interface {
  27. CreateRWLayerByGraphID(string, string, layer.ChainID) error
  28. }
  29. type checksumCalculator interface {
  30. ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID layer.DiffID, size int64, err error)
  31. }
  32. const (
  33. graphDirName = "graph"
  34. tarDataFileName = "tar-data.json.gz"
  35. migrationFileName = ".migration-v1-images.json"
  36. migrationTagsFileName = ".migration-v1-tags"
  37. migrationDiffIDFileName = ".migration-diffid"
  38. migrationSizeFileName = ".migration-size"
  39. migrationTarDataFileName = ".migration-tardata"
  40. containersDirName = "containers"
  41. configFileNameLegacy = "config.json"
  42. configFileName = "config.v2.json"
  43. repositoriesFilePrefixLegacy = "repositories-"
  44. )
  45. var (
  46. errUnsupported = errors.New("migration is not supported")
  47. )
  48. // Migrate takes an old graph directory and transforms the metadata into the
  49. // new format.
  50. func Migrate(root, driverName string, ls layer.Store, is image.Store, rs reference.Store, ms metadata.Store) error {
  51. graphDir := filepath.Join(root, graphDirName)
  52. if _, err := os.Lstat(graphDir); os.IsNotExist(err) {
  53. return nil
  54. }
  55. mappings, err := restoreMappings(root)
  56. if err != nil {
  57. return err
  58. }
  59. if cc, ok := ls.(checksumCalculator); ok {
  60. CalculateLayerChecksums(root, cc, mappings)
  61. }
  62. if registrar, ok := ls.(graphIDRegistrar); !ok {
  63. return errUnsupported
  64. } else if err := migrateImages(root, registrar, is, ms, mappings); err != nil {
  65. return err
  66. }
  67. err = saveMappings(root, mappings)
  68. if err != nil {
  69. return err
  70. }
  71. if mounter, ok := ls.(graphIDMounter); !ok {
  72. return errUnsupported
  73. } else if err := migrateContainers(root, mounter, is, mappings); err != nil {
  74. return err
  75. }
  76. if err := migrateRefs(root, driverName, rs, mappings); err != nil {
  77. return err
  78. }
  79. return nil
  80. }
  81. // CalculateLayerChecksums walks an old graph directory and calculates checksums
  82. // for each layer. These checksums are later used for migration.
  83. func CalculateLayerChecksums(root string, ls checksumCalculator, mappings map[string]image.ID) {
  84. graphDir := filepath.Join(root, graphDirName)
  85. // spawn some extra workers also for maximum performance because the process is bounded by both cpu and io
  86. workers := runtime.NumCPU() * 3
  87. workQueue := make(chan string, workers)
  88. wg := sync.WaitGroup{}
  89. for i := 0; i < workers; i++ {
  90. wg.Add(1)
  91. go func() {
  92. for id := range workQueue {
  93. start := time.Now()
  94. if err := calculateLayerChecksum(graphDir, id, ls); err != nil {
  95. logrus.Errorf("could not calculate checksum for %q, %q", id, err)
  96. }
  97. elapsed := time.Since(start)
  98. logrus.Debugf("layer %s took %.2f seconds", id, elapsed.Seconds())
  99. }
  100. wg.Done()
  101. }()
  102. }
  103. dir, err := ioutil.ReadDir(graphDir)
  104. if err != nil {
  105. logrus.Errorf("could not read directory %q", graphDir)
  106. return
  107. }
  108. for _, v := range dir {
  109. v1ID := v.Name()
  110. if err := imagev1.ValidateID(v1ID); err != nil {
  111. continue
  112. }
  113. if _, ok := mappings[v1ID]; ok { // support old migrations without helper files
  114. continue
  115. }
  116. workQueue <- v1ID
  117. }
  118. close(workQueue)
  119. wg.Wait()
  120. }
  121. func calculateLayerChecksum(graphDir, id string, ls checksumCalculator) error {
  122. diffIDFile := filepath.Join(graphDir, id, migrationDiffIDFileName)
  123. if _, err := os.Lstat(diffIDFile); err == nil {
  124. return nil
  125. } else if !os.IsNotExist(err) {
  126. return err
  127. }
  128. parent, err := getParent(filepath.Join(graphDir, id))
  129. if err != nil {
  130. return err
  131. }
  132. diffID, size, err := ls.ChecksumForGraphID(id, parent, filepath.Join(graphDir, id, tarDataFileName), filepath.Join(graphDir, id, migrationTarDataFileName))
  133. if err != nil {
  134. return err
  135. }
  136. if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationSizeFileName), []byte(strconv.Itoa(int(size))), 0600); err != nil {
  137. return err
  138. }
  139. if err := ioutils.AtomicWriteFile(filepath.Join(graphDir, id, migrationDiffIDFileName), []byte(diffID), 0600); err != nil {
  140. return err
  141. }
  142. logrus.Infof("calculated checksum for layer %s: %s", id, diffID)
  143. return nil
  144. }
  145. func restoreMappings(root string) (map[string]image.ID, error) {
  146. mappings := make(map[string]image.ID)
  147. mfile := filepath.Join(root, migrationFileName)
  148. f, err := os.Open(mfile)
  149. if err != nil && !os.IsNotExist(err) {
  150. return nil, err
  151. } else if err == nil {
  152. err := json.NewDecoder(f).Decode(&mappings)
  153. if err != nil {
  154. f.Close()
  155. return nil, err
  156. }
  157. f.Close()
  158. }
  159. return mappings, nil
  160. }
  161. func saveMappings(root string, mappings map[string]image.ID) error {
  162. mfile := filepath.Join(root, migrationFileName)
  163. f, err := os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
  164. if err != nil {
  165. return err
  166. }
  167. defer f.Close()
  168. if err := json.NewEncoder(f).Encode(mappings); err != nil {
  169. return err
  170. }
  171. return nil
  172. }
  173. func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error {
  174. graphDir := filepath.Join(root, graphDirName)
  175. dir, err := ioutil.ReadDir(graphDir)
  176. if err != nil {
  177. return err
  178. }
  179. for _, v := range dir {
  180. v1ID := v.Name()
  181. if err := imagev1.ValidateID(v1ID); err != nil {
  182. continue
  183. }
  184. if _, exists := mappings[v1ID]; exists {
  185. continue
  186. }
  187. if err := migrateImage(v1ID, root, ls, is, ms, mappings); err != nil {
  188. continue
  189. }
  190. }
  191. return nil
  192. }
  193. func migrateContainers(root string, ls graphIDMounter, is image.Store, imageMappings map[string]image.ID) error {
  194. containersDir := filepath.Join(root, containersDirName)
  195. dir, err := ioutil.ReadDir(containersDir)
  196. if err != nil {
  197. return err
  198. }
  199. for _, v := range dir {
  200. id := v.Name()
  201. if _, err := os.Stat(filepath.Join(containersDir, id, configFileName)); err == nil {
  202. continue
  203. }
  204. containerJSON, err := ioutil.ReadFile(filepath.Join(containersDir, id, configFileNameLegacy))
  205. if err != nil {
  206. logrus.Errorf("migrate container error: %v", err)
  207. continue
  208. }
  209. var c map[string]*json.RawMessage
  210. if err := json.Unmarshal(containerJSON, &c); err != nil {
  211. logrus.Errorf("migrate container error: %v", err)
  212. continue
  213. }
  214. imageStrJSON, ok := c["Image"]
  215. if !ok {
  216. return fmt.Errorf("invalid container configuration for %v", id)
  217. }
  218. var image string
  219. if err := json.Unmarshal([]byte(*imageStrJSON), &image); err != nil {
  220. logrus.Errorf("migrate container error: %v", err)
  221. continue
  222. }
  223. imageID, ok := imageMappings[image]
  224. if !ok {
  225. logrus.Errorf("image not migrated %v", imageID) // non-fatal error
  226. continue
  227. }
  228. c["Image"] = rawJSON(imageID)
  229. containerJSON, err = json.Marshal(c)
  230. if err != nil {
  231. return err
  232. }
  233. if err := ioutil.WriteFile(filepath.Join(containersDir, id, configFileName), containerJSON, 0600); err != nil {
  234. return err
  235. }
  236. img, err := is.Get(imageID)
  237. if err != nil {
  238. return err
  239. }
  240. if err := ls.CreateRWLayerByGraphID(id, id, img.RootFS.ChainID()); err != nil {
  241. logrus.Errorf("migrate container error: %v", err)
  242. continue
  243. }
  244. logrus.Infof("migrated container %s to point to %s", id, imageID)
  245. }
  246. return nil
  247. }
  248. type refAdder interface {
  249. AddTag(ref reference.Named, id image.ID, force bool) error
  250. AddDigest(ref reference.Canonical, id image.ID, force bool) error
  251. }
  252. func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image.ID) error {
  253. migrationFile := filepath.Join(root, migrationTagsFileName)
  254. if _, err := os.Lstat(migrationFile); !os.IsNotExist(err) {
  255. return err
  256. }
  257. type repositories struct {
  258. Repositories map[string]map[string]string
  259. }
  260. var repos repositories
  261. f, err := os.Open(filepath.Join(root, repositoriesFilePrefixLegacy+driverName))
  262. if err != nil {
  263. if os.IsNotExist(err) {
  264. return nil
  265. }
  266. return err
  267. }
  268. defer f.Close()
  269. if err := json.NewDecoder(f).Decode(&repos); err != nil {
  270. return err
  271. }
  272. for name, repo := range repos.Repositories {
  273. for tag, id := range repo {
  274. if strongID, exists := mappings[id]; exists {
  275. ref, err := reference.WithName(name)
  276. if err != nil {
  277. logrus.Errorf("migrate tags: invalid name %q, %q", name, err)
  278. continue
  279. }
  280. if dgst, err := digest.ParseDigest(tag); err == nil {
  281. canonical, err := reference.WithDigest(ref, dgst)
  282. if err != nil {
  283. logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err)
  284. continue
  285. }
  286. if err := rs.AddDigest(canonical, strongID, false); err != nil {
  287. logrus.Errorf("can't migrate digest %q for %q, err: %q", ref.String(), strongID, err)
  288. }
  289. } else {
  290. tagRef, err := reference.WithTag(ref, tag)
  291. if err != nil {
  292. logrus.Errorf("migrate tags: invalid tag %q, %q", tag, err)
  293. continue
  294. }
  295. if err := rs.AddTag(tagRef, strongID, false); err != nil {
  296. logrus.Errorf("can't migrate tag %q for %q, err: %q", ref.String(), strongID, err)
  297. }
  298. }
  299. logrus.Infof("migrated tag %s:%s to point to %s", name, tag, strongID)
  300. }
  301. }
  302. }
  303. mf, err := os.Create(migrationFile)
  304. if err != nil {
  305. return err
  306. }
  307. mf.Close()
  308. return nil
  309. }
  310. func getParent(confDir string) (string, error) {
  311. jsonFile := filepath.Join(confDir, "json")
  312. imageJSON, err := ioutil.ReadFile(jsonFile)
  313. if err != nil {
  314. return "", err
  315. }
  316. var parent struct {
  317. Parent string
  318. ParentID digest.Digest `json:"parent_id"`
  319. }
  320. if err := json.Unmarshal(imageJSON, &parent); err != nil {
  321. return "", err
  322. }
  323. if parent.Parent == "" && parent.ParentID != "" { // v1.9
  324. parent.Parent = parent.ParentID.Hex()
  325. }
  326. // compatibilityID for parent
  327. parentCompatibilityID, err := ioutil.ReadFile(filepath.Join(confDir, "parent"))
  328. if err == nil && len(parentCompatibilityID) > 0 {
  329. parent.Parent = string(parentCompatibilityID)
  330. }
  331. return parent.Parent, nil
  332. }
  333. func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) (err error) {
  334. defer func() {
  335. if err != nil {
  336. logrus.Errorf("migration failed for %v, err: %v", id, err)
  337. }
  338. }()
  339. parent, err := getParent(filepath.Join(root, graphDirName, id))
  340. if err != nil {
  341. return err
  342. }
  343. var parentID image.ID
  344. if parent != "" {
  345. var exists bool
  346. if parentID, exists = mappings[parent]; !exists {
  347. if err := migrateImage(parent, root, ls, is, ms, mappings); err != nil {
  348. // todo: fail or allow broken chains?
  349. return err
  350. }
  351. parentID = mappings[parent]
  352. }
  353. }
  354. rootFS := image.NewRootFS()
  355. var history []image.History
  356. if parentID != "" {
  357. parentImg, err := is.Get(parentID)
  358. if err != nil {
  359. return err
  360. }
  361. rootFS = parentImg.RootFS
  362. history = parentImg.History
  363. }
  364. diffIDData, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName))
  365. if err != nil {
  366. return err
  367. }
  368. diffID, err := digest.ParseDigest(string(diffIDData))
  369. if err != nil {
  370. return err
  371. }
  372. sizeStr, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationSizeFileName))
  373. if err != nil {
  374. return err
  375. }
  376. size, err := strconv.ParseInt(string(sizeStr), 10, 64)
  377. if err != nil {
  378. return err
  379. }
  380. layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), layer.DiffID(diffID), filepath.Join(root, graphDirName, id, migrationTarDataFileName), size)
  381. if err != nil {
  382. return err
  383. }
  384. logrus.Infof("migrated layer %s to %s", id, layer.DiffID())
  385. jsonFile := filepath.Join(root, graphDirName, id, "json")
  386. imageJSON, err := ioutil.ReadFile(jsonFile)
  387. if err != nil {
  388. return err
  389. }
  390. h, err := imagev1.HistoryFromConfig(imageJSON, false)
  391. if err != nil {
  392. return err
  393. }
  394. history = append(history, h)
  395. rootFS.Append(layer.DiffID())
  396. config, err := imagev1.MakeConfigFromV1Config(imageJSON, rootFS, history)
  397. if err != nil {
  398. return err
  399. }
  400. strongID, err := is.Create(config)
  401. if err != nil {
  402. return err
  403. }
  404. logrus.Infof("migrated image %s to %s", id, strongID)
  405. if parentID != "" {
  406. if err := is.SetParent(strongID, parentID); err != nil {
  407. return err
  408. }
  409. }
  410. checksum, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, "checksum"))
  411. if err == nil { // best effort
  412. dgst, err := digest.ParseDigest(string(checksum))
  413. if err == nil {
  414. V2MetadataService := metadata.NewV2MetadataService(ms)
  415. V2MetadataService.Add(layer.DiffID(), metadata.V2Metadata{Digest: dgst})
  416. }
  417. }
  418. _, err = ls.Release(layer)
  419. if err != nil {
  420. return err
  421. }
  422. mappings[id] = strongID
  423. return
  424. }
  425. func rawJSON(value interface{}) *json.RawMessage {
  426. jsonval, err := json.Marshal(value)
  427. if err != nil {
  428. return nil
  429. }
  430. return (*json.RawMessage)(&jsonval)
  431. }