From 6b4310fc701acdcd5de21a8a4ad31db5538c3983 Mon Sep 17 00:00:00 2001 From: wjun Date: Wed, 26 Dec 2018 18:59:24 +0800 Subject: [PATCH] Add image layer storage usage (#8430) The current implementation of image storage usage always use vCenter datastore file browser to get all layers of images and sum the related vmdk file sizes. In some vsphere environments, the browser response is very slow with 30+ seconds with only 10 layers. This fix changes to accumulate storage usage when a layer is added or deleted from datastore, and keep the latest usage value into cache. --- .../restapi/handlers/storage_handlers.go | 16 +++-- lib/portlayer/storage/image/cache/cache.go | 62 ++++++++++++++++++- lib/portlayer/storage/image/image.go | 3 + lib/portlayer/storage/image/mock/store.go | 4 ++ lib/portlayer/storage/image/vsphere/store.go | 6 +- 5 files changed, 83 insertions(+), 8 deletions(-) diff --git a/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go b/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go index a7247f4faf..f3c20f1b0f 100644 --- a/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go +++ b/lib/apiservers/portlayer/restapi/handlers/storage_handlers.go @@ -738,13 +738,19 @@ func (h *StorageHandlersImpl) GetImageStorageUsage(params storage.GetImageStorag op := trace.NewOperationFromID(context.Background(), params.OpID, "GetImageStorageUsage(%s)", params.StoreName) defer trace.End(trace.Begin("GetImageStorageUsage", op)) - result, err := h.imageCache.DataStore.GetImageStorageUsage(op, params.StoreName) - if err != nil { - op.Errorf("Error gettting image storage usage: %s", err) - return storage.NewGetImageStorageUsageDefault(500) + cachedResult := h.imageCache.ImageStorageUsage() + // If cache is not set, read it from datastore + if cachedResult < 0 { + result, err := h.imageCache.DataStore.GetImageStorageUsage(op, params.StoreName) + if err != nil { + op.Errorf("Error getting image storage usage from datastore: %s", err) + return storage.NewGetImageStorageUsageDefault(500) + } + h.imageCache.SetImageStorageUsage(result) + cachedResult = result } - return storage.NewGetImageStorageUsageOK().WithPayload(result) + return storage.NewGetImageStorageUsageOK().WithPayload(cachedResult) } //utility functions diff --git a/lib/portlayer/storage/image/cache/cache.go b/lib/portlayer/storage/image/cache/cache.go index 9e8b3f407c..b9d506fea1 100644 --- a/lib/portlayer/storage/image/cache/cache.go +++ b/lib/portlayer/storage/image/cache/cache.go @@ -50,12 +50,16 @@ type NameLookupCache struct { // The image store implementation. This mutates the actual disk images. DataStore image.ImageStorer + + // Track image layers storage usage + imageStorageUsage int64 } func NewLookupCache(ds image.ImageStorer) *NameLookupCache { return &NameLookupCache{ - DataStore: ds, - storeCache: make(map[url.URL]*index.Index), + DataStore: ds, + storeCache: make(map[url.URL]*index.Index), + imageStorageUsage: -1, } } @@ -265,6 +269,10 @@ func (c *NameLookupCache) WriteImage(op trace.Operation, parent *image.Image, ID return nil, err } + if err = c.UpdateStorageUsage(op, i.Store, i.ID); err != nil { + return nil, err + } + c.storeCacheLock.Lock() indx := c.storeCache[*parent.Store] c.storeCacheLock.Unlock() @@ -277,6 +285,25 @@ func (c *NameLookupCache) WriteImage(op trace.Operation, parent *image.Image, ID return i, nil } +func (c *NameLookupCache) UpdateStorageUsage(op trace.Operation, store *url.URL, id string) error { + storeName, err := util.ImageStoreName(store) + if err != nil { + return err + } + size, err := c.DataStore.GetImageLayerStorageUsage(op, storeName, id) + if err != nil { + op.Errorf("Get image layer size of %s failed with: %s", id, err) + // size is 0 and continue + } + + if size > 0 { + c.storeCacheLock.Lock() + c.imageStorageUsage += size + c.storeCacheLock.Unlock() + } + return nil +} + func (c *NameLookupCache) Export(op trace.Operation, store *url.URL, id, ancestor string, spec *archive.FilterSpec, data bool) (io.ReadCloser, error) { return c.DataStore.Export(op, id, ancestor, spec, data) } @@ -424,12 +451,31 @@ func (c *NameLookupCache) DeleteImage(op trace.Operation, img *image.Image) (*im return nil, &image.ErrImageInUse{Msg: img.Self() + " in use by child images"} } + // Get image layer storage usage + storeName, err := util.ImageStoreName(img.Store) + if err != nil { + return nil, err + } + + size, err := c.DataStore.GetImageLayerStorageUsage(op, storeName, img.ID) + if err != nil { + op.Errorf("Get image layer size of %s failed with: %s", img.ID, err) + // size is 0 and continue + } + // The datastore will tell us if the image is attached if _, err = c.DataStore.DeleteImage(op, img); err != nil { op.Errorf("%s", err) return nil, err } + // update image storage usage + if size > 0 { + c.storeCacheLock.Lock() + c.imageStorageUsage -= size + c.storeCacheLock.Unlock() + } + // Remove the image from the cache if _, err = indx.Delete(img.Self()); err != nil { op.Errorf("%s", err) @@ -507,3 +553,15 @@ func (c *NameLookupCache) DeleteBranch(op trace.Operation, img *image.Image, kee return deletedImages, nil } + +func (c *NameLookupCache) ImageStorageUsage() int64 { + c.storeCacheLock.Lock() + defer c.storeCacheLock.Unlock() + return c.imageStorageUsage +} + +func (c *NameLookupCache) SetImageStorageUsage(value int64) { + c.storeCacheLock.Lock() + defer c.storeCacheLock.Unlock() + c.imageStorageUsage = value +} diff --git a/lib/portlayer/storage/image/image.go b/lib/portlayer/storage/image/image.go index 67c362d2ec..ec306f7977 100644 --- a/lib/portlayer/storage/image/image.go +++ b/lib/portlayer/storage/image/image.go @@ -79,6 +79,9 @@ type ImageStorer interface { // GetImageStorageUsage gets the image storage usage from the image store. GetImageStorageUsage(op trace.Operation, storeName string) (int64, error) + // GetImageLayerStorageUsage gets the image layer storage usage from the image store. + GetImageLayerStorageUsage(op trace.Operation, storeName, ID string) (int64, error) + storage.Resolver storage.Importer storage.Exporter diff --git a/lib/portlayer/storage/image/mock/store.go b/lib/portlayer/storage/image/mock/store.go index 9c143f25f4..c13d743dde 100644 --- a/lib/portlayer/storage/image/mock/store.go +++ b/lib/portlayer/storage/image/mock/store.go @@ -190,3 +190,7 @@ func (c *MockDataStore) DeleteImage(op trace.Operation, image *image.Image) (*im func (c *MockDataStore) GetImageStorageUsage(op trace.Operation, storeName string) (int64, error) { return 0, nil } + +func (c *MockDataStore) GetImageLayerStorageUsage(op trace.Operation, storeName, ID string) (int64, error) { + return 0, nil +} diff --git a/lib/portlayer/storage/image/vsphere/store.go b/lib/portlayer/storage/image/vsphere/store.go index cb96d20365..1a7788cbbe 100644 --- a/lib/portlayer/storage/image/vsphere/store.go +++ b/lib/portlayer/storage/image/vsphere/store.go @@ -122,7 +122,7 @@ func (v *ImageStore) imageStorePath(storeName string) string { // Returns the path to the image relative to the given // store. The dir structure for an image in the datastore is -// `/VIC/imageStoreName (currently the vch uuid)/imageName/imageName.vmkd` +// `/VIC/imageStoreName (currently the vch uuid)/imageName/imageName.vmdk` func (v *ImageStore) imageDirPath(storeName, imageName string) string { return path.Join(v.imageStorePath(storeName), imageName) } @@ -594,6 +594,10 @@ func (v *ImageStore) GetImageStorageUsage(op trace.Operation, storeName string) return v.Helper.GetFilesSize(op, v.imageStorePath(storeName), true, "*.vmdk") } +func (v *ImageStore) GetImageLayerStorageUsage(op trace.Operation, storeName, ID string) (int64, error) { + return v.Helper.GetFilesSize(op, v.imageDirPath(storeName, ID), true, "*.vmdk") +} + // 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.