Initial commit

This commit is contained in:
Ria Bhatia
2017-12-04 13:32:57 -06:00
committed by Erik St. Martin
commit 0075e5b0f3
9056 changed files with 2523100 additions and 0 deletions

48
vendor/github.com/hyperhq/hypercli/layer/empty.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package layer
import (
"archive/tar"
"bytes"
"io"
"io/ioutil"
)
// DigestSHA256EmptyTar is the canonical sha256 digest of empty tar file -
// (1024 NULL bytes)
const DigestSHA256EmptyTar = DiffID("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef")
type emptyLayer struct{}
// EmptyLayer is a layer that corresponds to empty tar.
var EmptyLayer = &emptyLayer{}
func (el *emptyLayer) TarStream() (io.ReadCloser, error) {
buf := new(bytes.Buffer)
tarWriter := tar.NewWriter(buf)
tarWriter.Close()
return ioutil.NopCloser(buf), nil
}
func (el *emptyLayer) ChainID() ChainID {
return ChainID(DigestSHA256EmptyTar)
}
func (el *emptyLayer) DiffID() DiffID {
return DigestSHA256EmptyTar
}
func (el *emptyLayer) Parent() Layer {
return nil
}
func (el *emptyLayer) Size() (size int64, err error) {
return 0, nil
}
func (el *emptyLayer) DiffSize() (size int64, err error) {
return 0, nil
}
func (el *emptyLayer) Metadata() (map[string]string, error) {
return make(map[string]string), nil
}

46
vendor/github.com/hyperhq/hypercli/layer/empty_test.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
package layer
import (
"io"
"testing"
"github.com/docker/distribution/digest"
)
func TestEmptyLayer(t *testing.T) {
if EmptyLayer.ChainID() != ChainID(DigestSHA256EmptyTar) {
t.Fatal("wrong ID for empty layer")
}
if EmptyLayer.DiffID() != DigestSHA256EmptyTar {
t.Fatal("wrong DiffID for empty layer")
}
if EmptyLayer.Parent() != nil {
t.Fatal("expected no parent for empty layer")
}
if size, err := EmptyLayer.Size(); err != nil || size != 0 {
t.Fatal("expected zero size for empty layer")
}
if diffSize, err := EmptyLayer.DiffSize(); err != nil || diffSize != 0 {
t.Fatal("expected zero diffsize for empty layer")
}
tarStream, err := EmptyLayer.TarStream()
if err != nil {
t.Fatalf("error streaming tar for empty layer: %v", err)
}
digester := digest.Canonical.New()
_, err = io.Copy(digester.Hash(), tarStream)
if err != nil {
t.Fatalf("error hashing empty tar layer: %v", err)
}
if digester.Digest() != digest.Digest(DigestSHA256EmptyTar) {
t.Fatal("empty layer tar stream hashes to wrong value")
}
}

326
vendor/github.com/hyperhq/hypercli/layer/filestore.go generated vendored Normal file
View File

@@ -0,0 +1,326 @@
package layer
import (
"compress/gzip"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/hyperhq/hypercli/pkg/ioutils"
)
var (
stringIDRegexp = regexp.MustCompile(`^[a-f0-9]{64}(-init)?$`)
supportedAlgorithms = []digest.Algorithm{
digest.SHA256,
// digest.SHA384, // Currently not used
// digest.SHA512, // Currently not used
}
)
type fileMetadataStore struct {
root string
}
type fileMetadataTransaction struct {
store *fileMetadataStore
root string
}
// NewFSMetadataStore returns an instance of a metadata store
// which is backed by files on disk using the provided root
// as the root of metadata files.
func NewFSMetadataStore(root string) (MetadataStore, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, err
}
return &fileMetadataStore{
root: root,
}, nil
}
func (fms *fileMetadataStore) getLayerDirectory(layer ChainID) string {
dgst := digest.Digest(layer)
return filepath.Join(fms.root, string(dgst.Algorithm()), dgst.Hex())
}
func (fms *fileMetadataStore) getLayerFilename(layer ChainID, filename string) string {
return filepath.Join(fms.getLayerDirectory(layer), filename)
}
func (fms *fileMetadataStore) getMountDirectory(mount string) string {
return filepath.Join(fms.root, "mounts", mount)
}
func (fms *fileMetadataStore) getMountFilename(mount, filename string) string {
return filepath.Join(fms.getMountDirectory(mount), filename)
}
func (fms *fileMetadataStore) StartTransaction() (MetadataTransaction, error) {
tmpDir := filepath.Join(fms.root, "tmp")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
return nil, err
}
td, err := ioutil.TempDir(tmpDir, "layer-")
if err != nil {
return nil, err
}
// Create a new tempdir
return &fileMetadataTransaction{
store: fms,
root: td,
}, nil
}
func (fm *fileMetadataTransaction) SetSize(size int64) error {
content := fmt.Sprintf("%d", size)
return ioutil.WriteFile(filepath.Join(fm.root, "size"), []byte(content), 0644)
}
func (fm *fileMetadataTransaction) SetParent(parent ChainID) error {
return ioutil.WriteFile(filepath.Join(fm.root, "parent"), []byte(digest.Digest(parent).String()), 0644)
}
func (fm *fileMetadataTransaction) SetDiffID(diff DiffID) error {
return ioutil.WriteFile(filepath.Join(fm.root, "diff"), []byte(digest.Digest(diff).String()), 0644)
}
func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
return ioutil.WriteFile(filepath.Join(fm.root, "cache-id"), []byte(cacheID), 0644)
}
func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
f, err := os.OpenFile(filepath.Join(fm.root, "tar-split.json.gz"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
var wc io.WriteCloser
if compressInput {
wc = gzip.NewWriter(f)
} else {
wc = f
}
return ioutils.NewWriteCloserWrapper(wc, func() error {
wc.Close()
return f.Close()
}), nil
}
func (fm *fileMetadataTransaction) Commit(layer ChainID) error {
finalDir := fm.store.getLayerDirectory(layer)
if err := os.MkdirAll(filepath.Dir(finalDir), 0755); err != nil {
return err
}
return os.Rename(fm.root, finalDir)
}
func (fm *fileMetadataTransaction) Cancel() error {
return os.RemoveAll(fm.root)
}
func (fm *fileMetadataTransaction) String() string {
return fm.root
}
func (fms *fileMetadataStore) GetSize(layer ChainID) (int64, error) {
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "size"))
if err != nil {
return 0, err
}
size, err := strconv.ParseInt(string(content), 10, 64)
if err != nil {
return 0, err
}
return size, nil
}
func (fms *fileMetadataStore) GetParent(layer ChainID) (ChainID, error) {
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "parent"))
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}
return ChainID(dgst), nil
}
func (fms *fileMetadataStore) GetDiffID(layer ChainID) (DiffID, error) {
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "diff"))
if err != nil {
return "", err
}
dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}
return DiffID(dgst), nil
}
func (fms *fileMetadataStore) GetCacheID(layer ChainID) (string, error) {
contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "cache-id"))
if err != nil {
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if !stringIDRegexp.MatchString(content) {
return "", errors.New("invalid cache id value")
}
return content, nil
}
func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, error) {
fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz"))
if err != nil {
return nil, err
}
f, err := gzip.NewReader(fz)
if err != nil {
return nil, err
}
return ioutils.NewReadCloserWrapper(f, func() error {
f.Close()
return fz.Close()
}), nil
}
func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
return err
}
return ioutil.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0644)
}
func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
return err
}
return ioutil.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0644)
}
func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
return err
}
return ioutil.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0644)
}
func (fms *fileMetadataStore) GetMountID(mount string) (string, error) {
contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "mount-id"))
if err != nil {
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if !stringIDRegexp.MatchString(content) {
return "", errors.New("invalid mount id value")
}
return content, nil
}
func (fms *fileMetadataStore) GetInitID(mount string) (string, error) {
contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "init-id"))
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if !stringIDRegexp.MatchString(content) {
return "", errors.New("invalid init id value")
}
return content, nil
}
func (fms *fileMetadataStore) GetMountParent(mount string) (ChainID, error) {
content, err := ioutil.ReadFile(fms.getMountFilename(mount, "parent"))
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}
return ChainID(dgst), nil
}
func (fms *fileMetadataStore) List() ([]ChainID, []string, error) {
var ids []ChainID
for _, algorithm := range supportedAlgorithms {
fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, string(algorithm)))
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, nil, err
}
for _, fi := range fileInfos {
if fi.IsDir() && fi.Name() != "mounts" {
dgst := digest.NewDigestFromHex(string(algorithm), fi.Name())
if err := dgst.Validate(); err != nil {
logrus.Debugf("Ignoring invalid digest %s:%s", algorithm, fi.Name())
} else {
ids = append(ids, ChainID(dgst))
}
}
}
}
fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, "mounts"))
if err != nil {
if os.IsNotExist(err) {
return ids, []string{}, nil
}
return nil, nil, err
}
var mounts []string
for _, fi := range fileInfos {
if fi.IsDir() {
mounts = append(mounts, fi.Name())
}
}
return ids, mounts, nil
}
func (fms *fileMetadataStore) Remove(layer ChainID) error {
return os.RemoveAll(fms.getLayerDirectory(layer))
}
func (fms *fileMetadataStore) RemoveMount(mount string) error {
return os.RemoveAll(fms.getMountDirectory(mount))
}

View File

