github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/store/imagestore/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 imagestore 16 17 import ( 18 "database/sql" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "reflect" 24 "testing" 25 "time" 26 27 "github.com/davecgh/go-spew/spew" 28 "github.com/jonboulle/clockwork" 29 "github.com/rkt/rkt/store/db" 30 ) 31 32 type testdb interface { 33 version() int 34 populate(db *db.DB) error 35 load(db *db.DB) error 36 compare(db testdb) bool 37 } 38 39 type DBV0 struct { 40 aciinfos []*ACIInfoV0_2 41 remotes []*RemoteV0_1 42 } 43 44 func (d *DBV0) version() int { 45 return 0 46 } 47 48 func (d *DBV0) populate(db *db.DB) error { 49 // As DBV0 and DBV1 have the same schema use a common populate 50 // function. 51 return populateDBV0_1(db, d.version(), d.aciinfos, d.remotes) 52 } 53 54 // load populates the given struct with the data in db. 55 // the given struct d should be empty 56 func (d *DBV0) load(db *db.DB) error { 57 fn := func(tx *sql.Tx) error { 58 var err error 59 d.aciinfos, err = getAllACIInfosV0_2(tx) 60 if err != nil { 61 return err 62 } 63 d.remotes, err = getAllRemoteV0_1(tx) 64 if err != nil { 65 return err 66 } 67 return nil 68 } 69 if err := db.Do(fn); err != nil { 70 return err 71 } 72 return nil 73 } 74 75 func (d *DBV0) compare(td testdb) bool { 76 d2, ok := td.(*DBV0) 77 if !ok { 78 return false 79 } 80 if !compareSlicesNoOrder(d.aciinfos, d2.aciinfos) { 81 return false 82 } 83 if !compareSlicesNoOrder(d.remotes, d2.remotes) { 84 return false 85 } 86 return true 87 } 88 89 type DBV1 struct { 90 aciinfos []*ACIInfoV0_2 91 remotes []*RemoteV0_1 92 } 93 94 func (d *DBV1) version() int { 95 return 1 96 } 97 func (d *DBV1) populate(db *db.DB) error { 98 return populateDBV0_1(db, d.version(), d.aciinfos, d.remotes) 99 } 100 101 func (d *DBV1) load(db *db.DB) error { 102 fn := func(tx *sql.Tx) error { 103 var err error 104 d.aciinfos, err = getAllACIInfosV0_2(tx) 105 if err != nil { 106 return err 107 } 108 d.remotes, err = getAllRemoteV0_1(tx) 109 if err != nil { 110 return err 111 } 112 return nil 113 } 114 if err := db.Do(fn); err != nil { 115 return err 116 } 117 return nil 118 } 119 120 func (d *DBV1) compare(td testdb) bool { 121 d2, ok := td.(*DBV1) 122 if !ok { 123 return false 124 } 125 if !compareSlicesNoOrder(d.aciinfos, d2.aciinfos) { 126 return false 127 } 128 if !compareSlicesNoOrder(d.remotes, d2.remotes) { 129 return false 130 } 131 return true 132 } 133 134 type DBV2 struct { 135 aciinfos []*ACIInfoV0_2 136 remotes []*RemoteV2_7 137 } 138 139 func (d *DBV2) version() int { 140 return 2 141 } 142 func (d *DBV2) populate(db *db.DB) error { 143 return populateDBV2(db, d.version(), d.aciinfos, d.remotes) 144 } 145 146 func (d *DBV2) load(db *db.DB) error { 147 fn := func(tx *sql.Tx) error { 148 var err error 149 d.aciinfos, err = getAllACIInfosV0_2(tx) 150 if err != nil { 151 return err 152 } 153 d.remotes, err = getAllRemoteV2_7(tx) 154 if err != nil { 155 return err 156 } 157 return nil 158 } 159 if err := db.Do(fn); err != nil { 160 return err 161 } 162 return nil 163 } 164 165 func (d *DBV2) compare(td testdb) bool { 166 d2, ok := td.(*DBV2) 167 if !ok { 168 return false 169 } 170 if !compareSlicesNoOrder(d.aciinfos, d2.aciinfos) { 171 return false 172 } 173 if !compareSlicesNoOrder(d.remotes, d2.remotes) { 174 return false 175 } 176 return true 177 } 178 179 type DBV3 struct { 180 aciinfos []*ACIInfoV3 181 remotes []*RemoteV2_7 182 } 183 184 func (d *DBV3) version() int { 185 return 3 186 } 187 func (d *DBV3) populate(db *db.DB) error { 188 return populateDBV3(db, d.version(), d.aciinfos, d.remotes) 189 } 190 191 func (d *DBV3) load(db *db.DB) error { 192 fn := func(tx *sql.Tx) error { 193 var err error 194 d.aciinfos, err = getAllACIInfosV3(tx) 195 if err != nil { 196 return err 197 } 198 d.remotes, err = getAllRemoteV2_7(tx) 199 if err != nil { 200 return err 201 } 202 return nil 203 } 204 if err := db.Do(fn); err != nil { 205 return err 206 } 207 return nil 208 } 209 210 func (d *DBV3) compare(td testdb) bool { 211 d3, ok := td.(*DBV3) 212 if !ok { 213 return false 214 } 215 if !compareSlicesNoOrder(d.aciinfos, d3.aciinfos) { 216 return false 217 } 218 if !compareSlicesNoOrder(d.remotes, d3.remotes) { 219 return false 220 } 221 return true 222 } 223 224 type DBV4 struct { 225 aciinfos []*ACIInfoV4 226 remotes []*RemoteV2_7 227 } 228 229 func (d *DBV4) version() int { 230 return 4 231 } 232 func (d *DBV4) populate(db *db.DB) error { 233 return populateDBV4(db, d.version(), d.aciinfos, d.remotes) 234 } 235 236 func (d *DBV4) load(db *db.DB) error { 237 fn := func(tx *sql.Tx) error { 238 var err error 239 d.aciinfos, err = getAllACIInfosV4(tx) 240 if err != nil { 241 return err 242 } 243 d.remotes, err = getAllRemoteV2_7(tx) 244 if err != nil { 245 return err 246 } 247 return nil 248 } 249 if err := db.Do(fn); err != nil { 250 return err 251 } 252 return nil 253 } 254 255 func (d *DBV4) compare(td testdb) bool { 256 d4, ok := td.(*DBV4) 257 if !ok { 258 return false 259 } 260 if !compareSlicesNoOrder(d.aciinfos, d4.aciinfos) { 261 return false 262 } 263 if !compareSlicesNoOrder(d.remotes, d4.remotes) { 264 return false 265 } 266 return true 267 } 268 269 type DBV5 struct { 270 aciinfos []*ACIInfoV5 271 remotes []*RemoteV2_7 272 } 273 274 func (d *DBV5) version() int { 275 return 5 276 } 277 func (d *DBV5) populate(db *db.DB) error { 278 return populateDBV5(db, d.version(), d.aciinfos, d.remotes) 279 } 280 281 func (d *DBV5) load(db *db.DB) error { 282 fn := func(tx *sql.Tx) error { 283 var err error 284 d.aciinfos, err = getAllACIInfosV5(tx) 285 if err != nil { 286 return err 287 } 288 d.remotes, err = getAllRemoteV2_7(tx) 289 if err != nil { 290 return err 291 } 292 return nil 293 } 294 if err := db.Do(fn); err != nil { 295 return err 296 } 297 return nil 298 } 299 300 func (d *DBV5) compare(td testdb) bool { 301 d5, ok := td.(*DBV5) 302 if !ok { 303 return false 304 } 305 if !compareSlicesNoOrder(d.aciinfos, d5.aciinfos) { 306 return false 307 } 308 if !compareSlicesNoOrder(d.remotes, d5.remotes) { 309 return false 310 } 311 return true 312 } 313 314 type DBV6 struct { 315 aciinfos []*ACIInfoV6 316 remotes []*RemoteV2_7 317 } 318 319 func (d *DBV6) version() int { 320 return 6 321 } 322 func (d *DBV6) populate(db *db.DB) error { 323 return populateDBV6(db, d.version(), d.aciinfos, d.remotes) 324 } 325 326 func (d *DBV6) load(db *db.DB) error { 327 fn := func(tx *sql.Tx) error { 328 var err error 329 d.aciinfos, err = getAllACIInfosV6(tx) 330 if err != nil { 331 return err 332 } 333 d.remotes, err = getAllRemoteV2_7(tx) 334 if err != nil { 335 return err 336 } 337 return nil 338 } 339 if err := db.Do(fn); err != nil { 340 return err 341 } 342 return nil 343 } 344 345 func (d *DBV6) compare(td testdb) bool { 346 d6, ok := td.(*DBV6) 347 if !ok { 348 return false 349 } 350 if !compareSlicesNoOrder(d.aciinfos, d6.aciinfos) { 351 return false 352 } 353 if !compareSlicesNoOrder(d.remotes, d6.remotes) { 354 return false 355 } 356 return true 357 } 358 359 type DBV7 struct { 360 aciinfos []*ACIInfoV7 361 remotes []*RemoteV2_7 362 } 363 364 func (d *DBV7) version() int { 365 return 7 366 } 367 func (d *DBV7) populate(db *db.DB) error { 368 return populateDBV7(db, d.version(), d.aciinfos, d.remotes) 369 } 370 371 func (d *DBV7) load(db *db.DB) error { 372 fn := func(tx *sql.Tx) error { 373 var err error 374 d.aciinfos, err = getAllACIInfosV7(tx) 375 if err != nil { 376 return err 377 } 378 d.remotes, err = getAllRemoteV2_7(tx) 379 if err != nil { 380 return err 381 } 382 return nil 383 } 384 if err := db.Do(fn); err != nil { 385 return err 386 } 387 return nil 388 } 389 390 func (d *DBV7) compare(td testdb) bool { 391 d7, ok := td.(*DBV7) 392 if !ok { 393 return false 394 } 395 if !compareSlicesNoOrder(d.aciinfos, d7.aciinfos) { 396 return false 397 } 398 if !compareSlicesNoOrder(d.remotes, d7.remotes) { 399 return false 400 } 401 return true 402 } 403 404 // The ACIInfo struct for different db versions. The ending VX_Y represent the 405 // first and the last version where the format isn't changed 406 // The latest existing struct should be updated when updating the db version 407 // without changing the struct format (ex. V0_1 to V0_2). 408 // A new struct and its relative function should be added if the format is changed. 409 // The same applies for all of the other structs. 410 type ACIInfoV0_2 struct { 411 BlobKey string 412 AppName string 413 ImportTime time.Time 414 Latest bool 415 } 416 417 type ACIInfoV3 struct { 418 BlobKey string 419 Name string 420 ImportTime time.Time 421 Latest bool 422 } 423 424 type ACIInfoV4 struct { 425 BlobKey string 426 Name string 427 ImportTime time.Time 428 LastUsed time.Time 429 Latest bool 430 } 431 432 type ACIInfoV5 struct { 433 BlobKey string 434 Name string 435 ImportTime time.Time 436 LastUsed time.Time 437 Latest bool 438 Size int64 439 TreeStoreSize int64 440 } 441 442 type ACIInfoV6 struct { 443 BlobKey string 444 Name string 445 ImportTime time.Time 446 LastUsed time.Time 447 Latest bool 448 Size int64 449 TreeStoreSize int64 450 InsecureOptions int64 451 } 452 453 type ACIInfoV7 struct { 454 BlobKey string 455 Name string 456 ImportTime time.Time 457 LastUsed time.Time 458 Latest bool 459 Size int64 460 TreeStoreSize int64 461 } 462 463 func getAllACIInfosV0_2(tx *sql.Tx) ([]*ACIInfoV0_2, error) { 464 var aciinfos []*ACIInfoV0_2 465 rows, err := tx.Query("SELECT * FROM aciinfo") 466 if err != nil { 467 return nil, err 468 } 469 defer rows.Close() 470 for rows.Next() { 471 aciinfo := &ACIInfoV0_2{} 472 if err := rows.Scan(&aciinfo.BlobKey, &aciinfo.AppName, &aciinfo.ImportTime, &aciinfo.Latest); err != nil { 473 return nil, err 474 } 475 aciinfos = append(aciinfos, aciinfo) 476 } 477 if err := rows.Err(); err != nil { 478 return nil, err 479 } 480 return aciinfos, nil 481 } 482 483 func getAllACIInfosV3(tx *sql.Tx) ([]*ACIInfoV3, error) { 484 var aciinfos []*ACIInfoV3 485 rows, err := tx.Query("SELECT * FROM aciinfo") 486 if err != nil { 487 return nil, err 488 } 489 defer rows.Close() 490 for rows.Next() { 491 aciinfo := &ACIInfoV3{} 492 if rows.Scan(&aciinfo.BlobKey, &aciinfo.Name, &aciinfo.ImportTime, &aciinfo.Latest); err != nil { 493 return nil, err 494 } 495 aciinfos = append(aciinfos, aciinfo) 496 } 497 if err := rows.Err(); err != nil { 498 return nil, err 499 } 500 return aciinfos, nil 501 } 502 503 func getAllACIInfosV4(tx *sql.Tx) ([]*ACIInfoV4, error) { 504 var aciinfos []*ACIInfoV4 505 rows, err := tx.Query("SELECT * FROM aciinfo") 506 if err != nil { 507 return nil, err 508 } 509 defer rows.Close() 510 for rows.Next() { 511 aciinfo := &ACIInfoV4{} 512 if rows.Scan(&aciinfo.BlobKey, &aciinfo.Name, &aciinfo.ImportTime, &aciinfo.LastUsed, &aciinfo.Latest); err != nil { 513 return nil, err 514 } 515 aciinfos = append(aciinfos, aciinfo) 516 } 517 if err := rows.Err(); err != nil { 518 return nil, err 519 } 520 return aciinfos, nil 521 } 522 523 func getAllACIInfosV5(tx *sql.Tx) ([]*ACIInfoV5, error) { 524 var aciinfos []*ACIInfoV5 525 rows, err := tx.Query("SELECT * FROM aciinfo") 526 if err != nil { 527 return nil, err 528 } 529 defer rows.Close() 530 for rows.Next() { 531 aciinfo := &ACIInfoV5{} 532 if rows.Scan(&aciinfo.BlobKey, &aciinfo.Name, &aciinfo.ImportTime, &aciinfo.LastUsed, &aciinfo.Latest, &aciinfo.Size, &aciinfo.TreeStoreSize); err != nil { 533 return nil, err 534 } 535 aciinfos = append(aciinfos, aciinfo) 536 } 537 if err := rows.Err(); err != nil { 538 return nil, err 539 } 540 return aciinfos, nil 541 } 542 543 func getAllACIInfosV6(tx *sql.Tx) ([]*ACIInfoV6, error) { 544 var aciinfos []*ACIInfoV6 545 rows, err := tx.Query("SELECT * FROM aciinfo") 546 if err != nil { 547 return nil, err 548 } 549 defer rows.Close() 550 for rows.Next() { 551 aciinfo := &ACIInfoV6{} 552 if rows.Scan(&aciinfo.BlobKey, &aciinfo.Name, &aciinfo.ImportTime, &aciinfo.LastUsed, &aciinfo.Latest, &aciinfo.Size, &aciinfo.TreeStoreSize, &aciinfo.InsecureOptions); err != nil { 553 return nil, err 554 } 555 aciinfos = append(aciinfos, aciinfo) 556 } 557 if err := rows.Err(); err != nil { 558 return nil, err 559 } 560 return aciinfos, nil 561 } 562 563 func getAllACIInfosV7(tx *sql.Tx) ([]*ACIInfoV7, error) { 564 var aciinfos []*ACIInfoV7 565 rows, err := tx.Query("SELECT * FROM aciinfo") 566 if err != nil { 567 return nil, err 568 } 569 defer rows.Close() 570 for rows.Next() { 571 aciinfo := &ACIInfoV7{} 572 if rows.Scan(&aciinfo.BlobKey, &aciinfo.Name, &aciinfo.ImportTime, &aciinfo.LastUsed, &aciinfo.Latest, &aciinfo.Size, &aciinfo.TreeStoreSize); err != nil { 573 return nil, err 574 } 575 aciinfos = append(aciinfos, aciinfo) 576 } 577 if err := rows.Err(); err != nil { 578 return nil, err 579 } 580 return aciinfos, nil 581 } 582 583 type RemoteV0_1 struct { 584 ACIURL string 585 SigURL string 586 ETag string 587 BlobKey string 588 } 589 590 func getAllRemoteV0_1(tx *sql.Tx) ([]*RemoteV0_1, error) { 591 var remotes []*RemoteV0_1 592 rows, err := tx.Query("SELECT * FROM remote") 593 if err != nil { 594 return nil, err 595 } 596 defer rows.Close() 597 for rows.Next() { 598 remote := &RemoteV0_1{} 599 if err := rows.Scan(&remote.ACIURL, &remote.SigURL, &remote.ETag, &remote.BlobKey); err != nil { 600 return nil, err 601 } 602 remotes = append(remotes, remote) 603 } 604 if err := rows.Err(); err != nil { 605 return nil, err 606 } 607 return remotes, nil 608 } 609 610 type RemoteV2_7 struct { 611 ACIURL string 612 SigURL string 613 ETag string 614 BlobKey string 615 CacheMaxAge int 616 DownloadTime time.Time 617 } 618 619 func getAllRemoteV2_7(tx *sql.Tx) ([]*RemoteV2_7, error) { 620 var remotes []*RemoteV2_7 621 rows, err := tx.Query("SELECT * FROM remote") 622 if err != nil { 623 return nil, err 624 } 625 defer rows.Close() 626 for rows.Next() { 627 remote := &RemoteV2_7{} 628 if err := rows.Scan(&remote.ACIURL, &remote.SigURL, &remote.ETag, &remote.BlobKey, &remote.CacheMaxAge, &remote.DownloadTime); err != nil { 629 return nil, err 630 } 631 remotes = append(remotes, remote) 632 } 633 if err := rows.Err(); err != nil { 634 return nil, err 635 } 636 return remotes, nil 637 } 638 639 func populateDBV0_1(db *db.DB, dbVersion int, aciInfos []*ACIInfoV0_2, remotes []*RemoteV0_1) error { 640 var dbCreateStmts = [...]string{ 641 // version table 642 "CREATE TABLE IF NOT EXISTS version (version int);", 643 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 644 645 // remote table. The primary key is "aciurl". 646 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string);", 647 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 648 649 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 650 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, appname string, importtime time, latest bool);", 651 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 652 "CREATE INDEX IF NOT EXISTS appnameidx ON aciinfo (appname)", 653 } 654 655 fn := func(tx *sql.Tx) error { 656 for _, stmt := range dbCreateStmts { 657 _, err := tx.Exec(stmt) 658 if err != nil { 659 return err 660 } 661 } 662 return nil 663 } 664 if err := db.Do(fn); err != nil { 665 return err 666 } 667 668 fn = func(tx *sql.Tx) error { 669 for _, aciinfo := range aciInfos { 670 _, err := tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.AppName, aciinfo.ImportTime, aciinfo.Latest) 671 if err != nil { 672 return err 673 } 674 } 675 return nil 676 } 677 if err := db.Do(fn); err != nil { 678 return err 679 } 680 681 fn = func(tx *sql.Tx) error { 682 for _, remote := range remotes { 683 _, err := tx.Exec("INSERT into remote values ($1, $2, $3, $4)", remote.ACIURL, remote.SigURL, remote.ETag, remote.BlobKey) 684 if err != nil { 685 return err 686 } 687 } 688 return nil 689 } 690 if err := db.Do(fn); err != nil { 691 return err 692 } 693 694 return nil 695 } 696 697 func populateDBV2(db *db.DB, dbVersion int, aciInfos []*ACIInfoV0_2, remotes []*RemoteV2_7) error { 698 var dbCreateStmts = [...]string{ 699 // version table 700 "CREATE TABLE IF NOT EXISTS version (version int);", 701 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 702 703 // remote table. The primary key is "aciurl". 704 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 705 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 706 707 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 708 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, appname string, importtime time, latest bool);", 709 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 710 "CREATE INDEX IF NOT EXISTS appnameidx ON aciinfo (appname)", 711 } 712 713 fn := func(tx *sql.Tx) error { 714 for _, stmt := range dbCreateStmts { 715 _, err := tx.Exec(stmt) 716 if err != nil { 717 return err 718 } 719 } 720 return nil 721 } 722 if err := db.Do(fn); err != nil { 723 return err 724 } 725 726 fn = func(tx *sql.Tx) error { 727 for _, aciinfo := range aciInfos { 728 _, err := tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.AppName, aciinfo.ImportTime, aciinfo.Latest) 729 if err != nil { 730 return err 731 } 732 } 733 return nil 734 } 735 if err := db.Do(fn); err != nil { 736 return err 737 } 738 739 fn = func(tx *sql.Tx) error { 740 for _, remote := range remotes { 741 _, 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) 742 if err != nil { 743 return err 744 } 745 } 746 return nil 747 } 748 if err := db.Do(fn); err != nil { 749 return err 750 } 751 752 return nil 753 } 754 755 func populateDBV3(db *db.DB, dbVersion int, aciInfos []*ACIInfoV3, remotes []*RemoteV2_7) error { 756 var dbCreateStmts = [...]string{ 757 // version table 758 "CREATE TABLE IF NOT EXISTS version (version int);", 759 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 760 761 // remote table. The primary key is "aciurl". 762 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 763 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 764 765 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 766 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, importtime time, latest bool, name string);", 767 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 768 "CREATE INDEX IF NOT EXISTS nameidx ON aciinfo (name)", 769 } 770 fn := func(tx *sql.Tx) error { 771 for _, stmt := range dbCreateStmts { 772 _, err := tx.Exec(stmt) 773 if err != nil { 774 return err 775 } 776 } 777 return nil 778 } 779 if err := db.Do(fn); err != nil { 780 return err 781 } 782 783 fn = func(tx *sql.Tx) error { 784 for _, aciinfo := range aciInfos { 785 _, err := tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.ImportTime, aciinfo.Latest, aciinfo.Name) 786 if err != nil { 787 return err 788 } 789 } 790 return nil 791 } 792 if err := db.Do(fn); err != nil { 793 return err 794 } 795 796 fn = func(tx *sql.Tx) error { 797 for _, remote := range remotes { 798 _, 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) 799 if err != nil { 800 return err 801 } 802 } 803 return nil 804 } 805 if err := db.Do(fn); err != nil { 806 return err 807 } 808 809 return nil 810 } 811 812 func populateDBV4(db *db.DB, dbVersion int, aciInfos []*ACIInfoV4, remotes []*RemoteV2_7) error { 813 var dbCreateStmts = [...]string{ 814 // version table 815 "CREATE TABLE IF NOT EXISTS version (version int);", 816 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 817 818 // remote table. The primary key is "aciurl". 819 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 820 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 821 822 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 823 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, name string, importtime time, lastusedtime time, latest bool);", 824 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 825 "CREATE INDEX IF NOT EXISTS nameidx ON aciinfo (name)", 826 } 827 fn := func(tx *sql.Tx) error { 828 for _, stmt := range dbCreateStmts { 829 _, err := tx.Exec(stmt) 830 if err != nil { 831 return err 832 } 833 } 834 return nil 835 } 836 if err := db.Do(fn); err != nil { 837 return err 838 } 839 840 fn = func(tx *sql.Tx) error { 841 for _, aciinfo := range aciInfos { 842 _, err := tx.Exec("INSERT INTO aciinfo (blobkey, name, importtime, lastusedtime, latest) VALUES ($1, $2, $3, $4, $5)", aciinfo.BlobKey, aciinfo.Name, aciinfo.ImportTime, aciinfo.LastUsed, aciinfo.Latest) 843 if err != nil { 844 return err 845 } 846 } 847 return nil 848 } 849 if err := db.Do(fn); err != nil { 850 return err 851 } 852 853 fn = func(tx *sql.Tx) error { 854 for _, remote := range remotes { 855 _, 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) 856 if err != nil { 857 return err 858 } 859 } 860 return nil 861 } 862 if err := db.Do(fn); err != nil { 863 return err 864 } 865 866 return nil 867 } 868 869 func populateDBV5(db *db.DB, dbVersion int, aciInfos []*ACIInfoV5, remotes []*RemoteV2_7) error { 870 var dbCreateStmts = [...]string{ 871 // version table 872 "CREATE TABLE IF NOT EXISTS version (version int);", 873 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 874 875 // remote table. The primary key is "aciurl". 876 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 877 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 878 879 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 880 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, name string, importtime time, lastused time, latest bool, size int64, treestoresize int64);", 881 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 882 "CREATE INDEX IF NOT EXISTS nameidx ON aciinfo (name)", 883 } 884 fn := func(tx *sql.Tx) error { 885 for _, stmt := range dbCreateStmts { 886 _, err := tx.Exec(stmt) 887 if err != nil { 888 return err 889 } 890 } 891 return nil 892 } 893 if err := db.Do(fn); err != nil { 894 return err 895 } 896 897 fn = func(tx *sql.Tx) error { 898 for _, aciinfo := range aciInfos { 899 _, err := tx.Exec("INSERT INTO aciinfo (blobkey, name, importtime, lastused, latest, size, treestoresize) VALUES ($1, $2, $3, $4, $5, $6, $7)", aciinfo.BlobKey, aciinfo.Name, aciinfo.ImportTime, aciinfo.LastUsed, aciinfo.Latest, aciinfo.Size, aciinfo.TreeStoreSize) 900 if err != nil { 901 return err 902 } 903 } 904 return nil 905 } 906 if err := db.Do(fn); err != nil { 907 return err 908 } 909 910 fn = func(tx *sql.Tx) error { 911 for _, remote := range remotes { 912 _, 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) 913 if err != nil { 914 return err 915 } 916 } 917 return nil 918 } 919 if err := db.Do(fn); err != nil { 920 return err 921 } 922 923 return nil 924 } 925 926 func populateDBV6(db *db.DB, dbVersion int, aciInfos []*ACIInfoV6, remotes []*RemoteV2_7) error { 927 var dbCreateStmts = [...]string{ 928 // version table 929 "CREATE TABLE IF NOT EXISTS version (version int);", 930 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 931 932 // remote table. The primary key is "aciurl". 933 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 934 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 935 936 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 937 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, name string, importtime time, lastused time, latest bool, size int64, treestoresize int64, insecureoptions int64 DEFAULT 0);", 938 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 939 "CREATE INDEX IF NOT EXISTS nameidx ON aciinfo (name)", 940 } 941 fn := func(tx *sql.Tx) error { 942 for _, stmt := range dbCreateStmts { 943 _, err := tx.Exec(stmt) 944 if err != nil { 945 return err 946 } 947 } 948 return nil 949 } 950 if err := db.Do(fn); err != nil { 951 return err 952 } 953 954 fn = func(tx *sql.Tx) error { 955 for _, aciinfo := range aciInfos { 956 _, err := tx.Exec("INSERT INTO aciinfo (blobkey, name, importtime, lastused, latest, size, treestoresize, insecureoptions) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", aciinfo.BlobKey, aciinfo.Name, aciinfo.ImportTime, aciinfo.LastUsed, aciinfo.Latest, aciinfo.Size, aciinfo.TreeStoreSize, aciinfo.InsecureOptions) 957 if err != nil { 958 return err 959 } 960 } 961 return nil 962 } 963 if err := db.Do(fn); err != nil { 964 return err 965 } 966 967 fn = func(tx *sql.Tx) error { 968 for _, remote := range remotes { 969 _, 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) 970 if err != nil { 971 return err 972 } 973 } 974 return nil 975 } 976 if err := db.Do(fn); err != nil { 977 return err 978 } 979 980 return nil 981 } 982 983 func populateDBV7(db *db.DB, dbVersion int, aciInfos []*ACIInfoV7, remotes []*RemoteV2_7) error { 984 var dbCreateStmts = [...]string{ 985 // version table 986 "CREATE TABLE IF NOT EXISTS version (version int);", 987 fmt.Sprintf("INSERT INTO version VALUES (%d)", dbVersion), 988 989 // remote table. The primary key is "aciurl". 990 "CREATE TABLE IF NOT EXISTS remote (aciurl string, sigurl string, etag string, blobkey string, cachemaxage int, downloadtime time);", 991 "CREATE UNIQUE INDEX IF NOT EXISTS aciurlidx ON remote (aciurl)", 992 993 // aciinfo table. The primary key is "blobkey" and it matches the key used to save that aci in the blob store 994 "CREATE TABLE IF NOT EXISTS aciinfo (blobkey string, name string, importtime time, lastused time, latest bool, size int64, treestoresize int64);", 995 "CREATE UNIQUE INDEX IF NOT EXISTS blobkeyidx ON aciinfo (blobkey)", 996 "CREATE INDEX IF NOT EXISTS nameidx ON aciinfo (name)", 997 } 998 fn := func(tx *sql.Tx) error { 999 for _, stmt := range dbCreateStmts { 1000 _, err := tx.Exec(stmt) 1001 if err != nil { 1002 return err 1003 } 1004 } 1005 return nil 1006 } 1007 if err := db.Do(fn); err != nil { 1008 return err 1009 } 1010 1011 fn = func(tx *sql.Tx) error { 1012 for _, aciinfo := range aciInfos { 1013 _, err := tx.Exec("INSERT INTO aciinfo (blobkey, name, importtime, lastused, latest, size, treestoresize) VALUES ($1, $2, $3, $4, $5, $6, $7)", aciinfo.BlobKey, aciinfo.Name, aciinfo.ImportTime, aciinfo.LastUsed, aciinfo.Latest, aciinfo.Size, aciinfo.TreeStoreSize) 1014 if err != nil { 1015 return err 1016 } 1017 } 1018 return nil 1019 } 1020 if err := db.Do(fn); err != nil { 1021 return err 1022 } 1023 1024 fn = func(tx *sql.Tx) error { 1025 for _, remote := range remotes { 1026 _, 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) 1027 if err != nil { 1028 return err 1029 } 1030 } 1031 return nil 1032 } 1033 if err := db.Do(fn); err != nil { 1034 return err 1035 } 1036 1037 return nil 1038 } 1039 1040 type migrateTest struct { 1041 predb testdb 1042 postdb testdb 1043 // Needed to have the right DB type to load from 1044 curdb testdb 1045 } 1046 1047 func testMigrate(tt migrateTest) error { 1048 dir, err := ioutil.TempDir("", tstprefix) 1049 if err != nil { 1050 return fmt.Errorf("error creating tempdir: %v", err) 1051 } 1052 defer os.RemoveAll(dir) 1053 1054 storeDir := filepath.Join(dir, "store") 1055 db, err := db.NewDB(filepath.Join(storeDir, "db")) 1056 if err != nil { 1057 return err 1058 } 1059 if err = tt.predb.populate(db); err != nil { 1060 return err 1061 } 1062 1063 fn := func(tx *sql.Tx) error { 1064 err := migrate(tx, tt.postdb.version()) 1065 if err != nil { 1066 return err 1067 } 1068 return nil 1069 } 1070 if err = db.Do(fn); err != nil { 1071 return err 1072 } 1073 1074 var curDBVersion int 1075 fn = func(tx *sql.Tx) error { 1076 var err error 1077 curDBVersion, err = getDBVersion(tx) 1078 if err != nil { 1079 return err 1080 } 1081 return nil 1082 } 1083 if err = db.Do(fn); err != nil { 1084 return err 1085 } 1086 if curDBVersion != tt.postdb.version() { 1087 return fmt.Errorf("wrong db version: got %#v, want %#v", curDBVersion, tt.postdb.version()) 1088 } 1089 1090 if err := tt.curdb.load(db); err != nil { 1091 return err 1092 } 1093 1094 if !tt.curdb.compare(tt.postdb) { 1095 return spew.Errorf("while comparing DBs:\n\tgot %#v\n\twant %#v\n", tt.curdb, tt.postdb) 1096 } 1097 1098 return nil 1099 } 1100 1101 func TestMigrate(t *testing.T) { 1102 dir, err := ioutil.TempDir("", tstprefix) 1103 if err != nil { 1104 t.Fatalf("error creating tempdir: %v", err) 1105 } 1106 defer os.RemoveAll(dir) 1107 1108 blobKeys := []string{ 1109 "sha512-aaaaaaaa", 1110 "sha512-bbbbbbbb", 1111 } 1112 1113 names := []string{ 1114 "example.com/app01", 1115 "example.com/app02", 1116 } 1117 1118 sizes := []int64{ 1119 0, 1120 2 << 8, 1121 } 1122 1123 treeStoreSizes := []int64{ 1124 0, 1125 2 << 8, 1126 2 << 16, 1127 } 1128 1129 insecureOptions := []int64{ 1130 0, 1131 2, 1132 } 1133 1134 now := time.Now().UTC() 1135 1136 // Injects a fake clock to store pkg clock 1137 clock = clockwork.NewFakeClockAt(now) 1138 1139 tests := []migrateTest{ 1140 { 1141 // Test migration from V0 to V1 with an empty database 1142 &DBV0{}, 1143 &DBV1{}, 1144 &DBV1{}, 1145 }, 1146 1147 { 1148 // Test migration from V0 to V1 1149 &DBV0{ 1150 []*ACIInfoV0_2{ 1151 {blobKeys[0], names[0], now, false}, 1152 {blobKeys[1], names[1], now, true}, 1153 }, 1154 []*RemoteV0_1{ 1155 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0]}, 1156 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1]}, 1157 }, 1158 }, 1159 &DBV1{ 1160 []*ACIInfoV0_2{ 1161 {blobKeys[0], names[0], now, false}, 1162 {blobKeys[1], names[1], now, true}, 1163 }, 1164 []*RemoteV0_1{ 1165 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0]}, 1166 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1]}, 1167 }, 1168 }, 1169 &DBV1{}, 1170 }, 1171 1172 { 1173 // Test migration from V1 to V2 1174 &DBV1{ 1175 []*ACIInfoV0_2{ 1176 {blobKeys[0], "example.com/app01", now, false}, 1177 {blobKeys[1], "example.com/app02", now, true}, 1178 }, 1179 []*RemoteV0_1{ 1180 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0]}, 1181 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1]}, 1182 }, 1183 }, 1184 &DBV2{ 1185 []*ACIInfoV0_2{ 1186 {blobKeys[0], "example.com/app01", now, false}, 1187 {blobKeys[1], "example.com/app02", now, true}, 1188 }, 1189 []*RemoteV2_7{ 1190 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1191 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1192 }, 1193 }, 1194 &DBV2{}, 1195 }, 1196 1197 { 1198 // Test migration from V2 to V3 1199 &DBV2{ 1200 []*ACIInfoV0_2{ 1201 {blobKeys[0], "example.com/app01", now, false}, 1202 {blobKeys[1], "example.com/app02", now, true}, 1203 }, 1204 []*RemoteV2_7{ 1205 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1206 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1207 }, 1208 }, 1209 &DBV3{ 1210 []*ACIInfoV3{ 1211 {blobKeys[0], "example.com/app01", now, false}, 1212 {blobKeys[1], "example.com/app02", now, true}, 1213 }, 1214 []*RemoteV2_7{ 1215 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1216 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1217 }, 1218 }, 1219 &DBV3{}, 1220 }, 1221 1222 { 1223 // Test migration from V3 to V4 1224 &DBV3{ 1225 []*ACIInfoV3{ 1226 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, Latest: false}, 1227 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, Latest: true}, 1228 }, 1229 []*RemoteV2_7{ 1230 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1231 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1232 }, 1233 }, 1234 &DBV4{ 1235 []*ACIInfoV4{ 1236 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false}, 1237 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true}, 1238 }, 1239 []*RemoteV2_7{ 1240 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1241 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1242 }, 1243 }, 1244 &DBV4{}, 1245 }, 1246 1247 { 1248 // Test migration from V4 to V5 1249 &DBV4{ 1250 []*ACIInfoV4{ 1251 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false}, 1252 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true}, 1253 }, 1254 []*RemoteV2_7{ 1255 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1256 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1257 }, 1258 }, 1259 &DBV5{ 1260 []*ACIInfoV5{ 1261 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false}, 1262 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true}, 1263 }, 1264 []*RemoteV2_7{ 1265 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1266 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1267 }, 1268 }, 1269 &DBV5{}, 1270 }, 1271 1272 { 1273 // Test migration from V5 to V6 1274 &DBV5{ 1275 []*ACIInfoV5{ 1276 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false, Size: sizes[0], TreeStoreSize: treeStoreSizes[0]}, 1277 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true, Size: sizes[1], TreeStoreSize: treeStoreSizes[1]}, 1278 }, 1279 []*RemoteV2_7{ 1280 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1281 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1282 }, 1283 }, 1284 &DBV6{ 1285 []*ACIInfoV6{ 1286 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false, Size: sizes[0], TreeStoreSize: treeStoreSizes[0]}, 1287 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true, Size: sizes[1], TreeStoreSize: treeStoreSizes[1]}, 1288 }, 1289 []*RemoteV2_7{ 1290 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1291 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1292 }, 1293 }, 1294 &DBV6{}, 1295 }, 1296 { 1297 // Test migration from V6 to V7 1298 &DBV6{ 1299 []*ACIInfoV6{ 1300 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false, Size: sizes[0], TreeStoreSize: treeStoreSizes[0], InsecureOptions: insecureOptions[0]}, 1301 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true, Size: sizes[1], TreeStoreSize: treeStoreSizes[1], InsecureOptions: insecureOptions[1]}, 1302 }, 1303 []*RemoteV2_7{ 1304 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1305 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1306 }, 1307 }, 1308 &DBV7{ 1309 []*ACIInfoV7{ 1310 {BlobKey: blobKeys[0], Name: names[0], ImportTime: now, LastUsed: now, Latest: false, Size: sizes[0], TreeStoreSize: treeStoreSizes[0]}, 1311 {BlobKey: blobKeys[1], Name: names[1], ImportTime: now, LastUsed: now, Latest: true, Size: sizes[1], TreeStoreSize: treeStoreSizes[1]}, 1312 }, 1313 []*RemoteV2_7{ 1314 {"http://example.com/app01.aci", "http://example.com/app01.aci.asc", "", blobKeys[0], 0, time.Time{}.UTC()}, 1315 {"http://example.com/app02.aci", "http://example.com/app02.aci.asc", "", blobKeys[1], 0, time.Time{}.UTC()}, 1316 }, 1317 }, 1318 &DBV7{}, 1319 }, 1320 } 1321 1322 for i, tt := range tests { 1323 if err := testMigrate(tt); err != nil { 1324 t.Errorf("#%d: unexpected error: %v", i, err) 1325 } 1326 } 1327 } 1328 1329 // compareSlices compare slices regardless of the slice elements order 1330 func compareSlicesNoOrder(i1 interface{}, i2 interface{}) bool { 1331 s1 := interfaceToSlice(i1) 1332 s2 := interfaceToSlice(i2) 1333 1334 if len(s1) != len(s2) { 1335 return false 1336 } 1337 1338 seen := map[int]bool{} 1339 for _, v1 := range s1 { 1340 found := false 1341 for i2, v2 := range s2 { 1342 if _, ok := seen[i2]; ok { 1343 continue 1344 } 1345 1346 if reflect.DeepEqual(v1, v2) { 1347 found = true 1348 seen[i2] = true 1349 continue 1350 } 1351 } 1352 1353 if !found { 1354 return false 1355 } 1356 1357 } 1358 return true 1359 } 1360 1361 func interfaceToSlice(s interface{}) []interface{} { 1362 v := reflect.ValueOf(s) 1363 if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { 1364 panic(fmt.Errorf("Expected slice or array, got %T", s)) 1365 } 1366 l := v.Len() 1367 m := make([]interface{}, l) 1368 for i := 0; i < l; i++ { 1369 m[i] = v.Index(i).Interface() 1370 } 1371 return m 1372 }