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