@@ -0,0 +1,115 @@
package layer
import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"github.com/docker/distribution/digest"
)
func randomLayerID(seed int64) ChainID {
r := rand.New(rand.NewSource(seed))
return ChainID(digest.FromBytes([]byte(fmt.Sprintf("%d", r.Int63()))))
}
func newFileMetadataStore(t *testing.T) (*fileMetadataStore, string, func()) {
td, err := ioutil.TempDir("", "layers-")
if err != nil {
t.Fatal(err)
}
fms, err := NewFSMetadataStore(td)
if err != nil {
t.Fatal(err)
}
return fms.(*fileMetadataStore), td, func() {
if err := os.RemoveAll(td); err != nil {
t.Logf("Failed to cleanup %q: %s", td, err)
}
}
}
func assertNotDirectoryError(t *testing.T, err error) {
perr, ok := err.(*os.PathError)
if !ok {
t.Fatalf("Unexpected error %#v, expected path error", err)
}
if perr.Err != syscall.ENOTDIR {
t.Fatalf("Unexpected error %s, expected %s", perr.Err, syscall.ENOTDIR)
}
}
func assertPermissionError(t *testing.T, err error) {
perr, ok := err.(*os.PathError)
if !ok {
t.Fatalf("Unexpected error %#v, expected path error", err)
}
if perr.Err != syscall.EACCES {
t.Fatalf("Unexpected error %s, expected %s", perr.Err, syscall.EACCES)
}
}
func TestCommitFailure(t *testing.T) {
fms, td, cleanup := newFileMetadataStore(t)
defer cleanup()
if err := ioutil.WriteFile(filepath.Join(td, "sha256"), []byte("was here first!"), 0644); err != nil {
t.Fatal(err)
}
tx, err := fms.StartTransaction()
if err != nil {
t.Fatal(err)
}
if err := tx.SetSize(0); err != nil {
t.Fatal(err)
}
err = tx.Commit(randomLayerID(5))
if err == nil {
t.Fatalf("Expected error committing with invalid layer parent directory")
}
assertNotDirectoryError(t, err)
}
func TestStartTransactionFailure(t *testing.T) {
fms, td, cleanup := newFileMetadataStore(t)
defer cleanup()
if err := ioutil.WriteFile(filepath.Join(td, "tmp"), []byte("was here first!"), 0644); err != nil {
t.Fatal(err)
}
_, err := fms.StartTransaction()
if err == nil {
t.Fatalf("Expected error starting transaction with invalid layer parent directory")
}
assertNotDirectoryError(t, err)
if err := os.Remove(filepath.Join(td, "tmp")); err != nil {
t.Fatal(err)
}
tx, err := fms.StartTransaction()
if err != nil {
t.Fatal(err)
}
if expected := filepath.Join(td, "tmp"); strings.HasPrefix(expected, tx.String()) {
t.Fatalf("Unexpected transaction string %q, expected prefix %q", tx.String(), expected)
}
if err := tx.Cancel(); err != nil {
t.Fatal(err)
}
}

256
vendor/github.com/hyperhq/hypercli/layer/layer.go generated vendored Normal file
View File

@@ -0,0 +1,256 @@
// Package layer is package for managing read only
// and read-write mounts on the union file system
// driver. Read-only mounts are referenced using a
// content hash and are protected from mutation in
// the exposed interface. The tar format is used
// to create read only layers and export both
// read only and writable layers. The exported
// tar data for a read only layer should match
// the tar used to create the layer.
package layer
import (
"errors"
"io"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/hyperhq/hypercli/pkg/archive"
)
var (
// ErrLayerDoesNotExist is used when an operation is
// attempted on a layer which does not exist.
ErrLayerDoesNotExist = errors.New("layer does not exist")
// ErrLayerNotRetained is used when a release is
// attempted on a layer which is not retained.
ErrLayerNotRetained = errors.New("layer not retained")
// ErrMountDoesNotExist is used when an operation is
// attempted on a mount layer which does not exist.
ErrMountDoesNotExist = errors.New("mount does not exist")
// ErrMountNameConflict is used when a mount is attempted
// to be created but there is already a mount with the name
// used for creation.
ErrMountNameConflict = errors.New("mount already exists with name")
// ErrActiveMount is used when an operation on a
// mount is attempted but the layer is still
// mounted and the operation cannot be performed.
ErrActiveMount = errors.New("mount still active")
// ErrNotMounted is used when requesting an active
// mount but the layer is not mounted.
ErrNotMounted = errors.New("not mounted")
// ErrMaxDepthExceeded is used when a layer is attempted
// to be created which would result in a layer depth
// greater than the 125 max.
ErrMaxDepthExceeded = errors.New("max depth exceeded")
)
// ChainID is the content-addressable ID of a layer.
type ChainID digest.Digest
// String returns a string rendition of a layer ID
func (id ChainID) String() string {
return string(id)
}
// DiffID is the hash of an individual layer tar.
type DiffID digest.Digest
// String returns a string rendition of a layer DiffID
func (diffID DiffID) String() string {
return string(diffID)
}
// TarStreamer represents an object which may
// have its contents exported as a tar stream.
type TarStreamer interface {
// TarStream returns a tar archive stream
// for the contents of a layer.
TarStream() (io.ReadCloser, error)
}
// Layer represents a read only layer
type Layer interface {
TarStreamer
// ChainID returns the content hash of the entire layer chain. The hash
// chain is made up of DiffID of top layer and all of its parents.
ChainID() ChainID
// DiffID returns the content hash of the layer
// tar stream used to create this layer.
DiffID() DiffID
// Parent returns the next layer in the layer chain.
Parent() Layer
// Size returns the size of the entire layer chain. The size
// is calculated from the total size of all files in the layers.
Size() (int64, error)
// DiffSize returns the size difference of the top layer
// from parent layer.
DiffSize() (int64, error)
// Metadata returns the low level storage metadata associated
// with layer.
Metadata() (map[string]string, error)
}
// RWLayer represents a layer which is
// read and writable
type RWLayer interface {
TarStreamer
// Name of mounted layer
Name() string
// Parent returns the layer which the writable
// layer was created from.
Parent() Layer
// Mount mounts the RWLayer and returns the filesystem path
// the to the writable layer.
Mount(mountLabel string) (string, error)
// Unmount unmounts the RWLayer. This should be called
// for every mount. If there are multiple mount calls
// this operation will only decrement the internal mount counter.
Unmount() error
// Size represents the size of the writable layer
// as calculated by the total size of the files
// changed in the mutable layer.
Size() (int64, error)
// Changes returns the set of changes for the mutable layer
// from the base layer.
Changes() ([]archive.Change, error)
// Metadata returns the low level metadata for the mutable layer
Metadata() (map[string]string, error)
}
// Metadata holds information about a
// read only layer
type Metadata struct {
// ChainID is the content hash of the layer
ChainID ChainID
// DiffID is the hash of the tar data used to
// create the layer
DiffID DiffID
// Size is the size of the layer and all parents
Size int64
// DiffSize is the size of the top layer
DiffSize int64
}
// MountInit is a function to initialize a
// writable mount. Changes made here will
// not be included in the Tar stream of the
// RWLayer.
type MountInit func(root string) error
// Store represents a backend for managing both
// read-only and read-write layers.
type Store interface {
Register(io.Reader, ChainID) (Layer, error)
Get(ChainID) (Layer, error)
Release(Layer) ([]Metadata, error)
CreateRWLayer(id string, parent ChainID, mountLabel string, initFunc MountInit) (RWLayer, error)
GetRWLayer(id string) (RWLayer, error)
ReleaseRWLayer(RWLayer) ([]Metadata, error)
Cleanup() error
DriverStatus() [][2]string
DriverName() string
}
// MetadataTransaction represents functions for setting layer metadata
// with a single transaction.
type MetadataTransaction interface {
SetSize(int64) error
SetParent(parent ChainID) error
SetDiffID(DiffID) error
SetCacheID(string) error
TarSplitWriter(compressInput bool) (io.WriteCloser, error)
Commit(ChainID) error
Cancel() error
String() string
}
// MetadataStore represents a backend for persisting
// metadata about layers and providing the metadata
// for restoring a Store.
type MetadataStore interface {
// StartTransaction starts an update for new metadata
// which will be used to represent an ID on commit.
StartTransaction() (MetadataTransaction, error)
GetSize(ChainID) (int64, error)
GetParent(ChainID) (ChainID, error)
GetDiffID(ChainID) (DiffID, error)
GetCacheID(ChainID) (string, error)
TarSplitReader(ChainID) (io.ReadCloser, error)
SetMountID(string, string) error
SetInitID(string, string) error
SetMountParent(string, ChainID) error
GetMountID(string) (string, error)
GetInitID(string) (string, error)
GetMountParent(string) (ChainID, error)
// List returns the full list of referenced
// read-only and read-write layers
List() ([]ChainID, []string, error)
Remove(ChainID) error
RemoveMount(string) error
}
// CreateChainID returns ID for a layerDigest slice
func CreateChainID(dgsts []DiffID) ChainID {
return createChainIDFromParent("", dgsts...)
}
func createChainIDFromParent(parent ChainID, dgsts ...DiffID) ChainID {
if len(dgsts) == 0 {
return parent
}
if parent == "" {
return createChainIDFromParent(ChainID(dgsts[0]), dgsts[1:]...)
}
// H = "H(n-1) SHA256(n)"
dgst := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
return createChainIDFromParent(ChainID(dgst), dgsts[1:]...)
}
// ReleaseAndLog releases the provided layer from the given layer
// store, logging any error and release metadata
func ReleaseAndLog(ls Store, l Layer) {
metadata, err := ls.Release(l)
if err != nil {
logrus.Errorf("Error releasing layer %s: %v", l.ChainID(), err)
}
LogReleaseMetadata(metadata)
}
// LogReleaseMetadata logs a metadata array, use this to
// ensure consistent logging for release metadata
func LogReleaseMetadata(metadatas []Metadata) {
for _, metadata := range metadatas {
logrus.Infof("Layer %s cleaned up", metadata.ChainID)
}
}

626
vendor/github.com/hyperhq/hypercli/layer/layer_store.go generated vendored Normal file
View File

