mirror of
https://github.com/clearlinux/rkt.git
synced 2026-09-03 20:31:35 +00:00
559 lines
13 KiB
Go
559 lines
13 KiB
Go
// Copyright 2014 The rkt Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package store
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/coreos/rkt/pkg/aci"
|
|
"github.com/coreos/rkt/pkg/multicall"
|
|
"github.com/coreos/rkt/pkg/sys"
|
|
|
|
"github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/types"
|
|
)
|
|
|
|
const tstprefix = "store-test"
|
|
|
|
func init() {
|
|
multicall.MaybeExec()
|
|
}
|
|
|
|
func TestBlobStore(t *testing.T) {
|
|
dir, err := ioutil.TempDir("", tstprefix)
|
|
if err != nil {
|
|
t.Fatalf("error creating tempdir: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
s, err := NewStore(dir)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
for _, valueStr := range []string{
|
|
"I am a manually placed object",
|
|
} {
|
|
s.stores[blobType].Write(types.NewHashSHA512([]byte(valueStr)).String(), []byte(valueStr))
|
|
}
|
|
|
|
s.Dump(false)
|
|
}
|
|
|
|
func TestResolveKey(t *testing.T) {
|
|
dir, err := ioutil.TempDir("", tstprefix)
|
|
if err != nil {
|
|
t.Fatalf("error creating tempdir: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
s, err := NewStore(dir)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Return a hash key buffer from a hex string
|
|
str2key := func(s string) *bytes.Buffer {
|
|
k, _ := hex.DecodeString(s)
|
|
return bytes.NewBufferString(keyToString(k))
|
|
}
|
|
|
|
// Set up store (use key == data for simplicity)
|
|
data := []*bytes.Buffer{
|
|
str2key("12345678900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
|
str2key("abcdefabc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
|
str2key("abcabcabc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
|
str2key("abc01234500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
|
str2key("67147019a5b56f5e2ee01e989a8aa4787f56b8445960be2d8678391cf111009bc0780f31001fd181a2b61507547aee4caa44cda4b8bdb238d0e4ba830069ed2c"),
|
|
}
|
|
for _, d := range data {
|
|
// Save aciinfo
|
|
err := s.db.Do(func(tx *sql.Tx) error {
|
|
aciinfo := &ACIInfo{
|
|
BlobKey: d.String(),
|
|
AppName: "example.com/app",
|
|
ImportTime: time.Now(),
|
|
}
|
|
return WriteACIInfo(tx, aciinfo)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("error writing to store: %v", err)
|
|
}
|
|
}
|
|
|
|
// Full key already - should return short version of the full key
|
|
fkl := "sha512-67147019a5b56f5e2ee01e989a8aa4787f56b8445960be2d8678391cf111009bc0780f31001fd181a2b61507547aee4caa44cda4b8bdb238d0e4ba830069ed2c"
|
|
fks := "sha512-67147019a5b56f5e2ee01e989a8aa4787f56b8445960be2d8678391cf111009b"
|
|
for _, k := range []string{fkl, fks} {
|
|
key, err := s.ResolveKey(k)
|
|
if key != fks {
|
|
t.Errorf("expected ResolveKey to return unaltered short key, but got %q", key)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("expected err=nil, got %v", err)
|
|
}
|
|
}
|
|
|
|
// Unambiguous prefix match
|
|
k, err := s.ResolveKey("sha512-123")
|
|
if k != "sha512-1234567890000000000000000000000000000000000000000000000000000000" {
|
|
t.Errorf("expected %q, got %q", "sha512-1234567890000000000000000000000000000000000000000000000000000000", k)
|
|
}
|
|
if err != nil {
|
|
t.Errorf("expected err=nil, got %v", err)
|
|
}
|
|
|
|
// Ambiguous prefix match
|
|
k, err = s.ResolveKey("sha512-abc")
|
|
if k != "" {
|
|
t.Errorf("expected %q, got %q", "", k)
|
|
}
|
|
if err == nil {
|
|
t.Errorf("expected non-nil error!")
|
|
}
|
|
|
|
// wrong key prefix
|
|
k, err = s.ResolveKey("badprefix-1")
|
|
expectedErr := "wrong key prefix"
|
|
if err == nil {
|
|
t.Errorf("expected non-nil error!")
|
|
}
|
|
if err.Error() != expectedErr {
|
|
t.Errorf("expected err=%q, got %q", expectedErr, err)
|
|
}
|
|
|
|
// key too short
|
|
k, err = s.ResolveKey("sha512-1")
|
|
expectedErr = "key too short"
|
|
if err == nil {
|
|
t.Errorf("expected non-nil error!")
|
|
}
|
|
if err.Error() != expectedErr {
|
|
t.Errorf("expected err=%q, got %q", expectedErr, err)
|
|
}
|
|
}
|
|
|
|
func TestGetImageManifest(t *testing.T) {
|
|
dir, err := ioutil.TempDir("", tstprefix)
|
|
if err != nil {
|
|
t.Fatalf("error creating tempdir: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
s, err := NewStore(dir)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
imj := `{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.5.2",
|
|
"name": "example.com/test01"
|
|
}`
|
|
|
|
aci, err := aci.NewACI(dir, imj, nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating test tar: %v", err)
|
|
}
|
|
// Rewind the ACI
|
|
if _, err := aci.Seek(0, 0); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
key, err := s.WriteACI(aci, false)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
wanted := "example.com/test01"
|
|
im, err := s.GetImageManifest(key)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if im.Name.String() != wanted {
|
|
t.Errorf("expected im with name: %s, got: %s", wanted, im.Name.String())
|
|
}
|
|
|
|
// test unexistent key
|
|
im, err = s.GetImageManifest("sha512-aaaaaaaaaaaaaaaaa")
|
|
if err == nil {
|
|
t.Fatalf("expected non-nil error!")
|
|
}
|
|
}
|
|
|
|
func TestGetAci(t *testing.T) {
|
|
type test struct {
|
|
name types.ACName
|
|
labels types.Labels
|
|
expected int // the aci index to expect or -1 if not result expected,
|
|
}
|
|
|
|
type acidef struct {
|
|
imj string
|
|
latest bool
|
|
}
|
|
|
|
dir, err := ioutil.TempDir("", tstprefix)
|
|
if err != nil {
|
|
t.Fatalf("error creating tempdir: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
s, err := NewStore(dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
acidefs []acidef
|
|
tests []test
|
|
}{
|
|
{
|
|
[]acidef{
|
|
{
|
|
`{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.1.1",
|
|
"name": "example.com/test01"
|
|
}`,
|
|
false,
|
|
},
|
|
{
|
|
`{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.1.1",
|
|
"name": "example.com/test02",
|
|
"labels": [
|
|
{
|
|
"name": "version",
|
|
"value": "1.0.0"
|
|
}
|
|
]
|
|
}`,
|
|
true,
|
|
},
|
|
{
|
|
`{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.1.1",
|
|
"name": "example.com/test02",
|
|
"labels": [
|
|
{
|
|
"name": "version",
|
|
"value": "2.0.0"
|
|
}
|
|
]
|
|
}`,
|
|
false,
|
|
},
|
|
},
|
|
[]test{
|
|
{
|
|
"example.com/unexistentaci",
|
|
types.Labels{},
|
|
-1,
|
|
},
|
|
{
|
|
"example.com/test01",
|
|
types.Labels{},
|
|
0,
|
|
},
|
|
{
|
|
"example.com/test02",
|
|
types.Labels{
|
|
{
|
|
Name: "version",
|
|
Value: "1.0.0",
|
|
},
|
|
},
|
|
1,
|
|
},
|
|
{
|
|
"example.com/test02",
|
|
types.Labels{
|
|
{
|
|
Name: "version",
|
|
Value: "2.0.0",
|
|
},
|
|
},
|
|
2,
|
|
},
|
|
{
|
|
"example.com/test02",
|
|
types.Labels{},
|
|
1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
keys := []string{}
|
|
// Create ACIs
|
|
for _, ad := range tt.acidefs {
|
|
aci, err := aci.NewACI(dir, ad.imj, nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating test tar: %v", err)
|
|
}
|
|
|
|
// Rewind the ACI
|
|
if _, err := aci.Seek(0, 0); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
key, err := s.WriteACI(aci, ad.latest)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
keys = append(keys, key)
|
|
}
|
|
|
|
for _, test := range tt.tests {
|
|
key, err := s.GetACI(test.name, test.labels)
|
|
if test.expected == -1 {
|
|
if err == nil {
|
|
t.Fatalf("Expected no key for appName %s, got %s", test.name, key)
|
|
}
|
|
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("unexpected error on GetACI for name %s, labels: %v: %v", test.name, test.labels, err)
|
|
}
|
|
if keys[test.expected] != key {
|
|
t.Errorf("expected key: %s, got %s. GetACI with name: %s, labels: %v", key, keys[test.expected], test.name, test.labels)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTreeStore(t *testing.T) {
|
|
if !sys.HasChrootCapability() {
|
|
t.Skipf("chroot capability not available. Disabling test.")
|
|
}
|
|
|
|
dir, err := ioutil.TempDir("", tstprefix)
|
|
if err != nil {
|
|
t.Fatalf("error creating tempdir: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
s, err := NewStore(dir)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
imj := `
|
|
{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.5.2",
|
|
"name": "example.com/test01"
|
|
}
|
|
`
|
|
|
|
entries := []*aci.ACIEntry{
|
|
// An empty dir
|
|
{
|
|
Header: &tar.Header{
|
|
Name: "rootfs/a",
|
|
Typeflag: tar.TypeDir,
|
|
},
|
|
},
|
|
{
|
|
Contents: "hello",
|
|
Header: &tar.Header{
|
|
Name: "hello.txt",
|
|
Size: 5,
|
|
},
|
|
},
|
|
{
|
|
Header: &tar.Header{
|
|
Name: "rootfs/link.txt",
|
|
Linkname: "rootfs/hello.txt",
|
|
Typeflag: tar.TypeSymlink,
|
|
},
|
|
},
|
|
// dangling symlink
|
|
{
|
|
Header: &tar.Header{
|
|
Name: "rootfs/link2.txt",
|
|
Linkname: "rootfs/missingfile.txt",
|
|
Typeflag: tar.TypeSymlink,
|
|
},
|
|
},
|
|
{
|
|
Header: &tar.Header{
|
|
Name: "rootfs/fifo",
|
|
Typeflag: tar.TypeFifo,
|
|
},
|
|
},
|
|
}
|
|
aci, err := aci.NewACI(dir, imj, entries)
|
|
if err != nil {
|
|
t.Fatalf("error creating test tar: %v", err)
|
|
}
|
|
defer aci.Close()
|
|
|
|
// Rewind the ACI
|
|
if _, err := aci.Seek(0, 0); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
|
|
// Import the new ACI
|
|
key, err := s.WriteACI(aci, false)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Ask the store to render the treestore
|
|
err = s.RenderTreeStore(key, false)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify image Hash. Should be the same.
|
|
err = s.CheckTreeStore(key)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Change a file permission
|
|
rootfs := s.GetTreeStoreRootFS(key)
|
|
err = os.Chmod(filepath.Join(rootfs, "a"), 0600)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify image Hash. Should be different
|
|
err = s.CheckTreeStore(key)
|
|
if err == nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// rebuild the tree
|
|
err = s.RenderTreeStore(key, true)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Add a file
|
|
rootfs = s.GetTreeStoreRootFS(key)
|
|
err = ioutil.WriteFile(filepath.Join(rootfs, "newfile"), []byte("newfile"), 0644)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify image Hash. Should be different
|
|
err = s.CheckTreeStore(key)
|
|
if err == nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRemoveACI(t *testing.T) {
|
|
dir, err := ioutil.TempDir("", tstprefix)
|
|
if err != nil {
|
|
t.Fatalf("error creating tempdir: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
ds, err := NewStore(dir)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
imj := `{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.4.0",
|
|
"name": "example.com/test01"
|
|
}`
|
|
|
|
aciFile, err := aci.NewACI(dir, imj, nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating test tar: %v", err)
|
|
}
|
|
// Rewind the ACI
|
|
if _, err := aciFile.Seek(0, 0); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
key, err := ds.WriteACI(aciFile, false)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
aciURL := "http://example.com/test01.aci"
|
|
// Create our first Remote, and simulate Store() to create row in the table
|
|
na := NewRemote(aciURL, "")
|
|
na.BlobKey = key
|
|
ds.WriteRemote(na)
|
|
|
|
err = ds.RemoveACI(key)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify that no remote for the specified key exists
|
|
_, found, err := ds.GetRemote(aciURL)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if found {
|
|
t.Fatalf("expected to find no remote, but a remote was found")
|
|
}
|
|
|
|
// Try to remove a non-existent key
|
|
err = ds.RemoveACI("sha512-aaaaaaaaaaaaaaaaa")
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
|
|
// Simulate error removing from the
|
|
imj = `{
|
|
"acKind": "ImageManifest",
|
|
"acVersion": "0.3.0",
|
|
"name": "example.com/test01"
|
|
}`
|
|
|
|
aciFile, err = aci.NewACI(dir, imj, nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating test tar: %v", err)
|
|
}
|
|
// Rewind the ACI
|
|
if _, err := aciFile.Seek(0, 0); err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
key, err = ds.WriteACI(aciFile, false)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
aciURL = "http://example.com/test02.aci"
|
|
// Create our first Remote, and simulate Store() to create row in the table
|
|
na = NewRemote(aciURL, "")
|
|
na.BlobKey = key
|
|
ds.WriteRemote(na)
|
|
|
|
err = os.Remove(filepath.Join(dir, "cas", "blob", blockTransform(key)[0], blockTransform(key)[1], key))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
err = ds.RemoveACI(key)
|
|
if err == nil {
|
|
t.Fatalf("expected error: %v", err)
|
|
}
|
|
if _, ok := err.(*StoreRemovalError); !ok {
|
|
t.Fatalf("expected StoreRemovalError got: %v", err)
|
|
}
|
|
|
|
}
|