github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/store/migrate_test.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package store 16 17 import ( 18 "database/sql" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "reflect" 24 "testing" 25 "time" 26 ) 27 28 type testdb interface { 29 version() int 30 populate(db *DB) error 31 load(db *DB) error 32 compare(db testdb) bool 33 } 34 35 type DBV0 struct { 36 aciinfos []*ACIInfoV0_2 37 remotes []*RemoteV0_1 38 } 39 40 func (d *DBV0) version() int { 41 return 0 42 } 43 44 func (d *DBV0) populate(db *DB) error { 45 // As DBV0 and DBV1 have the same schema use a common populate 46 // function. 47 return populateDBV0_1(db, d.version(), d.aciinfos, d.remotes) 48 } 49 50 // load populates the given struct with the data in db. 51 // the given struct d should be empty 52 func (d *DBV0) load(db *DB) error { 53 fn := func(tx *sql.Tx) error { 54 var err error 55 d.aciinfos, err = getAllACIInfosV0_2(tx) 56 if err != nil { 57 return err 58 } 59 d.remotes, err = getAllRemoteV0_1(tx) 60 if err != nil { 61 return err 62 } 63 return nil 64 } 65 if err := db.Do(fn); err != nil { 66 return err 67 } 68 return nil 69 } 70 71 func (d *DBV0) compare(td testdb) bool { 72 d2, ok := td.(*DBV0) 73 if !ok { 74 return false 75 } 76 if !compareSlicesNoOrder(d.aciinfos, d2.aciinfos) { 77 return false 78 } 79 if !compareSlicesNoOrder(d.remotes, d2.remotes) { 80 return false 81 } 82 return true 83 } 84 85 type DBV1 struct { 86 aciinfos []*ACIInfoV0_2 87 remotes []*RemoteV0_1 88 } 89 90 func (d *DBV1) version() int { 91 return 1 92 } 93 func (d *DBV1) populate(db *DB) error { 94 return populateDBV0_1(db, d.version(), d.aciinfos, d.remotes) 95 } 96 97 func (d *DBV1) load(db *DB) error { 98 fn := func(tx *sql.Tx) error { 99 var err error 100 d.aciinfos, err = getAllACIInfosV0_2(tx) 101 if err != nil { 102 return err 103 } 104 d.remotes, err = getAllRemoteV0_1(tx) 105 if err != nil { 106 return err 107 } 108 return nil 109 } 110 if err := db.Do(fn); err != nil { 111 return err 112 } 113 return nil 114 } 115 116 func (d *DBV1) compare(td testdb) bool { 117 d2, ok := td.(*DBV1) 118 if !ok { 119 return false 120 } 121 if !compareSlicesNoOrder(d.aciinfos, d2.aciinfos) { 122 return false 123 } 124 if !compareSlicesNoOrder(d.remotes, d2.remotes) { 125 return false 126 } 127 return true 128 } 129 130 type DBV2 struct { 131 aciinfos []*ACIInfoV0_2 132 remotes []*RemoteV2_3 133 } 134 135 func (d *DBV2) version() int { 136 return 2 137 } 138 func (d *DBV2) populate(db *DB) error { 139 return populateDBV2(db, d.version(), d.aciinfos, d.remotes) 140 } 141 142 func (d *DBV2) load(db *DB) error { 143 fn := func(tx *sql.Tx) error { 144 var err error 145 d.aciinfos, err = getAllACIInfosV0_2(tx) 146 if err != nil { 147 return err 148 } 149 d.remotes, err = getAllRemoteV2_3(tx) 150 if err != nil { 151 return err 152 } 153 return nil 154 } 155 if err := db.Do(fn); err != nil { 156 return err 157 } 158 return nil 159 } 160 161 func (d *DBV2) compare(td testdb) bool { 162 d2, ok := td.(*DBV2) 163 if !ok { 164 return false 165 } 166 if !compareSlicesNoOrder(d.aciinfos, d2.aciinfos) { 167 return false 168 } 169 if !compareSlicesNoOrder(d.remotes, d2.remotes) { 170 return false 171 } 172 return true 173 } 174 175 type DBV3 struct { 176 aciinfos []*ACIInfoV3 177 remotes []*RemoteV2_3 178 } 179 180 func (d *DBV3) version() int { 181 return 3 182 } 183 func (d *DBV3) populate(db *DB) error { 184 return populateDBV3(db, d.version(), d.aciinfos, d.remotes) 185 } 186 187 func (d *DBV3) load(db *DB) error { 188 fn := func(tx *sql.Tx) error { 189 var err error 190 d.aciinfos, err = getAllACIInfosV3(tx) 191 if err != nil { 192 return err 193 } 194 d.remotes, err = getAllRemoteV2_3(tx) 195 if err != nil { 196 return err 197 } 198 return nil 199 } 200 if err := db.Do(fn); err != nil { 201 return err 202 } 203 return nil 204 } 205 206 func (d *DBV3) compare(td testdb) bool { 207 d3, ok := td.(*DBV3) 208 if !ok { 209 return false 210 } 211 if !compareSlicesNoOrder(d.aciinfos, d3.aciinfos) { 212 return false 213 } 214 if !compareSlicesNoOrder(d.remotes, d3.remotes) { 215 return false 216 } 217 return true 218 } 219 220 // The ACIInfo struct for different db versions. The ending VX_Y represent the 221 // first and the last version where the format isn't changed 222 // The latest existing struct should be updated when updating the db version 223 // without changing the struct format (ex. V0_1 to V0_2). 224 // A new struct and its relative function should be added if the format is changed. 225 // The same applies for all of the the other structs. 226 type ACIInfoV0_2 struct { 227 BlobKey string 228 AppName string 229 ImportTime time.Time 230 Latest bool 231 } 232 233 type ACIInfoV3 struct { 234 BlobKey string 235 Name string 236 ImportTime time.Time 237 Latest bool 238 } 239 240 func getAllACIInfosV0_2(tx *sql.Tx) ([]*ACIInfoV0_2, error) { 241 var aciinfos []*ACIInfoV0_2 242 rows, err := tx.Query("SELECT * from aciinfo") 243 if err != nil { 244 return nil, err 245 } 246 for rows.Next() { 247 aciinfo := &ACIInfoV0_2{} 248 if err := rows.Scan(&aciinfo.BlobKey, &aciinfo.AppName, &aciinfo.ImportTime, &aciinfo.Latest); err != nil { 249 return nil, err 250 } 251 aciinfos = append(aciinfos, aciinfo) 252 } 253 if err := rows.Err(); err != nil { 254 return nil, err 255 } 256 return aciinfos, nil 257 } 258 259 func getAllACIInfosV3(tx *sql.Tx) ([]*ACIInfoV3, error) { 260 var aciinfos []*ACIInfoV3 261 rows, err := tx.Query("SELECT * from aciinfo") 262 if err != nil { 263 return nil, err 264 } 265 for rows.Next() { 266 aciinfo := &ACIInfoV3{} 267 if rows.Scan(&aciinfo.BlobKey, &aciinfo.Name, &aciinfo.ImportTime, &aciinfo.Latest); err != nil { 268 return nil, err 269 } 270 aciinfos = append(aciinfos, aciinfo) 271 } 272 if err := rows.Err(); err != nil { 273 return nil, err 274 } 275 return aciinfos, nil 276 } 277 278 type RemoteV0_1 struct { 279 ACIURL string 280 SigURL string 281 ETag string 282 BlobKey string 283 } 284 285 func getAllRemoteV0_1(tx *sql.Tx) ([]*RemoteV0_1, error) { 286 var remotes []*RemoteV0_1 287 rows, err := tx.Query("SELECT * from remote") 288 if err != nil { 289 return nil, err 290 } 291 for rows.Next() { 292 remote := &RemoteV0_1{} 293 if err := rows.Scan(&remote.ACIURL, &remote.SigURL, &remote.ETag, &remote.BlobKey); err != nil { 294 return nil, err 295 } 296 remotes = append(remotes, remote) 297 } 298 if err := rows.Err(); err != nil { 299 return nil, err 300 } 301 return remotes, nil 302 } 303 304 type RemoteV2_3 struct { 305 ACIURL string 306 SigURL string 307 ETag string 308 BlobKey string 309 CacheMaxAge int 310 DownloadTime time.Time 311 } 312 313 func getAllRemoteV2_3(tx *sql.Tx) ([]*RemoteV2_3, error) { 314 var remotes []*RemoteV2_3 315 rows, err := tx.Query("SELECT * from remote") 316 if err != nil { 317 return nil, err 318 } 319 for rows.Next() { 320 remote := &RemoteV2_3{} 321 if err := rows.Scan(&remote.ACIURL, &remote.SigURL, &remote.ETag, &remote.BlobKey, &remote.CacheMaxAge, &remote.DownloadTime); err != nil { 322 return nil, err 323 } 324 remotes = append(remotes, remote) 325 } 326 if err := rows.Err(); err != nil { 327 return nil, err 328 } 329 return remotes, nil 330 } 331 332 func populateDBV0_1(db *DB, dbVersion int, aciInfos []*ACIInfoV0_2, remotes []*RemoteV0_1) error { 333 var dbCreateStmts = [...]string{ 334 // version table 335 "CREATE TABLE IF NOT EXISTS version (version int);", 336 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 337 338 // remote table. The primary key is "aciurl". 339 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string);", 340 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 341 342 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 343 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, appname string, importtime time, latest bool);", 344 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 345 "CREATE INDEX IF NOT EXISTS appnameidx ON aciinfo (appname)", 346 } 347 348 fn := func(tx *sql.Tx) error { 349 for _, stmt := range dbCreateStmts { 350 _, err := tx.Exec(stmt) 351 if err != nil { 352 return err 353 } 354 } 355 return nil 356 } 357 if err := db.Do(fn); err != nil { 358 return err 359 } 360 361 fn = func(tx *sql.Tx) error { 362 for _, aciinfo := range aciInfos { 363 _, err := tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.AppName, aciinfo.ImportTime, aciinfo.Latest) 364 if err != nil { 365 return err 366 } 367 } 368 return nil 369 } 370 if err := db.Do(fn); err != nil { 371 return err 372 } 373 374 fn = func(tx *sql.Tx) error { 375 for _, remote := range remotes { 376 _, err := tx.Exec("INSERT into remote values ($1, $2, $3, $4)", remote.ACIURL, remote.SigURL, remote.ETag, remote.BlobKey) 377 if err != nil { 378 return err 379 } 380 } 381 return nil 382 } 383 if err := db.Do(fn); err != nil { 384 return err 385 } 386 387 return nil 388 } 389 390 func populateDBV2(db *DB, dbVersion int, aciInfos []*ACIInfoV0_2, remotes []*RemoteV2_3) error { 391 var dbCreateStmts = [...]string{ 392 // version table 393 "CREATE TABLE IF NOT EXISTS version (version int);", 394 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 395 396 // remote table. The primary key is "aciurl". 397 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 398 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 399 400 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 401 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, appname string, importtime time, latest bool);", 402 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 403 "CREATE INDEX IF NOT EXISTS appnameidx ON aciinfo (appname)", 404 } 405 406 fn := func(tx *sql.Tx) error { 407 for _, stmt := range dbCreateStmts { 408 _, err := tx.Exec(stmt) 409 if err != nil { 410 return err 411 } 412 } 413 return nil 414 } 415 if err := db.Do(fn); err != nil { 416 return err 417 } 418 419 fn = func(tx *sql.Tx) error { 420 for _, aciinfo := range aciInfos { 421 _, err := tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.AppName, aciinfo.ImportTime, aciinfo.Latest) 422 if err != nil { 423 return err 424 } 425 } 426 return nil 427 } 428 if err := db.Do(fn); err != nil { 429 return err 430 } 431 432 fn = func(tx *sql.Tx) error { 433 for _, remote := range remotes { 434 _, err := tx.Exec("INSERT into remote values ($1, $2, $3, $4, $5, $6)", remote.ACIURL, remote.SigURL, remote.ETag, remote.BlobKey, remote.CacheMaxAge, remote.DownloadTime) 435 if err != nil { 436 return err 437 } 438 } 439 return nil 440 } 441 if err := db.Do(fn); err != nil { 442 return err 443 } 444 445 return nil 446 } 447 448 func populateDBV3(db *DB, dbVersion int, aciInfos []*ACIInfoV3, remotes []*RemoteV2_3) error { 449 var dbCreateStmts = [...]string{ 450 // version table 451 "CREATE TABLE IF NOT EXISTS version (version int);", 452 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 453 454 // remote table. The primary key is "aciurl". 455 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 456 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 457 458 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 459 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, importtime time, latest bool, name string);", 460 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 461 "CREATE INDEX IF NOT EXISTS nameidx ON aciinfo (name)", 462 } 463 fn := func(tx *sql.Tx) error { 464 for _, stmt := range dbCreateStmts { 465 _, err := tx.Exec(stmt) 466 if err != nil { 467 return err 468 } 469 } 470 return nil 471 } 472 if err := db.Do(fn); err != nil { 473 return err 474 } 475 476 fn = func(tx *sql.Tx) error { 477 for _, aciinfo := range aciInfos { 478 _, err := tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.ImportTime, aciinfo.Latest, aciinfo.Name) 479 if err != nil { 480 return err 481 } 482 } 483 return nil 484 } 485 if err := db.Do(fn); err != nil { 486 return err 487 } 488 489 fn = func(tx *sql.Tx) error { 490 for _, remote := range remotes { 491 _, err := tx.Exec("INSERT into remote values ($1, $2, $3, $4, $5, $6)", remote.ACIURL, remote.SigURL, remote.ETag, remote.BlobKey, remote.CacheMaxAge, remote.DownloadTime) 492 if err != nil { 493 return err 494 } 495 } 496 return nil 497 } 498 if err := db.Do(fn); err != nil { 499 return err 500 } 501 502 return nil 503 504 } 505 506 type migrateTest struct { 507 predb testdb 508 postdb testdb 509 // Needed to have the right DB type to load from 510 curdb testdb 511 } 512 513 func testMigrate(tt migrateTest) error { 514 dir, err := ioutil.TempDir("", tstprefix) 515 if err != nil { 516 return fmt.Errorf("error creating tempdir: %v", err) 517 } 518 defer os.RemoveAll(dir) 519 520 casDir := filepath.Join(dir, "cas") 521 db, err := NewDB(filepath.Join(casDir, "db")) 522 if err != nil { 523 return err 524 } 525 if err = tt.predb.populate(db); err != nil { 526 return err 527 } 528 529 fn := func(tx *sql.Tx) error { 530 err := migrate(tx, tt.postdb.version()) 531 if err != nil { 532 return err 533 } 534 return nil 535 } 536 if err = db.Do(fn); err != nil { 537 return err 538 } 539 540 var curDBVersion int 541 fn = func(tx *sql.Tx) error { 542 var err error 543 curDBVersion, err = getDBVersion(tx) 544 if err != nil { 545 return err 546 } 547 return nil 548 } 549 if err = db.Do(fn); err != nil { 550 return err 551 } 552 if curDBVersion != tt.postdb.version() { 553 return fmt.Errorf("wrong db version: got %#v, want %#v", curDBVersion, tt.postdb.version()) 554 } 555 556 if err := tt.curdb.load(db); err != nil { 557 return err 558 } 559 if !tt.curdb.compare(tt.postdb) { 560 // TODO(sgotti) not very useful as these are pointers. 561 // Use something like go-spew to write the full data? 562 return fmt.Errorf("got %#v, want %#v", tt.curdb, tt.postdb) 563 } 564 return nil 565 } 566 567 func TestMigrate(t *testing.T) { 568 dir, err := ioutil.TempDir("", tstprefix) 569 if err != nil { 570 t.Fatalf("error creating tempdir: %v", err) 571 } 572 defer os.RemoveAll(dir) 573 574 now := time.Now() 575 tests := []migrateTest{ 576 // Test migration from V0 to V1 577 // Empty db 578 { 579 &DBV0{}, 580 &DBV1{}, 581 &DBV1{}, 582 }, 583 { 584 &DBV0{ 585 []*ACIInfoV0_2{ 586 {"sha512-aaaaaaaa", "example.com/app01", now, false}, 587 {"sha512-bbbbbbbb", "example.com/app02", now, true}, 588 }, 589 []*RemoteV0_1{ 590 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", "sha512-aaaaaaaa"}, 591 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", "sha512-bbbbbbbb"}, 592 }, 593 }, 594 &DBV1{ 595 []*ACIInfoV0_2{ 596 {"sha512-aaaaaaaa", "example.com/app01", now, false}, 597 {"sha512-bbbbbbbb", "example.com/app02", now, true}, 598 }, 599 []*RemoteV0_1{ 600 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", "sha512-aaaaaaaa"}, 601 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", "sha512-bbbbbbbb"}, 602 }, 603 }, 604 &DBV1{}, 605 }, 606 // Test migration from V1 to V2 607 { 608 &DBV1{ 609 []*ACIInfoV0_2{ 610 {"sha512-aaaaaaaa", "example.com/app01", now, false}, 611 {"sha512-bbbbbbbb", "example.com/app02", now, true}, 612 }, 613 []*RemoteV0_1{ 614 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", "sha512-aaaaaaaa"}, 615 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", "sha512-bbbbbbbb"}, 616 }, 617 }, 618 &DBV2{ 619 []*ACIInfoV0_2{ 620 {"sha512-aaaaaaaa", "example.com/app01", now, false}, 621 {"sha512-bbbbbbbb", "example.com/app02", now, true}, 622 }, 623 []*RemoteV2_3{ 624 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", "sha512-aaaaaaaa", 0, time.Time{}.UTC()}, 625 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", "sha512-bbbbbbbb", 0, time.Time{}.UTC()}, 626 }, 627 }, 628 &DBV2{}, 629 }, 630 // Test migration from V1 to V3 631 { 632 &DBV2{ 633 []*ACIInfoV0_2{ 634 {"sha512-aaaaaaaa", "example.com/app01", now, false}, 635 {"sha512-bbbbbbbb", "example.com/app02", now, true}, 636 }, 637 []*RemoteV2_3{ 638 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", "sha512-aaaaaaaa", 0, time.Time{}.UTC()}, 639 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", "sha512-bbbbbbbb", 0, time.Time{}.UTC()}, 640 }, 641 }, 642 &DBV3{ 643 []*ACIInfoV3{ 644 {"sha512-aaaaaaaa", "example.com/app01", now, false}, 645 {"sha512-bbbbbbbb", "example.com/app02", now, true}, 646 }, 647 []*RemoteV2_3{ 648 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", "sha512-aaaaaaaa", 0, time.Time{}.UTC()}, 649 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", "sha512-bbbbbbbb", 0, time.Time{}.UTC()}, 650 }, 651 }, 652 &DBV3{}, 653 }, 654 } 655 656 for i, tt := range tests { 657 if err := testMigrate(tt); err != nil { 658 t.Errorf("#%d: unexpected error: %v", i, err) 659 } 660 } 661 } 662 663 // compareSlices compare slices regardless of the slice elements order 664 func compareSlicesNoOrder(i1 interface{}, i2 interface{}) bool { 665 s1 := interfaceToSlice(i1) 666 s2 := interfaceToSlice(i2) 667 668 if len(s1) != len(s2) { 669 return false 670 } 671 672 seen := map[int]bool{} 673 for _, v1 := range s1 { 674 found := false 675 for i2, v2 := range s2 { 676 if _, ok := seen[i2]; ok { 677 continue 678 } 679 if reflect.DeepEqual(v1, v2) { 680 found = true 681 seen[i2] = true 682 continue 683 } 684 } 685 if !found { 686 return false 687 } 688 689 } 690 return true 691 } 692 693 func interfaceToSlice(s interface{}) []interface{} { 694 v := reflect.ValueOf(s) 695 if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { 696 panic(fmt.Errorf("Expected slice or array, got %T", s)) 697 } 698 l := v.Len() 699 m := make([]interface{}, l) 700 for i := 0; i < l; i++ { 701 m[i] = v.Index(i).Interface() 702 } 703 return m 704 }