@@ -0,0 +1,626 @@
package layer
import (
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/hyperhq/hypercli/daemon/graphdriver"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/idtools"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/vbatts/tar-split/tar/asm"
"github.com/vbatts/tar-split/tar/storage"
)
// maxLayerDepth represents the maximum number of
// layers which can be chained together. 125 was
// chosen to account for the 127 max in some
// graphdrivers plus the 2 additional layers
// used to create a rwlayer.
const maxLayerDepth = 125
type layerStore struct {
store MetadataStore
driver graphdriver.Driver
layerMap map[ChainID]*roLayer
layerL sync.Mutex
mounts map[string]*mountedLayer
mountL sync.Mutex
}
// StoreOptions are the options used to create a new Store instance
type StoreOptions struct {
StorePath string
MetadataStorePathTemplate string
GraphDriver string
GraphDriverOptions []string
UIDMaps []idtools.IDMap
GIDMaps []idtools.IDMap
}
// NewStoreFromOptions creates a new Store instance
func NewStoreFromOptions(options StoreOptions) (Store, error) {
driver, err := graphdriver.New(
options.StorePath,
options.GraphDriver,
options.GraphDriverOptions,
options.UIDMaps,
options.GIDMaps)
if err != nil {
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
}
logrus.Debugf("Using graph driver %s", driver)
fms, err := NewFSMetadataStore(fmt.Sprintf(options.MetadataStorePathTemplate, driver))
if err != nil {
return nil, err
}
return NewStoreFromGraphDriver(fms, driver)
}
// NewStoreFromGraphDriver creates a new Store instance using the provided
// metadata store and graph driver. The metadata store will be used to restore
// the Store.
func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) {
ls := &layerStore{
store: store,
driver: driver,
layerMap: map[ChainID]*roLayer{},
mounts: map[string]*mountedLayer{},
}
ids, mounts, err := store.List()
if err != nil {
return nil, err
}
for _, id := range ids {
l, err := ls.loadLayer(id)
if err != nil {
logrus.Debugf("Failed to load layer %s: %s", id, err)
continue
}
if l.parent != nil {
l.parent.referenceCount++
}
}
for _, mount := range mounts {
if err := ls.loadMount(mount); err != nil {
logrus.Debugf("Failed to load mount %s: %s", mount, err)
}
}
return ls, nil
}
func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
cl, ok := ls.layerMap[layer]
if ok {
return cl, nil
}
diff, err := ls.store.GetDiffID(layer)
if err != nil {
return nil, fmt.Errorf("failed to get diff id for %s: %s", layer, err)
}
size, err := ls.store.GetSize(layer)
if err != nil {
return nil, fmt.Errorf("failed to get size for %s: %s", layer, err)
}
cacheID, err := ls.store.GetCacheID(layer)
if err != nil {
return nil, fmt.Errorf("failed to get cache id for %s: %s", layer, err)
}
parent, err := ls.store.GetParent(layer)
if err != nil {
return nil, fmt.Errorf("failed to get parent for %s: %s", layer, err)
}
cl = &roLayer{
chainID: layer,
diffID: diff,
size: size,
cacheID: cacheID,
layerStore: ls,
references: map[Layer]struct{}{},
}
if parent != "" {
p, err := ls.loadLayer(parent)
if err != nil {
return nil, err
}
cl.parent = p
}
ls.layerMap[cl.chainID] = cl
return cl, nil
}
func (ls *layerStore) loadMount(mount string) error {
if _, ok := ls.mounts[mount]; ok {
return nil
}
mountID, err := ls.store.GetMountID(mount)
if err != nil {
return err
}
initID, err := ls.store.GetInitID(mount)
if err != nil {
return err
}
parent, err := ls.store.GetMountParent(mount)
if err != nil {
return err
}
ml := &mountedLayer{
name: mount,
mountID: mountID,
initID: initID,
layerStore: ls,
references: map[RWLayer]*referencedRWLayer{},
}
if parent != "" {
p, err := ls.loadLayer(parent)
if err != nil {
return err
}
ml.parent = p
p.referenceCount++
}
ls.mounts[ml.name] = ml
return nil
}
func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent string, layer *roLayer) error {
digester := digest.Canonical.New()
tr := io.TeeReader(ts, digester.Hash())
tsw, err := tx.TarSplitWriter(true)
if err != nil {
return err
}
metaPacker := storage.NewJSONPacker(tsw)
defer tsw.Close()
// we're passing nil here for the file putter, because the ApplyDiff will
// handle the extraction of the archive
rdr, err := asm.NewInputTarStream(tr, metaPacker, nil)
if err != nil {
return err
}
applySize, err := ls.driver.ApplyDiff(layer.cacheID, parent, archive.Reader(rdr))
if err != nil {
return err
}
// Discard trailing data but ensure metadata is picked up to reconstruct stream
io.Copy(ioutil.Discard, rdr) // ignore error as reader may be closed
layer.size = applySize
layer.diffID = DiffID(digester.Digest())
logrus.Debugf("Applied tar %s to %s, size: %d", layer.diffID, layer.cacheID, applySize)
return nil
}
func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
// err is used to hold the error which will always trigger
// cleanup of creates sources but may not be an error returned
// to the caller (already exists).
var err error
var pid string
var p *roLayer
if string(parent) != "" {
p = ls.get(parent)
if p == nil {
return nil, ErrLayerDoesNotExist
}
pid = p.cacheID
// Release parent chain if error
defer func() {
if err != nil {
ls.layerL.Lock()
ls.releaseLayer(p)
ls.layerL.Unlock()
}
}()
if p.depth() >= maxLayerDepth {
err = ErrMaxDepthExceeded
return nil, err
}
}
// Create new roLayer
layer := &roLayer{
parent: p,
cacheID: stringid.GenerateRandomID(),
referenceCount: 1,
layerStore: ls,
references: map[Layer]struct{}{},
}
if err = ls.driver.Create(layer.cacheID, pid, ""); err != nil {
return nil, err
}
tx, err := ls.store.StartTransaction()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, err)
if err := ls.driver.Remove(layer.cacheID); err != nil {
logrus.Errorf("Error cleaning up cache layer %s: %v", layer.cacheID, err)
}
if err := tx.Cancel(); err != nil {
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
}
}
}()
if err = ls.applyTar(tx, ts, pid, layer); err != nil {
return nil, err
}
if layer.parent == nil {
layer.chainID = ChainID(layer.diffID)
} else {
layer.chainID = createChainIDFromParent(layer.parent.chainID, layer.diffID)
}
if err = storeLayer(tx, layer); err != nil {
return nil, err
}
ls.layerL.Lock()
defer ls.layerL.Unlock()
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
// Set error for cleanup, but do not return the error
err = errors.New("layer already exists")
return existingLayer.getReference(), nil
}
if err = tx.Commit(layer.chainID); err != nil {
return nil, err
}
ls.layerMap[layer.chainID] = layer
return layer.getReference(), nil
}
func (ls *layerStore) getWithoutLock(layer ChainID) *roLayer {
l, ok := ls.layerMap[layer]
if !ok {
return nil
}
l.referenceCount++
return l
}
func (ls *layerStore) get(l ChainID) *roLayer {
ls.layerL.Lock()
defer ls.layerL.Unlock()
return ls.getWithoutLock(l)
}
func (ls *layerStore) Get(l ChainID) (Layer, error) {
layer := ls.get(l)
if layer == nil {
return nil, ErrLayerDoesNotExist
}
return layer.getReference(), nil
}
func (ls *layerStore) deleteLayer(layer *roLayer, metadata *Metadata) error {
err := ls.driver.Remove(layer.cacheID)
if err != nil {
return err
}
err = ls.store.Remove(layer.chainID)
if err != nil {
return err
}
metadata.DiffID = layer.diffID
metadata.ChainID = layer.chainID
metadata.Size, err = layer.Size()
if err != nil {
return err
}
metadata.DiffSize = layer.size
return nil
}
func (ls *layerStore) releaseLayer(l *roLayer) ([]Metadata, error) {
depth := 0
removed := []Metadata{}
for {
if l.referenceCount == 0 {
panic("layer not retained")
}
l.referenceCount--
if l.referenceCount != 0 {
return removed, nil
}
if len(removed) == 0 && depth > 0 {
panic("cannot remove layer with child")
}
if l.hasReferences() {
panic("cannot delete referenced layer")
}
var metadata Metadata
if err := ls.deleteLayer(l, &metadata); err != nil {
return nil, err
}
delete(ls.layerMap, l.chainID)
removed = append(removed, metadata)
if l.parent == nil {
return removed, nil
}
depth++
l = l.parent
}
}
func (ls *layerStore) Release(l Layer) ([]Metadata, error) {
ls.layerL.Lock()
defer ls.layerL.Unlock()
layer, ok := ls.layerMap[l.ChainID()]
if !ok {
return []Metadata{}, nil
}
if !layer.hasReference(l) {
return nil, ErrLayerNotRetained
}
layer.deleteReference(l)
return ls.releaseLayer(layer)
}
func (ls *layerStore) CreateRWLayer(name string, parent ChainID, mountLabel string, initFunc MountInit) (RWLayer, error) {
ls.mountL.Lock()
defer ls.mountL.Unlock()
m, ok := ls.mounts[name]
if ok {
return nil, ErrMountNameConflict
}
var err error
var pid string
var p *roLayer
if string(parent) != "" {
p = ls.get(parent)
if p == nil {
return nil, ErrLayerDoesNotExist
}
pid = p.cacheID
// Release parent chain if error
defer func() {
if err != nil {
ls.layerL.Lock()
ls.releaseLayer(p)
ls.layerL.Unlock()
}
}()
}
m = &mountedLayer{
name: name,
parent: p,
mountID: ls.mountID(name),
layerStore: ls,
references: map[RWLayer]*referencedRWLayer{},
}
if initFunc != nil {
pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc)
if err != nil {
return nil, err
}
m.initID = pid
}
if err = ls.driver.Create(m.mountID, pid, ""); err != nil {
return nil, err
}
if err = ls.saveMount(m); err != nil {
return nil, err
}
return m.getReference(), nil
}
func (ls *layerStore) GetRWLayer(id string) (RWLayer, error) {
ls.mountL.Lock()
defer ls.mountL.Unlock()
mount, ok := ls.mounts[id]
if !ok {
return nil, ErrMountDoesNotExist
}
return mount.getReference(), nil
}
func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) {
ls.mountL.Lock()
defer ls.mountL.Unlock()
m, ok := ls.mounts[l.Name()]
if !ok {
return []Metadata{}, nil
}
if err := m.deleteReference(l); err != nil {
return nil, err
}
if m.hasReferences() {
return []Metadata{}, nil
}
if err := ls.driver.Remove(m.mountID); err != nil {
logrus.Errorf("Error removing mounted layer %s: %s", m.name, err)
return nil, err
}
if m.initID != "" {
if err := ls.driver.Remove(m.initID); err != nil {
logrus.Errorf("Error removing init layer %s: %s", m.name, err)
return nil, err
}
}
if err := ls.store.RemoveMount(m.name); err != nil {
logrus.Errorf("Error removing mount metadata: %s: %s", m.name, err)
return nil, err
}
delete(ls.mounts, m.Name())
ls.layerL.Lock()
defer ls.layerL.Unlock()
if m.parent != nil {
return ls.releaseLayer(m.parent)
}
return []Metadata{}, nil
}
func (ls *layerStore) saveMount(mount *mountedLayer) error {
if err := ls.store.SetMountID(mount.name, mount.mountID); err != nil {
return err
}
if mount.initID != "" {
if err := ls.store.SetInitID(mount.name, mount.initID); err != nil {
return err
}
}
if mount.parent != nil {
if err := ls.store.SetMountParent(mount.name, mount.parent.chainID); err != nil {
return err
}
}
ls.mounts[mount.name] = mount
return nil
}
func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit) (string, error) {
// Use "<graph-id>-init" to maintain compatibility with graph drivers
// which are expecting this layer with this special name. If all
// graph drivers can be updated to not rely on knowing about this layer
// then the initID should be randomly generated.
initID := fmt.Sprintf("%s-init", graphID)
if err := ls.driver.Create(initID, parent, mountLabel); err != nil {
return "", err
}
p, err := ls.driver.Get(initID, "")
if err != nil {
return "", err
}
if err := initFunc(p); err != nil {
ls.driver.Put(initID)
return "", err
}
if err := ls.driver.Put(initID); err != nil {
return "", err
}
return initID, nil
}
func (ls *layerStore) assembleTarTo(graphID string, metadata io.ReadCloser, size *int64, w io.Writer) error {
type diffPathDriver interface {
DiffPath(string) (string, func() error, error)
}
diffDriver, ok := ls.driver.(diffPathDriver)
if !ok {
diffDriver = &naiveDiffPathDriver{ls.driver}
}
defer metadata.Close()
// get our relative path to the container
fsPath, releasePath, err := diffDriver.DiffPath(graphID)
if err != nil {
return err
}
defer releasePath()
metaUnpacker := storage.NewJSONUnpacker(metadata)
upackerCounter := &unpackSizeCounter{metaUnpacker, size}
fileGetter := storage.NewPathFileGetter(fsPath)
logrus.Debugf("Assembling tar data for %s from %s", graphID, fsPath)
return asm.WriteOutputTarStream(fileGetter, upackerCounter, w)
}
func (ls *layerStore) Cleanup() error {
return ls.driver.Cleanup()
}
func (ls *layerStore) DriverStatus() [][2]string {
return ls.driver.Status()
}
func (ls *layerStore) DriverName() string {
return ls.driver.String()
}
type naiveDiffPathDriver struct {
graphdriver.Driver
}
func (n *naiveDiffPathDriver) DiffPath(id string) (string, func() error, error) {
p, err := n.Driver.Get(id, "")
if err != nil {
return "", nil, err
}
return p, func() error {
return n.Driver.Put(id)
}, nil
}

