github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/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 if err := ioutil.WriteFile(filepath.Join(graphDir, id, migrationDiffIDFileName), []byte(diffID), 0600); err != nil { 164 return err 165 } 166 167 logrus.Infof("calculated checksum for layer %s: %s", id, diffID) 168 return nil 169 } 170 171 func restoreMappings(root string) (map[string]image.ID, error) { 172 mappings := make(map[string]image.ID) 173 174 mfile := filepath.Join(root, migrationFileName) 175 f, err := os.Open(mfile) 176 if err != nil && !os.IsNotExist(err) { 177 return nil, err 178 } else if err == nil { 179 err := json.NewDecoder(f).Decode(&mappings) 180 if err != nil { 181 f.Close() 182 return nil, err 183 } 184 f.Close() 185 } 186 187 return mappings, nil 188 } 189 190 func saveMappings(root string, mappings map[string]image.ID) error { 191 mfile := filepath.Join(root, migrationFileName) 192 f, err := os.OpenFile(mfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 193 if err != nil { 194 return err 195 } 196 defer f.Close() 197 if err := json.NewEncoder(f).Encode(mappings); err != nil { 198 return err 199 } 200 return nil 201 } 202 203 func migrateImages(root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) error { 204 graphDir := filepath.Join(root, graphDirName) 205 206 dir, err := ioutil.ReadDir(graphDir) 207 if err != nil { 208 return err 209 } 210 for _, v := range dir { 211 v1ID := v.Name() 212 if err := imagev1.ValidateID(v1ID); err != nil { 213 continue 214 } 215 if _, exists := mappings[v1ID]; exists { 216 continue 217 } 218 if err := migrateImage(v1ID, root, ls, is, ms, mappings); err != nil { 219 continue 220 } 221 } 222 223 return nil 224 } 225 226 func migrateContainers(root string, ls graphIDMounter, is image.Store, imageMappings map[string]image.ID) error { 227 containersDir := filepath.Join(root, containersDirName) 228 dir, err := ioutil.ReadDir(containersDir) 229 if err != nil { 230 return err 231 } 232 for _, v := range dir { 233 id := v.Name() 234 235 if _, err := os.Stat(filepath.Join(containersDir, id, configFileName)); err == nil { 236 continue 237 } 238 239 containerJSON, err := ioutil.ReadFile(filepath.Join(containersDir, id, configFileNameLegacy)) 240 if err != nil { 241 logrus.Errorf("migrate container error: %v", err) 242 continue 243 } 244 245 var c map[string]*json.RawMessage 246 if err := json.Unmarshal(containerJSON, &c); err != nil { 247 logrus.Errorf("migrate container error: %v", err) 248 continue 249 } 250 251 imageStrJSON, ok := c["Image"] 252 if !ok { 253 return fmt.Errorf("invalid container configuration for %v", id) 254 } 255 256 var image string 257 if err := json.Unmarshal([]byte(*imageStrJSON), &image); err != nil { 258 logrus.Errorf("migrate container error: %v", err) 259 continue 260 } 261 262 imageID, ok := imageMappings[image] 263 if !ok { 264 logrus.Errorf("image not migrated %v", imageID) // non-fatal error 265 continue 266 } 267 268 c["Image"] = rawJSON(imageID) 269 270 containerJSON, err = json.Marshal(c) 271 if err != nil { 272 return err 273 } 274 275 if err := ioutil.WriteFile(filepath.Join(containersDir, id, configFileName), containerJSON, 0600); err != nil { 276 return err 277 } 278 279 img, err := is.Get(imageID) 280 if err != nil { 281 return err 282 } 283 284 if err := ls.CreateRWLayerByGraphID(id, id, img.RootFS.ChainID()); err != nil { 285 return err 286 } 287 288 logrus.Infof("migrated container %s to point to %s", id, imageID) 289 290 } 291 return nil 292 } 293 294 type refAdder interface { 295 AddTag(ref reference.Named, id image.ID, force bool) error 296 AddDigest(ref reference.Canonical, id image.ID, force bool) error 297 } 298 299 func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image.ID) error { 300 migrationFile := filepath.Join(root, migrationTagsFileName) 301 if _, err := os.Lstat(migrationFile); !os.IsNotExist(err) { 302 return err 303 } 304 305 type repositories struct { 306 Repositories map[string]map[string]string 307 } 308 309 var repos repositories 310 311 f, err := os.Open(filepath.Join(root, repositoriesFilePrefixLegacy+driverName)) 312 if err != nil { 313 if os.IsNotExist(err) { 314 return nil 315 } 316 return err 317 } 318 defer f.Close() 319 if err := json.NewDecoder(f).Decode(&repos); err != nil { 320 return err 321 } 322 323 for name, repo := range repos.Repositories { 324 for tag, id := range repo { 325 if strongID, exists := mappings[id]; exists { 326 ref, err := reference.WithName(name) 327 if err != nil { 328 logrus.Errorf("migrate tags: invalid name %q, %q", name, err) 329 continue 330 } 331 if dgst, err := digest.ParseDigest(tag); err == nil { 332 canonical, err := reference.WithDigest(ref, dgst) 333 if err != nil { 334 logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err) 335 continue 336 } 337 if err := rs.AddDigest(canonical, strongID, false); err != nil { 338 logrus.Errorf("can't migrate digest %q for %q, err: %q", ref.String(), strongID, err) 339 } 340 } else { 341 tagRef, err := reference.WithTag(ref, tag) 342 if err != nil { 343 logrus.Errorf("migrate tags: invalid tag %q, %q", tag, err) 344 continue 345 } 346 if err := rs.AddTag(tagRef, strongID, false); err != nil { 347 logrus.Errorf("can't migrate tag %q for %q, err: %q", ref.String(), strongID, err) 348 } 349 } 350 logrus.Infof("migrated tag %s:%s to point to %s", name, tag, strongID) 351 } 352 } 353 } 354 355 mf, err := os.Create(migrationFile) 356 if err != nil { 357 return err 358 } 359 mf.Close() 360 361 return nil 362 } 363 364 func getParent(confDir string) (string, error) { 365 jsonFile := filepath.Join(confDir, "json") 366 imageJSON, err := ioutil.ReadFile(jsonFile) 367 if err != nil { 368 return "", err 369 } 370 var parent struct { 371 Parent string 372 ParentID digest.Digest `json:"parent_id"` 373 } 374 if err := json.Unmarshal(imageJSON, &parent); err != nil { 375 return "", err 376 } 377 if parent.Parent == "" && parent.ParentID != "" { // v1.9 378 parent.Parent = parent.ParentID.Hex() 379 } 380 // compatibilityID for parent 381 parentCompatibilityID, err := ioutil.ReadFile(filepath.Join(confDir, "parent")) 382 if err == nil && len(parentCompatibilityID) > 0 { 383 parent.Parent = string(parentCompatibilityID) 384 } 385 return parent.Parent, nil 386 } 387 388 func migrateImage(id, root string, ls graphIDRegistrar, is image.Store, ms metadata.Store, mappings map[string]image.ID) (err error) { 389 defer func() { 390 if err != nil { 391 logrus.Errorf("migration failed for %v, err: %v", id, err) 392 } 393 }() 394 395 parent, err := getParent(filepath.Join(root, graphDirName, id)) 396 if err != nil { 397 return err 398 } 399 400 var parentID image.ID 401 if parent != "" { 402 var exists bool 403 if parentID, exists = mappings[parent]; !exists { 404 if err := migrateImage(parent, root, ls, is, ms, mappings); err != nil { 405 // todo: fail or allow broken chains? 406 return err 407 } 408 parentID = mappings[parent] 409 } 410 } 411 412 rootFS := image.NewRootFS() 413 var history []image.History 414 415 if parentID != "" { 416 parentImg, err := is.Get(parentID) 417 if err != nil { 418 return err 419 } 420 421 rootFS = parentImg.RootFS 422 history = parentImg.History 423 } 424 425 diffID, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationDiffIDFileName)) 426 if err != nil { 427 return err 428 } 429 430 sizeStr, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, migrationSizeFileName)) 431 if err != nil { 432 return err 433 } 434 size, err := strconv.ParseInt(string(sizeStr), 10, 64) 435 if err != nil { 436 return err 437 } 438 439 layer, err := ls.RegisterByGraphID(id, rootFS.ChainID(), layer.DiffID(diffID), filepath.Join(root, graphDirName, id, migrationTarDataFileName), size) 440 if err != nil { 441 return err 442 } 443 logrus.Infof("migrated layer %s to %s", id, layer.DiffID()) 444 445 jsonFile := filepath.Join(root, graphDirName, id, "json") 446 imageJSON, err := ioutil.ReadFile(jsonFile) 447 if err != nil { 448 return err 449 } 450 451 h, err := imagev1.HistoryFromConfig(imageJSON, false) 452 if err != nil { 453 return err 454 } 455 history = append(history, h) 456 457 rootFS.Append(layer.DiffID()) 458 459 config, err := imagev1.MakeConfigFromV1Config(imageJSON, rootFS, history) 460 if err != nil { 461 return err 462 } 463 strongID, err := is.Create(config) 464 if err != nil { 465 return err 466 } 467 logrus.Infof("migrated image %s to %s", id, strongID) 468 469 if parentID != "" { 470 if err := is.SetParent(strongID, parentID); err != nil { 471 return err 472 } 473 } 474 475 checksum, err := ioutil.ReadFile(filepath.Join(root, graphDirName, id, "checksum")) 476 if err == nil { // best effort 477 dgst, err := digest.ParseDigest(string(checksum)) 478 if err == nil { 479 blobSumService := metadata.NewBlobSumService(ms) 480 blobSumService.Add(layer.DiffID(), dgst) 481 } 482 } 483 _, err = ls.Release(layer) 484 if err != nil { 485 return err 486 } 487 488 mappings[id] = strongID 489 return 490 } 491 492 func rawJSON(value interface{}) *json.RawMessage { 493 jsonval, err := json.Marshal(value) 494 if err != nil { 495 return nil 496 } 497 return (*json.RawMessage)(&jsonval) 498 }