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