migratev1.go 13 KB

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