767
vendor/github.com/hyperhq/hypercli/layer/layer_test.go generated vendored Normal file
View File

@@ -0,0 +1,767 @@
package layer
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/docker/distribution/digest"
"github.com/hyperhq/hypercli/daemon/graphdriver"
"github.com/hyperhq/hypercli/daemon/graphdriver/vfs"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/idtools"
"github.com/hyperhq/hypercli/pkg/stringid"
)
func init() {
graphdriver.ApplyUncompressedLayer = archive.UnpackLayer
vfs.CopyWithTar = archive.CopyWithTar
}
func newVFSGraphDriver(td string) (graphdriver.Driver, error) {
uidMap := []idtools.IDMap{
{
ContainerID: 0,
HostID: os.Getuid(),
Size: 1,
},
}
gidMap := []idtools.IDMap{
{
ContainerID: 0,
HostID: os.Getgid(),
Size: 1,
},
}
return graphdriver.GetDriver("vfs", td, nil, uidMap, gidMap)
}
func newTestGraphDriver(t *testing.T) (graphdriver.Driver, func()) {
td, err := ioutil.TempDir("", "graph-")
if err != nil {
t.Fatal(err)
}
driver, err := newVFSGraphDriver(td)
if err != nil {
t.Fatal(err)
}
return driver, func() {
os.RemoveAll(td)
}
}
func newTestStore(t *testing.T) (Store, func()) {
td, err := ioutil.TempDir("", "layerstore-")
if err != nil {
t.Fatal(err)
}
graph, graphcleanup := newTestGraphDriver(t)
fms, err := NewFSMetadataStore(td)
if err != nil {
t.Fatal(err)
}
ls, err := NewStoreFromGraphDriver(fms, graph)
if err != nil {
t.Fatal(err)
}
return ls, func() {
graphcleanup()
os.RemoveAll(td)
}
}
type layerInit func(root string) error
func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
containerID := stringid.GenerateRandomID()
mount, err := ls.CreateRWLayer(containerID, parent, "", nil)
if err != nil {
return nil, err
}
path, err := mount.Mount("")
if err != nil {
return nil, err
}
if err := layerFunc(path); err != nil {
return nil, err
}
ts, err := mount.TarStream()
if err != nil {
return nil, err
}
defer ts.Close()
layer, err := ls.Register(ts, parent)
if err != nil {
return nil, err
}
if err := mount.Unmount(); err != nil {
return nil, err
}
if _, err := ls.ReleaseRWLayer(mount); err != nil {
return nil, err
}
return layer, nil
}
type FileApplier interface {
ApplyFile(root string) error
}
type testFile struct {
name string
content []byte
permission os.FileMode
}
func newTestFile(name string, content []byte, perm os.FileMode) FileApplier {
return &testFile{
name: name,
content: content,
permission: perm,
}
}
func (tf *testFile) ApplyFile(root string) error {
fullPath := filepath.Join(root, tf.name)
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
return err
}
// Check if already exists
if stat, err := os.Stat(fullPath); err == nil && stat.Mode().Perm() != tf.permission {
if err := os.Chmod(fullPath, tf.permission); err != nil {
return err
}
}
if err := ioutil.WriteFile(fullPath, tf.content, tf.permission); err != nil {
return err
}
return nil
}
func initWithFiles(files ...FileApplier) layerInit {
return func(root string) error {
for _, f := range files {
if err := f.ApplyFile(root); err != nil {
return err
}
}
return nil
}
}
func getCachedLayer(l Layer) *roLayer {
if rl, ok := l.(*referencedCacheLayer); ok {
return rl.roLayer
}
return l.(*roLayer)
}
func getMountLayer(l RWLayer) *mountedLayer {
if rl, ok := l.(*referencedRWLayer); ok {
return rl.mountedLayer
}
return l.(*mountedLayer)
}
func createMetadata(layers ...Layer) []Metadata {
metadata := make([]Metadata, len(layers))
for i := range layers {
size, err := layers[i].Size()
if err != nil {
panic(err)
}
metadata[i].ChainID = layers[i].ChainID()
metadata[i].DiffID = layers[i].DiffID()
metadata[i].Size = size
metadata[i].DiffSize = getCachedLayer(layers[i]).size
}
return metadata
}
func assertMetadata(t *testing.T, metadata, expectedMetadata []Metadata) {
if len(metadata) != len(expectedMetadata) {
t.Fatalf("Unexpected number of deletes %d, expected %d", len(metadata), len(expectedMetadata))
}
for i := range metadata {
if metadata[i] != expectedMetadata[i] {
t.Errorf("Unexpected metadata\n\tExpected: %#v\n\tActual: %#v", expectedMetadata[i], metadata[i])
}
}
if t.Failed() {
t.FailNow()
}
}
func releaseAndCheckDeleted(t *testing.T, ls Store, layer Layer, removed ...Layer) {
layerCount := len(ls.(*layerStore).layerMap)
expectedMetadata := createMetadata(removed...)
metadata, err := ls.Release(layer)
if err != nil {
t.Fatal(err)
}
assertMetadata(t, metadata, expectedMetadata)
if expected := layerCount - len(removed); len(ls.(*layerStore).layerMap) != expected {
t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected)
}
}
func cacheID(l Layer) string {
return getCachedLayer(l).cacheID
}
func assertLayerEqual(t *testing.T, l1, l2 Layer) {
if l1.ChainID() != l2.ChainID() {
t.Fatalf("Mismatched ID: %s vs %s", l1.ChainID(), l2.ChainID())
}
if l1.DiffID() != l2.DiffID() {
t.Fatalf("Mismatched DiffID: %s vs %s", l1.DiffID(), l2.DiffID())
}
size1, err := l1.Size()
if err != nil {
t.Fatal(err)
}
size2, err := l2.Size()
if err != nil {
t.Fatal(err)
}
if size1 != size2 {
t.Fatalf("Mismatched size: %d vs %d", size1, size2)
}
if cacheID(l1) != cacheID(l2) {
t.Fatalf("Mismatched cache id: %s vs %s", cacheID(l1), cacheID(l2))
}
p1 := l1.Parent()
p2 := l2.Parent()
if p1 != nil && p2 != nil {
assertLayerEqual(t, p1, p2)
} else if p1 != nil || p2 != nil {
t.Fatalf("Mismatched parents: %v vs %v", p1, p2)
}
}
func TestMountAndRegister(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
li := initWithFiles(newTestFile("testfile.txt", []byte("some test data"), 0644))
layer, err := createLayer(ls, "", li)
if err != nil {
t.Fatal(err)
}
size, _ := layer.Size()
t.Logf("Layer size: %d", size)
mount2, err := ls.CreateRWLayer("new-test-mount", layer.ChainID(), "", nil)
if err != nil {
t.Fatal(err)
}
path2, err := mount2.Mount("")
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(filepath.Join(path2, "testfile.txt"))
if err != nil {
t.Fatal(err)
}
if expected := "some test data"; string(b) != expected {
t.Fatalf("Wrong file data, expected %q, got %q", expected, string(b))
}
if err := mount2.Unmount(); err != nil {
t.Fatal(err)
}
if _, err := ls.ReleaseRWLayer(mount2); err != nil {
t.Fatal(err)
}
}
func TestLayerRelease(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
if err != nil {
t.Fatal(err)
}
layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644)))
if err != nil {
t.Fatal(err)
}
if _, err := ls.Release(layer1); err != nil {
t.Fatal(err)
}
layer3a, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3a file"), 0644)))
if err != nil {
t.Fatal(err)
}
layer3b, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3b file"), 0644)))
if err != nil {
t.Fatal(err)
}
if _, err := ls.Release(layer2); err != nil {
t.Fatal(err)
}
t.Logf("Layer1: %s", layer1.ChainID())
t.Logf("Layer2: %s", layer2.ChainID())
t.Logf("Layer3a: %s", layer3a.ChainID())
t.Logf("Layer3b: %s", layer3b.ChainID())
if expected := 4; len(ls.(*layerStore).layerMap) != expected {
t.Fatalf("Unexpected number of layers %d, expected %d", len(ls.(*layerStore).layerMap), expected)
}
releaseAndCheckDeleted(t, ls, layer3b, layer3b)
releaseAndCheckDeleted(t, ls, layer3a, layer3a, layer2, layer1)
}
func TestStoreRestore(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
layer1, err := createLayer(ls, "", initWithFiles(newTestFile("layer1.txt", []byte("layer 1 file"), 0644)))
if err != nil {
t.Fatal(err)
}
layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("layer2.txt", []byte("layer 2 file"), 0644)))
if err != nil {
t.Fatal(err)
}
if _, err := ls.Release(layer1); err != nil {
t.Fatal(err)
}
layer3, err := createLayer(ls, layer2.ChainID(), initWithFiles(newTestFile("layer3.txt", []byte("layer 3 file"), 0644)))
if err != nil {
t.Fatal(err)
}
if _, err := ls.Release(layer2); err != nil {
t.Fatal(err)
}
m, err := ls.CreateRWLayer("some-mount_name", layer3.ChainID(), "", nil)
if err != nil {
t.Fatal(err)
}
path, err := m.Mount("")
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(path, "testfile.txt"), []byte("nothing here"), 0644); err != nil {
t.Fatal(err)
}
assertActivityCount(t, m, 1)
if err := m.Unmount(); err != nil {
t.Fatal(err)
}
assertActivityCount(t, m, 0)
ls2, err := NewStoreFromGraphDriver(ls.(*layerStore).store, ls.(*layerStore).driver)
if err != nil {
t.Fatal(err)
}
layer3b, err := ls2.Get(layer3.ChainID())
if err != nil {
t.Fatal(err)
}
assertLayerEqual(t, layer3b, layer3)
// Create again with same name, should return error
if _, err := ls2.CreateRWLayer("some-mount_name", layer3b.ChainID(), "", nil); err == nil {
t.Fatal("Expected error creating mount with same name")
} else if err != ErrMountNameConflict {
t.Fatal(err)
}
m2, err := ls2.GetRWLayer("some-mount_name")
if err != nil {
t.Fatal(err)
}
if mountPath, err := m2.Mount(""); err != nil {
t.Fatal(err)
} else if path != mountPath {
t.Fatalf("Unexpected path %s, expected %s", mountPath, path)
}
assertActivityCount(t, m2, 1)
if mountPath, err := m2.Mount(""); err != nil {
t.Fatal(err)
} else if path != mountPath {
t.Fatalf("Unexpected path %s, expected %s", mountPath, path)
}
assertActivityCount(t, m2, 2)
if err := m2.Unmount(); err != nil {
t.Fatal(err)
}
assertActivityCount(t, m2, 1)
b, err := ioutil.ReadFile(filepath.Join(path, "testfile.txt"))
if err != nil {
t.Fatal(err)
}
if expected := "nothing here"; string(b) != expected {
t.Fatalf("Unexpected content %q, expected %q", string(b), expected)
}
if err := m2.Unmount(); err != nil {
t.Fatal(err)
}
assertActivityCount(t, m2, 0)
if metadata, err := ls2.ReleaseRWLayer(m2); err != nil {
t.Fatal(err)
} else if len(metadata) != 0 {
t.Fatalf("Unexpectedly deleted layers: %#v", metadata)
}
if metadata, err := ls2.ReleaseRWLayer(m2); err != nil {
t.Fatal(err)
} else if len(metadata) != 0 {
t.Fatalf("Unexpectedly deleted layers: %#v", metadata)
}
releaseAndCheckDeleted(t, ls2, layer3b, layer3, layer2, layer1)
}
func TestTarStreamStability(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
files1 := []FileApplier{
newTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
}
addedFile := newTestFile("/etc/shadow", []byte("root:::::::"), 0644)
files2 := []FileApplier{
newTestFile("/etc/hosts", []byte("mydomain 10.0.0.2"), 0644),
newTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0664),
newTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
}
tar1, err := tarFromFiles(files1...)
if err != nil {
t.Fatal(err)
}
tar2, err := tarFromFiles(files2...)
if err != nil {
t.Fatal(err)
}
layer1, err := ls.Register(bytes.NewReader(tar1), "")
if err != nil {
t.Fatal(err)
}
// hack layer to add file
p, err := ls.(*layerStore).driver.Get(layer1.(*referencedCacheLayer).cacheID, "")
if err != nil {
t.Fatal(err)
}
if err := addedFile.ApplyFile(p); err != nil {
t.Fatal(err)
}
if err := ls.(*layerStore).driver.Put(layer1.(*referencedCacheLayer).cacheID); err != nil {
t.Fatal(err)
}
layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID())
if err != nil {
t.Fatal(err)
}
id1 := layer1.ChainID()
t.Logf("Layer 1: %s", layer1.ChainID())
t.Logf("Layer 2: %s", layer2.ChainID())
if _, err := ls.Release(layer1); err != nil {
t.Fatal(err)
}
assertLayerDiff(t, tar2, layer2)
layer1b, err := ls.Get(id1)
if err != nil {
t.Logf("Content of layer map: %#v", ls.(*layerStore).layerMap)
t.Fatal(err)
}
if _, err := ls.Release(layer2); err != nil {
t.Fatal(err)
}
assertLayerDiff(t, tar1, layer1b)
if _, err := ls.Release(layer1b); err != nil {
t.Fatal(err)
}
}
func assertLayerDiff(t *testing.T, expected []byte, layer Layer) {
expectedDigest := digest.FromBytes(expected)
if digest.Digest(layer.DiffID()) != expectedDigest {
t.Fatalf("Mismatched diff id for %s, got %s, expected %s", layer.ChainID(), layer.DiffID(), expected)
}
ts, err := layer.TarStream()
if err != nil {
t.Fatal(err)
}
defer ts.Close()
actual, err := ioutil.ReadAll(ts)
if err != nil {
t.Fatal(err)
}
if len(actual) != len(expected) {
logByteDiff(t, actual, expected)
t.Fatalf("Mismatched tar stream size for %s, got %d, expected %d", layer.ChainID(), len(actual), len(expected))
}
actualDigest := digest.FromBytes(actual)
if actualDigest != expectedDigest {
logByteDiff(t, actual, expected)
t.Fatalf("Wrong digest of tar stream, got %s, expected %s", actualDigest, expectedDigest)
}
}
const maxByteLog = 4 * 1024
func logByteDiff(t *testing.T, actual, expected []byte) {
d1, d2 := byteDiff(actual, expected)
if len(d1) == 0 && len(d2) == 0 {
return
}
prefix := len(actual) - len(d1)
if len(d1) > maxByteLog || len(d2) > maxByteLog {
t.Logf("Byte diff after %d matching bytes", prefix)
} else {
t.Logf("Byte diff after %d matching bytes\nActual bytes after prefix:\n%x\nExpected bytes after prefix:\n%x", prefix, d1, d2)
}
}
// byteDiff returns the differing bytes after the matching prefix
func byteDiff(b1, b2 []byte) ([]byte, []byte) {
i := 0
for i < len(b1) && i < len(b2) {
if b1[i] != b2[i] {
break
}
i++
}
return b1[i:], b2[i:]
}
func tarFromFiles(files ...FileApplier) ([]byte, error) {
td, err := ioutil.TempDir("", "tar-")
if err != nil {
return nil, err
}
defer os.RemoveAll(td)
for _, f := range files {
if err := f.ApplyFile(td); err != nil {
return nil, err
}
}
r, err := archive.Tar(td, archive.Uncompressed)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(nil)
if _, err := io.Copy(buf, r); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// assertReferences asserts that all the references are to the same
// image and represent the full set of references to that image.
func assertReferences(t *testing.T, references ...Layer) {
if len(references) == 0 {
return
}
base := references[0].(*referencedCacheLayer).roLayer
seenReferences := map[Layer]struct{}{
references[0]: {},
}
for i := 1; i < len(references); i++ {
other := references[i].(*referencedCacheLayer).roLayer
if base != other {
t.Fatalf("Unexpected referenced cache layer %s, expecting %s", other.ChainID(), base.ChainID())
}
if _, ok := base.references[references[i]]; !ok {
t.Fatalf("Reference not part of reference list: %v", references[i])
}
if _, ok := seenReferences[references[i]]; ok {
t.Fatalf("Duplicated reference %v", references[i])
}
}
if rc := len(base.references); rc != len(references) {
t.Fatalf("Unexpected number of references %d, expecting %d", rc, len(references))
}
}
func assertActivityCount(t *testing.T, l RWLayer, expected int) {
rl := l.(*referencedRWLayer)
if rl.activityCount != expected {
t.Fatalf("Unexpected activity count %d, expected %d", rl.activityCount, expected)
}
}
func TestRegisterExistingLayer(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
baseFiles := []FileApplier{
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
}
layerFiles := []FileApplier{
newTestFile("/root/.bashrc", []byte("# Root configuration"), 0644),
}
li := initWithFiles(baseFiles...)
layer1, err := createLayer(ls, "", li)
if err != nil {
t.Fatal(err)
}
tar1, err := tarFromFiles(layerFiles...)
if err != nil {
t.Fatal(err)
}
layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
if err != nil {
t.Fatal(err)
}
layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID())
if err != nil {
t.Fatal(err)
}
assertReferences(t, layer2a, layer2b)
}
func graphDiffSize(ls Store, l Layer) (int64, error) {
cl := getCachedLayer(l)
var parent string
if cl.parent != nil {
parent = cl.parent.cacheID
}
return ls.(*layerStore).driver.DiffSize(cl.cacheID, parent)
}
func TestLayerSize(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
content1 := []byte("Base contents")
content2 := []byte("Added contents")
layer1, err := createLayer(ls, "", initWithFiles(newTestFile("file1", content1, 0644)))
if err != nil {
t.Fatal(err)
}
layer2, err := createLayer(ls, layer1.ChainID(), initWithFiles(newTestFile("file2", content2, 0644)))
if err != nil {
t.Fatal(err)
}
layer1DiffSize, err := graphDiffSize(ls, layer1)
if err != nil {
t.Fatal(err)
}
if int(layer1DiffSize) != len(content1) {
t.Fatalf("Unexpected diff size %d, expected %d", layer1DiffSize, len(content1))
}
layer1Size, err := layer1.Size()
if err != nil {
t.Fatal(err)
}
if expected := len(content1); int(layer1Size) != expected {
t.Fatalf("Unexpected size %d, expected %d", layer1Size, expected)
}
layer2DiffSize, err := graphDiffSize(ls, layer2)
if err != nil {
t.Fatal(err)
}
if int(layer2DiffSize) != len(content2) {
t.Fatalf("Unexpected diff size %d, expected %d", layer2DiffSize, len(content2))
}
layer2Size, err := layer2.Size()
if err != nil {
t.Fatal(err)
}
if expected := len(content1) + len(content2); int(layer2Size) != expected {
t.Fatalf("Unexpected size %d, expected %d", layer2Size, expected)
}
}

