From 16ce0efa962a54fa4c8b7a918ab73a01c1efc9b1 Mon Sep 17 00:00:00 2001 From: George Hicken Date: Mon, 10 Dec 2018 18:06:35 -0800 Subject: [PATCH] Portlayer images use standard Join pattern (#7397) This brings consistent organisation and naming of files to the storage portlayer and moves Image to use the standard Join/Bind semantic. Previously container images were injected into the cVM spec inline in the exec component of the portlayer. This was tech debt from the very early days of the project before the Join/Bind semantic was fully established. This also moves and renames files to make it easier to locate specific portions of code, both within a single implementation and across implementations (implementations are currently nfs and vsphere). This adds package documentation to provide developers with some introduction and guidance at a high level. It does not detail many of the lower level packages. There are some additional structure naming changes that could be made for consistency, and some leaking abstractions (e.g. exposing volume type to handlers) that should eventually be addressed. This is merge without those as it is a significant improvement over current state of affairs and may simplify some outstanding work. --- infra/scripts/bash-helpers.sh | 6 +- lib/apiservers/engine/backends/container.go | 5 + .../engine/backends/container_test.go | 29 +- .../engine/backends/eventmonitor.go | 2 +- .../engine/proxy/container_proxy.go | 26 -- lib/apiservers/engine/proxy/storage_proxy.go | 56 ++++ .../restapi/handlers/containers_handlers.go | 11 +- .../restapi/handlers/storage_handlers.go | 112 +++++--- .../restapi/handlers/storage_handlers_test.go | 225 +-------------- lib/apiservers/portlayer/swagger.json | 120 ++++++-- lib/guest/guest.go | 3 +- lib/guest/linux.go | 10 +- lib/portlayer/exec/handle.go | 14 +- lib/portlayer/storage/config.go | 4 +- .../container.go => container/export.go} | 259 ++++-------------- lib/portlayer/storage/container/import.go | 120 ++++++++ lib/portlayer/storage/container/store.go | 84 ++++++ .../{image_cache.go => image/cache/cache.go} | 62 +++-- .../cache/cache_test.go} | 221 +++------------ lib/portlayer/storage/image/errors.go | 32 +++ lib/portlayer/storage/{ => image}/image.go | 26 +- .../storage/{ => image}/image_test.go | 9 +- .../vsphere.go => image/mock/export.go} | 26 +- lib/portlayer/storage/image/mock/import.go | 31 +++ lib/portlayer/storage/image/mock/join.go | 17 ++ lib/portlayer/storage/image/mock/store.go | 188 +++++++++++++ lib/portlayer/storage/image/vsphere/export.go | 111 ++++++++ lib/portlayer/storage/image/vsphere/import.go | 65 +++++ lib/portlayer/storage/image/vsphere/join.go | 70 +++++ .../image.go => image/vsphere/store.go} | 95 +++---- .../vsphere/store_test.go} | 15 +- .../{data_sink.go => mount_datasink.go} | 2 + .../{data_source.go => mount_datasource.go} | 0 lib/portlayer/storage/nfs/target.go | 8 +- lib/portlayer/storage/storage.go | 227 ++++++++++----- .../cache/cache.go} | 36 +-- .../cache/cache_test.go} | 34 +-- lib/portlayer/storage/{ => volume}/errors.go | 21 +- lib/portlayer/storage/volume/mock/export.go | 31 +++ lib/portlayer/storage/volume/mock/import.go | 31 +++ lib/portlayer/storage/volume/mock/join.go | 17 ++ lib/portlayer/storage/volume/mock/store.go | 109 ++++++++ .../storage/{ => volume}/nfs/disk.go | 2 +- lib/portlayer/storage/volume/nfs/export.go | 42 +++ lib/portlayer/storage/volume/nfs/import.go | 34 +++ .../storage/{nfs/vm.go => volume/nfs/join.go} | 8 +- .../{nfs/volume.go => volume/nfs/store.go} | 44 +-- .../nfs/store_test.go} | 11 +- lib/portlayer/storage/{ => volume}/volume.go | 11 +- .../storage/{ => volume}/volume_test.go | 4 +- .../storage/{ => volume}/vsphere/export.go | 93 +------ .../storage/{ => volume}/vsphere/import.go | 48 +--- .../{vsphere/vm.go => volume/vsphere/join.go} | 58 ++-- .../volume.go => volume/vsphere/store.go} | 33 ++- .../vsphere/store_test.go} | 19 +- lib/portlayer/storage/vsphere/metadata.go | 6 +- .../storage/vsphere/toolbox_common.go | 6 +- lib/spec/disk.go | 40 +-- lib/spec/spec.go | 24 -- lib/tether/shared/constants.go | 10 +- lib/tether/toolbox.go | 4 +- pkg/fs/ext4.go | 12 +- tests/concurrent/concurrent.go | 1 - 63 files changed, 1828 insertions(+), 1252 deletions(-) rename lib/portlayer/storage/{vsphere/container.go => container/export.go} (51%) create mode 100644 lib/portlayer/storage/container/import.go create mode 100644 lib/portlayer/storage/container/store.go rename lib/portlayer/storage/{image_cache.go => image/cache/cache.go} (87%) rename lib/portlayer/storage/{image_cache_test.go => image/cache/cache_test.go} (72%) create mode 100644 lib/portlayer/storage/image/errors.go rename lib/portlayer/storage/{ => image}/image.go (86%) rename lib/portlayer/storage/{ => image}/image_test.go (88%) rename lib/portlayer/storage/{vsphere/vsphere.go => image/mock/export.go} (54%) create mode 100644 lib/portlayer/storage/image/mock/import.go create mode 100644 lib/portlayer/storage/image/mock/join.go create mode 100644 lib/portlayer/storage/image/mock/store.go create mode 100644 lib/portlayer/storage/image/vsphere/export.go create mode 100644 lib/portlayer/storage/image/vsphere/import.go create mode 100644 lib/portlayer/storage/image/vsphere/join.go rename lib/portlayer/storage/{vsphere/image.go => image/vsphere/store.go} (91%) rename lib/portlayer/storage/{vsphere/image_test.go => image/vsphere/store_test.go} (97%) rename lib/portlayer/storage/{data_sink.go => mount_datasink.go} (94%) rename lib/portlayer/storage/{data_source.go => mount_datasource.go} (100%) rename lib/portlayer/storage/{volume_cache.go => volume/cache/cache.go} (89%) rename lib/portlayer/storage/{volume_cache_test.go => volume/cache/cache_test.go} (90%) rename lib/portlayer/storage/{ => volume}/errors.go (80%) create mode 100644 lib/portlayer/storage/volume/mock/export.go create mode 100644 lib/portlayer/storage/volume/mock/import.go create mode 100644 lib/portlayer/storage/volume/mock/join.go create mode 100644 lib/portlayer/storage/volume/mock/store.go rename lib/portlayer/storage/{ => volume}/nfs/disk.go (95%) create mode 100644 lib/portlayer/storage/volume/nfs/export.go create mode 100644 lib/portlayer/storage/volume/nfs/import.go rename lib/portlayer/storage/{nfs/vm.go => volume/nfs/join.go} (92%) rename lib/portlayer/storage/{nfs/volume.go => volume/nfs/store.go} (82%) rename lib/portlayer/storage/{nfs/volume_test.go => volume/nfs/store_test.go} (97%) rename lib/portlayer/storage/{ => volume}/volume.go (95%) rename lib/portlayer/storage/{ => volume}/volume_test.go (94%) rename lib/portlayer/storage/{ => volume}/vsphere/export.go (64%) rename lib/portlayer/storage/{ => volume}/vsphere/import.go (75%) rename lib/portlayer/storage/{vsphere/vm.go => volume/vsphere/join.go} (51%) rename lib/portlayer/storage/{vsphere/volume.go => volume/vsphere/store.go} (83%) rename lib/portlayer/storage/{vsphere/volume_test.go => volume/vsphere/store_test.go} (87%) diff --git a/infra/scripts/bash-helpers.sh b/infra/scripts/bash-helpers.sh index d854c981d8..c3f86fdf6d 100644 --- a/infra/scripts/bash-helpers.sh +++ b/infra/scripts/bash-helpers.sh @@ -136,7 +136,11 @@ vic-inspect () { vic-upgrade () { vicProfileTranscode - "$(vic-path)/bin/${VIC_VERSION}/vic-machine-$OS" upgrade --target="$TARGET_URL" --compute-resource="$COMPUTE" --name="${VIC_NAME:-${USER}test}" --thumbprint="$THUMBPRINT" "$@" + # change to the bin directory as that's where our certs would have been generated by vic-create + ( + cd "$(vic-path)"/bin || return + "$(vic-path)/bin/${VIC_VERSION}/vic-machine-$OS" upgrade --target="$TARGET_URL" --compute-resource="$COMPUTE" --name="${VIC_NAME:-${USER}test}" --thumbprint="$THUMBPRINT" "$@" + ) } vic-ls () { diff --git a/lib/apiservers/engine/backends/container.go b/lib/apiservers/engine/backends/container.go index f60a533c59..0101853de7 100644 --- a/lib/apiservers/engine/backends/container.go +++ b/lib/apiservers/engine/backends/container.go @@ -752,6 +752,11 @@ func (c *ContainerBackend) containerCreate(op trace.Operation, vc *viccontainer. return "", err } + h, err = c.storageProxy.AddImageToContainer(op, h, id, vc.LayerID, vc.ImageID, config) + if err != nil { + return "", err + } + h, err = c.containerProxy.CreateContainerTask(op, h, id, config) if err != nil { return "", err diff --git a/lib/apiservers/engine/backends/container_test.go b/lib/apiservers/engine/backends/container_test.go index a7d14a38df..6439d2cb33 100644 --- a/lib/apiservers/engine/backends/container_test.go +++ b/lib/apiservers/engine/backends/container_test.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -59,6 +59,15 @@ type CreateHandleMockData struct { createErrSubstr string } +type AddImageMockData struct { + retHandle string + retErr error + deltaID string + layerID string + imageID string + createErrSubstr string +} + type AddToScopeMockData struct { createInputID string retHandle string @@ -99,6 +108,7 @@ type LogMockData struct { type MockContainerProxy struct { mockRespIndices []int mockCreateHandleData []CreateHandleMockData + mockAddImageData []AddImageMockData mockAddToScopeData []AddToScopeMockData mockAddVolumesData []AddVolumesMockData mockAddInteractionData []AddInteractionMockData @@ -135,8 +145,9 @@ var dummyContainers = []string{dummyContainerID, dummyContainerIDTTY} func NewMockContainerProxy() *MockContainerProxy { return &MockContainerProxy{ - mockRespIndices: make([]int, 6), + mockRespIndices: make([]int, 7), mockCreateHandleData: MockCreateHandleData(), + mockAddImageData: MockAddImageData(), mockAddToScopeData: MockAddToScopeData(), mockAddVolumesData: MockAddVolumesData(), mockAddInteractionData: MockAddInteractionData(), @@ -166,6 +177,10 @@ func MockCreateHandleData() []CreateHandleMockData { return mockCreateHandleData } +func MockAddImageData() []AddImageMockData { + return nil +} + func MockAddToScopeData() []AddToScopeMockData { addToScopeNotFound := plscopes.AddContainerNotFound{ Payload: &plmodels.Error{ @@ -461,6 +476,10 @@ func (s *MockStorageProxy) Remove(ctx context.Context, name string) error { return nil } +func (s *MockStorageProxy) AddImageToContainer(ctx context.Context, handle, id, layerID, imageID string, config types.ContainerCreateConfig) (string, error) { + return "", nil +} + func (s *MockStorageProxy) AddVolumesToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) { return "", nil } @@ -504,10 +523,12 @@ func (sp *MockStreamProxy) StreamContainerStats(ctx context.Context, config *con // cache func TestContainerCreateEmptyImageCache(t *testing.T) { mockContainerProxy := NewMockContainerProxy() + mockStorageProxy := NewMockStorageProxy() // Create our personality Container backend cb := &ContainerBackend{ containerProxy: mockContainerProxy, + storageProxy: mockStorageProxy, } // mock a container create config @@ -528,10 +549,12 @@ func TestContainerCreateEmptyImageCache(t *testing.T) { // then vicbackends.ContainerCreate() should return errors from that. func TestCreateHandle(t *testing.T) { mockContainerProxy := NewMockContainerProxy() + mockStorageProxy := NewMockStorageProxy() // Create our personality Container backend cb := &ContainerBackend{ containerProxy: mockContainerProxy, + storageProxy: mockStorageProxy, } AddMockImageToCache() @@ -573,10 +596,12 @@ func TestCreateHandle(t *testing.T) { // possible input/outputs for adding container to scope and calls vicbackends.ContainerCreate() func TestContainerAddToScope(t *testing.T) { mockContainerProxy := NewMockContainerProxy() + mockStorageProxy := NewMockStorageProxy() // Create our personality Container backend cb := &ContainerBackend{ containerProxy: mockContainerProxy, + storageProxy: mockStorageProxy, } AddMockImageToCache() diff --git a/lib/apiservers/engine/backends/eventmonitor.go b/lib/apiservers/engine/backends/eventmonitor.go index 3b82473806..3529b6bcd2 100644 --- a/lib/apiservers/engine/backends/eventmonitor.go +++ b/lib/apiservers/engine/backends/eventmonitor.go @@ -129,7 +129,7 @@ func (m *PortlayerEventMonitor) Start() error { select { case <-m.stop: log.Infof("Portlayer Event Monitor stopped normally") - break + return default: if err = m.monitor(); err != nil { log.Errorf("Restarting Portlayer event monitor due to error: %s", err) diff --git a/lib/apiservers/engine/proxy/container_proxy.go b/lib/apiservers/engine/proxy/container_proxy.go index 542c62ed08..c2c98ecbb8 100644 --- a/lib/apiservers/engine/proxy/container_proxy.go +++ b/lib/apiservers/engine/proxy/container_proxy.go @@ -169,14 +169,6 @@ func (c *ContainerProxy) CreateContainerHandle(ctx context.Context, vc *vicconta return "", "", errors.NillPortlayerClientError("ContainerProxy") } - if vc.ImageID == "" { - return "", "", errors.NotFoundError("No image specified") - } - - if vc.LayerID == "" { - return "", "", errors.NotFoundError("No layer specified") - } - // Call the Exec port layer to create the container host, err := sys.UUID() if err != nil { @@ -186,12 +178,6 @@ func (c *ContainerProxy) CreateContainerHandle(ctx context.Context, vc *vicconta plCreateParams := dockerContainerCreateParamsToPortlayer(ctx, config, vc, host).WithOpID(&opID) createResults, err := c.client.Containers.Create(plCreateParams) if err != nil { - if _, ok := err.(*containers.CreateNotFound); ok { - cerr := fmt.Errorf("No such image: %s", vc.ImageID) - log.Errorf("%s (%s)", cerr, err) - return "", "", errors.NotFoundError(cerr.Error()) - } - // If we get here, most likely something went wrong with the port layer API server return "", "", errors.InternalServerError(err.Error()) } @@ -1083,21 +1069,9 @@ func dockerContainerCreateParamsToPortlayer(ctx context.Context, cc types.Contai config.NumCpus = cc.HostConfig.CPUCount config.MemoryMB = cc.HostConfig.Memory - // Layer/vmdk to use - config.Layer = vc.LayerID - - // Image ID - config.Image = vc.ImageID - - // Repo Requested - config.RepoName = cc.Config.Image - //copy friendly name config.Name = cc.Name - // image store - config.ImageStore = &models.ImageStore{Name: imageStore} - // network config.NetworkDisabled = cc.Config.NetworkDisabled diff --git a/lib/apiservers/engine/proxy/storage_proxy.go b/lib/apiservers/engine/proxy/storage_proxy.go index 2102d39347..20848b77be 100644 --- a/lib/apiservers/engine/proxy/storage_proxy.go +++ b/lib/apiservers/engine/proxy/storage_proxy.go @@ -37,6 +37,7 @@ import ( "github.com/vmware/vic/lib/apiservers/portlayer/models" "github.com/vmware/vic/lib/constants" "github.com/vmware/vic/pkg/trace" + "github.com/vmware/vic/pkg/vsphere/sys" ) type VicStorageProxy interface { @@ -45,6 +46,7 @@ type VicStorageProxy interface { VolumeInfo(ctx context.Context, name string) (*models.VolumeResponse, error) Remove(ctx context.Context, name string) error + AddImageToContainer(ctx context.Context, handle, deltaID, layerID, imageID string, config types.ContainerCreateConfig) (string, error) AddVolumesToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) } @@ -239,6 +241,60 @@ func (s *StorageProxy) Remove(ctx context.Context, name string) error { return nil } +// AddImageToContainer adds the specified image to a container, referenced by handle. +// If an error is returned, the returned handle should not be used. +// - deltaID is the ID to use for the read/write layer - it's expected that this does not exist +// - layerID is the parent layer for the delta layer and is expected to exist +// - imageID is the current label by which we address this image and is recorded as metadata +// - repoName is the repository for the image and is recorded as metadata +// returns: +// modified handle +func (s *StorageProxy) AddImageToContainer(ctx context.Context, handle, deltaID, layerID, imageID string, config types.ContainerCreateConfig) (string, error) { + op := trace.FromContext(ctx, "AddImageToContainer: %s", deltaID) + defer trace.End(trace.Begin(deltaID, op)) + opID := op.ID() + + if s.client == nil { + return "", errors.InternalServerError("ContainerProxy.AddImageToContainer failed to get the portlayer client") + } + + if imageID == "" { + return "", errors.NotFoundError("No image specified") + } + + if layerID == "" { + return "", errors.NotFoundError("No layer specified") + } + + host, err := sys.UUID() + if err != nil { + return "", errors.InternalServerError("ContainerProxy.AddImageToContainer got unexpected error getting VCH UUID") + } + + response, err := s.client.Storage.ImageJoin(storage.NewImageJoinParamsWithContext(op).WithOpID(&opID).WithStoreName(host).WithID(layerID). + WithConfig(&models.ImageJoinConfig{ + Handle: handle, + DeltaID: deltaID, + ImageID: imageID, + RepoName: config.Config.Image, + })) + if err != nil { + if _, ok := err.(*storage.ImageJoinNotFound); ok { + cerr := fmt.Errorf("No such image: %s", imageID) + op.Errorf("%s: %s", cerr, err) + return "", errors.NotFoundError(cerr.Error()) + } + + return "", errors.InternalServerError(err.Error()) + } + handle, ok := response.Payload.Handle.(string) + if !ok { + return "", errors.InternalServerError(fmt.Sprintf("Type assertion failed for %#+v", handle)) + } + + return handle, nil +} + // AddVolumesToContainer adds volumes to a container, referenced by handle. // If an error is returned, the returned handle should not be used. // diff --git a/lib/apiservers/portlayer/restapi/handlers/containers_handlers.go b/lib/apiservers/portlayer/restapi/handlers/containers_handlers.go index ed88709d9f..b283a8f673 100644 --- a/lib/apiservers/portlayer/restapi/handlers/containers_handlers.go +++ b/lib/apiservers/portlayer/restapi/handlers/containers_handlers.go @@ -80,8 +80,6 @@ func (handler *ContainersHandlersImpl) CreateHandler(params containers.CreatePar op := trace.NewOperationFromID(context.Background(), params.OpID, "containers.CreateHandler(%s)", params.CreateConfig.Name) defer trace.End(trace.Begin("CreateHandler", op)) - var err error - session := handler.handlerCtx.Session id := uid.New().String() @@ -106,9 +104,6 @@ func (handler *ContainersHandlersImpl) CreateHandler(params containers.CreatePar CreateTime: time.Now().UTC().UnixNano(), Version: version.GetBuild(), Key: pem.EncodeToMemory(&privateKeyBlock), - LayerID: params.CreateConfig.Layer, - ImageID: params.CreateConfig.Image, - RepoName: params.CreateConfig.RepoName, Hostname: params.CreateConfig.Hostname, Domainname: params.CreateConfig.Domainname, } @@ -122,9 +117,7 @@ func (handler *ContainersHandlersImpl) CreateHandler(params containers.CreatePar // Create the executor.ExecutorCreateConfig c := &exec.ContainerCreateConfig{ - Metadata: m, - ParentImageID: params.CreateConfig.Layer, - ImageStoreName: params.CreateConfig.ImageStore.Name, + Metadata: m, Resources: exec.Resources{ NumCPUs: params.CreateConfig.NumCpus, MemoryMB: params.CreateConfig.MemoryMB, @@ -133,7 +126,7 @@ func (handler *ContainersHandlersImpl) CreateHandler(params containers.CreatePar h, err := exec.Create(op, session, c) if err != nil { - log.Errorf("ContainerCreate error: %s", err.Error()) + op.Errorf("ContainerCreate error: %s", err.Error()) return containers.NewCreateNotFound().WithPayload(&models.Error{Message: err.Error()}) } diff --git a/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go b/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go index 3f9c298d9c..ce91ec5860 100644 --- a/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go +++ b/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,7 +31,15 @@ import ( "github.com/vmware/vic/lib/archive" epl "github.com/vmware/vic/lib/portlayer/exec" spl "github.com/vmware/vic/lib/portlayer/storage" - "github.com/vmware/vic/lib/portlayer/storage/nfs" + "github.com/vmware/vic/lib/portlayer/storage/container" + "github.com/vmware/vic/lib/portlayer/storage/image" + icache "github.com/vmware/vic/lib/portlayer/storage/image/cache" + vsimage "github.com/vmware/vic/lib/portlayer/storage/image/vsphere" + nfsclient "github.com/vmware/vic/lib/portlayer/storage/nfs" + "github.com/vmware/vic/lib/portlayer/storage/volume" + vcache "github.com/vmware/vic/lib/portlayer/storage/volume/cache" + "github.com/vmware/vic/lib/portlayer/storage/volume/nfs" + vsvolume "github.com/vmware/vic/lib/portlayer/storage/volume/vsphere" "github.com/vmware/vic/lib/portlayer/storage/vsphere" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" @@ -40,9 +48,8 @@ import ( // StorageHandlersImpl is the receiver for all of the storage handler methods type StorageHandlersImpl struct { - imageCache *spl.NameLookupCache - volumeCache *spl.VolumeLookupCache - containerStore *vsphere.ContainerStore + imageCache *icache.NameLookupCache + volumeCache *vcache.VolumeLookupCache } const ( @@ -69,7 +76,7 @@ func (h *StorageHandlersImpl) Configure(api *operations.PortLayerAPI, handlerCtx op.Warnf("Multiple image stores found. Multiple image stores are not yet supported. Using [%s] %s", imageStoreURL.Host, imageStoreURL.Path) } - ds, err := vsphere.NewImageStore(op, handlerCtx.Session, &imageStoreURL) + imageStore, err := vsimage.NewImageStore(op, handlerCtx.Session, &imageStoreURL) if err != nil { op.Panicf("Cannot instantiate storage layer: %s", err) } @@ -77,19 +84,18 @@ func (h *StorageHandlersImpl) Configure(api *operations.PortLayerAPI, handlerCtx // The imagestore is implemented via a cache which is backed via an // implementation that writes to disks. The cache is used to avoid // expensive metadata lookups. - h.imageCache = spl.NewLookupCache(ds) + h.imageCache = icache.NewLookupCache(imageStore) - spl.RegisterImporter(op, imageStoreURL.String(), ds) - spl.RegisterExporter(op, imageStoreURL.String(), ds) + spl.RegisterImporter(op, imageStoreURL.String(), imageStore) + spl.RegisterExporter(op, imageStoreURL.String(), imageStore) - c, err := vsphere.NewContainerStore(op, handlerCtx.Session, h.imageCache) + containerStore, err := container.NewContainerStore(op, handlerCtx.Session, h.imageCache) if err != nil { - op.Panicf("Couldn't create containerStore: %s", err.Error()) + op.Panicf("Couldn't create container store: %s", err.Error()) } - h.containerStore = c - spl.RegisterImporter(op, "container", h.containerStore) - spl.RegisterExporter(op, "container", h.containerStore) + spl.RegisterImporter(op, "container", containerStore) + spl.RegisterExporter(op, "container", containerStore) // add the volume stores, errors are logged within this function. h.configureVolumeStores(op, handlerCtx) @@ -98,6 +104,7 @@ func (h *StorageHandlersImpl) Configure(api *operations.PortLayerAPI, handlerCtx api.StorageGetImageHandler = storage.GetImageHandlerFunc(h.GetImage) api.StorageListImagesHandler = storage.ListImagesHandlerFunc(h.ListImages) api.StorageWriteImageHandler = storage.WriteImageHandlerFunc(h.WriteImage) + api.StorageImageJoinHandler = storage.ImageJoinHandlerFunc(h.ImageJoin) api.StorageDeleteImageHandler = storage.DeleteImageHandlerFunc(h.DeleteImage) api.StorageVolumeStoresListHandler = storage.VolumeStoresListHandlerFunc(h.VolumeStoresList) @@ -114,11 +121,11 @@ func (h *StorageHandlersImpl) Configure(api *operations.PortLayerAPI, handlerCtx func (h *StorageHandlersImpl) configureVolumeStores(op trace.Operation, handlerCtx *HandlerContext) { var ( - vs spl.VolumeStorer + vs volume.VolumeStorer err error ) - h.volumeCache = spl.NewVolumeLookupCache(op) + h.volumeCache = vcache.NewVolumeLookupCache(op) // register the pseudo-store to handle the generic "volume" store name spl.RegisterImporter(op, "volume", h.volumeCache) @@ -254,7 +261,7 @@ func (h *StorageHandlersImpl) DeleteImage(params storage.DeleteImageParams) midd return ferr(err, http.StatusInternalServerError) } - image, err := spl.Parse(imageURL) + img, err := image.Parse(imageURL) if err != nil { return ferr(err, http.StatusInternalServerError) } @@ -269,10 +276,11 @@ func (h *StorageHandlersImpl) DeleteImage(params storage.DeleteImageParams) midd keepNodes[idx] = k } - deletedImages, err := h.imageCache.DeleteBranch(op, image, keepNodes) + op2 := trace.FromOperation(op, fmt.Sprintf("DeleteBranch(%s)", img.ID)) + deletedImages, err := h.imageCache.DeleteBranch(op2, img, keepNodes) if err != nil { switch { - case spl.IsErrImageInUse(err): + case image.IsErrImageInUse(err): return ferr(err, http.StatusLocked) case os.IsNotExist(err): @@ -284,8 +292,8 @@ func (h *StorageHandlersImpl) DeleteImage(params storage.DeleteImageParams) midd } result := make([]*models.Image, len(deletedImages)) - for idx, image := range deletedImages { - result[idx] = convertImage(image) + for idx, img := range deletedImages { + result[idx] = convertImage(img) } return storage.NewDeleteImageOK().WithPayload(result) @@ -338,7 +346,7 @@ func (h *StorageHandlersImpl) WriteImage(params storage.WriteImageParams) middle }) } - parent := &spl.Image{ + parent := &image.Image{ Store: u, ID: params.ParentID, } @@ -361,6 +369,38 @@ func (h *StorageHandlersImpl) WriteImage(params storage.WriteImageParams) middle return storage.NewWriteImageCreated().WithPayload(i) } +//ImageJoin modifies the config spec of a container to include the specified image +func (h *StorageHandlersImpl) ImageJoin(params storage.ImageJoinParams) middleware.Responder { + op := trace.NewOperation(context.Background(), "ImageJoin %s", params.ID) + defer trace.End(trace.Begin("", op)) + + handle := epl.HandleFromInterface(params.Config.Handle) + if handle == nil { + err := &models.Error{Message: "Failed to get the Handle"} + return storage.NewImageJoinInternalServerError().WithPayload(err) + } + + storeURL, _ := util.ImageStoreNameToURL(params.StoreName) + img, err := h.imageCache.GetImage(op, storeURL, params.ID) + if err != nil { + op.Errorf("Volumes: StorageHandler : %#v", err) + return storage.NewImageJoinNotFound().WithPayload(&models.Error{Code: http.StatusNotFound, Message: err.Error()}) + } + + cfg := params.Config + handleprime, err := vsimage.Join(op, handle, cfg.DeltaID, cfg.ImageID, cfg.RepoName, img) + if err != nil { + op.Errorf("join image failed: %#v", err) + return storage.NewImageJoinInternalServerError().WithPayload(&models.Error{Message: err.Error()}) + } + + op.Debugf("image %s has been joined as %s", params.ID, cfg.DeltaID) + res := &models.ImageJoinResponse{ + Handle: epl.ReferenceFromHandle(handleprime), + } + return storage.NewImageJoinOK().WithPayload(res) +} + // VolumeStoresList lists the configured volume stores and their datastore path URIs. func (h *StorageHandlersImpl) VolumeStoresList(params storage.VolumeStoresListParams) middleware.Responder { op := trace.NewOperationFromID(context.Background(), params.OpID, "VolumeStoresList") @@ -407,7 +447,7 @@ func (h *StorageHandlersImpl) CreateVolume(params storage.CreateVolumeParams) mi capacity = uint64(params.VolumeRequest.Capacity) } - volume, err := h.volumeCache.VolumeCreate(op, params.VolumeRequest.Name, storeURL, capacity*1024, byteMap) + vol, err := h.volumeCache.VolumeCreate(op, params.VolumeRequest.Name, storeURL, capacity*1024, byteMap) if err != nil { if os.IsExist(err) { @@ -419,7 +459,7 @@ func (h *StorageHandlersImpl) CreateVolume(params storage.CreateVolumeParams) mi } op.Errorf("storagehandler: VolumeCreate error: %#v", err) - if _, ok := err.(spl.VolumeStoreNotFoundError); ok { + if _, ok := err.(volume.VolumeStoreNotFoundError); ok { return storage.NewCreateVolumeNotFound().WithPayload(&models.Error{ Code: http.StatusNotFound, Message: err.Error(), @@ -432,7 +472,7 @@ func (h *StorageHandlersImpl) CreateVolume(params storage.CreateVolumeParams) mi }) } - response := volumeToCreateResponse(volume, params.VolumeRequest) + response := volumeToCreateResponse(vol, params.VolumeRequest) return storage.NewCreateVolumeCreated().WithPayload(&response) } @@ -474,7 +514,7 @@ func (h *StorageHandlersImpl) RemoveVolume(params storage.RemoveVolumeParams) mi Message: err.Error(), }) - case spl.IsErrVolumeInUse(err): + case volume.IsErrVolumeInUse(err): return storage.NewRemoveVolumeConflict().WithPayload(&models.Error{ Message: err.Error(), }) @@ -540,11 +580,13 @@ func (h *StorageHandlersImpl) VolumeJoin(params storage.VolumeJoinParams) middle }) } + // NOTE: unclear to me why we are leaking this logic at this level - the volume should be able to switch Join implementations + // based on its type switch volume.Device.DiskPath().Scheme { case nfsScheme: actualHandle, err = nfs.VolumeJoin(op, actualHandle, volume, params.JoinArgs.MountPath, params.JoinArgs.Flags) case dsScheme: - actualHandle, err = vsphere.VolumeJoin(op, actualHandle, volume, params.JoinArgs.MountPath, params.JoinArgs.Flags) + actualHandle, err = vsvolume.VolumeJoin(op, actualHandle, volume, params.JoinArgs.MountPath, params.JoinArgs.Flags) default: err = fmt.Errorf("unknown scheme (%s) for Volume (%#v)", volume.Device.DiskPath().Scheme, *volume) } @@ -693,7 +735,7 @@ func (h *StorageHandlersImpl) StatPath(params storage.StatPathParams) middleware //utility functions // convert an SPL Image to a swagger-defined Image -func convertImage(image *spl.Image) *models.Image { +func convertImage(image *image.Image) *models.Image { var parent, selfLink string // scratch image @@ -721,7 +763,7 @@ func convertImage(image *spl.Image) *models.Image { } } -func volumeToCreateResponse(volume *spl.Volume, model *models.VolumeRequest) models.VolumeResponse { +func volumeToCreateResponse(volume *volume.Volume, model *models.VolumeRequest) models.VolumeResponse { response := models.VolumeResponse{ Driver: model.Driver, Name: volume.ID, @@ -732,7 +774,7 @@ func volumeToCreateResponse(volume *spl.Volume, model *models.VolumeRequest) mod return response } -func fillVolumeModel(volume *spl.Volume) (models.VolumeResponse, error) { +func fillVolumeModel(volume *volume.Volume) (models.VolumeResponse, error) { storeName, err := util.VolumeStoreName(volume.Store) if err != nil { return models.VolumeResponse{}, err @@ -751,7 +793,7 @@ func fillVolumeModel(volume *spl.Volume) (models.VolumeResponse, error) { return model, nil } -func createMetadataMap(volume *spl.Volume) map[string]string { +func createMetadataMap(volume *volume.Volume) map[string]string { stringMap := make(map[string]string) for k, v := range volume.Info { stringMap[k] = string(v) @@ -759,7 +801,7 @@ func createMetadataMap(volume *spl.Volume) map[string]string { return stringMap } -func createNFSVolumeStore(op trace.Operation, dsurl *url.URL, name string) (spl.VolumeStorer, error) { +func createNFSVolumeStore(op trace.Operation, dsurl *url.URL, name string) (volume.VolumeStorer, error) { var err error uid, gid, err := parseUIDAndGID(dsurl) if err != nil { @@ -768,7 +810,7 @@ func createNFSVolumeStore(op trace.Operation, dsurl *url.URL, name string) (spl. } // XXX replace with the vch name - mnt := nfs.NewMount(dsurl, "vic", uint32(uid), uint32(gid)) + mnt := nfsclient.NewMount(dsurl, "vic", uint32(uid), uint32(gid)) vs, err := nfs.NewVolumeStore(op, name, mnt) if err != nil { op.Errorf("%s", err.Error()) @@ -815,7 +857,7 @@ func parseUIDAndGID(queryURL *url.URL) (int, int, error) { return uid, gid, nil } -func createVsphereVolumeStore(op trace.Operation, dsurl *url.URL, name string, handlerCtx *HandlerContext) (spl.VolumeStorer, error) { +func createVsphereVolumeStore(op trace.Operation, dsurl *url.URL, name string, handlerCtx *HandlerContext) (volume.VolumeStorer, error) { ds, err := datastore.NewHelperFromURL(op, handlerCtx.Session, dsurl) if err != nil { err = fmt.Errorf("cannot find datastores: %s", err) @@ -823,7 +865,7 @@ func createVsphereVolumeStore(op trace.Operation, dsurl *url.URL, name string, h return nil, err } - vs, err := vsphere.NewVolumeStore(op, name, handlerCtx.Session, ds) + vs, err := vsvolume.NewVolumeStore(op, name, handlerCtx.Session, ds) if err != nil { err = fmt.Errorf("cannot instantiate the volume store: %s", err) op.Errorf("%s", err.Error()) diff --git a/lib/apiservers/portlayer/restapi/handlers/storage_handlers_test.go b/lib/apiservers/portlayer/restapi/handlers/storage_handlers_test.go index 2a99465976..0d3d786ad8 100644 --- a/lib/apiservers/portlayer/restapi/handlers/storage_handlers_test.go +++ b/lib/apiservers/portlayer/restapi/handlers/storage_handlers_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package handlers import ( "context" "fmt" - "io" "net/http" "net/url" "os" @@ -25,15 +24,16 @@ import ( "github.com/stretchr/testify/assert" - "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/vic/lib/apiservers/portlayer/models" "github.com/vmware/vic/lib/apiservers/portlayer/restapi/operations/storage" - "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/constants" - spl "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/image" + icache "github.com/vmware/vic/lib/portlayer/storage/image/cache" + imock "github.com/vmware/vic/lib/portlayer/storage/image/mock" + vcache "github.com/vmware/vic/lib/portlayer/storage/volume/cache" + vmock "github.com/vmware/vic/lib/portlayer/storage/volume/mock" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" - "github.com/vmware/vic/pkg/vsphere/vm" ) var ( @@ -48,204 +48,9 @@ var ( } ) -type MockDataStore struct { -} - -type MockVolumeStore struct { - // id -> volume - db map[string]*spl.Volume -} - -func NewMockVolumeStore() *MockVolumeStore { - m := &MockVolumeStore{ - db: make(map[string]*spl.Volume), - } - - return m -} - -// Creates a volume on the given volume store, of the given size, with the given metadata. -func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*spl.Volume, error) { - storeName, err := util.VolumeStoreName(store) - if err != nil { - return nil, err - } - - selfLink, err := util.VolumeURL(storeName, ID) - if err != nil { - return nil, err - } - - vol := &spl.Volume{ - ID: ID, - Store: store, - SelfLink: selfLink, - } - - m.db[ID] = vol - - return vol, nil -} - -// Get an existing volume via it's ID and volume store. -func (m *MockVolumeStore) VolumeGet(op trace.Operation, ID string) (*spl.Volume, error) { - vol, ok := m.db[ID] - if !ok { - return nil, os.ErrNotExist - } - - return vol, nil -} - -// Destroys a volume -func (m *MockVolumeStore) VolumeDestroy(op trace.Operation, vol *spl.Volume) error { - if _, ok := m.db[vol.ID]; !ok { - return os.ErrNotExist - } - - delete(m.db, vol.ID) - - return nil -} - -func (m *MockVolumeStore) VolumeStoresList(op trace.Operation) (map[string]url.URL, error) { - return nil, fmt.Errorf("not implemented") -} - -// Lists all volumes on the given volume store` -func (m *MockVolumeStore) VolumesList(op trace.Operation) ([]*spl.Volume, error) { - var i int - list := make([]*spl.Volume, len(m.db)) - for _, v := range m.db { - t := *v - list[i] = &t - i++ - } - - return list, nil -} - -func (m *MockVolumeStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { - return nil, nil -} - -func (m *MockVolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { - return nil -} - -func (m *MockVolumeStore) NewDataSink(op trace.Operation, id string) (spl.DataSink, error) { - return nil, nil -} - -func (m *MockVolumeStore) NewDataSource(op trace.Operation, id string) (spl.DataSource, error) { - return nil, nil -} - -func (m *MockVolumeStore) URL(op trace.Operation, id string) (*url.URL, error) { - return nil, nil -} - -func (m *MockVolumeStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { - return nil, nil -} - -// GetImageStore checks to see if a named image store exists and returls the -// URL to it if so or error. -func (c *MockDataStore) GetImageStore(op trace.Operation, storeName string) (*url.URL, error) { - _, err := util.ImageStoreNameToURL(storeName) - if err != nil { - return nil, err - } - return nil, os.ErrNotExist -} - -func (c *MockDataStore) CreateImageStore(op trace.Operation, storeName string) (*url.URL, error) { - u, err := util.ImageStoreNameToURL(storeName) - if err != nil { - return nil, err - } - - return u, nil -} - -func (c *MockDataStore) DeleteImageStore(op trace.Operation, storeName string) error { - return nil -} - -func (c *MockDataStore) ListImageStores(op trace.Operation) ([]*url.URL, error) { - return nil, nil -} - -func (c *MockDataStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { - return nil, nil -} - -func (c *MockDataStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { - return nil -} - -func (c *MockDataStore) NewDataSink(op trace.Operation, id string) (spl.DataSink, error) { - return nil, nil -} - -func (c *MockDataStore) NewDataSource(op trace.Operation, id string) (spl.DataSource, error) { - return nil, nil -} - -func (c *MockDataStore) URL(op trace.Operation, id string) (*url.URL, error) { - return nil, nil -} - -func (c *MockDataStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { - return nil, nil -} - -func (c *MockDataStore) WriteImage(op trace.Operation, parent *spl.Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*spl.Image, error) { - storeName, err := util.ImageStoreName(parent.Store) - if err != nil { - return nil, err - } - - selflink, err := util.ImageURL(storeName, ID) - if err != nil { - return nil, err - } - - i := spl.Image{ - ID: ID, - Store: parent.Store, - ParentLink: parent.SelfLink, - SelfLink: selflink, - Metadata: meta, - } - - return &i, nil -} -func (c *MockDataStore) WriteMetadata(op trace.Operation, storeName string, ID string, meta map[string][]byte) error { - return nil -} - -// GetImage gets the specified image from the given store by retreiving it from the cache. -func (c *MockDataStore) GetImage(op trace.Operation, store *url.URL, ID string) (*spl.Image, error) { - if ID == constants.ScratchLayerID { - return &spl.Image{Store: store}, nil - } - - return nil, os.ErrNotExist -} - -// ListImages resturns a list of Images for a list of IDs, or all if no IDs are passed -func (c *MockDataStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*spl.Image, error) { - return nil, fmt.Errorf("store (%s) doesn't exist", store.String()) -} - -func (c *MockDataStore) DeleteImage(op trace.Operation, image *spl.Image) (*spl.Image, error) { - return nil, nil -} - func TestCreateImageStore(t *testing.T) { s := &StorageHandlersImpl{ - imageCache: spl.NewLookupCache(&MockDataStore{}), + imageCache: icache.NewLookupCache(imock.NewMockDataStore(nil)), } store := &models.ImageStore{ @@ -281,7 +86,7 @@ func TestCreateImageStore(t *testing.T) { func TestGetImage(t *testing.T) { s := &StorageHandlersImpl{ - imageCache: spl.NewLookupCache(&MockDataStore{}), + imageCache: icache.NewLookupCache(imock.NewMockDataStore(nil)), } params := &storage.GetImageParams{ @@ -313,7 +118,7 @@ func TestGetImage(t *testing.T) { } // add image to store - parent := spl.Image{ + parent := image.Image{ ID: "scratch", SelfLink: nil, ParentLink: nil, @@ -365,7 +170,7 @@ func TestGetImage(t *testing.T) { func TestListImages(t *testing.T) { s := &StorageHandlersImpl{ - imageCache: spl.NewLookupCache(&MockDataStore{}), + imageCache: icache.NewLookupCache(imock.NewMockDataStore(nil)), } params := &storage.ListImagesParams{ @@ -389,8 +194,8 @@ func TestListImages(t *testing.T) { } // create a set of images - images := make(map[string]*spl.Image) - parent := spl.Image{ + images := make(map[string]*image.Image) + parent := image.Image{ ID: constants.ScratchLayerID, } parent.Store = &testStoreURL @@ -446,7 +251,7 @@ func TestListImages(t *testing.T) { } func TestWriteImage(t *testing.T) { - ic := spl.NewLookupCache(&MockDataStore{}) + ic := icache.NewLookupCache(imock.NewMockDataStore(nil)) // create image store op := trace.NewOperation(context.Background(), "test") @@ -511,9 +316,9 @@ func TestWriteImage(t *testing.T) { func TestVolumeCreate(t *testing.T) { op := trace.NewOperation(context.Background(), "test") - volCache := spl.NewVolumeLookupCache(op) + volCache := vcache.NewVolumeLookupCache(op) - testStore := NewMockVolumeStore() + testStore := vmock.NewMockVolumeStore() _, err := volCache.AddStore(op, "testStore", testStore) if !assert.NoError(t, err) { return diff --git a/lib/apiservers/portlayer/swagger.json b/lib/apiservers/portlayer/swagger.json index 39c1f2dcf9..95e65f7b5c 100644 --- a/lib/apiservers/portlayer/swagger.json +++ b/lib/apiservers/portlayer/swagger.json @@ -637,6 +637,67 @@ } } }, + "post": { + "description": "Sets the image a container will use as its filesystem", + "operationId": "ImageJoin", + "tags": [ + "storage" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "store_name", + "type": "string", + "in": "path", + "required": true + }, + { + "name": "id", + "required": true, + "type": "string", + "in": "path" + }, + { + "name": "config", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ImageJoinConfig" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ImageJoinResponse" + } + }, + "404": { + "description": "Not found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "ServerError", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "default": { + "description": "error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, "delete": { "description": "Delete an image by id in an image store", "summary": "Delete an image", @@ -1964,12 +2025,12 @@ "$ref": "#/definitions/Error" } }, - "409": { - "description": "Task failed due to power state change", - "schema": { - "$ref": "#/definitions/Error" - } - }, + "409": { + "description": "Task failed due to power state change", + "schema": { + "$ref": "#/definitions/Error" + } + }, "500": { "description": "Inspect of task failed", "schema": { @@ -3129,6 +3190,41 @@ } } }, + "ImageJoinConfig": { + "type": "object", + "required": [ + "handle", + "deltaID" + ], + "properties": { + "handle": { + "type": "object" + }, + "deltaID": { + "x-nullable": false, + "type": "string" + }, + "imageID": { + "x-nullable": false, + "type": "string" + }, + "repoName": { + "x-nullable": false, + "type": "string" + } + } + }, + "ImageJoinResponse": { + "type": "object", + "required": [ + "handle" + ], + "properties": { + "handle": { + "type": "object" + } + } + }, "ScopeConfig": { "type": "object", "required": [ @@ -3258,18 +3354,6 @@ "name": { "type": "string" }, - "imageStore": { - "$ref": "#/definitions/ImageStore" - }, - "image": { - "type": "string" - }, - "layer": { - "type": "string" - }, - "repoName": { - "type": "string" - }, "numCPUs": { "type": "integer", "format": "int64" diff --git a/lib/guest/guest.go b/lib/guest/guest.go index 413e28efbf..e12916b011 100644 --- a/lib/guest/guest.go +++ b/lib/guest/guest.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,4 +24,5 @@ type Guest interface { GuestID() string Spec() *spec.VirtualMachineConfigSpec Controller() *types.BaseVirtualController + NewDisk() *types.VirtualDisk } diff --git a/lib/guest/linux.go b/lib/guest/linux.go index 3c54c94c5e..fa68ec7b84 100644 --- a/lib/guest/linux.go +++ b/lib/guest/linux.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,10 +56,6 @@ func NewLinuxGuest(ctx context.Context, session *session.Session, config *spec.V pv := spec.NewParaVirtualSCSIController(scsi) s.AddParaVirtualSCSIController(pv) - // Disk - disk := spec.NewVirtualSCSIDisk(scsi) - s.AddVirtualDisk(disk) - // IDE controller ide := spec.NewVirtualIDEController(ideKey) s.AddVirtualIDEController(ide) @@ -93,6 +89,10 @@ func (l *LinuxGuestType) Controller() *types.BaseVirtualController { return &l.controller } +func (l *LinuxGuestType) NewDisk() *types.VirtualDisk { + return spec.NewVirtualDisk(l.controller) +} + // GetSelf gets VirtualMachine reference for the VM this process is running on func GetSelf(ctx context.Context, s *session.Session) (*object.VirtualMachine, error) { u, err := sys.UUID() diff --git a/lib/portlayer/exec/handle.go b/lib/portlayer/exec/handle.go index 47fe04744d..fdf37fcb3d 100644 --- a/lib/portlayer/exec/handle.go +++ b/lib/portlayer/exec/handle.go @@ -51,9 +51,7 @@ type Resources struct { type ContainerCreateConfig struct { Metadata *executor.ExecutorConfig - ParentImageID string - ImageStoreName string - Resources Resources + Resources Resources } var handles *lru.Cache @@ -72,6 +70,9 @@ type Handle struct { // copy from container cache containerBase + // The guest used to generate specific device types + Guest guest.Guest + // desired spec Spec *spec.VirtualMachineConfigSpec // desired changes to extraconfig @@ -319,13 +320,9 @@ func Create(ctx context.Context, vmomiSession *session.Session, config *Containe Name: config.Metadata.Name, BiosUUID: uuid, - ParentImageID: config.ParentImageID, BootMediaPath: Config.BootstrapImagePath, VMPathName: fmt.Sprintf("[%s]", vmomiSession.Datastore.Name()), - ImageStoreName: config.ImageStoreName, - ImageStorePath: &Config.ImageStores[0], - Metadata: config.Metadata, } @@ -338,7 +335,7 @@ func Create(ctx context.Context, vmomiSession *session.Session, config *Containe // log only core portions s := specconfig - log.Debugf("id: %s, name: %s, cpu: %d, mem: %d, parent: %s, os: %s, path: %s", s.ID, s.Name, s.NumCPUs, s.MemoryMB, s.ParentImageID, s.BootMediaPath, s.VMPathName) + log.Debugf("id: %s, name: %s, cpu: %d, mem: %d, os: %s, path: %s", s.ID, s.Name, s.NumCPUs, s.MemoryMB, s.BootMediaPath, s.VMPathName) m := s.Metadata log.Debugf("annotations: %#v, reponame: %s", m.Annotations, m.RepoName) for name, sess := range m.Sessions { @@ -364,6 +361,7 @@ func Create(ctx context.Context, vmomiSession *session.Session, config *Containe return nil, err } + h.Guest = linux h.Spec = linux.Spec() handlesLock.Lock() diff --git a/lib/portlayer/storage/config.go b/lib/portlayer/storage/config.go index 9e5b34e4fb..d3e675a10a 100644 --- a/lib/portlayer/storage/config.go +++ b/lib/portlayer/storage/config.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import ( var Config Configuration -// Configuration is a slice of the VCH config that is relevant to the exec part of the port layer +// Configuration is a slice of the VCH config that is relevant to the storage part of the port layer type Configuration struct { // Turn on debug logging DebugLevel int `vic:"0.1" scope:"read-only" key:"init/diagnostics/debug"` diff --git a/lib/portlayer/storage/vsphere/container.go b/lib/portlayer/storage/container/export.go similarity index 51% rename from lib/portlayer/storage/vsphere/container.go rename to lib/portlayer/storage/container/export.go index 505a758cc7..522ce91e0c 100644 --- a/lib/portlayer/storage/vsphere/container.go +++ b/lib/portlayer/storage/container/export.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,80 +12,86 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vsphere +package container import ( "errors" "io" "net/url" "os" - "strings" - "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/guest" "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/vsphere" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/disk" - "github.com/vmware/vic/pkg/vsphere/session" "github.com/vmware/vic/pkg/vsphere/vm" ) -// ContainerStorer defines the interface contract expected to allow import and export -// against containers -type ContainerStorer interface { - storage.Resolver - storage.Importer - storage.Exporter -} - -// ContainerStore stores container storage information -type ContainerStore struct { - disk.Vmdk +func (c *ContainerStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { + l, err := c.NewDataSource(op, id) + if err != nil { + return nil, err + } - // used to resolve images when diffing - images storage.Resolver -} + if ancestor == "" { + op.Infof("No ancestor specified so following basic export path") + return l.Export(op, spec, data) + } -// NewContainerStore creates and returns a new container store -func NewContainerStore(op trace.Operation, s *session.Session, imageResolver storage.Resolver) (*ContainerStore, error) { - cs := &ContainerStore{ - Vmdk: disk.Vmdk{ - Manager: storage.Config.DiskManager, - //ds: ds, - Session: s, - }, + // for now we assume ancetor instead of entirely generic left/right + // this allows us to assume it's an image + img, err := c.images.URL(op, ancestor) + if err != nil { + op.Errorf("Failed to map ancestor %s to image: %s", ancestor, err) - images: imageResolver, + l.Close() + return nil, err } - return cs, nil -} + op.Debugf("Mapped ancestor %s to %s", ancestor, img.String()) -// URL converts the id of a resource to a URL -func (c *ContainerStore) URL(op trace.Operation, id string) (*url.URL, error) { - // using diskfinder with a basic suffix match is an inefficient and potentially error prone way of doing this - // mapping, but until the container store has a structured means of knowing this information it's at least - // not going to be incorrect without an ID collision. - dsPath, err := c.DiskFinder(op, func(filename string) bool { - return strings.HasSuffix(filename, id+".vmdk") - }) + r, err := c.newDataSource(op, img, false) if err != nil { + op.Debugf("Unable to get datasource for ancestor: %s", err) + + l.Close() return nil, err } - return &url.URL{ - Scheme: "ds", - Path: dsPath, - }, nil -} + closers := func() error { + op.Debugf("Callback to io.Closer function for container export") + + l.Close() + r.Close() + + return nil + } + + ls := l.Source() + rs := r.Source() + + fl, lok := ls.(*os.File) + fr, rok := rs.(*os.File) -// Owners returns a list of VMs that are using the resource specified by `url` -func (c *ContainerStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { - if url.Scheme != "ds" { - return nil, errors.New("vmdk path must be a datastore url with \"ds\" scheme") + if !lok || !rok { + go closers() + return nil, errors.New("mismatched datasource types") } - return c.Vmdk.Owners(op, url, filter) + // if we want data, exclude the xattrs, otherwise assume diff + xattrs := !data + + tar, err := archive.Diff(op, fl.Name(), fr.Name(), spec, data, xattrs) + if err != nil { + go closers() + return nil, err + } + + return &storage.ProxyReadCloser{ + ReadCloser: tar, + Closer: closers, + }, nil } // NewDataSource creates and returns an DataSource associated with container storage @@ -163,164 +169,9 @@ func (c *ContainerStore) newDataSource(op trace.Operation, url *url.URL, persist func (c *ContainerStore) newOnlineDataSource(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSource, error) { op.Debugf("Constructing toolbox data source: %s.%s", owner.Reference(), id) - return &ToolboxDataSource{ + return &vsphere.ToolboxDataSource{ VM: owner, ID: id, Clean: func() { return }, }, nil } - -// NewDataSink creates and returns an DataSink associated with container storage -func (c *ContainerStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { - uri, err := c.URL(op, id) - if err != nil { - return nil, err - } - - offlineAttempt := 0 -offline: - offlineAttempt++ - - sink, err := c.newDataSink(op, uri) - if err == nil { - return sink, err - } - - // check for vmdk locked error here - if !disk.IsLockedError(err) { - op.Warnf("Unable to mount %s and do not know how to recover from error") - // continue anyway because maybe there's an online option - } - - // online - Owners() should filter out the appliance VM - // #nosec: Errors unhandled. - owners, _ := c.Owners(op, uri, disk.LockedVMDKFilter) - if len(owners) == 0 { - op.Infof("No online owners were found for %s", id) - return nil, errors.New("unable to create offline data sink and no online owners found") - } - - for _, o := range owners { - // sanity check to see if we are the owner - this should catch transitions - // from container running to diff or commit for example between the offline attempt and here - uuid, err := o.UUID(op) - if err == nil { - // check if the vm is appliance VM if we can successfully get its UUID - // #nosec: Errors unhandled. - self, _ := guest.IsSelf(op, uuid) - if self && offlineAttempt < 2 { - op.Infof("Appliance is owner of online vmdk - retrying offline source path") - goto offline - } - } - - online, err := c.newOnlineDataSink(op, o, id) - if online != nil { - return online, err - } - - op.Debugf("Failed to create online datasink with owner %s: %s", o.Reference(), err) - } - - return nil, errors.New("unable to create online or offline data sink") -} - -func (c *ContainerStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) { - mountPath, cleanFunc, err := c.Mount(op, url, true) - if err != nil { - return nil, err - } - - f, err := os.Open(mountPath) - if err != nil { - cleanFunc() - return nil, err - } - - op.Debugf("Created mount data sink for access to %s at %s", url, mountPath) - return storage.NewMountDataSink(op, f, cleanFunc), nil -} - -func (c *ContainerStore) newOnlineDataSink(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSink, error) { - op.Debugf("Constructing toolbox data sink: %s.%s", owner.Reference(), id) - - return &ToolboxDataSink{ - VM: owner, - ID: id, - Clean: func() { return }, - }, nil -} - -func (c *ContainerStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { - l, err := c.NewDataSink(op, id) - if err != nil { - return err - } - - return l.Import(op, spec, tarstream) -} - -func (c *ContainerStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { - l, err := c.NewDataSource(op, id) - if err != nil { - return nil, err - } - - if ancestor == "" { - op.Infof("No ancestor specified so following basic export path") - return l.Export(op, spec, data) - } - - // for now we assume ancetor instead of entirely generic left/right - // this allows us to assume it's an image - img, err := c.images.URL(op, ancestor) - if err != nil { - op.Errorf("Failed to map ancestor %s to image: %s", ancestor, err) - - l.Close() - return nil, err - } - op.Debugf("Mapped ancestor %s to %s", ancestor, img.String()) - - r, err := c.newDataSource(op, img, false) - if err != nil { - op.Debugf("Unable to get datasource for ancestor: %s", err) - - l.Close() - return nil, err - } - - closers := func() error { - op.Debugf("Callback to io.Closer function for container export") - - l.Close() - r.Close() - - return nil - } - - ls := l.Source() - rs := r.Source() - - fl, lok := ls.(*os.File) - fr, rok := rs.(*os.File) - - if !lok || !rok { - go closers() - return nil, errors.New("mismatched datasource types") - } - - // if we want data, exclude the xattrs, otherwise assume diff - xattrs := !data - - tar, err := archive.Diff(op, fl.Name(), fr.Name(), spec, data, xattrs) - if err != nil { - go closers() - return nil, err - } - - return &storage.ProxyReadCloser{ - ReadCloser: tar, - Closer: closers, - }, nil -} diff --git a/lib/portlayer/storage/container/import.go b/lib/portlayer/storage/container/import.go new file mode 100644 index 0000000000..2431110ba0 --- /dev/null +++ b/lib/portlayer/storage/container/import.go @@ -0,0 +1,120 @@ +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. +// +// 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 container + +import ( + "errors" + "io" + "net/url" + "os" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/guest" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/vsphere" + "github.com/vmware/vic/pkg/trace" + "github.com/vmware/vic/pkg/vsphere/disk" + "github.com/vmware/vic/pkg/vsphere/vm" +) + +func (c *ContainerStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { + l, err := c.NewDataSink(op, id) + if err != nil { + return err + } + + return l.Import(op, spec, tarstream) +} + +// NewDataSink creates and returns an DataSink associated with container storage +func (c *ContainerStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { + uri, err := c.URL(op, id) + if err != nil { + return nil, err + } + + offlineAttempt := 0 +offline: + offlineAttempt++ + + sink, err := c.newDataSink(op, uri) + if err == nil { + return sink, err + } + + // check for vmdk locked error here + if !disk.IsLockedError(err) { + op.Warnf("Unable to mount %s and do not know how to recover from error") + // continue anyway because maybe there's an online option + } + + // online - Owners() should filter out the appliance VM + // #nosec: Errors unhandled. + owners, _ := c.Owners(op, uri, disk.LockedVMDKFilter) + if len(owners) == 0 { + op.Infof("No online owners were found for %s", id) + return nil, errors.New("unable to create offline data sink and no online owners found") + } + + for _, o := range owners { + // sanity check to see if we are the owner - this should catch transitions + // from container running to diff or commit for example between the offline attempt and here + uuid, err := o.UUID(op) + if err == nil { + // check if the vm is appliance VM if we can successfully get its UUID + // #nosec: Errors unhandled. + self, _ := guest.IsSelf(op, uuid) + if self && offlineAttempt < 2 { + op.Infof("Appliance is owner of online vmdk - retrying offline source path") + goto offline + } + } + + online, err := c.newOnlineDataSink(op, o, id) + if online != nil { + return online, err + } + + op.Debugf("Failed to create online datasink with owner %s: %s", o.Reference(), err) + } + + return nil, errors.New("unable to create online or offline data sink") +} + +func (c *ContainerStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) { + mountPath, cleanFunc, err := c.Mount(op, url, true) + if err != nil { + return nil, err + } + + f, err := os.Open(mountPath) + if err != nil { + cleanFunc() + return nil, err + } + + op.Debugf("Created mount data sink for access to %s at %s", url, mountPath) + return storage.NewMountDataSink(op, f, cleanFunc), nil +} + +func (c *ContainerStore) newOnlineDataSink(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSink, error) { + op.Debugf("Constructing toolbox data sink: %s.%s", owner.Reference(), id) + + return &vsphere.ToolboxDataSink{ + VM: owner, + ID: id, + Clean: func() { return }, + }, nil +} diff --git a/lib/portlayer/storage/container/store.go b/lib/portlayer/storage/container/store.go new file mode 100644 index 0000000000..7650f2258c --- /dev/null +++ b/lib/portlayer/storage/container/store.go @@ -0,0 +1,84 @@ +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. +// +// 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 container + +import ( + "errors" + "net/url" + "strings" + + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" + "github.com/vmware/vic/pkg/vsphere/disk" + "github.com/vmware/vic/pkg/vsphere/session" + "github.com/vmware/vic/pkg/vsphere/vm" +) + +// ContainerStorer defines the interface contract expected to allow import and export +// against containers +type ContainerStorer interface { + storage.Resolver + storage.Importer + storage.Exporter +} + +// ContainerStore stores container storage information +type ContainerStore struct { + disk.Vmdk + + // used to resolve images when diffing + images storage.Resolver +} + +// NewContainerStore creates and returns a new container store +func NewContainerStore(op trace.Operation, s *session.Session, imageResolver storage.Resolver) (*ContainerStore, error) { + cs := &ContainerStore{ + Vmdk: disk.Vmdk{ + Manager: storage.Config.DiskManager, + Session: s, + }, + + images: imageResolver, + } + return cs, nil +} + +// URL converts the id of a resource to a URL +func (c *ContainerStore) URL(op trace.Operation, id string) (*url.URL, error) { + // using diskfinder with a basic suffix match is an inefficient and potentially error prone way of doing this + // mapping, but until the container store has a structured means of knowing this information it's at least + // not going to be incorrect without an ID collision. + dsPath, err := c.DiskFinder(op, func(filename string) bool { + return strings.HasSuffix(filename, id+".vmdk") + }) + if err != nil { + return nil, err + } + + return &url.URL{ + Scheme: "ds", + Path: dsPath, + }, nil +} + +// Owners returns a list of VMs that are using the resource specified by `url` +func (c *ContainerStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { + if url.Scheme != "ds" { + return nil, errors.New("vmdk path must be a datastore url with \"ds\" scheme") + } + + return c.Vmdk.Owners(op, url, filter) +} diff --git a/lib/portlayer/storage/image_cache.go b/lib/portlayer/storage/image/cache/cache.go similarity index 87% rename from lib/portlayer/storage/image_cache.go rename to lib/portlayer/storage/image/cache/cache.go index 8a1ae5899f..9e8b3f407c 100644 --- a/lib/portlayer/storage/image_cache.go +++ b/lib/portlayer/storage/image/cache/cache.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package cache import ( "errors" @@ -26,6 +26,8 @@ import ( "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/constants" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/image" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/index" "github.com/vmware/vic/pkg/retry" @@ -47,10 +49,10 @@ type NameLookupCache struct { storeCacheLock sync.Mutex // The image store implementation. This mutates the actual disk images. - DataStore ImageStorer + DataStore image.ImageStorer } -func NewLookupCache(ds ImageStorer) *NameLookupCache { +func NewLookupCache(ds image.ImageStorer) *NameLookupCache { return &NameLookupCache{ DataStore: ds, storeCache: make(map[url.URL]*index.Index), @@ -127,7 +129,7 @@ func (c *NameLookupCache) GetImageStore(op trace.Operation, storeName string) (* op.Debugf("Found %d images", len(images)) // Build image map to simplify tree traversal. - imageMap := make(map[string]*Image, len(images)) + imageMap := make(map[string]*image.Image, len(images)) for _, img := range images { if img.ID == constants.ScratchLayerID { continue @@ -144,7 +146,7 @@ func (c *NameLookupCache) GetImageStore(op trace.Operation, storeName string) (* } // parentTree adds images into the cache starting from the parent. -func parentTree(op trace.Operation, imgLink string, idx *index.Index, imageMap map[string]*Image) { +func parentTree(op trace.Operation, imgLink string, idx *index.Index, imageMap map[string]*image.Image) { img, ok := imageMap[imgLink] if !ok { return @@ -209,7 +211,7 @@ func (c *NameLookupCache) CreateImageStore(op trace.Operation, storeName string) } // Create the root image - scratch, err := c.DataStore.WriteImage(op, &Image{Store: store}, constants.ScratchLayerID, nil, "", nil) + scratch, err := c.DataStore.WriteImage(op, &image.Image{Store: store}, constants.ScratchLayerID, nil, "", nil) if err != nil { // if we failed here, remove the image store op.Infof("Removing failed image store %s", storeName) @@ -241,7 +243,7 @@ func (c *NameLookupCache) ListImageStores(op trace.Operation) ([]*url.URL, error return stores, nil } -func (c *NameLookupCache) WriteImage(op trace.Operation, parent *Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*Image, error) { +func (c *NameLookupCache) WriteImage(op trace.Operation, parent *image.Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*image.Image, error) { // Check the parent exists (at least in the cache). p, err := c.GetImage(op, parent.Store, parent.ID) if err != nil { @@ -283,7 +285,7 @@ func (c *NameLookupCache) Import(op trace.Operation, store *url.URL, diskID stri return c.DataStore.Import(op, diskID, spec, tarStream) } -func (c *NameLookupCache) NewDataSource(op trace.Operation, id string) (DataSource, error) { +func (c *NameLookupCache) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { return c.DataStore.NewDataSource(op, id) } @@ -296,7 +298,7 @@ func (c *NameLookupCache) Owners(op trace.Operation, url *url.URL, filter func(v } // GetImage gets the specified image from the given store by retreiving it from the cache. -func (c *NameLookupCache) GetImage(op trace.Operation, store *url.URL, ID string) (*Image, error) { +func (c *NameLookupCache) GetImage(op trace.Operation, store *url.URL, ID string) (*image.Image, error) { op.Debugf("Getting image %s from %s", ID, store.String()) storeName, err := util.ImageStoreName(store) @@ -319,7 +321,7 @@ func (c *NameLookupCache) GetImage(op trace.Operation, store *url.URL, ID string } node, err := c.storeCache[*store].Get(imgURL.String()) - var img *Image + var img *image.Image if err != nil { if err == index.ErrNodeNotFound { op.Debugf("Image %s not in cache, retreiving from datastore", ID) @@ -336,16 +338,16 @@ func (c *NameLookupCache) GetImage(op trace.Operation, store *url.URL, ID string return nil, err } } else { - img, _ = node.(*Image) + img, _ = node.(*image.Image) } return img, nil } // ListImages returns a list of Images for a list of IDs, or all if no IDs are passed -func (c *NameLookupCache) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*Image, error) { +func (c *NameLookupCache) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*image.Image, error) { // Filter the results - imageList := make([]*Image, 0, len(IDs)) + imageList := make([]*image.Image, 0, len(IDs)) if len(IDs) > 0 { for _, id := range IDs { @@ -377,7 +379,7 @@ func (c *NameLookupCache) ListImages(op trace.Operation, store *url.URL, IDs []s } for _, v := range images { - img, _ := v.(*Image) + img, _ := v.(*image.Image) // filter out scratch if img.ID == constants.ScratchLayerID { continue @@ -392,16 +394,16 @@ func (c *NameLookupCache) ListImages(op trace.Operation, store *url.URL, IDs []s // DeleteImage deletes an image from the image store. If it is in use or is // being inheritted from, then this will return an error. -func (c *NameLookupCache) DeleteImage(op trace.Operation, image *Image) (*Image, error) { +func (c *NameLookupCache) DeleteImage(op trace.Operation, img *image.Image) (*image.Image, error) { // prevent deletes of scratch - if image.ID == constants.ScratchLayerID { + if img.ID == constants.ScratchLayerID { return nil, nil } - op.Infof("DeleteImage: deleting %s", image.Self()) + op.Infof("DeleteImage: deleting %s", img.Self()) // Check the image exists. This will rehydrate the cache if necessary. - img, err := c.GetImage(op, image.Store, image.ID) + img, err := c.GetImage(op, img.Store, img.ID) if err != nil { op.Errorf("DeleteImage: %s", err) return nil, err @@ -419,7 +421,7 @@ func (c *NameLookupCache) DeleteImage(op trace.Operation, image *Image) (*Image, } if hasChildren { - return nil, &ErrImageInUse{img.Self() + " in use by child images"} + return nil, &image.ErrImageInUse{Msg: img.Self() + " in use by child images"} } // The datastore will tell us if the image is attached @@ -440,10 +442,10 @@ func (c *NameLookupCache) DeleteImage(op trace.Operation, image *Image) (*Image, // DeleteBranch deletes a branch of images, starting from nodeID, up to the // first node with degree greater than 1. keepNodes is the array of images to // keep (and their branches). -func (c *NameLookupCache) DeleteBranch(op trace.Operation, image *Image, keepNodes []*url.URL) ([]*Image, error) { - op.Infof("DeleteBranch: deleting branch starting at %s", image.Self()) +func (c *NameLookupCache) DeleteBranch(op trace.Operation, img *image.Image, keepNodes []*url.URL) ([]*image.Image, error) { + op.Infof("DeleteBranch: deleting branch starting at %s", img.Self()) - var deletedImages []*Image + var deletedImages []*image.Image // map of images to keep keep := make(map[url.URL]int) @@ -455,7 +457,7 @@ func (c *NameLookupCache) DeleteBranch(op trace.Operation, image *Image, keepNod // Check if the error is actually an error. If we deleted something, // then eat the error. This should really only return an error if the leaf // has issues. - checkErr := func(err error, deleted []*Image) ([]*Image, error) { + checkErr := func(err error, deleted []*image.Image) ([]*image.Image, error) { if err != nil { if len(deleted) == 0 { // we failed deleting any elements. @@ -473,11 +475,11 @@ func (c *NameLookupCache) DeleteBranch(op trace.Operation, image *Image, keepNod } for { - if _, ok := keep[*image.SelfLink]; ok { - return checkErr(fmt.Errorf("%s can't be deleted", image.Self()), deletedImages) + if _, ok := keep[*img.SelfLink]; ok { + return checkErr(fmt.Errorf("%s can't be deleted", img.Self()), deletedImages) } - deletedImage, err := c.DeleteImage(op, image) + deletedImage, err := c.DeleteImage(op, img) if err != nil { op.Debugf(err.Error()) return checkErr(err, deletedImages) @@ -486,18 +488,18 @@ func (c *NameLookupCache) DeleteBranch(op trace.Operation, image *Image, keepNod deletedImages = append(deletedImages, deletedImage) // iterate to the parent - parent, err := Parse(deletedImage.ParentLink) + parent, err := image.Parse(deletedImage.ParentLink) if err != nil { return deletedImages, err } // set image to the parent - image, err = c.GetImage(op, parent.Store, parent.ID) + img, err = c.GetImage(op, parent.Store, parent.ID) if err != nil { return deletedImages, err } - if image.ID == constants.ScratchLayerID { + if img.ID == constants.ScratchLayerID { op.Infof("DeleteBranch: Done deleting images") break } diff --git a/lib/portlayer/storage/image_cache_test.go b/lib/portlayer/storage/image/cache/cache_test.go similarity index 72% rename from lib/portlayer/storage/image_cache_test.go rename to lib/portlayer/storage/image/cache/cache_test.go index 94ba844479..f75552e290 100644 --- a/lib/portlayer/storage/image_cache_test.go +++ b/lib/portlayer/storage/image/cache/cache_test.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,174 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package cache import ( "context" "fmt" - "io" "net/url" - "os" "strconv" "testing" "github.com/Sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "github.com/vmware/govmomi/vim25/mo" - "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/constants" + "github.com/vmware/vic/lib/portlayer/storage/image" + "github.com/vmware/vic/lib/portlayer/storage/image/mock" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" - "github.com/vmware/vic/pkg/vsphere/vm" ) -type MockDataStore struct { - // id -> image - db map[url.URL]map[string]*Image - - createImageStoreError error - writeImageError error -} - -func NewMockDataStore() *MockDataStore { - m := &MockDataStore{ - db: make(map[url.URL]map[string]*Image), - } - - return m -} - -// GetImageStore checks to see if a named image store exists and returls the -// URL to it if so or error. -func (c *MockDataStore) GetImageStore(op trace.Operation, storeName string) (*url.URL, error) { - u, err := util.ImageStoreNameToURL(storeName) - if err != nil { - return nil, err - } - - if _, ok := c.db[*u]; !ok { - return nil, os.ErrNotExist - } - - return u, nil -} - -func (c *MockDataStore) CreateImageStore(op trace.Operation, storeName string) (*url.URL, error) { - if c.createImageStoreError != nil { - return nil, c.createImageStoreError - } - - u, err := util.ImageStoreNameToURL(storeName) - if err != nil { - return nil, err - } - - c.db[*u] = make(map[string]*Image) - return u, nil -} - -func (c *MockDataStore) DeleteImageStore(op trace.Operation, storeName string) error { - u, err := util.ImageStoreNameToURL(storeName) - if err != nil { - return err - } - - c.db[*u] = nil - return nil -} - -func (c *MockDataStore) ListImageStores(op trace.Operation) ([]*url.URL, error) { - return nil, nil -} - -func (c *MockDataStore) WriteImage(op trace.Operation, parent *Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*Image, error) { - if c.writeImageError != nil { - op.Infof("WriteImage: returning error") - return nil, c.writeImageError - } - - storeName, err := util.ImageStoreName(parent.Store) - if err != nil { - return nil, err - } - - selflink, err := util.ImageURL(storeName, ID) - if err != nil { - return nil, err - } - - var parentLink *url.URL - if parent.ID != "" { - parentLink, err = util.ImageURL(storeName, parent.ID) - if err != nil { - return nil, err - } - } - - i := &Image{ - ID: ID, - Store: parent.Store, - ParentLink: parentLink, - SelfLink: selflink, - Metadata: meta, - } - - c.db[*parent.Store][ID] = i - - return i, nil -} - -// GetImage gets the specified image from the given store by retreiving it from the cache. -func (c *MockDataStore) GetImage(op trace.Operation, store *url.URL, ID string) (*Image, error) { - i, ok := c.db[*store][ID] - if !ok { - return nil, fmt.Errorf("not found") - } - return i, nil -} - -// ListImages resturns a list of Images for a list of IDs, or all if no IDs are passed -func (c *MockDataStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*Image, error) { - var imageList []*Image - for _, i := range c.db[*store] { - imageList = append(imageList, i) - } - return imageList, nil -} - -// DeleteImage removes an image from the image store -func (c *MockDataStore) DeleteImage(op trace.Operation, image *Image) (*Image, error) { - delete(c.db[*image.Store], image.ID) - return image, nil -} - -func (c *MockDataStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { - return nil, nil -} - -func (c *MockDataStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { - return nil -} - -func (c *MockDataStore) NewDataSink(op trace.Operation, id string) (DataSink, error) { - return nil, nil -} - -func (c *MockDataStore) NewDataSource(op trace.Operation, id string) (DataSource, error) { - return nil, nil -} - -func (c *MockDataStore) URL(op trace.Operation, id string) (*url.URL, error) { - return nil, nil -} - -func (c *MockDataStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { - return nil, nil -} - func TestListImages(t *testing.T) { - s := NewLookupCache(NewMockDataStore()) + s := NewLookupCache(mock.NewMockDataStore(nil)) op := trace.NewOperation(context.Background(), "test") storeURL, err := s.CreateImageStore(op, "testStore") @@ -191,8 +45,8 @@ func TestListImages(t *testing.T) { } // Create a set of images - images := make(map[string]*Image) - parent := Image{ + images := make(map[string]*image.Image) + parent := image.Image{ ID: constants.ScratchLayerID, } parent.Store = storeURL @@ -250,7 +104,7 @@ func TestListImages(t *testing.T) { // cache. The datastore should reflect the image already exists and bale out // without an error. func TestOutsideCacheWriteImage(t *testing.T) { - s := NewLookupCache(NewMockDataStore()) + s := NewLookupCache(mock.NewMockDataStore(nil)) op := trace.NewOperation(context.Background(), "test") storeURL, err := s.CreateImageStore(op, "testStore") @@ -262,8 +116,8 @@ func TestOutsideCacheWriteImage(t *testing.T) { } // Create a set of images - images := make(map[string]*Image) - parent := Image{ + images := make(map[string]*image.Image) + parent := image.Image{ ID: constants.ScratchLayerID, } parent.Store = storeURL @@ -308,7 +162,7 @@ func TestOutsideCacheWriteImage(t *testing.T) { // restart since the second cache is empty and has to go to the backing store. func TestImageStoreRestart(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) - ds := NewMockDataStore() + ds := mock.NewMockDataStore(nil) op := trace.NewOperation(context.Background(), "test") firstCache := NewLookupCache(ds) @@ -323,7 +177,7 @@ func TestImageStoreRestart(t *testing.T) { } // Create a set of images - expectedImages := make(map[string]*Image) + expectedImages := make(map[string]*image.Image) parent, err := firstCache.GetImage(op, storeURL, constants.ScratchLayerID) if !assert.NoError(t, err) { @@ -384,7 +238,7 @@ func TestImageStoreRestart(t *testing.T) { func TestDeleteImage(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) - imageCache := NewLookupCache(NewMockDataStore()) + imageCache := NewLookupCache(mock.NewMockDataStore(nil)) op := trace.NewOperation(context.Background(), "test") storeURL, err := imageCache.CreateImageStore(op, "testStore") @@ -399,7 +253,7 @@ func TestDeleteImage(t *testing.T) { // create a 3 level tree with 4 branches branches := 4 - images := make(map[int]*Image) + images := make(map[int]*image.Image) for branch := 1; branch < branches; branch++ { // level 1 img, err := imageCache.WriteImage(op, scratch, strconv.Itoa(branch), nil, "", nil) @@ -442,7 +296,7 @@ func TestDeleteImage(t *testing.T) { // Deletion of leaves should be fine for branch := 1; branch < branches; branch++ { // range up the branch - for _, img := range []*Image{images[branch*100], images[branch*10], images[branch]} { + for _, img := range []*image.Image{images[branch*100], images[branch*10], images[branch]} { _, err = imageCache.DeleteImage(op, img) if !assert.NoError(t, err) { @@ -472,7 +326,7 @@ func TestDeleteBranch(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) trace.Logger.Level = logrus.DebugLevel - imageCache := NewLookupCache(NewMockDataStore()) + imageCache := NewLookupCache(mock.NewMockDataStore(nil)) op := trace.NewOperation(context.Background(), "test") storeURL, err := imageCache.CreateImageStore(op, "testStore") @@ -491,7 +345,7 @@ func TestDeleteBranch(t *testing.T) { // 10 20 30 // 100 200 300 301 branches := 4 - images := make(map[int]*Image) + images := make(map[int]*image.Image) for branch := 1; branch < branches; branch++ { // level 1 img, err := imageCache.WriteImage(op, scratch, strconv.Itoa(branch), nil, "", nil) @@ -606,25 +460,25 @@ func TestCreateImageStoreFailureCleanup(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) trace.Logger.Level = logrus.DebugLevel - mds := NewMockDataStore() + mds := mock.NewMockDataStore(nil) imageCache := NewLookupCache(mds) op := trace.NewOperation(context.Background(), "create image store error") - mds.createImageStoreError = fmt.Errorf("foo error") + mds.SetCreateImageStoreError(fmt.Errorf("foo error")) storeURL, err := imageCache.CreateImageStore(op, "testStore") if !assert.Error(t, err) || !assert.Nil(t, storeURL) { return } - mds.createImageStoreError = nil + mds.SetCreateImageStoreError(nil) storeURL, err = imageCache.CreateImageStore(op, "testStore") if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) { return } op = trace.NewOperation(context.Background(), "write image error") - mds = NewMockDataStore() - mds.writeImageError = fmt.Errorf("foo error") + mds = mock.NewMockDataStore(nil) + mds.SetWriteImageError(fmt.Errorf("foo error")) imageCache = NewLookupCache(mds) storeURL, err = imageCache.CreateImageStore(op, "testStore2") @@ -632,7 +486,7 @@ func TestCreateImageStoreFailureCleanup(t *testing.T) { return } - mds.writeImageError = nil + mds.SetWriteImageError(nil) storeURL, err = imageCache.CreateImageStore(op, "testStore2") if !assert.NoError(t, err) || !assert.NotNil(t, storeURL) { return @@ -642,7 +496,9 @@ func TestCreateImageStoreFailureCleanup(t *testing.T) { // Cache population should be happening in order starting from parent(id1) to children(id4) func TestPopulateCacheInExpectedOrder(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) - st := NewMockDataStore() + trace.EnableTracing() + trace.Logger.Level = logrus.DebugLevel + op := trace.NewOperation(context.Background(), "test") storeURL, _ := util.ImageStoreNameToURL("testStore") @@ -655,11 +511,11 @@ func TestPopulateCacheInExpectedOrder(t *testing.T) { url4, _ := url.Parse(storageURLStr + "/id4") scratchURL, _ := url.Parse(storageURLStr + constants.ScratchLayerID) - img1 := &Image{ID: "id1", SelfLink: url1, ParentLink: scratchURL, Store: storeURL} - img2 := &Image{ID: "id2", SelfLink: url2, ParentLink: url1, Store: storeURL} - img3 := &Image{ID: "id3", SelfLink: url3, ParentLink: url2, Store: storeURL} - img4 := &Image{ID: "id4", SelfLink: url4, ParentLink: url3, Store: storeURL} - scratchImg := &Image{ + img1 := &image.Image{ID: "id1", SelfLink: url1, ParentLink: scratchURL, Store: storeURL} + img2 := &image.Image{ID: "id2", SelfLink: url2, ParentLink: url1, Store: storeURL} + img3 := &image.Image{ID: "id3", SelfLink: url3, ParentLink: url2, Store: storeURL} + img4 := &image.Image{ID: "id4", SelfLink: url4, ParentLink: url3, Store: storeURL} + scratchImg := &image.Image{ ID: constants.ScratchLayerID, SelfLink: scratchURL, ParentLink: scratchURL, @@ -667,7 +523,7 @@ func TestPopulateCacheInExpectedOrder(t *testing.T) { } // Order does matter for some reason. - imageMap := map[string]*Image{ + imageMap := map[string]*image.Image{ img1.ID: img1, img4.ID: img4, img2.ID: img2, @@ -675,15 +531,20 @@ func TestPopulateCacheInExpectedOrder(t *testing.T) { scratchImg.ID: scratchImg, } - st.db[*storeURL] = imageMap + storeImages := map[url.URL]map[string]*image.Image{ + *storeURL: imageMap, + } + st := mock.NewMockDataStore(storeImages) imageCache := NewLookupCache(st) - imageCache.GetImageStore(op, "testStore") + sURL, err := imageCache.GetImageStore(op, "testStore") + require.NoError(t, err, "Received error while getting image store from cache") // Check if all images are available. imageIds := []string{"id1", "id2", "id3", "id4"} for _, imageID := range imageIds { - v, _ := imageCache.GetImage(op, storeURL, imageID) + v, err := imageCache.GetImage(op, sURL, imageID) + require.NoError(t, err, "Received error instead of image from cache") assert.NotNil(t, v) } } diff --git a/lib/portlayer/storage/image/errors.go b/lib/portlayer/storage/image/errors.go new file mode 100644 index 0000000000..5b1f48cd9e --- /dev/null +++ b/lib/portlayer/storage/image/errors.go @@ -0,0 +1,32 @@ +// Copyright 2018-2018 VMware, Inc. All Rights Reserved. +// +// 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 image + +type ErrImageInUse struct { + Msg string +} + +func (e *ErrImageInUse) Error() string { + return e.Msg +} + +func IsErrImageInUse(err error) bool { + if err == nil { + return false + } + _, ok := err.(*ErrImageInUse) + + return ok +} diff --git a/lib/portlayer/storage/image.go b/lib/portlayer/storage/image/image.go similarity index 86% rename from lib/portlayer/storage/image.go rename to lib/portlayer/storage/image/image.go index b38a1be1f3..9a8a659bff 100644 --- a/lib/portlayer/storage/image.go +++ b/lib/portlayer/storage/image/image.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package image import ( "errors" @@ -21,6 +21,8 @@ import ( "path/filepath" "strings" + "github.com/vmware/govmomi/object" + "github.com/vmware/vic/lib/portlayer/storage" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/index" "github.com/vmware/vic/pkg/trace" @@ -74,9 +76,9 @@ type ImageStorer interface { // container, this will return an error. DeleteImage(op trace.Operation, image *Image) (*Image, error) - Resolver - Importer - Exporter + storage.Resolver + storage.Importer + storage.Exporter } // Image is the handle to identify an image layer on the backing store. The @@ -103,6 +105,13 @@ type Image struct { // Disk is the underlying disk implementation Disk *disk.VirtualDisk + + // DatastorePath is the dspath for actually using this image + // NOTE: this should be replaced by structure accessors for the data and updated storage + // interfaces that use _one_ variant of url/path for identifying images, volumes and stores. + // URL was only suggested as an existing structure that could be leveraged when object.DatastorePath + // was not available. The suggestion seems to have spawned monstruous unnecessary complexity. + DatastorePath *object.DatastorePath } func (i *Image) Copy() index.Element { @@ -125,6 +134,13 @@ func (i *Image) Copy() index.Element { Store: store, } + if i.DatastorePath != nil { + c.DatastorePath = &object.DatastorePath{ + Datastore: i.DatastorePath.Datastore, + Path: i.DatastorePath.Path, + } + } + if i.Metadata != nil { c.Metadata = make(map[string][]byte) diff --git a/lib/portlayer/storage/image_test.go b/lib/portlayer/storage/image/image_test.go similarity index 88% rename from lib/portlayer/storage/image_test.go rename to lib/portlayer/storage/image/image_test.go index 4f35a51639..1833d9de06 100644 --- a/lib/portlayer/storage/image_test.go +++ b/lib/portlayer/storage/image/image_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package image import ( "testing" "github.com/stretchr/testify/assert" + "github.com/vmware/govmomi/object" "github.com/vmware/vic/lib/constants" "github.com/vmware/vic/lib/portlayer/util" ) @@ -57,6 +58,10 @@ func TestImageCopy(t *testing.T) { "2": {byte(2)}, "3": []byte("three"), }, + DatastorePath: &object.DatastorePath{ + Datastore: "ds", + Path: "/some/path", + }, } actual := expected.Copy().(*Image) diff --git a/lib/portlayer/storage/vsphere/vsphere.go b/lib/portlayer/storage/image/mock/export.go similarity index 54% rename from lib/portlayer/storage/vsphere/vsphere.go rename to lib/portlayer/storage/image/mock/export.go index 5d753bff3e..e0651c41e5 100644 --- a/lib/portlayer/storage/vsphere/vsphere.go +++ b/lib/portlayer/storage/image/mock/export.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,28 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vsphere +package mock import ( "io" -) -const ( - // Persistent is used as a constant input for disk persistence configuration - persistent = true + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" ) -// cleanReader wraps an io.ReadCloser, calling the supplied cleanup function on Close() -type cleanReader struct { - io.ReadCloser - clean func() -} - -func (c *cleanReader) Read(p []byte) (int, error) { - return c.ReadCloser.Read(p) +func (m *MockDataStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { + return nil, nil } -func (c *cleanReader) Close() error { - defer c.clean() - return c.ReadCloser.Close() +func (m *MockDataStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { + return nil, nil } diff --git a/lib/portlayer/storage/image/mock/import.go b/lib/portlayer/storage/image/mock/import.go new file mode 100644 index 0000000000..5b0c48e547 --- /dev/null +++ b/lib/portlayer/storage/image/mock/import.go @@ -0,0 +1,31 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +import ( + "io" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +func (m *MockDataStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { + return nil +} + +func (m *MockDataStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { + return nil, nil +} diff --git a/lib/portlayer/storage/image/mock/join.go b/lib/portlayer/storage/image/mock/join.go new file mode 100644 index 0000000000..df710801d3 --- /dev/null +++ b/lib/portlayer/storage/image/mock/join.go @@ -0,0 +1,17 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +// Join was not present in the existing test code diff --git a/lib/portlayer/storage/image/mock/store.go b/lib/portlayer/storage/image/mock/store.go new file mode 100644 index 0000000000..4f2589b9a5 --- /dev/null +++ b/lib/portlayer/storage/image/mock/store.go @@ -0,0 +1,188 @@ +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +import ( + "fmt" + "io" + "net/url" + "os" + + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/vic/lib/portlayer/storage/image" + "github.com/vmware/vic/lib/portlayer/util" + "github.com/vmware/vic/pkg/trace" + "github.com/vmware/vic/pkg/vsphere/vm" +) + +type MockDataStore struct { + object.DatastorePath + + // id -> image + db map[url.URL]map[string]*image.Image + + createImageStoreError error + writeImageError error +} + +func NewMockDataStore(images map[url.URL]map[string]*image.Image) *MockDataStore { + m := &MockDataStore{ + db: images, + } + + if m.db == nil { + m.db = make(map[url.URL]map[string]*image.Image) + } + + return m +} + +// SetWriteImageError injects an error to be returned on next call to WriteImage +func (c *MockDataStore) SetWriteImageError(err error) { + c.writeImageError = err +} + +// SetCreateImageStoreError injects an error to be returned on next call to CreateImageStore +func (c *MockDataStore) SetCreateImageStoreError(err error) { + c.createImageStoreError = err +} + +// GetImageStore checks to see if a named image store exists and returls the +// URL to it if so or error. +func (c *MockDataStore) GetImageStore(op trace.Operation, storeName string) (*url.URL, error) { + u, err := util.ImageStoreNameToURL(storeName) + if err != nil { + return nil, err + } + + if _, ok := c.db[*u]; !ok { + return nil, os.ErrNotExist + } + + return u, nil +} + +func (c *MockDataStore) CreateImageStore(op trace.Operation, storeName string) (*url.URL, error) { + if c.createImageStoreError != nil { + return nil, c.createImageStoreError + } + + u, err := util.ImageStoreNameToURL(storeName) + if err != nil { + return nil, err + } + + c.db[*u] = make(map[string]*image.Image) + return u, nil +} + +func (c *MockDataStore) DeleteImageStore(op trace.Operation, storeName string) error { + u, err := util.ImageStoreNameToURL(storeName) + if err != nil { + return err + } + + c.db[*u] = nil + return nil +} + +func (c *MockDataStore) ListImageStores(op trace.Operation) ([]*url.URL, error) { + stores := make([]*url.URL, len(c.db)) + i := 0 + for k := range c.db { + stores[i] = &k + i++ + } + + return stores, nil +} + +func (c *MockDataStore) URL(op trace.Operation, id string) (*url.URL, error) { + return nil, nil +} + +func (c *MockDataStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { + return nil, nil +} + +func (c *MockDataStore) WriteImage(op trace.Operation, parent *image.Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*image.Image, error) { + if c.writeImageError != nil { + op.Infof("WriteImage: returning error") + return nil, c.writeImageError + } + + storeName, err := util.ImageStoreName(parent.Store) + if err != nil { + return nil, err + } + + selflink, err := util.ImageURL(storeName, ID) + if err != nil { + return nil, err + } + + var parentLink *url.URL + if parent.ID != "" { + parentLink, err = util.ImageURL(storeName, parent.ID) + if err != nil { + return nil, err + } + } + + i := &image.Image{ + ID: ID, + Store: parent.Store, + ParentLink: parentLink, + SelfLink: selflink, + Metadata: meta, + DatastorePath: &object.DatastorePath{ + Datastore: c.Datastore, + Path: c.Path, + }, + } + + c.db[*parent.Store][ID] = i + + return i, nil +} + +func (c *MockDataStore) WriteMetadata(op trace.Operation, storeName string, ID string, meta map[string][]byte) error { + return nil +} + +// GetImage gets the specified image from the given store by retreiving it from the cache. +func (c *MockDataStore) GetImage(op trace.Operation, store *url.URL, ID string) (*image.Image, error) { + i, ok := c.db[*store][ID] + if !ok { + return nil, fmt.Errorf("not found") + } + return i, nil +} + +// ListImages resturns a list of Images for a list of IDs, or all if no IDs are passed +func (c *MockDataStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*image.Image, error) { + var imageList []*image.Image + for _, i := range c.db[*store] { + imageList = append(imageList, i) + } + return imageList, nil +} + +// DeleteImage removes an image from the image store +func (c *MockDataStore) DeleteImage(op trace.Operation, image *image.Image) (*image.Image, error) { + delete(c.db[*image.Store], image.ID) + return image, nil +} diff --git a/lib/portlayer/storage/image/vsphere/export.go b/lib/portlayer/storage/image/vsphere/export.go new file mode 100644 index 0000000000..b69074d401 --- /dev/null +++ b/lib/portlayer/storage/image/vsphere/export.go @@ -0,0 +1,111 @@ +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. +// +// 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 vsphere + +import ( + "fmt" + "io" + "net/url" + "os" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +// Export reads the delta between child and parent image layers, returning +// the difference as a tar archive. +// +// id - must inherit from ancestor if ancestor is specified +// ancestor - the layer up the chain against which to diff +// spec - describes filters on paths found in the data (include, exclude, rebase, strip) +// data - set to true to include file data in the tar archive, false to include headers only +func (i *ImageStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { + l, err := i.NewDataSource(op, id) + if err != nil { + return nil, err + } + + if ancestor == "" { + return l.Export(op, spec, data) + } + + // for now we assume ancestor instead of entirely generic left/right + // this allows us to assume it's an image + r, err := i.NewDataSource(op, ancestor) + if err != nil { + op.Debugf("Unable to get datasource for ancestor: %s", err) + + l.Close() + return nil, err + } + + closers := func() error { + op.Debugf("Callback to io.Closer function for image delta export") + + l.Close() + r.Close() + + return nil + } + + ls := l.Source() + rs := r.Source() + + fl, lok := ls.(*os.File) + fr, rok := rs.(*os.File) + + if !lok || !rok { + go closers() + return nil, fmt.Errorf("mismatched datasource types: %T, %T", ls, rs) + } + + // if we want data, exclude the xattrs, otherwise assume diff + tar, err := archive.Diff(op, fl.Name(), fr.Name(), spec, data, !data) + if err != nil { + go closers() + return nil, err + } + + return &storage.ProxyReadCloser{ + ReadCloser: tar, + Closer: closers, + }, nil +} + +func (i *ImageStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { + url, err := i.URL(op, id) + if err != nil { + return nil, err + } + + return i.newDataSource(op, url) +} + +func (i *ImageStore) newDataSource(op trace.Operation, url *url.URL) (storage.DataSource, error) { + mountPath, cleanFunc, err := i.Mount(op, url, false) + if err != nil { + return nil, err + } + + f, err := os.Open(mountPath) + if err != nil { + cleanFunc() + return nil, err + } + + op.Debugf("Created mount data source for access to %s at %s", url, mountPath) + return storage.NewMountDataSource(op, f, cleanFunc), nil +} diff --git a/lib/portlayer/storage/image/vsphere/import.go b/lib/portlayer/storage/image/vsphere/import.go new file mode 100644 index 0000000000..d374422831 --- /dev/null +++ b/lib/portlayer/storage/image/vsphere/import.go @@ -0,0 +1,65 @@ +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. +// +// 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 vsphere + +import ( + "io" + "net/url" + "os" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +func (i *ImageStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error { + l, err := i.NewDataSink(op, id) + if err != nil { + return err + } + + return l.Import(op, spec, tarStream) +} + +// NewDataSink creates and returns an DataSource associated with image storage +func (i *ImageStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { + uri, err := i.URL(op, id) + if err != nil { + return nil, err + } + + // there is no online fail over path for images + // we should probably have a check in here as to whether the image is "sealed" and can no longer + // be modified. + return i.newDataSink(op, uri) +} + +func (i *ImageStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) { + mountPath, cleanFunc, err := i.Mount(op, url, true) + if err != nil { + return nil, err + } + + f, err := os.Open(mountPath) + if err != nil { + cleanFunc() + return nil, err + } + + return &storage.MountDataSink{ + Path: f, + Clean: cleanFunc, + }, nil +} diff --git a/lib/portlayer/storage/image/vsphere/join.go b/lib/portlayer/storage/image/vsphere/join.go new file mode 100644 index 0000000000..dfb4cf700e --- /dev/null +++ b/lib/portlayer/storage/image/vsphere/join.go @@ -0,0 +1,70 @@ +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. +// +// 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 vsphere + +import ( + "fmt" + "path" + + "github.com/vmware/govmomi/vim25/types" + "github.com/vmware/vic/lib/portlayer/exec" + "github.com/vmware/vic/lib/portlayer/storage/image" + "github.com/vmware/vic/pkg/trace" +) + +func Join(op trace.Operation, handle *exec.Handle, id, imgID, repoName string, img *image.Image) (*exec.Handle, error) { + defer trace.End(trace.Begin(img.ID, op)) + + // set the rw layer name + // NOTE: this is a POOR assumption - I'm not clear on how it's functioning on vSAN at all in shipping code given the assumption that + // "[ds] id/id.vmdk" is a legitimate path. Some vsphere magic path adjustment? + rwlayer := fmt.Sprintf("%s/%s.vmdk", path.Dir(handle.Spec.VMPathName()), id) + + disk := handle.Guest.NewDisk() + moref := handle.Spec.Datastore.Reference() + + // NOTE: this spec construction really should be captured in one place down in the disk layer. That code is currently biased towards + // the appliance disk flows so couples spec creation with disk creation/attach. + // TODO: we absolutely shouldn't be mixing the handle.Spec.Datastore (wtf does this come from) and the DatastorePath for the disk + disk.GetVirtualDevice().Backing = &types.VirtualDiskFlatVer2BackingInfo{ + DiskMode: string(types.VirtualDiskModePersistent), + ThinProvisioned: types.NewBool(true), + + VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ + FileName: rwlayer, + Datastore: &moref, + }, + + Parent: &types.VirtualDiskFlatVer2BackingInfo{ + VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ + FileName: img.DatastorePath.String(), + }, + }, + } + + handle.Spec.AddVirtualDisk(disk) + + // record the repo name and image ID that resolved to the layer in question + // NOTE: these really shouldn't be recorded directly like this, and are 1:1 with the image, not with the ExecConfig. + // I suspect there's some tech-debt reason they got dropped into the main configuration like this. + // I do recall that the repoName at least was recorded because many names/tags can point to the same layer so it's the + // point-and-time-of-use name that we're recording. I assume the same is true for the imageID whereas the layerID is actually + // stable + handle.ExecConfig.LayerID = img.ID + handle.ExecConfig.ImageID = imgID + handle.ExecConfig.RepoName = repoName + + return handle, nil +} diff --git a/lib/portlayer/storage/vsphere/image.go b/lib/portlayer/storage/image/vsphere/store.go similarity index 91% rename from lib/portlayer/storage/vsphere/image.go rename to lib/portlayer/storage/image/vsphere/store.go index a2195eeb58..62d526bada 100644 --- a/lib/portlayer/storage/vsphere/image.go +++ b/lib/portlayer/storage/image/vsphere/store.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,8 +31,11 @@ import ( "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/constants" "github.com/vmware/vic/lib/portlayer/exec" - portlayer "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/image" + "github.com/vmware/vic/lib/portlayer/storage/vsphere" "github.com/vmware/vic/lib/portlayer/util" + "github.com/vmware/vic/lib/tether/shared" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/datastore" "github.com/vmware/vic/pkg/vsphere/disk" @@ -40,8 +43,8 @@ import ( "github.com/vmware/vic/pkg/vsphere/vm" ) -// Set to false for unit tests var ( + // Set to false for unit tests DetachAll = true FileForMinOS = map[string]os.FileMode{ @@ -67,8 +70,8 @@ var ( ) const ( - StorageImageDir = "images" - defaultDiskLabel = "containerfs" + StorageImageDir = "images" + defaultDiskSizeInKB = 8 * 1024 * 1024 metaDataDir = "imageMetadata" manifest = "manifest" @@ -81,7 +84,7 @@ type ImageStore struct { func NewImageStore(op trace.Operation, s *session.Session, u *url.URL) (*ImageStore, error) { if DetachAll { // we can and should assume that Config objects are fully initialized - if err := portlayer.Config.DiskManager.DetachAll(op); err != nil { + if err := storage.Config.DiskManager.DetachAll(op); err != nil { return nil, err } } @@ -102,7 +105,7 @@ func NewImageStore(op trace.Operation, s *session.Session, u *url.URL) (*ImageSt vis := &ImageStore{ Vmdk: disk.Vmdk{ - Manager: portlayer.Config.DiskManager, + Manager: storage.Config.DiskManager, Helper: ds, Session: s, }, @@ -231,9 +234,7 @@ func (v *ImageStore) ListImageStores(op trace.Operation) ([]*url.URL, error) { // ID - textual ID for the image to be written // meta - metadata associated with the image // Tag - the tag of the image to be written -func (v *ImageStore) WriteImage(op trace.Operation, parent *portlayer.Image, ID string, meta map[string][]byte, sum string, - r io.Reader) (*portlayer.Image, error) { - +func (v *ImageStore) WriteImage(op trace.Operation, parent *image.Image, ID string, meta map[string][]byte, sum string, r io.Reader) (*image.Image, error) { storeName, err := util.ImageStoreName(parent.Store) if err != nil { return nil, err @@ -249,7 +250,7 @@ func (v *ImageStore) WriteImage(op trace.Operation, parent *portlayer.Image, ID // will be descended from this created and prepared fs. if ID == constants.ScratchLayerID { // Create the scratch layer - if err := v.scratch(op, storeName); err != nil { + if dsk, err = v.scratch(op, storeName); err != nil { return nil, err } } else { @@ -264,13 +265,14 @@ func (v *ImageStore) WriteImage(op trace.Operation, parent *portlayer.Image, ID } } - newImage := &portlayer.Image{ - ID: ID, - SelfLink: imageURL, - ParentLink: parent.SelfLink, - Store: parent.Store, - Metadata: meta, - Disk: dsk, + newImage := &image.Image{ + ID: ID, + SelfLink: imageURL, + ParentLink: parent.SelfLink, + Store: parent.Store, + Metadata: meta, + Disk: dsk, + DatastorePath: dsk.DatastoreURI, } return newImage, nil @@ -351,7 +353,7 @@ func (v *ImageStore) writeImage(op trace.Operation, storeName, parentID, ID stri // Write the metadata to the datastore metaDataDir := v.imageMetadataDirPath(storeName, ID) - err = writeMetadata(op, v.Helper, metaDataDir, meta) + err = vsphere.WriteMetadata(op, v.Helper, metaDataDir, meta) if err != nil { return nil, err } @@ -426,7 +428,7 @@ func (v *ImageStore) writeImage(op trace.Operation, storeName, parentID, ID stri return vmdisk, nil } -func (v *ImageStore) scratch(op trace.Operation, storeName string) error { +func (v *ImageStore) scratch(op trace.Operation, storeName string) (*disk.VirtualDisk, error) { var ( vmdisk *disk.VirtualDisk size int64 @@ -436,21 +438,21 @@ func (v *ImageStore) scratch(op trace.Operation, storeName string) error { // Create the image directory in the store. imageDir := v.imageDirPath(storeName, constants.ScratchLayerID) if _, err := v.Mkdir(op, false, imageDir); err != nil { - return err + return nil, err } // Write the metadata to the datastore metaDataDir := v.imageMetadataDirPath(storeName, constants.ScratchLayerID) - if err := writeMetadata(op, v.Helper, metaDataDir, nil); err != nil { - return err + if err := vsphere.WriteMetadata(op, v.Helper, metaDataDir, nil); err != nil { + return nil, err } imageDiskDsURI := v.imageDiskDSPath(storeName, constants.ScratchLayerID) op.Infof("Creating image %s (%s)", constants.ScratchLayerID, imageDiskDsURI) size = defaultDiskSizeInKB - if portlayer.Config.ScratchSize != 0 { - size = portlayer.Config.ScratchSize + if storage.Config.ScratchSize != 0 { + size = storage.Config.ScratchSize } defer func() { @@ -465,36 +467,36 @@ func (v *ImageStore) scratch(op trace.Operation, storeName string) error { vmdisk, err = v.CreateAndAttach(op, config) if err != nil { op.Errorf("CreateAndAttach(%s) error: %s", imageDiskDsURI, err) - return err + return nil, err } - op.Debugf("Scratch disk created with size %d", portlayer.Config.ScratchSize) + op.Debugf("Scratch disk created with size %d", storage.Config.ScratchSize) // Make the filesystem and set its label to defaultDiskLabel - if err = vmdisk.Mkfs(op, defaultDiskLabel); err != nil { + if err = vmdisk.Mkfs(op, shared.ScratchDiskLabel); err != nil { op.Errorf("Failed to create scratch filesystem: %s", err) - return err + return nil, err } if err = createBaseStructure(op, vmdisk); err != nil { op.Errorf("Failed to create base filesystem structure: %s", err) - return err + return nil, err } if err = v.Detach(op, vmdisk.VirtualDiskConfig); err != nil { op.Errorf("Failed to detach scratch image: %s", err) - return err + return nil, err } if err = v.writeManifest(op, storeName, constants.ScratchLayerID, nil); err != nil { op.Errorf("Failed to create manifest for scratch image: %s", err) - return err + return nil, err } - return nil + return vmdisk, nil } -func (v *ImageStore) GetImage(op trace.Operation, store *url.URL, ID string) (*portlayer.Image, error) { +func (v *ImageStore) GetImage(op trace.Operation, store *url.URL, ID string) (*image.Image, error) { defer trace.End(trace.Begin(store.String() + "/" + ID)) storeName, err := util.ImageStoreName(store) if err != nil { @@ -512,7 +514,7 @@ func (v *ImageStore) GetImage(op trace.Operation, store *url.URL, ID string) (*p // get the metadata metaDataDir := v.imageMetadataDirPath(storeName, ID) - meta, err := getMetadata(op, v.Helper, metaDataDir) + meta, err := vsphere.GetMetadata(op, v.Helper, metaDataDir) if err != nil { return nil, err } @@ -536,20 +538,21 @@ func (v *ImageStore) GetImage(op trace.Operation, store *url.URL, ID string) (*p } } - newImage := &portlayer.Image{ - ID: ID, - SelfLink: imageURL, - Store: &s, - ParentLink: parentURL, - Metadata: meta, - Disk: dsk, + newImage := &image.Image{ + ID: ID, + SelfLink: imageURL, + Store: &s, + ParentLink: parentURL, + Metadata: meta, + Disk: dsk, + DatastorePath: diskDsURI, } op.Debugf("GetImage(%s) has parent %s", newImage.SelfLink, newImage.Parent()) return newImage, nil } -func (v *ImageStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*portlayer.Image, error) { +func (v *ImageStore) ListImages(op trace.Operation, store *url.URL, IDs []string) ([]*image.Image, error) { storeName, err := util.ImageStoreName(store) if err != nil { @@ -561,7 +564,7 @@ func (v *ImageStore) ListImages(op trace.Operation, store *url.URL, IDs []string return nil, err } - images := []*portlayer.Image{} + images := []*image.Image{} for _, f := range res.File { file, ok := f.(*types.FolderFileInfo) if !ok { @@ -590,7 +593,7 @@ func (v *ImageStore) ListImages(op trace.Operation, store *url.URL, IDs []string // DeleteImage deletes an image from the image store. If the image is in // use either by way of inheritance or because it's attached to a // container, this will return an error. -func (v *ImageStore) DeleteImage(op trace.Operation, image *portlayer.Image) (*portlayer.Image, error) { +func (v *ImageStore) DeleteImage(op trace.Operation, image *image.Image) (*image.Image, error) { // check if the image is in use. if err := imagesInUse(op, image.ID); err != nil { op.Errorf("ImageStore: delete image error: %s", err.Error()) @@ -708,7 +711,7 @@ func imagesInUse(op trace.Operation, ID string) error { layerID := cont.ExecConfig.LayerID if layerID == ID { - return &portlayer.ErrImageInUse{ + return &image.ErrImageInUse{ Msg: fmt.Sprintf("image %s in use by %s", ID, cont.ExecConfig.ID), } } diff --git a/lib/portlayer/storage/vsphere/image_test.go b/lib/portlayer/storage/image/vsphere/store_test.go similarity index 97% rename from lib/portlayer/storage/vsphere/image_test.go rename to lib/portlayer/storage/image/vsphere/store_test.go index f1d0d92a6e..f32cd07bd6 100644 --- a/lib/portlayer/storage/vsphere/image_test.go +++ b/lib/portlayer/storage/image/vsphere/store_test.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -35,7 +35,8 @@ import ( "github.com/vmware/govmomi/object" "github.com/vmware/vic/lib/constants" "github.com/vmware/vic/lib/portlayer/exec" - portlayer "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/image" + "github.com/vmware/vic/lib/portlayer/storage/image/cache" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/datastore" "github.com/vmware/vic/pkg/vsphere/datastore/test" @@ -43,7 +44,7 @@ import ( "github.com/vmware/vic/pkg/vsphere/session" ) -func setup(t *testing.T) (*portlayer.NameLookupCache, *session.Session, string, error) { +func setup(t *testing.T) (*cache.NameLookupCache, *session.Session, string, error) { logrus.SetLevel(logrus.DebugLevel) trace.Logger.Level = logrus.DebugLevel DetachAll = false @@ -68,7 +69,7 @@ func setup(t *testing.T) (*portlayer.NameLookupCache, *session.Session, string, return nil, nil, "", err } - s := portlayer.NewLookupCache(vsImageStore) + s := cache.NewLookupCache(vsImageStore) return s, client, storeURL.Path, nil } @@ -214,7 +215,7 @@ func TestCreateImageLayers(t *testing.T) { expectedFilesOnDisk := []string{"lost+found"} // Keep a list of images we created - expectedImages := make(map[string]*portlayer.Image) + expectedImages := make(map[string]*image.Image) expectedImages[parent.ID] = parent for layer := 0; layer < numLayers; layer++ { @@ -335,7 +336,7 @@ func TestCreateImageLayers(t *testing.T) { // Try to delete an intermediate image (should fail) exec.NewContainerCache() _, err = cacheStore.DeleteImage(op, expectedImages["dir1"]) - if !assert.Error(t, err) || !assert.True(t, portlayer.IsErrImageInUse(err)) { + if !assert.Error(t, err) || !assert.True(t, image.IsErrImageInUse(err)) { return } @@ -571,7 +572,7 @@ func tarFiles(files []tarFile) (*bytes.Buffer, error) { return buf, nil } -func mountLayerRO(v *ImageStore, parent *portlayer.Image) (*disk.VirtualDisk, error) { +func mountLayerRO(v *ImageStore, parent *image.Image) (*disk.VirtualDisk, error) { roName := v.imageDiskDSPath("testStore", parent.ID) roName.Path = roName.Path + "-ro.vmdk" diff --git a/lib/portlayer/storage/data_sink.go b/lib/portlayer/storage/mount_datasink.go similarity index 94% rename from lib/portlayer/storage/data_sink.go rename to lib/portlayer/storage/mount_datasink.go index 3096450b5f..863f2eee66 100644 --- a/lib/portlayer/storage/data_sink.go +++ b/lib/portlayer/storage/mount_datasink.go @@ -79,6 +79,8 @@ func (m *MountDataSink) Import(op trace.Operation, spec *archive.FilterSpec, dat return archive.OfflineUnpack(op, data, spec, name) } +// Close handles conditional cleanup of the underlying file path and calls any cleanup function associated +// with the datasink during it's creation. func (m *MountDataSink) Close() error { m.cleanOp.Infof("cleaning up after import") diff --git a/lib/portlayer/storage/data_source.go b/lib/portlayer/storage/mount_datasource.go similarity index 100% rename from lib/portlayer/storage/data_source.go rename to lib/portlayer/storage/mount_datasource.go diff --git a/lib/portlayer/storage/nfs/target.go b/lib/portlayer/storage/nfs/target.go index fdb23a0ac3..7bc6dede00 100644 --- a/lib/portlayer/storage/nfs/target.go +++ b/lib/portlayer/storage/nfs/target.go @@ -19,7 +19,7 @@ import ( "net/url" "os" - nfsClient "github.com/vmware/go-nfs-client/nfs" + "github.com/vmware/go-nfs-client/nfs" "github.com/vmware/go-nfs-client/nfs/rpc" "github.com/vmware/vic/pkg/trace" @@ -68,7 +68,7 @@ type NfsMount struct { // The URL (host + path) of the NFS server and target path TargetURL *url.URL - s *nfsClient.Mount + s *nfs.Mount } func NewMount(t *url.URL, hostname string, uid, gid uint32) *NfsMount { @@ -82,7 +82,7 @@ func NewMount(t *url.URL, hostname string, uid, gid uint32) *NfsMount { func (m *NfsMount) Mount(op trace.Operation) (Target, error) { op.Debugf("Mounting %s", m.TargetURL.String()) - s, err := nfsClient.DialMount(m.TargetURL.Host) + s, err := nfs.DialMount(m.TargetURL.Host) if err != nil { return nil, err } @@ -127,7 +127,7 @@ func (m *NfsMount) URL() (*url.URL, error) { // wrap ReadDir to return a slice of os.FileInfo type target struct { - *nfsClient.Target + *nfs.Target } func (t *target) ReadDir(path string) ([]os.FileInfo, error) { diff --git a/lib/portlayer/storage/storage.go b/lib/portlayer/storage/storage.go index 42114539d5..5af638a734 100644 --- a/lib/portlayer/storage/storage.go +++ b/lib/portlayer/storage/storage.go @@ -12,6 +12,81 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package storage provides abstractions for interacting with storage locations, both management and access. +// There are two primary type of storage handled by this package at this time: +// a. VMDKs on a vSphere datastore +// b. NFS shares +// +// We have more than two sub-packages however as we use distinct packages to provide different lifecycle and +// usage semantics for types of data using those mechanisms. Examples include: +// * application logic (images) +// * application transient data (image read/write layer) +// * application persistent data (volumes) +// +// As such there are a set of concepts, with all but `store` captured as interfaces: +// * store +// * resolver +// * exporter +// * importer +// * data source +// * data sink +// +// The odd one out for these concepts is the `store`. This is because we did not want to be prescriptive about +// what a store supports, but it may well distill into an interface at a later date. Currently the various +// store implementations consist of a composition of `resolver`, `importer`, and `exporter` interfaces. +// +// The `data source` and `data sink` interfaces are present to allow abstracted read and write access respectively +// to a specific storage mechanism. The data source and data sink instances should be considered single use and not +// reused for multiple access. This semantic was chosen as it's the least prescriptive on storage availability and +// connectivity - the precise means used to read or write from storage may change, for example, based on whether +// the storage is in use (has an owner). +// The `resolver` interface is present to translate a potentially store specific ID into an unambiguous URI, and to +// allow discovery of which virtual machines, if any, currently claim that ID. This returns virtual machines instead +// of a further abstracted interface because it's not clear at time of writing what that abstraction should or would +// be. +// The `importer` and `exporter` interfaces are the factories used to acquire `data source` and `data sink` instances. +// They also provide convenience methods for directly creating and consuming the source/sink in one operation. +// +// It is not required that all storage elements implement both Importer and Exporter mechanisms - having write only or +// read only storage is viable. +// +// The data source and sink interfaces also specify a basic accessor method for the underlying mechanism. This was +// added to allow for some future-proofing, with the primary extension expected to be a generic FileWalker interface +// so that directory walk and listings can be done without needing to care about the access mechanism (e.g. common +// between a local filesystem mount, an XDR NFS client, or a VM toolbox client). +// +// The general structure of a specific storage implementation is as follows and is imposed so that it is easier to +// locate a specific functional path for a given usage/storage type combination. As of this writing there are two +// additional folders in this structure, nfs and vsphere. These contain implementation specific logic used across +// store types and must be at this level to avoid cyclic package dependencies: +// ``` +// storage/ - this package +// ├── [type]/ - the high level type of use, implies semantics (i.e., container, image, volume) +// │ ├── [type].go - the common store implementation aspects across implementations +// │ ├── errors.go - [type] specific error types +// │ ├── cache - a `store` compliant cache implementation, if needed, with appropriate semantics for the type of use +// │ │ └── cache.go +// │ └── [implementation]/ - a specific implementation type (e.g., vsphere) +// │ ├── bind.go - modifies a portlayer handle to configure active use of a specific `joined` storage +// │ ├── export.go - implements the `read` side of the interfaces (Exporter and DataSource) +// │ ├── import.go - a specific implementation type +// │ ├── join.go - modifies a portlayer handle to configure basic access to a specific instance of the storage type +// │ └── store.go - constructor and implementation for specific store type and implementation +// ├── storage.go - interface definitions, portlayer lifecycle management functions, and the registration/lookup +// | mechanisms for store instances. +// └── [purpose].go - common functions used by the various type/implementation combinations +// ``` +// +// This structure is not completely consistent at the time of writing. Most notable is that the bind.go functions +// have all been rolled into the Join calls (the portlayer uses the following common verbs across components, again +// with some inconsistencies at this time: Join, Bind, Inspect, Unbind, Remove, Wait). +// In the case of the `container` store type there is only one implementation at this time (vsphere VMDKs) and the +// `implementation` subdirectory has been omitted. +// +// This file implements lookup of store implementations via the `RegisterImporter` and `RegisterExporter` functions. +// This is provided to allow a common pattern for implementing, finding, and accessing store implementations without +// the caller requiring specific knowledge about the type of a specific store. All that's required is knowledge of +// the set of `store` interfaces and the identifier with which a given store was implemented. package storage import ( @@ -41,6 +116,82 @@ var ( exporters map[string]Exporter ) +// Resolver defines methods for mapping ids to URLS, and urls to owners of that device +type Resolver interface { + // URL returns a url to the data source representing `id` + // For historic reasons this is not the same URL that other parts of the storage component use, but an actual + // URL suited for locating the storage element without having additional precursor knowledge. + URL(op trace.Operation, id string) (*url.URL, error) + + // Owners returns a list of VMs that are using the resource specified by `url` + Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) +} + +// DataSource defines the methods for exporting data from a specific storage element as a tar stream +type DataSource interface { + // Close releases all resources associated with this source. Shared resources should be reference counted. + io.Closer + + // Export performs an export of the specified files, returning the data as a tar stream. This is single use; once + // the export has completed it should not be assumed that the source remains functional. + // + // spec: specifies which files will be included/excluded in the export and allows for path rebasing/stripping + // data: if true the actual file data is included, if false only the file headers are present + Export(op trace.Operation, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) + + // Source returns the mechanism by which the data source is accessed + // Examples: + // vmdk mounted locally: *os.File + // nfs volume: XDR-client + // via guesttools: toolbox client + Source() interface{} + + // Stat stats the filesystem target indicated by the last entry in the given Filterspecs inclusion map + Stat(op trace.Operation, spec *archive.FilterSpec) (*FileStat, error) +} + +// DataSink defines the methods for importing data to a specific storage element from a tar stream +type DataSink interface { + // Close releases all resources associated with this sink. Shared resources should be reference counted. + io.Closer + + // Import performs an import of the tar stream to the source held by this DataSink. This is single use; once + // the export has completed it should not be assumed that the sink remains functional. + // + // spec: specifies which files will be included/excluded in the import and allows for path rebasing/stripping + // tarStream: the tar stream to from which to import data + Import(op trace.Operation, spec *archive.FilterSpec, tarStream io.ReadCloser) error + + // Sink returns the mechanism by which the data sink is accessed + // Examples: + // vmdk mounted locally: *os.File + // nfs volume: XDR-client + // via guesttools: toolbox client + Sink() interface{} +} + +// Importer defines the methods needed to write data into a storage element. This should be implemented by the various +// store types. +type Importer interface { + // Import allows direct construction and invocation of a data sink for the specified ID. + Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error + + // NewDataSink constructs a data sink for the specified ID within the context of the Importer. This is a single + // use sink which may hold resources until Closed. + NewDataSink(op trace.Operation, id string) (DataSink, error) +} + +// Exporter defines the methods needed to read data from a storage element, optionally diff with an ancestor. This +// shoiuld be implemented by the various store types. +type Exporter interface { + // Export allows direct construction and invocation of a data source for the specified ID. + Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) + + // NewDataSource constructs a data source for the specified ID within the context of the Exporter. This is a single + // use source which may hold resources until Closed. + NewDataSource(op trace.Operation, id string) (DataSource, error) +} + type FileStat struct { LinkTarget string Mode uint32 @@ -148,79 +299,3 @@ func GetExporters() []string { return keys } - -// Resolver defines methods for mapping ids to URLS, and urls to owners of that device -type Resolver interface { - // URL returns a url to the data source representing `id` - // For historic reasons this is not the same URL that other parts of the storage component use, but an actual - // URL suited for locating the storage element without having additional precursor knowledge. - URL(op trace.Operation, id string) (*url.URL, error) - - // Owners returns a list of VMs that are using the resource specified by `url` - Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) -} - -// DataSource defines the methods for exporting data from a specific storage element as a tar stream -type DataSource interface { - // Close releases all resources associated with this source. Shared resources should be reference counted. - io.Closer - - // Export performs an export of the specified files, returning the data as a tar stream. This is single use; once - // the export has completed it should not be assumed that the source remains functional. - // - // spec: specifies which files will be included/excluded in the export and allows for path rebasing/stripping - // data: if true the actual file data is included, if false only the file headers are present - Export(op trace.Operation, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) - - // Source returns the mechanism by which the data source is accessed - // Examples: - // vmdk mounted locally: *os.File - // nfs volume: XDR-client - // via guesttools: toolbox client - Source() interface{} - - // Stat stats the filesystem target indicated by the last entry in the given Filterspecs inclusion map - Stat(op trace.Operation, spec *archive.FilterSpec) (*FileStat, error) -} - -// DataSink defines the methods for importing data to a specific storage element from a tar stream -type DataSink interface { - // Close releases all resources associated with this sink. Shared resources should be reference counted. - io.Closer - - // Import performs an import of the tar stream to the source held by this DataSink. This is single use; once - // the export has completed it should not be assumed that the sink remains functional. - // - // spec: specifies which files will be included/excluded in the import and allows for path rebasing/stripping - // tarStream: the tar stream to from which to import data - Import(op trace.Operation, spec *archive.FilterSpec, tarStream io.ReadCloser) error - - // Sink returns the mechanism by which the data sink is accessed - // Examples: - // vmdk mounted locally: *os.File - // nfs volume: XDR-client - // via guesttools: toolbox client - Sink() interface{} -} - -// Importer defines the methods needed to write data into a storage element. This should be implemented by the various -// store types. -type Importer interface { - // Import allows direct construction and invocation of a data sink for the specified ID. - Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error - - // NewDataSink constructs a data sink for the specified ID within the context of the Importer. This is a single - // use sink which may hold resources until Closed. - NewDataSink(op trace.Operation, id string) (DataSink, error) -} - -// Exporter defines the methods needed to read data from a storage element, optionally diff with an ancestor. This -// shoiuld be implemented by the various store types. -type Exporter interface { - // Export allows direct construction and invocation of a data source for the specified ID. - Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) - - // NewDataSource constructs a data source for the specified ID within the context of the Exporter. This is a single - // use source which may hold resources until Closed. - NewDataSource(op trace.Operation, id string) (DataSource, error) -} diff --git a/lib/portlayer/storage/volume_cache.go b/lib/portlayer/storage/volume/cache/cache.go similarity index 89% rename from lib/portlayer/storage/volume_cache.go rename to lib/portlayer/storage/volume/cache/cache.go index 95037eb190..0ea9e112fe 100644 --- a/lib/portlayer/storage/volume_cache.go +++ b/lib/portlayer/storage/volume/cache/cache.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package cache import ( "fmt" @@ -25,6 +25,8 @@ import ( "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/portlayer/exec" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" ) @@ -35,17 +37,17 @@ type VolumeLookupCache struct { // Maps IDs to Volumes. // // id -> Volume - vlc map[string]Volume + vlc map[string]volume.Volume vlcLock sync.RWMutex // Maps the service url of the volume store to the underlying data storage implementation - volumeStores map[string]VolumeStorer + volumeStores map[string]volume.VolumeStorer } func NewVolumeLookupCache(op trace.Operation) *VolumeLookupCache { v := &VolumeLookupCache{ - vlc: make(map[string]Volume), - volumeStores: make(map[string]VolumeStorer), + vlc: make(map[string]volume.Volume), + volumeStores: make(map[string]volume.VolumeStorer), } return v @@ -60,7 +62,7 @@ func (v *VolumeLookupCache) GetVolumeStore(op trace.Operation, storeName string) } // AddStore adds a volumestore by name. The url returned is the service url to the volume store. -func (v *VolumeLookupCache) AddStore(op trace.Operation, storeName string, vs VolumeStorer) (*url.URL, error) { +func (v *VolumeLookupCache) AddStore(op trace.Operation, storeName string, vs volume.VolumeStorer) (*url.URL, error) { v.vlcLock.Lock() defer v.vlcLock.Unlock() @@ -79,12 +81,12 @@ func (v *VolumeLookupCache) AddStore(op trace.Operation, storeName string, vs Vo return u, v.addVolumesToCache(op, storeURLStr, vs) } -func (v *VolumeLookupCache) volumeStore(store *url.URL) (VolumeStorer, error) { +func (v *VolumeLookupCache) volumeStore(store *url.URL) (volume.VolumeStorer, error) { // find the datastore vs, ok := v.volumeStores[store.String()] if !ok { - err := VolumeStoreNotFoundError{ + err := volume.VolumeStoreNotFoundError{ Msg: fmt.Sprintf("volume store (%s) not found", store.String()), } return nil, err @@ -118,7 +120,7 @@ func (v *VolumeLookupCache) VolumeStoresList(op trace.Operation) ([]string, erro return stores, nil } -func (v *VolumeLookupCache) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*Volume, error) { +func (v *VolumeLookupCache) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) { v.vlcLock.Lock() defer v.vlcLock.Unlock() @@ -172,7 +174,7 @@ func (v *VolumeLookupCache) VolumeDestroy(op trace.Operation, ID string) error { return nil } -func (v *VolumeLookupCache) VolumeGet(op trace.Operation, ID string) (*Volume, error) { +func (v *VolumeLookupCache) VolumeGet(op trace.Operation, ID string) (*volume.Volume, error) { v.vlcLock.RLock() defer v.vlcLock.RUnlock() @@ -185,15 +187,15 @@ func (v *VolumeLookupCache) VolumeGet(op trace.Operation, ID string) (*Volume, e return &vol, nil } -func (v *VolumeLookupCache) VolumesList(op trace.Operation) ([]*Volume, error) { +func (v *VolumeLookupCache) VolumesList(op trace.Operation) ([]*volume.Volume, error) { v.vlcLock.RLock() defer v.vlcLock.RUnlock() // look in the cache, return the list - l := make([]*Volume, 0, len(v.vlc)) + l := make([]*volume.Volume, 0, len(v.vlc)) for _, vol := range v.vlc { // this is idiotic - var e Volume + var e volume.Volume e = vol l = append(l, &e) } @@ -202,7 +204,7 @@ func (v *VolumeLookupCache) VolumesList(op trace.Operation) ([]*Volume, error) { } // addVolumesToCache finds the volumes in the input volume store and adds them to the cache. -func (v *VolumeLookupCache) addVolumesToCache(op trace.Operation, storeURLStr string, vs VolumeStorer) error { +func (v *VolumeLookupCache) addVolumesToCache(op trace.Operation, storeURLStr string, vs volume.VolumeStorer) error { op.Infof("Adding volumes in volume store %s to volume cache", storeURLStr) vols, err := vs.VolumesList(op) @@ -232,7 +234,7 @@ func volumeInUse(ID string) error { } if _, mounted := cont.ExecConfig.Mounts[ID]; mounted { - return &ErrVolumeInUse{ + return &volume.ErrVolumeInUse{ Msg: fmt.Sprintf("volume %s in use by %s", ID, cont.ExecConfig.ID), } } @@ -258,7 +260,7 @@ func (v *VolumeLookupCache) Import(op trace.Operation, id string, spec *archive. return store.Import(op, id, spec, tarStream) } -func (v *VolumeLookupCache) NewDataSink(op trace.Operation, id string) (DataSink, error) { +func (v *VolumeLookupCache) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { volume, err := v.VolumeGet(op, id) if err != nil { return nil, err @@ -290,7 +292,7 @@ func (v *VolumeLookupCache) Export(op trace.Operation, id, ancestor string, spec return store.Export(op, id, ancestor, spec, data) } -func (v *VolumeLookupCache) NewDataSource(op trace.Operation, id string) (DataSource, error) { +func (v *VolumeLookupCache) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { volume, err := v.VolumeGet(op, id) if err != nil { return nil, err diff --git a/lib/portlayer/storage/volume_cache_test.go b/lib/portlayer/storage/volume/cache/cache_test.go similarity index 90% rename from lib/portlayer/storage/volume_cache_test.go rename to lib/portlayer/storage/volume/cache/cache_test.go index 87533d81be..3e3efd8836 100644 --- a/lib/portlayer/storage/volume_cache_test.go +++ b/lib/portlayer/storage/volume/cache/cache_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package cache import ( "fmt" @@ -29,6 +29,8 @@ import ( "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/portlayer/exec" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/vm" @@ -36,12 +38,12 @@ import ( type MockVolumeStore struct { // id -> volume - db map[string]*Volume + db map[string]*volume.Volume } func NewMockVolumeStore() *MockVolumeStore { m := &MockVolumeStore{ - db: make(map[string]*Volume), + db: make(map[string]*volume.Volume), } return m @@ -52,7 +54,7 @@ func (m *MockVolumeStore) VolumeStoresList(op trace.Operation) (map[string]url.U } // Creates a volume on the given volume store, of the given size, with the given metadata. -func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*Volume, error) { +func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) { storeName, err := util.VolumeStoreName(store) if err != nil { return nil, err @@ -63,7 +65,7 @@ func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url return nil, err } - vol := &Volume{ + vol := &volume.Volume{ ID: ID, Store: store, SelfLink: selfLink, @@ -75,7 +77,7 @@ func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url } // Get an existing volume via it's ID and volume store. -func (m *MockVolumeStore) VolumeGet(op trace.Operation, ID string) (*Volume, error) { +func (m *MockVolumeStore) VolumeGet(op trace.Operation, ID string) (*volume.Volume, error) { vol, ok := m.db[ID] if !ok { return nil, os.ErrNotExist @@ -85,7 +87,7 @@ func (m *MockVolumeStore) VolumeGet(op trace.Operation, ID string) (*Volume, err } // Destroys a volume -func (m *MockVolumeStore) VolumeDestroy(op trace.Operation, vol *Volume) error { +func (m *MockVolumeStore) VolumeDestroy(op trace.Operation, vol *volume.Volume) error { if _, ok := m.db[vol.ID]; !ok { return os.ErrNotExist } @@ -96,9 +98,9 @@ func (m *MockVolumeStore) VolumeDestroy(op trace.Operation, vol *Volume) error { } // VolumesList lists all volumes on the given volume store. -func (m *MockVolumeStore) VolumesList(op trace.Operation) ([]*Volume, error) { +func (m *MockVolumeStore) VolumesList(op trace.Operation) ([]*volume.Volume, error) { var i int - list := make([]*Volume, len(m.db)) + list := make([]*volume.Volume, len(m.db)) for _, v := range m.db { t := *v list[i] = &t @@ -116,11 +118,11 @@ func (m *MockVolumeStore) Import(op trace.Operation, id string, spec *archive.Fi return nil } -func (m *MockVolumeStore) NewDataSink(op trace.Operation, id string) (DataSink, error) { +func (m *MockVolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { return nil, nil } -func (m *MockVolumeStore) NewDataSource(op trace.Operation, id string) (DataSource, error) { +func (m *MockVolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { return nil, nil } @@ -144,7 +146,7 @@ func TestVolumeCreateGetListAndDelete(t *testing.T) { return } - inVols := make(map[string]*Volume) + inVols := make(map[string]*volume.Volume) inVolsM := &sync.Mutex{} wg := &sync.WaitGroup{} @@ -172,7 +174,7 @@ func TestVolumeCreateGetListAndDelete(t *testing.T) { } wg.Wait() - getFn := func(inVol *Volume) { + getFn := func(inVol *volume.Volume) { vol, err := v.VolumeGet(op, inVol.ID) if !assert.NoError(t, err) || !assert.NotNil(t, vol) { return @@ -215,8 +217,8 @@ func TestVolumeCreateGetListAndDelete(t *testing.T) { } // createVolumes is a test helper that creates a set of num volumes on the input volume cache and volume store. -func createVolumes(t *testing.T, op trace.Operation, v *VolumeLookupCache, storeURL *url.URL, num int) map[string]*Volume { - vols := make(map[string]*Volume) +func createVolumes(t *testing.T, op trace.Operation, v *VolumeLookupCache, storeURL *url.URL, num int) map[string]*volume.Volume { + vols := make(map[string]*volume.Volume) for i := 1; i <= num; i++ { id := fmt.Sprintf("ID-%d", i) @@ -246,7 +248,7 @@ func TestAddVolumesToCache(t *testing.T) { vols := createVolumes(t, op, v, storeURL, 50) // Clear the volume map after it has been filled during volume creation. - v.vlc = make(map[string]Volume) + v.vlc = make(map[string]volume.Volume) err = v.addVolumesToCache(op, storeURLStr, mvs1) assert.Nil(t, err) diff --git a/lib/portlayer/storage/errors.go b/lib/portlayer/storage/volume/errors.go similarity index 80% rename from lib/portlayer/storage/errors.go rename to lib/portlayer/storage/volume/errors.go index 6a21cd04c7..c1ba47a311 100644 --- a/lib/portlayer/storage/errors.go +++ b/lib/portlayer/storage/volume/errors.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,24 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage - -type ErrImageInUse struct { - Msg string -} - -func (e *ErrImageInUse) Error() string { - return e.Msg -} - -func IsErrImageInUse(err error) bool { - if err == nil { - return false - } - _, ok := err.(*ErrImageInUse) - - return ok -} +package volume type ErrVolumeInUse struct { Msg string diff --git a/lib/portlayer/storage/volume/mock/export.go b/lib/portlayer/storage/volume/mock/export.go new file mode 100644 index 0000000000..d532a892d4 --- /dev/null +++ b/lib/portlayer/storage/volume/mock/export.go @@ -0,0 +1,31 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +import ( + "io" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +func (m *MockVolumeStore) Export(op trace.Operation, child, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { + return nil, nil +} + +func (m *MockVolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { + return nil, nil +} diff --git a/lib/portlayer/storage/volume/mock/import.go b/lib/portlayer/storage/volume/mock/import.go new file mode 100644 index 0000000000..4ed3a77912 --- /dev/null +++ b/lib/portlayer/storage/volume/mock/import.go @@ -0,0 +1,31 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +import ( + "io" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +func (m *MockVolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarstream io.ReadCloser) error { + return nil +} + +func (m *MockVolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { + return nil, nil +} diff --git a/lib/portlayer/storage/volume/mock/join.go b/lib/portlayer/storage/volume/mock/join.go new file mode 100644 index 0000000000..df710801d3 --- /dev/null +++ b/lib/portlayer/storage/volume/mock/join.go @@ -0,0 +1,17 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +// Join was not present in the existing test code diff --git a/lib/portlayer/storage/volume/mock/store.go b/lib/portlayer/storage/volume/mock/store.go new file mode 100644 index 0000000000..6568475bbe --- /dev/null +++ b/lib/portlayer/storage/volume/mock/store.go @@ -0,0 +1,109 @@ +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. +// +// 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 mock + +import ( + "fmt" + "net/url" + "os" + + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/vic/lib/portlayer/storage/volume" + "github.com/vmware/vic/lib/portlayer/util" + "github.com/vmware/vic/pkg/trace" + "github.com/vmware/vic/pkg/vsphere/vm" +) + +type MockVolumeStore struct { + // id -> volume + db map[string]*volume.Volume +} + +func NewMockVolumeStore() *MockVolumeStore { + m := &MockVolumeStore{ + db: make(map[string]*volume.Volume), + } + + return m +} + +// Creates a volume on the given volume store, of the given size, with the given metadata. +func (m *MockVolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) { + storeName, err := util.VolumeStoreName(store) + if err != nil { + return nil, err + } + + selfLink, err := util.VolumeURL(storeName, ID) + if err != nil { + return nil, err + } + + vol := &volume.Volume{ + ID: ID, + Store: store, + SelfLink: selfLink, + } + + m.db[ID] = vol + + return vol, nil +} + +// Get an existing volume via it's ID and volume store. +func (m *MockVolumeStore) VolumeGet(op trace.Operation, ID string) (*volume.Volume, error) { + vol, ok := m.db[ID] + if !ok { + return nil, os.ErrNotExist + } + + return vol, nil +} + +// Destroys a volume +func (m *MockVolumeStore) VolumeDestroy(op trace.Operation, vol *volume.Volume) error { + if _, ok := m.db[vol.ID]; !ok { + return os.ErrNotExist + } + + delete(m.db, vol.ID) + + return nil +} + +func (m *MockVolumeStore) VolumeStoresList(op trace.Operation) (map[string]url.URL, error) { + return nil, fmt.Errorf("not implemented") +} + +// Lists all volumes on the given volume store` +func (m *MockVolumeStore) VolumesList(op trace.Operation) ([]*volume.Volume, error) { + var i int + list := make([]*volume.Volume, len(m.db)) + for _, v := range m.db { + t := *v + list[i] = &t + i++ + } + + return list, nil +} + +func (m *MockVolumeStore) URL(op trace.Operation, id string) (*url.URL, error) { + return nil, nil +} + +func (m *MockVolumeStore) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) { + return nil, nil +} diff --git a/lib/portlayer/storage/nfs/disk.go b/lib/portlayer/storage/volume/nfs/disk.go similarity index 95% rename from lib/portlayer/storage/nfs/disk.go rename to lib/portlayer/storage/volume/nfs/disk.go index c3bf4b99dc..7dc374e5b7 100644 --- a/lib/portlayer/storage/nfs/disk.go +++ b/lib/portlayer/storage/volume/nfs/disk.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/lib/portlayer/storage/volume/nfs/export.go b/lib/portlayer/storage/volume/nfs/export.go new file mode 100644 index 0000000000..a67329843a --- /dev/null +++ b/lib/portlayer/storage/volume/nfs/export.go @@ -0,0 +1,42 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 nfs + +import ( + "errors" + "fmt" + "io" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +// Export reads the delta between child and parent volume layers, returning +// the difference as a tar archive. +// +// store - the volume store containing the two layers +// id - must inherit from ancestor if ancestor is specified +// ancestor - the volume layer up the chain against which to diff +// spec - describes filters on paths found in the data (include, exclude, strip) +// data - set to true to include file data in the tar archive, false to include headers only +// Export creates and returns a tar archive containing data found between an nfs layer one or all of its ancestors +func (v *VolumeStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { + return nil, fmt.Errorf("vSphere Integrated Containers does not yet implement Export for nfs volumes") +} + +func (v *VolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { + return nil, errors.New("NFS VolumeStore does not yet implement NewDataSource") +} diff --git a/lib/portlayer/storage/volume/nfs/import.go b/lib/portlayer/storage/volume/nfs/import.go new file mode 100644 index 0000000000..47a782690e --- /dev/null +++ b/lib/portlayer/storage/volume/nfs/import.go @@ -0,0 +1,34 @@ +// Copyright 2018 VMware, Inc. All Rights Reserved. +// +// 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 nfs + +import ( + "errors" + "fmt" + "io" + + "github.com/vmware/vic/lib/archive" + "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/pkg/trace" +) + +// Import takes a tar archive stream and extracts it into the target volume +func (v *VolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error { + return fmt.Errorf("Write for nfs volumes is not Implemented") +} + +func (v *VolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { + return nil, errors.New("NFS VolumeStore does not yet implement NewDataSink") +} diff --git a/lib/portlayer/storage/nfs/vm.go b/lib/portlayer/storage/volume/nfs/join.go similarity index 92% rename from lib/portlayer/storage/nfs/vm.go rename to lib/portlayer/storage/volume/nfs/join.go index 2d3909cd6f..411a5a4dbc 100644 --- a/lib/portlayer/storage/nfs/vm.go +++ b/lib/portlayer/storage/volume/nfs/join.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import ( "github.com/vmware/vic/lib/config/executor" "github.com/vmware/vic/lib/portlayer/exec" - "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" "github.com/vmware/vic/pkg/trace" ) @@ -39,7 +39,7 @@ const ( nfsMountOptions = "vers=3,rsize=131072,wsize=131072,hard,proto=tcp,timeo=600,sec=sys,mountvers=3,mountproto=tcp,nolock" ) -func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *storage.Volume, mountPath string, diskOpts map[string]string) (*exec.Handle, error) { +func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *volume.Volume, mountPath string, diskOpts map[string]string) (*exec.Handle, error) { defer trace.End(trace.Begin(fmt.Sprintf("handle.ID(%s), volume(%s), mountPath(%s), diskPath(%#v)", handle.ExecConfig.ID, volume.ID, mountPath, volume.Device.DiskPath()))) if _, ok := handle.ExecConfig.Mounts[volume.ID]; ok { @@ -56,7 +56,7 @@ func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *storage.Volume, return handle, nil } -func createMountSpec(volume *storage.Volume, mountPath string, diskOpts map[string]string) *executor.MountSpec { +func createMountSpec(volume *volume.Volume, mountPath string, diskOpts map[string]string) *executor.MountSpec { host := volume.Device.DiskPath() deviceMode := nfsMountOptions + ",addr=" + host.Host diff --git a/lib/portlayer/storage/nfs/volume.go b/lib/portlayer/storage/volume/nfs/store.go similarity index 82% rename from lib/portlayer/storage/nfs/volume.go rename to lib/portlayer/storage/volume/nfs/store.go index 02cd562701..87f131cbf5 100644 --- a/lib/portlayer/storage/nfs/volume.go +++ b/lib/portlayer/storage/volume/nfs/store.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package nfs import ( "errors" "fmt" - "io" "io/ioutil" "net/url" "os" @@ -26,7 +25,8 @@ import ( "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/config/executor" - "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/nfs" + "github.com/vmware/vic/lib/portlayer/storage/volume" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/vm" @@ -53,7 +53,7 @@ type VolumeStore struct { Name string // Service is the interface to the nfs target. - Service MountServer + Service nfs.MountServer // Service selflink to volume store. SelfLink *url.URL @@ -62,7 +62,7 @@ type VolumeStore struct { archive.Archiver } -func NewVolumeStore(op trace.Operation, storeName string, mount MountServer) (*VolumeStore, error) { +func NewVolumeStore(op trace.Operation, storeName string, mount nfs.MountServer) (*VolumeStore, error) { // #nosec: Errors unhandled. u, _ := mount.URL() op.Infof("Creating nfs volumestore %s on %s", storeName, u.String()) @@ -114,7 +114,7 @@ func (v *VolumeStore) volMetadataDirPath(ID string) string { } // Creates a volume directory and volume object for NFS based volumes -func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*storage.Volume, error) { +func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) { target, err := v.Service.Mount(op) if err != nil { return nil, err @@ -134,7 +134,7 @@ func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL return nil, fmt.Errorf("Unexpected scheme (%s) for volume store (%s)", u.Scheme, v.Name) } - vol, err := storage.NewVolume(v.SelfLink, ID, info, NewVolume(u, v.volDirPath(ID)), executor.CopyNew) + vol, err := volume.NewVolume(v.SelfLink, ID, info, NewVolume(u, v.volDirPath(ID)), executor.CopyNew) if err != nil { op.Errorf("Created volume directory but failed to create volume: %s", err) return nil, err @@ -149,7 +149,7 @@ func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL } // VolumeDestroy Removes a volume and all of its contents from the nfs store. We already know via the cache if it is in use. -func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *storage.Volume) error { +func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *volume.Volume) error { target, err := v.Service.Mount(op) if err != nil { return err @@ -173,7 +173,7 @@ func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *storage.Volume) err return nil } -func (v *VolumeStore) VolumesList(op trace.Operation) ([]*storage.Volume, error) { +func (v *VolumeStore) VolumesList(op trace.Operation) ([]*volume.Volume, error) { target, err := v.Service.Mount(op) if err != nil { @@ -185,7 +185,7 @@ func (v *VolumeStore) VolumesList(op trace.Operation) ([]*storage.Volume, error) if err != nil { return nil, err } - var volumes []*storage.Volume + var volumes []*volume.Volume var fetchErr error for _, fileInfo := range volFileInfo { @@ -203,7 +203,7 @@ func (v *VolumeStore) VolumesList(op trace.Operation) ([]*storage.Volume, error) // #nosec: Errors unhandled. u, _ := v.Service.URL() - vol, err := storage.NewVolume(v.SelfLink, fileInfo.Name(), volMetadata, NewVolume(u, v.volDirPath(fileInfo.Name())), executor.CopyNew) + vol, err := volume.NewVolume(v.SelfLink, fileInfo.Name(), volMetadata, NewVolume(u, v.volDirPath(fileInfo.Name())), executor.CopyNew) if err != nil { op.Errorf("Failed to create volume struct from volume directory (%s)", fileInfo.Name()) return nil, err @@ -219,24 +219,6 @@ func (v *VolumeStore) VolumesList(op trace.Operation) ([]*storage.Volume, error) return volumes, nil } -// Import takes a tar archive stream and extracts it into the target volume -func (v *VolumeStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error { - return fmt.Errorf("Write for nfs volumes is not Implemented") -} - -// Export creates and returns a tar archive containing data found between an nfs layer one or all of its ancestors -func (v *VolumeStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { - return nil, fmt.Errorf("vSphere Integrated Containers does not yet implement Export for nfs volumes") -} - -func (v *VolumeStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { - return nil, errors.New("NFS VolumeStore does not yet implement NewDataSource") -} - -func (v *VolumeStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { - return nil, errors.New("NFS VolumeStore does not yet implement NewDataSink") -} - func (v *VolumeStore) URL(op trace.Operation, id string) (*url.URL, error) { return nil, errors.New("NFS VolumeStore does not yet implement URL") } @@ -245,7 +227,7 @@ func (v *VolumeStore) Owners(op trace.Operation, url *url.URL, filter func(vm *m return nil, errors.New("NFS VolumeStore does not yet implement Owners") } -func (v *VolumeStore) writeMetadata(op trace.Operation, ID string, info map[string][]byte, target Target) error { +func (v *VolumeStore) writeMetadata(op trace.Operation, ID string, info map[string][]byte, target nfs.Target) error { // write metadata into the metadata directory by key (filename) / value // (data), namespaced by volume id // @@ -279,7 +261,7 @@ func (v *VolumeStore) writeMetadata(op trace.Operation, ID string, info map[stri return nil } -func (v *VolumeStore) getMetadata(op trace.Operation, ID string, target Target) (map[string][]byte, error) { +func (v *VolumeStore) getMetadata(op trace.Operation, ID string, target nfs.Target) (map[string][]byte, error) { metadataPath := v.volMetadataDirPath(ID) op.Debugf("Attempting to retrieve volume metadata for (%s) at (%s)", ID, metadataPath) diff --git a/lib/portlayer/storage/nfs/volume_test.go b/lib/portlayer/storage/volume/nfs/store_test.go similarity index 97% rename from lib/portlayer/storage/nfs/volume_test.go rename to lib/portlayer/storage/volume/nfs/store_test.go index a0ba5f7102..7b9e653f4f 100644 --- a/lib/portlayer/storage/nfs/volume_test.go +++ b/lib/portlayer/storage/volume/nfs/store_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/vmware/vic/lib/portlayer/storage/nfs" "github.com/vmware/vic/pkg/trace" ) @@ -38,7 +39,7 @@ type MockMount struct { Path string } -func (m MockMount) Mount(op trace.Operation) (Target, error) { +func (m MockMount) Mount(op trace.Operation) (nfs.Target, error) { return NewMocktarget(m.Path), nil } @@ -102,8 +103,8 @@ func (v MockTarget) Lookup(pth string) (os.FileInfo, []byte, error) { } var ( - expected Target - mnt MountServer + expected nfs.Target + mnt nfs.MountServer ) func TestMain(m *testing.M) { @@ -118,7 +119,7 @@ func TestMain(m *testing.M) { logrus.Infof("testing nfs against %#v", u) - mnt = NewMount(u, "hasselhoff", 1001, 10001) + mnt = nfs.NewMount(u, "hasselhoff", 1001, 10001) expected, err = mnt.Mount(trace.NewOperation(context.TODO(), "mount")) if err != nil { logrus.Errorf("error mounting %s: %s", u.String(), err.Error()) diff --git a/lib/portlayer/storage/volume.go b/lib/portlayer/storage/volume/volume.go similarity index 95% rename from lib/portlayer/storage/volume.go rename to lib/portlayer/storage/volume/volume.go index 837e8a97e4..cbf0e35fb4 100644 --- a/lib/portlayer/storage/volume.go +++ b/lib/portlayer/storage/volume/volume.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package volume import ( "crypto/md5" // #nosec: Use of weak cryptographic primitive @@ -23,6 +23,7 @@ import ( "strings" "github.com/vmware/vic/lib/config/executor" + "github.com/vmware/vic/lib/portlayer/storage" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" ) @@ -47,9 +48,9 @@ type VolumeStorer interface { VolumesList(op trace.Operation) ([]*Volume, error) // The interfaces necessary for Import and Export - Resolver - Importer - Exporter + storage.Resolver + storage.Importer + storage.Exporter } // Volume is the handle to identify a volume on the backing store. The URI diff --git a/lib/portlayer/storage/volume_test.go b/lib/portlayer/storage/volume/volume_test.go similarity index 94% rename from lib/portlayer/storage/volume_test.go rename to lib/portlayer/storage/volume/volume_test.go index dc75a6b2e2..44fe47f3af 100644 --- a/lib/portlayer/storage/volume_test.go +++ b/lib/portlayer/storage/volume/volume_test.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package volume import ( "net/url" diff --git a/lib/portlayer/storage/vsphere/export.go b/lib/portlayer/storage/volume/vsphere/export.go similarity index 64% rename from lib/portlayer/storage/vsphere/export.go rename to lib/portlayer/storage/volume/vsphere/export.go index 11fbafbc2f..4bc06856ba 100644 --- a/lib/portlayer/storage/vsphere/export.go +++ b/lib/portlayer/storage/volume/vsphere/export.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import ( "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/guest" "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" + "github.com/vmware/vic/lib/portlayer/storage/vsphere" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/disk" "github.com/vmware/vic/pkg/vsphere/vm" @@ -126,94 +128,9 @@ func (v *VolumeStore) newDataSource(op trace.Operation, url *url.URL) (storage.D func (v *VolumeStore) newOnlineDataSource(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSource, error) { op.Debugf("Constructing toolbox data source: %s.%s", owner.Reference(), id) - return &ToolboxDataSource{ + return &vsphere.ToolboxDataSource{ VM: owner, - ID: storage.Label(id), + ID: volume.Label(id), Clean: func() { return }, }, nil } - -// Export reads the delta between child and parent image layers, returning -// the difference as a tar archive. -// -// id - must inherit from ancestor if ancestor is specified -// ancestor - the layer up the chain against which to diff -// spec - describes filters on paths found in the data (include, exclude, rebase, strip) -// data - set to true to include file data in the tar archive, false to include headers only -func (i *ImageStore) Export(op trace.Operation, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { - l, err := i.NewDataSource(op, id) - if err != nil { - return nil, err - } - - if ancestor == "" { - return l.Export(op, spec, data) - } - - // for now we assume ancestor instead of entirely generic left/right - // this allows us to assume it's an image - r, err := i.NewDataSource(op, ancestor) - if err != nil { - op.Debugf("Unable to get datasource for ancestor: %s", err) - - l.Close() - return nil, err - } - - closers := func() error { - op.Debugf("Callback to io.Closer function for image delta export") - - l.Close() - r.Close() - - return nil - } - - ls := l.Source() - rs := r.Source() - - fl, lok := ls.(*os.File) - fr, rok := rs.(*os.File) - - if !lok || !rok { - go closers() - return nil, fmt.Errorf("mismatched datasource types: %T, %T", ls, rs) - } - - // if we want data, exclude the xattrs, otherwise assume diff - tar, err := archive.Diff(op, fl.Name(), fr.Name(), spec, data, !data) - if err != nil { - go closers() - return nil, err - } - - return &storage.ProxyReadCloser{ - ReadCloser: tar, - Closer: closers, - }, nil -} - -func (i *ImageStore) NewDataSource(op trace.Operation, id string) (storage.DataSource, error) { - url, err := i.URL(op, id) - if err != nil { - return nil, err - } - - return i.newDataSource(op, url) -} - -func (i *ImageStore) newDataSource(op trace.Operation, url *url.URL) (storage.DataSource, error) { - mountPath, cleanFunc, err := i.Mount(op, url, false) - if err != nil { - return nil, err - } - - f, err := os.Open(mountPath) - if err != nil { - cleanFunc() - return nil, err - } - - op.Debugf("Created mount data source for access to %s at %s", url, mountPath) - return storage.NewMountDataSource(op, f, cleanFunc), nil -} diff --git a/lib/portlayer/storage/vsphere/import.go b/lib/portlayer/storage/volume/vsphere/import.go similarity index 75% rename from lib/portlayer/storage/vsphere/import.go rename to lib/portlayer/storage/volume/vsphere/import.go index 48130be051..a2b79029d5 100644 --- a/lib/portlayer/storage/vsphere/import.go +++ b/lib/portlayer/storage/volume/vsphere/import.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import ( "github.com/vmware/vic/lib/archive" "github.com/vmware/vic/lib/guest" "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" + "github.com/vmware/vic/lib/portlayer/storage/vsphere" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/disk" "github.com/vmware/vic/pkg/vsphere/vm" @@ -111,49 +113,9 @@ func (v *VolumeStore) newDataSink(op trace.Operation, url *url.URL) (storage.Dat func (v *VolumeStore) newOnlineDataSink(op trace.Operation, owner *vm.VirtualMachine, id string) (storage.DataSink, error) { op.Debugf("Constructing toolbox data sink: %s.%s", owner.Reference(), id) - return &ToolboxDataSink{ + return &vsphere.ToolboxDataSink{ VM: owner, - ID: storage.Label(id), + ID: volume.Label(id), Clean: func() { return }, }, nil } - -func (i *ImageStore) Import(op trace.Operation, id string, spec *archive.FilterSpec, tarStream io.ReadCloser) error { - l, err := i.NewDataSink(op, id) - if err != nil { - return err - } - - return l.Import(op, spec, tarStream) -} - -// NewDataSink creates and returns an DataSource associated with image storage -func (i *ImageStore) NewDataSink(op trace.Operation, id string) (storage.DataSink, error) { - uri, err := i.URL(op, id) - if err != nil { - return nil, err - } - - // there is no online fail over path for images - // we should probably have a check in here as to whether the image is "sealed" and can no longer - // be modified. - return i.newDataSink(op, uri) -} - -func (i *ImageStore) newDataSink(op trace.Operation, url *url.URL) (storage.DataSink, error) { - mountPath, cleanFunc, err := i.Mount(op, url, true) - if err != nil { - return nil, err - } - - f, err := os.Open(mountPath) - if err != nil { - cleanFunc() - return nil, err - } - - return &storage.MountDataSink{ - Path: f, - Clean: cleanFunc, - }, nil -} diff --git a/lib/portlayer/storage/vsphere/vm.go b/lib/portlayer/storage/volume/vsphere/join.go similarity index 51% rename from lib/portlayer/storage/vsphere/vm.go rename to lib/portlayer/storage/volume/vsphere/join.go index 120ac67c7d..f5abaabafd 100644 --- a/lib/portlayer/storage/vsphere/vm.go +++ b/lib/portlayer/storage/volume/vsphere/join.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,61 +22,49 @@ import ( "github.com/vmware/vic/lib/config/executor" "github.com/vmware/vic/lib/constants" "github.com/vmware/vic/lib/portlayer/exec" - "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" "github.com/vmware/vic/pkg/trace" ) -func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *storage.Volume, mountPath string, diskOpts map[string]string) (*exec.Handle, error) { - defer trace.End(trace.Begin("vsphere.VolumeJoin")) +func VolumeJoin(op trace.Operation, handle *exec.Handle, volume *volume.Volume, mountPath string, diskOpts map[string]string) (*exec.Handle, error) { + defer trace.End(trace.Begin("vsphere.VolumeJoin", op)) if _, ok := handle.ExecConfig.Mounts[volume.ID]; ok { return nil, fmt.Errorf("Volume with ID %s is already in container %s's mountspec config", volume.ID, handle.ExecConfig.ID) } - //constuct MountSpec for the tether - mountSpec := createMountSpec(volume, mountPath, diskOpts) - //append a device addition spec change to the container config - diskDevice := createVolumeVirtualDisk(volume) - config := createDeviceConfigSpec(diskDevice) - handle.Spec.DeviceChange = append(handle.Spec.DeviceChange, config) - if handle.ExecConfig.Mounts == nil { handle.ExecConfig.Mounts = make(map[string]executor.MountSpec) } + + // construct MountSpec for the tether + mountSpec := createMountSpec(volume, mountPath, diskOpts) handle.ExecConfig.Mounts[volume.ID] = mountSpec + // append a device addition spec change to the container config + disk := handle.Guest.NewDisk() + configureVolumeVirtualDisk(disk, volume) + + handle.Spec.AddVirtualDevice(disk) + return handle, nil } -func createVolumeVirtualDisk(volume *storage.Volume) *types.VirtualDisk { +func configureVolumeVirtualDisk(disk *types.VirtualDisk, volume *volume.Volume) { + // the unit number hack may no longer be needed unitNumber := int32(-1) - diskDevice := &types.VirtualDisk{ - CapacityInKB: 0, - VirtualDevice: types.VirtualDevice{ - Key: -1, - ControllerKey: 100, //FIXME: This is hardcoded for now and should be located from the config spec in the future. - UnitNumber: &unitNumber, - Backing: &types.VirtualDiskFlatVer2BackingInfo{ - DiskMode: string(types.VirtualDiskModeIndependent_persistent), - VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ - FileName: volume.Device.DiskPath().Path, - }, - }, - }, - } - return diskDevice -} -func createDeviceConfigSpec(diskDevice *types.VirtualDisk) *types.VirtualDeviceConfigSpec { - config := &types.VirtualDeviceConfigSpec{ - Device: diskDevice, - Operation: types.VirtualDeviceConfigSpecOperationAdd, - FileOperation: "", //blank for existing disk + disk.CapacityInKB = 0 + disk.UnitNumber = &unitNumber + disk.Backing = &types.VirtualDiskFlatVer2BackingInfo{ + DiskMode: string(types.VirtualDiskModeIndependent_persistent), + VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ + FileName: volume.Device.DiskPath().Path, + }, } - return config } -func createMountSpec(volume *storage.Volume, mountPath string, diskOpts map[string]string) executor.MountSpec { +func createMountSpec(volume *volume.Volume, mountPath string, diskOpts map[string]string) executor.MountSpec { deviceMode := diskOpts[constants.Mode] newMountSpec := executor.MountSpec{ Source: url.URL{ diff --git a/lib/portlayer/storage/vsphere/volume.go b/lib/portlayer/storage/volume/vsphere/store.go similarity index 83% rename from lib/portlayer/storage/vsphere/volume.go rename to lib/portlayer/storage/volume/vsphere/store.go index bc1c73a0f0..c27b01045e 100644 --- a/lib/portlayer/storage/vsphere/volume.go +++ b/lib/portlayer/storage/volume/vsphere/store.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import ( "github.com/vmware/vic/lib/config/executor" "github.com/vmware/vic/lib/constants" "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" + "github.com/vmware/vic/lib/portlayer/storage/vsphere" "github.com/vmware/vic/lib/portlayer/util" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/datastore" @@ -32,6 +34,17 @@ import ( "github.com/vmware/vic/pkg/vsphere/session" ) +const ( + // TODO: this was shared with image store hence the disjoint naming. Should be updated + // but migration/upgrade implications are unclear + metaDataDir = "imageMetadata" +) + +var ( + // Set to false for unit tests + DetachAll = true +) + // VolumeStore caches Volume references to volumes in the system. type VolumeStore struct { disk.Vmdk @@ -89,7 +102,7 @@ func (v *VolumeStore) volDiskDSPath(ID string) *object.DatastorePath { } } -func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*storage.Volume, error) { +func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL, capacityKB uint64, info map[string][]byte) (*volume.Volume, error) { // Create the volume directory in the store. if _, err := v.Mkdir(op, false, v.volDirPath(ID)); err != nil { @@ -106,7 +119,7 @@ func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL return nil, err } defer v.Detach(op, vmdisk.VirtualDiskConfig) - vol, err := storage.NewVolume(store, ID, info, vmdisk, executor.CopyNew) + vol, err := volume.NewVolume(store, ID, info, vmdisk, executor.CopyNew) if err != nil { return nil, err } @@ -132,7 +145,7 @@ func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL // Persist the metadata metaDataDir := v.volMetadataDirPath(ID) - if err = writeMetadata(op, v.Helper, metaDataDir, info); err != nil { + if err = vsphere.WriteMetadata(op, v.Helper, metaDataDir, info); err != nil { return nil, err } @@ -140,7 +153,7 @@ func (v *VolumeStore) VolumeCreate(op trace.Operation, ID string, store *url.URL return vol, nil } -func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *storage.Volume) error { +func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *volume.Volume) error { volDir := v.volDirPath(vol.ID) op.Infof("VolumeStore: Deleting %s", volDir) @@ -151,13 +164,13 @@ func (v *VolumeStore) VolumeDestroy(op trace.Operation, vol *storage.Volume) err return nil } -func (v *VolumeStore) VolumeGet(op trace.Operation, ID string) (*storage.Volume, error) { +func (v *VolumeStore) VolumeGet(op trace.Operation, ID string) (*volume.Volume, error) { // We can't get the volume directly without looking up what datastore it's on. return nil, fmt.Errorf("not supported: use VolumesList") } -func (v *VolumeStore) VolumesList(op trace.Operation) ([]*storage.Volume, error) { - volumes := []*storage.Volume{} +func (v *VolumeStore) VolumesList(op trace.Operation) ([]*volume.Volume, error) { + volumes := []*volume.Volume{} res, err := v.Ls(op, constants.VolumesDir) if err != nil { @@ -177,12 +190,12 @@ func (v *VolumeStore) VolumesList(op trace.Operation) ([]*storage.Volume, error) } metaDataDir := v.volMetadataDirPath(ID) - meta, err := getMetadata(op, v.Helper, metaDataDir) + meta, err := vsphere.GetMetadata(op, v.Helper, metaDataDir) if err != nil { return nil, err } - vol, err := storage.NewVolume(v.SelfLink, ID, meta, dev, executor.CopyNew) + vol, err := volume.NewVolume(v.SelfLink, ID, meta, dev, executor.CopyNew) if err != nil { return nil, err } diff --git a/lib/portlayer/storage/vsphere/volume_test.go b/lib/portlayer/storage/volume/vsphere/store_test.go similarity index 87% rename from lib/portlayer/storage/vsphere/volume_test.go rename to lib/portlayer/storage/volume/vsphere/store_test.go index f1d5d1737e..6c2e651019 100644 --- a/lib/portlayer/storage/vsphere/volume_test.go +++ b/lib/portlayer/storage/volume/vsphere/store_test.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/vmware/govmomi/object" - portlayer "github.com/vmware/vic/lib/portlayer/storage" + "github.com/vmware/vic/lib/portlayer/storage/volume" + "github.com/vmware/vic/lib/portlayer/storage/volume/cache" "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/datastore" "github.com/vmware/vic/pkg/vsphere/datastore/test" @@ -61,19 +62,19 @@ func TestVolumeCreateListAndRestart(t *testing.T) { }() // Create the cache - cache := portlayer.NewVolumeLookupCache(op) - if !assert.NotNil(t, cache) { + firstCache := cache.NewVolumeLookupCache(op) + if !assert.NotNil(t, firstCache) { return } // add the vs to the cache and assert the url matches - storeURL, err := cache.AddStore(op, "testStoreName", vsVolumeStore) + storeURL, err := firstCache.AddStore(op, "testStoreName", vsVolumeStore) if !assert.NoError(t, err) || !assert.Equal(t, vsVolumeStore.SelfLink, storeURL) { return } // test we can list it - m, err := cache.VolumeStoresList(op) + m, err := firstCache.VolumeStoresList(op) if !assert.NoError(t, err) || !assert.Len(t, m, 1) || !assert.Equal(t, m[0], "testStoreName") { return } @@ -82,7 +83,7 @@ func TestVolumeCreateListAndRestart(t *testing.T) { numVols := 5 wg := &sync.WaitGroup{} wg.Add(numVols) - volumes := make(map[string]*portlayer.Volume) + volumes := make(map[string]*volume.Volume) for i := 0; i < numVols; i++ { go func(idx int) { defer wg.Done() @@ -96,7 +97,7 @@ func TestVolumeCreateListAndRestart(t *testing.T) { info[ID] = []byte(ID) } - outVol, err := cache.VolumeCreate(op, ID, storeURL, 10240, info) + outVol, err := firstCache.VolumeCreate(op, ID, storeURL, 10240, info) if !assert.NoError(t, err) || !assert.NotNil(t, outVol) { return } @@ -127,7 +128,7 @@ func TestVolumeCreateListAndRestart(t *testing.T) { return } - secondCache := portlayer.NewVolumeLookupCache(op) + secondCache := cache.NewVolumeLookupCache(op) if !assert.NotNil(t, secondCache) { return } diff --git a/lib/portlayer/storage/vsphere/metadata.go b/lib/portlayer/storage/vsphere/metadata.go index 5188b3528d..0752da49d1 100644 --- a/lib/portlayer/storage/vsphere/metadata.go +++ b/lib/portlayer/storage/vsphere/metadata.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import ( // Each blob in the metadata map is written to a file with the corresponding // name. Likewise, when we read it back (on restart) we populate the map // accordingly. -func writeMetadata(op trace.Operation, ds *datastore.Helper, dir string, meta map[string][]byte) error { +func WriteMetadata(op trace.Operation, ds *datastore.Helper, dir string, meta map[string][]byte) error { // XXX this should be done via disklib so this meta follows the disk in // case of motion. @@ -51,7 +51,7 @@ func writeMetadata(op trace.Operation, ds *datastore.Helper, dir string, meta ma } // Read the metadata from the given dir -func getMetadata(op trace.Operation, ds *datastore.Helper, dir string) (map[string][]byte, error) { +func GetMetadata(op trace.Operation, ds *datastore.Helper, dir string) (map[string][]byte, error) { res, err := ds.Ls(op, dir) if err != nil { diff --git a/lib/portlayer/storage/vsphere/toolbox_common.go b/lib/portlayer/storage/vsphere/toolbox_common.go index 802c539a3f..93e1f33dbc 100644 --- a/lib/portlayer/storage/vsphere/toolbox_common.go +++ b/lib/portlayer/storage/vsphere/toolbox_common.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -60,9 +60,9 @@ func BuildArchiveURL(op trace.Operation, disklabel, target string, fs *archive.F target = path.Join("/archive:/", target) // if diskLabel is longer than 16 characters, then the function was passed a containerID - // use containerfs as the diskLabel + // so use the container rootfs if len(disklabel) > 16 { - disklabel = "containerfs" + disklabel = shared.ScratchDiskLabel } // note that the query parameters a SkipX for recurse and data so values are inverted diff --git a/lib/spec/disk.go b/lib/spec/disk.go index 03ee619d21..5a1d52dd9d 100644 --- a/lib/spec/disk.go +++ b/lib/spec/disk.go @@ -1,4 +1,4 @@ -// Copyright 2016 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,17 +15,10 @@ package spec import ( - "fmt" - "github.com/vmware/govmomi/vim25/types" "github.com/vmware/vic/pkg/trace" ) -const ( - //from portlayer/vsphere/storage/store.go - defaultCapacityInKB = 8 * 1024 * 1024 -) - // NewVirtualDisk returns a new disk attached to the controller func NewVirtualDisk(controller types.BaseVirtualController) *types.VirtualDisk { @@ -58,37 +51,6 @@ func (s *VirtualMachineConfigSpec) AddVirtualDisk(device *types.VirtualDisk) *Vi defer trace.End(trace.Begin(s.ID())) device.GetVirtualDevice().Key = s.generateNextKey() - - device.CapacityInKB = defaultCapacityInKB - - moref := s.Datastore.Reference() - - device.GetVirtualDevice().Backing = &types.VirtualDiskFlatVer2BackingInfo{ - DiskMode: string(types.VirtualDiskModePersistent), - ThinProvisioned: types.NewBool(true), - - VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ - FileName: s.Datastore.Path(fmt.Sprintf("%s/%s.vmdk", s.ID(), s.ID())), - Datastore: &moref, - }, - } - - // Add the parent if we set ParentImageID - backing := device.GetVirtualDevice().Backing.(*types.VirtualDiskFlatVer2BackingInfo) - if s.ParentImageID() != "" { - backing.Parent = &types.VirtualDiskFlatVer2BackingInfo{ - VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{ - // XXX This needs to come from a storage helper in the future - // and should not be computed here like this. - - FileName: s.Datastore.Path(fmt.Sprintf("%s/VIC/%s/images/%s/%[3]s.vmdk", - s.ImageStorePath().Path, - s.ImageStoreName(), - s.ParentImageID())), - }, - } - } - return s.AddAndCreateVirtualDevice(device) } diff --git a/lib/spec/spec.go b/lib/spec/spec.go index ad8aea246c..9b901dd7a1 100644 --- a/lib/spec/spec.go +++ b/lib/spec/spec.go @@ -34,9 +34,6 @@ type VirtualMachineConfigSpecConfig struct { BiosUUID string VMFullName string - // ParentImageID of the VM - ParentImageID string - // Name of the VM Name string @@ -195,13 +192,6 @@ func (s *VirtualMachineConfigSpec) ID() string { return s.config.ID } -// ParentImageID returns the ID of the image that VM is based on -func (s *VirtualMachineConfigSpec) ParentImageID() string { - defer trace.End(trace.Begin(s.config.ParentImageID)) - - return s.config.ParentImageID -} - // BootMediaPath returns the image path func (s *VirtualMachineConfigSpec) BootMediaPath() string { defer trace.End(trace.Begin(s.config.ID)) @@ -216,20 +206,6 @@ func (s *VirtualMachineConfigSpec) VMPathName() string { return s.config.VMPathName } -// ImageStoreName returns the image store name -func (s *VirtualMachineConfigSpec) ImageStoreName() string { - defer trace.End(trace.Begin(s.config.ID)) - - return s.config.ImageStoreName -} - -// ImageStorePath returns the image store url -func (s *VirtualMachineConfigSpec) ImageStorePath() *url.URL { - defer trace.End(trace.Begin(s.config.ID)) - - return s.config.ImageStorePath -} - func (s *VirtualMachineConfigSpec) generateNextKey() int32 { s.key -= 10 diff --git a/lib/tether/shared/constants.go b/lib/tether/shared/constants.go index c544955ee9..52d47c6acf 100644 --- a/lib/tether/shared/constants.go +++ b/lib/tether/shared/constants.go @@ -1,4 +1,4 @@ -// Copyright 2017 VMware, Inc. All Rights Reserved. +// Copyright 2017-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,8 +29,10 @@ const ( GuestActionKill = "kill" GuestActionGroupKill = "group-kill" - GuestShutdownTimeout = 20 * time.Second - GuestRebootTimeout = 20 * time.Second - + GuestShutdownTimeout = 20 * time.Second + GuestRebootTimeout = 20 * time.Second WaitForSessionExitTimeout = 20 * time.Second + + // scratchDiskLabel labels the root image for the disk chain + ScratchDiskLabel = "containerfs" ) diff --git a/lib/tether/toolbox.go b/lib/tether/toolbox.go index f9459c6900..6b2d37b8b4 100644 --- a/lib/tether/toolbox.go +++ b/lib/tether/toolbox.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -435,7 +435,7 @@ func toolboxOverrideArchiveWrite(system System, u *url.URL, tw *tar.Writer) erro func mountDiskLabel(op trace.Operation, system System, label string) (string, error) { // We know the vmdk will always be attached at '/' - if label == "containerfs" { + if label == shared.ScratchDiskLabel { return "/", nil } diff --git a/pkg/fs/ext4.go b/pkg/fs/ext4.go index dd33c508b0..e48dde5127 100644 --- a/pkg/fs/ext4.go +++ b/pkg/fs/ext4.go @@ -1,4 +1,4 @@ -// Copyright 2016-2017 VMware, Inc. All Rights Reserved. +// Copyright 2016-2018 VMware, Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,9 @@ import ( "github.com/vmware/vic/pkg/trace" ) +// MaxLabelLength is the maximum allowed length of a label for this filesystem +const MaxLabelLength = 15 + // Ext4 satisfies the Filesystem interface type Ext4 struct{} @@ -72,8 +75,13 @@ func (e *Ext4) Unmount(op trace.Operation, path string) error { func (e *Ext4) SetLabel(op trace.Operation, devPath, labelName string) error { defer trace.End(trace.Begin(devPath)) + // Warn if truncating label + if len(labelName) > MaxLabelLength { + op.Debugf("Label truncated to %s", labelName[:MaxLabelLength]) + } + // #nosec: Subprocess launching with variable - cmd := exec.Command("/sbin/e2label", devPath, labelName) + cmd := exec.Command("/sbin/e2label", devPath, labelName[:MaxLabelLength]) if output, err := cmd.CombinedOutput(); err != nil { op.Errorf("failed to set label %s: %s", devPath, err) op.Errorf(string(output)) diff --git a/tests/concurrent/concurrent.go b/tests/concurrent/concurrent.go index f1ff7ee6d2..92eb22dd93 100644 --- a/tests/concurrent/concurrent.go +++ b/tests/concurrent/concurrent.go @@ -256,7 +256,6 @@ func main() { Name: name, VMFullName: name, - ParentImageID: parent, BootMediaPath: fmt.Sprintf("[%s] %s/%s", config.Datastore, config.VCH, ISO), VMPathName: fmt.Sprintf("[%s]", config.Datastore),