View File

@@ -0,0 +1,9 @@
// +build linux freebsd darwin
package layer
import "github.com/hyperhq/hypercli/pkg/stringid"
func (ls *layerStore) mountID(name string) string {
return stringid.GenerateRandomID()
}

View File

@@ -0,0 +1,98 @@
package layer
import (
"errors"
"fmt"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/hyperhq/hypercli/daemon/graphdriver"
)
// GetLayerPath returns the path to a layer
func GetLayerPath(s Store, layer ChainID) (string, error) {
ls, ok := s.(*layerStore)
if !ok {
return "", errors.New("unsupported layer store")
}
ls.layerL.Lock()
defer ls.layerL.Unlock()
rl, ok := ls.layerMap[layer]
if !ok {
return "", ErrLayerDoesNotExist
}
path, err := ls.driver.Get(rl.cacheID, "")
if err != nil {
return "", err
}
if err := ls.driver.Put(rl.cacheID); err != nil {
return "", err
}
return path, nil
}
func (ls *layerStore) RegisterDiffID(graphID string, size int64) (Layer, error) {
var err error // this is used for cleanup in existingLayer case
diffID := digest.FromBytes([]byte(graphID))
// Create new roLayer
layer := &roLayer{
cacheID: graphID,
diffID: DiffID(diffID),
referenceCount: 1,
layerStore: ls,
references: map[Layer]struct{}{},
size: size,
}
tx, err := ls.store.StartTransaction()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
if err := tx.Cancel(); err != nil {
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
}
}
}()
layer.chainID = createChainIDFromParent("", layer.diffID)
if !ls.driver.Exists(layer.cacheID) {
return nil, fmt.Errorf("layer %q is unknown to driver", layer.cacheID)
}
if err = storeLayer(tx, layer); err != nil {
return nil, err
}
ls.layerL.Lock()
defer ls.layerL.Unlock()
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
// Set error for cleanup, but do not return
err = errors.New("layer already exists")
return existingLayer.getReference(), nil
}
if err = tx.Commit(layer.chainID); err != nil {
return nil, err
}
ls.layerMap[layer.chainID] = layer
return layer.getReference(), nil
}
func (ls *layerStore) mountID(name string) string {
// windows has issues if container ID doesn't match mount ID
return name
}
func (ls *layerStore) GraphDriver() graphdriver.Driver {
return ls.driver
}

255
vendor/github.com/hyperhq/hypercli/layer/migration.go generated vendored Normal file
View File

@@ -0,0 +1,255 @@
package layer
import (
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/vbatts/tar-split/tar/asm"
"github.com/vbatts/tar-split/tar/storage"
)
// CreateRWLayerByGraphID creates a RWLayer in the layer store using
// the provided name with the given graphID. To get the RWLayer
// after migration the layer may be retrieved by the given name.
func (ls *layerStore) CreateRWLayerByGraphID(name string, graphID string, parent ChainID) (err error) {
ls.mountL.Lock()
defer ls.mountL.Unlock()
m, ok := ls.mounts[name]
if ok {
if m.parent.chainID != parent {
return errors.New("name conflict, mismatched parent")
}
if m.mountID != graphID {
return errors.New("mount already exists")
}
return nil
}
if !ls.driver.Exists(graphID) {
return errors.New("graph ID does not exist")
}
var p *roLayer
if string(parent) != "" {
p = ls.get(parent)
if p == nil {
return ErrLayerDoesNotExist
}
// Release parent chain if error
defer func() {
if err != nil {
ls.layerL.Lock()
ls.releaseLayer(p)
ls.layerL.Unlock()
}
}()
}
// TODO: Ensure graphID has correct parent
m = &mountedLayer{
name: name,
parent: p,
mountID: graphID,
layerStore: ls,
references: map[RWLayer]*referencedRWLayer{},
}
// Check for existing init layer
initID := fmt.Sprintf("%s-init", graphID)
if ls.driver.Exists(initID) {
m.initID = initID
}
if err = ls.saveMount(m); err != nil {
return err
}
return nil
}
func (ls *layerStore) ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID DiffID, size int64, err error) {
defer func() {
if err != nil {
logrus.Debugf("could not get checksum for %q with tar-split: %q", id, err)
diffID, size, err = ls.checksumForGraphIDNoTarsplit(id, parent, newTarDataPath)
}
}()
if oldTarDataPath == "" {
err = errors.New("no tar-split file")
return
}
tarDataFile, err := os.Open(oldTarDataPath)
if err != nil {
return
}
defer tarDataFile.Close()
uncompressed, err := gzip.NewReader(tarDataFile)
if err != nil {
return
}
dgst := digest.Canonical.New()
err = ls.assembleTarTo(id, uncompressed, &size, dgst.Hash())
if err != nil {
return
}
diffID = DiffID(dgst.Digest())
err = os.RemoveAll(newTarDataPath)
if err != nil {
return
}
err = os.Link(oldTarDataPath, newTarDataPath)
return
}
func (ls *layerStore) checksumForGraphIDNoTarsplit(id, parent, newTarDataPath string) (diffID DiffID, size int64, err error) {
rawarchive, err := ls.driver.Diff(id, parent)
if err != nil {
return
}
defer rawarchive.Close()
f, err := os.Create(newTarDataPath)
if err != nil {
return
}
defer f.Close()
mfz := gzip.NewWriter(f)
metaPacker := storage.NewJSONPacker(mfz)
packerCounter := &packSizeCounter{metaPacker, &size}
archive, err := asm.NewInputTarStream(rawarchive, packerCounter, nil)
if err != nil {
return
}
dgst, err := digest.FromReader(archive)
if err != nil {
return
}
diffID = DiffID(dgst)
return
}
func (ls *layerStore) RegisterByGraphID(graphID string, parent ChainID, diffID DiffID, tarDataFile string, size int64) (Layer, error) {
// err is used to hold the error which will always trigger
// cleanup of creates sources but may not be an error returned
// to the caller (already exists).
var err error
var p *roLayer
if string(parent) != "" {
p = ls.get(parent)
if p == nil {
return nil, ErrLayerDoesNotExist
}
// Release parent chain if error
defer func() {
if err != nil {
ls.layerL.Lock()
ls.releaseLayer(p)
ls.layerL.Unlock()
}
}()
}
// Create new roLayer
layer := &roLayer{
parent: p,
cacheID: graphID,
referenceCount: 1,
layerStore: ls,
references: map[Layer]struct{}{},
diffID: diffID,
size: size,
chainID: createChainIDFromParent(parent, diffID),
}
ls.layerL.Lock()
defer ls.layerL.Unlock()
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
// Set error for cleanup, but do not return
err = errors.New("layer already exists")
return existingLayer.getReference(), nil
}
tx, err := ls.store.StartTransaction()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
logrus.Debugf("Cleaning up transaction after failed migration for %s: %v", graphID, err)
if err := tx.Cancel(); err != nil {
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
}
}
}()
tsw, err := tx.TarSplitWriter(false)
if err != nil {
return nil, err
}
defer tsw.Close()
tdf, err := os.Open(tarDataFile)
if err != nil {
return nil, err
}
defer tdf.Close()
_, err = io.Copy(tsw, tdf)
if err != nil {
return nil, err
}
if err = storeLayer(tx, layer); err != nil {
return nil, err
}
if err = tx.Commit(layer.chainID); err != nil {
return nil, err
}
ls.layerMap[layer.chainID] = layer
return layer.getReference(), nil
}
type unpackSizeCounter struct {
unpacker storage.Unpacker
size *int64
}
func (u *unpackSizeCounter) Next() (*storage.Entry, error) {
e, err := u.unpacker.Next()
if err == nil && u.size != nil {
*u.size += e.Size
}
return e, err
}
type packSizeCounter struct {
packer storage.Packer
size *int64
}
func (p *packSizeCounter) AddEntry(e storage.Entry) (int, error) {
n, err := p.packer.AddEntry(e)
if err == nil && p.size != nil {
*p.size += e.Size
}
return n, err
}

View File

@@ -0,0 +1,435 @@
package layer
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/hyperhq/hypercli/daemon/graphdriver"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/vbatts/tar-split/tar/asm"
"github.com/vbatts/tar-split/tar/storage"
)
func writeTarSplitFile(name string, tarContent []byte) error {
f, err := os.OpenFile(name, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
fz := gzip.NewWriter(f)
metaPacker := storage.NewJSONPacker(fz)
defer fz.Close()
rdr, err := asm.NewInputTarStream(bytes.NewReader(tarContent), metaPacker, nil)
if err != nil {
return err
}
if _, err := io.Copy(ioutil.Discard, rdr); err != nil {
return err
}
return nil
}
func TestLayerMigration(t *testing.T) {
td, err := ioutil.TempDir("", "migration-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(td)
layer1Files := []FileApplier{
newTestFile("/root/.bashrc", []byte("# Boring configuration"), 0644),
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
}
layer2Files := []FileApplier{
newTestFile("/root/.bashrc", []byte("# Updated configuration"), 0644),
}
tar1, err := tarFromFiles(layer1Files...)
if err != nil {
t.Fatal(err)
}
tar2, err := tarFromFiles(layer2Files...)
if err != nil {
t.Fatal(err)
}
graph, err := newVFSGraphDriver(filepath.Join(td, "graphdriver-"))
if err != nil {
t.Fatal(err)
}
graphID1 := stringid.GenerateRandomID()
if err := graph.Create(graphID1, "", ""); err != nil {
t.Fatal(err)
}
if _, err := graph.ApplyDiff(graphID1, "", archive.Reader(bytes.NewReader(tar1))); err != nil {
t.Fatal(err)
}
tf1 := filepath.Join(td, "tar1.json.gz")
if err := writeTarSplitFile(tf1, tar1); err != nil {
t.Fatal(err)
}
fms, err := NewFSMetadataStore(filepath.Join(td, "layers"))
if err != nil {
t.Fatal(err)
}
ls, err := NewStoreFromGraphDriver(fms, graph)
if err != nil {
t.Fatal(err)
}
newTarDataPath := filepath.Join(td, ".migration-tardata")
diffID, size, err := ls.(*layerStore).ChecksumForGraphID(graphID1, "", tf1, newTarDataPath)
if err != nil {
t.Fatal(err)
}
layer1a, err := ls.(*layerStore).RegisterByGraphID(graphID1, "", diffID, newTarDataPath, size)
if err != nil {
t.Fatal(err)
}
layer1b, err := ls.Register(bytes.NewReader(tar1), "")
if err != nil {
t.Fatal(err)
}
assertReferences(t, layer1a, layer1b)
// Attempt register, should be same
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
if err != nil {
t.Fatal(err)
}
graphID2 := stringid.GenerateRandomID()
if err := graph.Create(graphID2, graphID1, ""); err != nil {
t.Fatal(err)
}
if _, err := graph.ApplyDiff(graphID2, graphID1, archive.Reader(bytes.NewReader(tar2))); err != nil {
t.Fatal(err)
}
tf2 := filepath.Join(td, "tar2.json.gz")
if err := writeTarSplitFile(tf2, tar2); err != nil {
t.Fatal(err)
}
diffID, size, err = ls.(*layerStore).ChecksumForGraphID(graphID2, graphID1, tf2, newTarDataPath)
if err != nil {
t.Fatal(err)
}
layer2b, err := ls.(*layerStore).RegisterByGraphID(graphID2, layer1a.ChainID(), diffID, tf2, size)
if err != nil {
t.Fatal(err)
}
assertReferences(t, layer2a, layer2b)
if metadata, err := ls.Release(layer2a); err != nil {
t.Fatal(err)
} else if len(metadata) > 0 {
t.Fatalf("Unexpected layer removal after first release: %#v", metadata)
}
metadata, err := ls.Release(layer2b)
if err != nil {
t.Fatal(err)
}
assertMetadata(t, metadata, createMetadata(layer2a))
}
func tarFromFilesInGraph(graph graphdriver.Driver, graphID, parentID string, files ...FileApplier) ([]byte, error) {
t, err := tarFromFiles(files...)
if err != nil {
return nil, err
}
if err := graph.Create(graphID, parentID, ""); err != nil {
return nil, err
}
if _, err := graph.ApplyDiff(graphID, parentID, archive.Reader(bytes.NewReader(t))); err != nil {
return nil, err
}
ar, err := graph.Diff(graphID, parentID)
if err != nil {
return nil, err
}
defer ar.Close()
return ioutil.ReadAll(ar)
}
func TestLayerMigrationNoTarsplit(t *testing.T) {
td, err := ioutil.TempDir("", "migration-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(td)
layer1Files := []FileApplier{
newTestFile("/root/.bashrc", []byte("# Boring configuration"), 0644),
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
}
layer2Files := []FileApplier{
newTestFile("/root/.bashrc", []byte("# Updated configuration"), 0644),
}
graph, err := newVFSGraphDriver(filepath.Join(td, "graphdriver-"))
if err != nil {
t.Fatal(err)
}
graphID1 := stringid.GenerateRandomID()
graphID2 := stringid.GenerateRandomID()
tar1, err := tarFromFilesInGraph(graph, graphID1, "", layer1Files...)
if err != nil {
t.Fatal(err)
}
tar2, err := tarFromFilesInGraph(graph, graphID2, graphID1, layer2Files...)
if err != nil {
t.Fatal(err)
}
fms, err := NewFSMetadataStore(filepath.Join(td, "layers"))
if err != nil {
t.Fatal(err)
}
ls, err := NewStoreFromGraphDriver(fms, graph)
if err != nil {
t.Fatal(err)
}
newTarDataPath := filepath.Join(td, ".migration-tardata")
diffID, size, err := ls.(*layerStore).ChecksumForGraphID(graphID1, "", "", newTarDataPath)
if err != nil {
t.Fatal(err)
}
layer1a, err := ls.(*layerStore).RegisterByGraphID(graphID1, "", diffID, newTarDataPath, size)
if err != nil {
t.Fatal(err)
}
layer1b, err := ls.Register(bytes.NewReader(tar1), "")
if err != nil {
t.Fatal(err)
}
assertReferences(t, layer1a, layer1b)
// Attempt register, should be same
layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID())
if err != nil {
t.Fatal(err)
}
diffID, size, err = ls.(*layerStore).ChecksumForGraphID(graphID2, graphID1, "", newTarDataPath)
if err != nil {
t.Fatal(err)
}
layer2b, err := ls.(*layerStore).RegisterByGraphID(graphID2, layer1a.ChainID(), diffID, newTarDataPath, size)
if err != nil {
t.Fatal(err)
}
assertReferences(t, layer2a, layer2b)
if metadata, err := ls.Release(layer2a); err != nil {
t.Fatal(err)
} else if len(metadata) > 0 {
t.Fatalf("Unexpected layer removal after first release: %#v", metadata)
}
metadata, err := ls.Release(layer2b)
if err != nil {
t.Fatal(err)
}
assertMetadata(t, metadata, createMetadata(layer2a))
}
func TestMountMigration(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
baseFiles := []FileApplier{
newTestFile("/root/.bashrc", []byte("# Boring configuration"), 0644),
newTestFile("/etc/profile", []byte("# Base configuration"), 0644),
}
initFiles := []FileApplier{
newTestFile("/etc/hosts", []byte{}, 0644),
newTestFile("/etc/resolv.conf", []byte{}, 0644),
}
mountFiles := []FileApplier{
newTestFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
newTestFile("/root/.bashrc", []byte("# Updated configuration"), 0644),
newTestFile("/root/testfile1.txt", []byte("nothing valuable"), 0644),
}
initTar, err := tarFromFiles(initFiles...)
if err != nil {
t.Fatal(err)
}
mountTar, err := tarFromFiles(mountFiles...)
if err != nil {
t.Fatal(err)
}
graph := ls.(*layerStore).driver
layer1, err := createLayer(ls, "", initWithFiles(baseFiles...))
if err != nil {
t.Fatal(err)
}
graphID1 := layer1.(*referencedCacheLayer).cacheID
containerID := stringid.GenerateRandomID()
containerInit := fmt.Sprintf("%s-init", containerID)
if err := graph.Create(containerInit, graphID1, ""); err != nil {
t.Fatal(err)
}
if _, err := graph.ApplyDiff(containerInit, graphID1, archive.Reader(bytes.NewReader(initTar))); err != nil {
t.Fatal(err)
}
if err := graph.Create(containerID, containerInit, ""); err != nil {
t.Fatal(err)
}
if _, err := graph.ApplyDiff(containerID, containerInit, archive.Reader(bytes.NewReader(mountTar))); err != nil {
t.Fatal(err)
}
if err := ls.(*layerStore).CreateRWLayerByGraphID("migration-mount", containerID, layer1.ChainID()); err != nil {
t.Fatal(err)
}
rwLayer1, err := ls.GetRWLayer("migration-mount")
if err != nil {
t.Fatal(err)
}
if _, err := rwLayer1.Mount(""); err != nil {
t.Fatal(err)
}
changes, err := rwLayer1.Changes()
if err != nil {
t.Fatal(err)
}
if expected := 5; len(changes) != expected {
t.Logf("Changes %#v", changes)
t.Fatalf("Wrong number of changes %d, expected %d", len(changes), expected)
}
sortChanges(changes)
assertChange(t, changes[0], archive.Change{
Path: "/etc",
Kind: archive.ChangeModify,
})
assertChange(t, changes[1], archive.Change{
Path: "/etc/hosts",
Kind: archive.ChangeModify,
})
assertChange(t, changes[2], archive.Change{
Path: "/root",
Kind: archive.ChangeModify,
})
assertChange(t, changes[3], archive.Change{
Path: "/root/.bashrc",
Kind: archive.ChangeModify,
})
assertChange(t, changes[4], archive.Change{
Path: "/root/testfile1.txt",
Kind: archive.ChangeAdd,
})
assertActivityCount(t, rwLayer1, 1)
if _, err := ls.CreateRWLayer("migration-mount", layer1.ChainID(), "", nil); err == nil {
t.Fatal("Expected error creating mount with same name")
} else if err != ErrMountNameConflict {
t.Fatal(err)
}
rwLayer2, err := ls.GetRWLayer("migration-mount")
if err != nil {
t.Fatal(err)
}
if getMountLayer(rwLayer1) != getMountLayer(rwLayer2) {
t.Fatal("Expected same layer from get with same name as from migrate")
}
if _, err := rwLayer2.Mount(""); err != nil {
t.Fatal(err)
}
assertActivityCount(t, rwLayer2, 1)
assertActivityCount(t, rwLayer1, 1)
if _, err := rwLayer2.Mount(""); err != nil {
t.Fatal(err)
}
assertActivityCount(t, rwLayer2, 2)
assertActivityCount(t, rwLayer1, 1)
if metadata, err := ls.Release(layer1); err != nil {
t.Fatal(err)
} else if len(metadata) > 0 {
t.Fatalf("Expected no layers to be deleted, deleted %#v", metadata)
}
if err := rwLayer1.Unmount(); err != nil {
t.Fatal(err)
}
assertActivityCount(t, rwLayer2, 2)
assertActivityCount(t, rwLayer1, 0)
if _, err := ls.ReleaseRWLayer(rwLayer1); err != nil {
t.Fatal(err)
}
if err := rwLayer2.Unmount(); err != nil {
t.Fatal(err)
}
if _, err := ls.ReleaseRWLayer(rwLayer2); err == nil {
t.Fatal("Expected error deleting active mount")
}
if err := rwLayer2.Unmount(); err != nil {
t.Fatal(err)
}
metadata, err := ls.ReleaseRWLayer(rwLayer2)
if err != nil {
t.Fatal(err)
}
if len(metadata) == 0 {
t.Fatal("Expected base layer to be deleted when deleting mount")
}
assertMetadata(t, metadata, createMetadata(layer1))
}

217
vendor/github.com/hyperhq/hypercli/layer/mount_test.go generated vendored Normal file
View File

@@ -0,0 +1,217 @@
package layer
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"testing"
"github.com/hyperhq/hypercli/pkg/archive"
)
func TestMountInit(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
basefile := newTestFile("testfile.txt", []byte("base data!"), 0644)
initfile := newTestFile("testfile.txt", []byte("init data!"), 0777)
li := initWithFiles(basefile)
layer, err := createLayer(ls, "", li)
if err != nil {
t.Fatal(err)
}
mountInit := func(root string) error {
return initfile.ApplyFile(root)
}
m, err := ls.CreateRWLayer("fun-mount", layer.ChainID(), "", mountInit)
if err != nil {
t.Fatal(err)
}
path, err := m.Mount("")
if err != nil {
t.Fatal(err)
}
f, err := os.Open(filepath.Join(path, "testfile.txt"))
if err != nil {
t.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
if expected := "init data!"; string(b) != expected {
t.Fatalf("Unexpected test file contents %q, expected %q", string(b), expected)
}
if fi.Mode().Perm() != 0777 {
t.Fatalf("Unexpected filemode %o, expecting %o", fi.Mode().Perm(), 0777)
}
}
func TestMountSize(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
content1 := []byte("Base contents")
content2 := []byte("Mutable contents")
contentInit := []byte("why am I excluded from the size ☹")
li := initWithFiles(newTestFile("file1", content1, 0644))
layer, err := createLayer(ls, "", li)
if err != nil {
t.Fatal(err)
}
mountInit := func(root string) error {
return newTestFile("file-init", contentInit, 0777).ApplyFile(root)
}
m, err := ls.CreateRWLayer("mount-size", layer.ChainID(), "", mountInit)
if err != nil {
t.Fatal(err)
}
path, err := m.Mount("")
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(path, "file2"), content2, 0755); err != nil {
t.Fatal(err)
}
mountSize, err := m.Size()
if err != nil {
t.Fatal(err)
}
if expected := len(content2); int(mountSize) != expected {
t.Fatalf("Unexpected mount size %d, expected %d", int(mountSize), expected)
}
}
func TestMountChanges(t *testing.T) {
ls, cleanup := newTestStore(t)
defer cleanup()
basefiles := []FileApplier{
newTestFile("testfile1.txt", []byte("base data!"), 0644),
newTestFile("testfile2.txt", []byte("base data!"), 0644),
newTestFile("testfile3.txt", []byte("base data!"), 0644),
}
initfile := newTestFile("testfile1.txt", []byte("init data!"), 0777)
li := initWithFiles(basefiles...)
layer, err := createLayer(ls, "", li)
if err != nil {
t.Fatal(err)
}
mountInit := func(root string) error {
return initfile.ApplyFile(root)
}
m, err := ls.CreateRWLayer("mount-changes", layer.ChainID(), "", mountInit)
if err != nil {
t.Fatal(err)
}
path, err := m.Mount("")
if err != nil {
t.Fatal(err)
}
if err := os.Chmod(filepath.Join(path, "testfile1.txt"), 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(path, "testfile1.txt"), []byte("mount data!"), 0755); err != nil {
t.Fatal(err)
}
if err := os.Remove(filepath.Join(path, "testfile2.txt")); err != nil {
t.Fatal(err)
}
if err := os.Chmod(filepath.Join(path, "testfile3.txt"), 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(path, "testfile4.txt"), []byte("mount data!"), 0644); err != nil {
t.Fatal(err)
}
changes, err := m.Changes()
if err != nil {
t.Fatal(err)
}
if expected := 4; len(changes) != expected {
t.Fatalf("Wrong number of changes %d, expected %d", len(changes), expected)
}
sortChanges(changes)
assertChange(t, changes[0], archive.Change{
Path: "/testfile1.txt",
Kind: archive.ChangeModify,
})
assertChange(t, changes[1], archive.Change{
Path: "/testfile2.txt",
Kind: archive.ChangeDelete,
})
assertChange(t, changes[2], archive.Change{
Path: "/testfile3.txt",
Kind: archive.ChangeModify,
})
assertChange(t, changes[3], archive.Change{
Path: "/testfile4.txt",
Kind: archive.ChangeAdd,
})
}
func assertChange(t *testing.T, actual, expected archive.Change) {
if actual.Path != expected.Path {
t.Fatalf("Unexpected change path %s, expected %s", actual.Path, expected.Path)
}
if actual.Kind != expected.Kind {
t.Fatalf("Unexpected change type %s, expected %s", actual.Kind, expected.Kind)
}
}
func sortChanges(changes []archive.Change) {
cs := &changeSorter{
changes: changes,
}
sort.Sort(cs)
}
type changeSorter struct {
changes []archive.Change
}
func (cs *changeSorter) Len() int {
return len(cs.changes)
}
func (cs *changeSorter) Swap(i, j int) {
cs.changes[i], cs.changes[j] = cs.changes[j], cs.changes[i]
}
func (cs *changeSorter) Less(i, j int) bool {
return cs.changes[i].Path < cs.changes[j].Path
}

View File

@@ -0,0 +1,144 @@
package layer
import (
"io"
"sync"
"github.com/hyperhq/hypercli/pkg/archive"
)
type mountedLayer struct {
name string
mountID string
initID string
parent *roLayer
layerStore *layerStore
references map[RWLayer]*referencedRWLayer
}
func (ml *mountedLayer) cacheParent() string {
if ml.initID != "" {
return ml.initID
}
if ml.parent != nil {
return ml.parent.cacheID
}
return ""
}
func (ml *mountedLayer) TarStream() (io.ReadCloser, error) {
archiver, err := ml.layerStore.driver.Diff(ml.mountID, ml.cacheParent())
if err != nil {
return nil, err
}
return archiver, nil
}
func (ml *mountedLayer) Name() string {
return ml.name
}
func (ml *mountedLayer) Parent() Layer {
if ml.parent != nil {
return ml.parent
}
// Return a nil interface instead of an interface wrapping a nil
// pointer.
return nil
}
func (ml *mountedLayer) Mount(mountLabel string) (string, error) {
return ml.layerStore.driver.Get(ml.mountID, mountLabel)
}
func (ml *mountedLayer) Unmount() error {
return ml.layerStore.driver.Put(ml.mountID)
}
func (ml *mountedLayer) Size() (int64, error) {
return ml.layerStore.driver.DiffSize(ml.mountID, ml.cacheParent())
}
func (ml *mountedLayer) Changes() ([]archive.Change, error) {
return ml.layerStore.driver.Changes(ml.mountID, ml.cacheParent())
}
func (ml *mountedLayer) Metadata() (map[string]string, error) {
return ml.layerStore.driver.GetMetadata(ml.mountID)
}
func (ml *mountedLayer) getReference() RWLayer {
ref := &referencedRWLayer{
mountedLayer: ml,
}
ml.references[ref] = ref
return ref
}
func (ml *mountedLayer) hasReferences() bool {
return len(ml.references) > 0
}
func (ml *mountedLayer) deleteReference(ref RWLayer) error {
rl, ok := ml.references[ref]
if !ok {
return ErrLayerNotRetained
}
if err := rl.release(); err != nil {
return err
}
delete(ml.references, ref)
return nil
}
type referencedRWLayer struct {
*mountedLayer
activityL sync.Mutex
activityCount int
}
func (rl *referencedRWLayer) release() error {
rl.activityL.Lock()
defer rl.activityL.Unlock()
if rl.activityCount > 0 {
return ErrActiveMount
}
rl.activityCount = -1
return nil
}
func (rl *referencedRWLayer) Mount(mountLabel string) (string, error) {
rl.activityL.Lock()
defer rl.activityL.Unlock()
if rl.activityCount == -1 {
return "", ErrLayerNotRetained
}
rl.activityCount++
return rl.mountedLayer.Mount(mountLabel)
}
func (rl *referencedRWLayer) Unmount() error {
rl.activityL.Lock()
defer rl.activityL.Unlock()
if rl.activityCount == 0 {
return ErrNotMounted
}
if rl.activityCount == -1 {
return ErrLayerNotRetained
}
rl.activityCount--
return rl.mountedLayer.Unmount()
}

119
vendor/github.com/hyperhq/hypercli/layer/ro_layer.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
package layer
import "io"
type roLayer struct {
chainID ChainID
diffID DiffID
parent *roLayer
cacheID string
size int64
layerStore *layerStore
referenceCount int
references map[Layer]struct{}
}
func (rl *roLayer) TarStream() (io.ReadCloser, error) {
r, err := rl.layerStore.store.TarSplitReader(rl.chainID)
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
go func() {
err := rl.layerStore.assembleTarTo(rl.cacheID, r, nil, pw)
if err != nil {
pw.CloseWithError(err)
} else {
pw.Close()
}
}()
return pr, nil
}
func (rl *roLayer) ChainID() ChainID {
return rl.chainID
}
func (rl *roLayer) DiffID() DiffID {
return rl.diffID
}
func (rl *roLayer) Parent() Layer {
if rl.parent == nil {
return nil
}
return rl.parent
}
func (rl *roLayer) Size() (size int64, err error) {
if rl.parent != nil {
size, err = rl.parent.Size()
if err != nil {
return
}
}
return size + rl.size, nil
}
func (rl *roLayer) DiffSize() (size int64, err error) {
return rl.size, nil
}
func (rl *roLayer) Metadata() (map[string]string, error) {
return rl.layerStore.driver.GetMetadata(rl.cacheID)
}
type referencedCacheLayer struct {
*roLayer
}
func (rl *roLayer) getReference() Layer {
ref := &referencedCacheLayer{
roLayer: rl,
}
rl.references[ref] = struct{}{}
return ref
}
func (rl *roLayer) hasReference(ref Layer) bool {
_, ok := rl.references[ref]
return ok
}
func (rl *roLayer) hasReferences() bool {
return len(rl.references) > 0
}
func (rl *roLayer) deleteReference(ref Layer) {
delete(rl.references, ref)
}
func (rl *roLayer) depth() int {
if rl.parent == nil {
return 1
}
return rl.parent.depth() + 1
}
func storeLayer(tx MetadataTransaction, layer *roLayer) error {
if err := tx.SetDiffID(layer.diffID); err != nil {
return err
}
if err := tx.SetSize(layer.size); err != nil {
return err
}
if err := tx.SetCacheID(layer.cacheID); err != nil {
return err
}
if layer.parent != nil {
if err := tx.SetParent(layer.parent.chainID); err != nil {
return err
}
}
return nil
}