¡@

Home 

OpenStack Study: imagecache.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright (c) 2014 VMware, Inc.

#

# 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.

"""

Image cache class

Images that are stored in the cache folder will be stored in a folder whose

name is the image ID. In the event that an image is discovered to be no longer

used then a timestamp will be added to the image folder.

The timestamp will be a folder - this is due to the fact that we can use the

VMware API's for creating and deleting of folders (it really simplifies

things). The timestamp will contain the time, on the compute node, when the

image was first seen to be unused.

At each aging iteration we check if the image can be aged.

This is done by comparing the current nova compute time to the time embedded

in the timestamp. If the time exceeds the configured aging time then

the parent folder, that is the image ID folder, will be deleted.

That effectivly ages the cached image.

If an image is used then the timestamps will be deleted.

When accessing a timestamp we make use of locking. This ensure that aging

will not delete an image during the spawn operiation. When spawning

the timestamp folder will be locked and the timestamps will be purged.

This will ensure that a image is not deleted during the spawn.

"""

from oslo.config import cfg

from nova.openstack.common.gettextutils import _

from nova.openstack.common import lockutils

from nova.openstack.common import log as logging

from nova.openstack.common import timeutils

from nova.virt import imagecache

from nova.virt.vmwareapi import ds_util

from nova.virt.vmwareapi import error_util

from nova.virt.vmwareapi import vim_util

LOG = logging.getLogger(__name__)

CONF = cfg.CONF

CONF.import_opt('remove_unused_original_minimum_age_seconds',

'nova.virt.imagecache')

TIMESTAMP_PREFIX = 'ts-'

TIMESTAMP_FORMAT = '%Y-%m-%d-%H-%M-%S'

**** CubicPower OpenStack Study ****

class ImageCacheManager(imagecache.ImageCacheManager):

**** CubicPower OpenStack Study ****

    def __init__(self, session, base_folder):

        super(ImageCacheManager, self).__init__()

        self._session = session

        self._base_folder = base_folder

        self._ds_browser = {}

**** CubicPower OpenStack Study ****

    def _folder_delete(self, path, dc_ref):

        try:

            ds_util.file_delete(self._session, path, dc_ref)

        except (error_util.CannotDeleteFileException,

                error_util.FileFaultException,

                error_util.FileLockedException) as e:

            # There may be more than one process or thread that tries

            # to delete the file.

            LOG.warning(_("Unable to delete %(file)s. Exception: %(ex)s"),

                        {'file': path, 'ex': e})

        except error_util.FileNotFoundException:

            LOG.debug(_("File not found: %s"), path)

**** CubicPower OpenStack Study ****

    def timestamp_folder_get(self, ds_path, image_id):

        """Returns the timestamp folder."""

        return '%s/%s' % (ds_path, image_id)

**** CubicPower OpenStack Study ****

    def timestamp_cleanup(self, dc_ref, ds_browser,

                          ds_ref, ds_name, ds_path):

        ts = self._get_timestamp(ds_browser, ds_path)

        if ts:

            ts_path = '%s/%s' % (ds_path, ts)

            LOG.debug(_("Timestamp path %s exists. Deleting!"), ts_path)

            # Image is used - no longer need timestamp folder

            self._folder_delete(ts_path, dc_ref)

**** CubicPower OpenStack Study ****

    def _get_timestamp(self, ds_browser, ds_path):

        files = ds_util.get_sub_folders(self._session, ds_browser, ds_path)

        if files:

            for file in files:

                if file.startswith(TIMESTAMP_PREFIX):

                    return file

**** CubicPower OpenStack Study ****

    def _get_timestamp_filename(self):

        return '%s%s' % (TIMESTAMP_PREFIX,

                         timeutils.strtime(fmt=TIMESTAMP_FORMAT))

**** CubicPower OpenStack Study ****

    def _get_datetime_from_filename(self, timestamp_filename):

        ts = timestamp_filename.lstrip(TIMESTAMP_PREFIX)

        return timeutils.parse_strtime(ts, fmt=TIMESTAMP_FORMAT)

**** CubicPower OpenStack Study ****

    def _get_ds_browser(self, ds_ref):

        ds_browser = self._ds_browser.get(ds_ref)

        if not ds_browser:

            ds_browser = vim_util.get_dynamic_property(

                    self._session._get_vim(), ds_ref,

                    "Datastore", "browser")

            self._ds_browser[ds_ref] = ds_browser

        return ds_browser

**** CubicPower OpenStack Study ****

    def _list_datastore_images(self, ds_path, datastore):

        """Return a list of the images present in _base.

        This method returns a dictionary with the following keys:

            - unexplained_images

            - originals

        """

        ds_browser = self._get_ds_browser(datastore['ref'])

        originals = ds_util.get_sub_folders(self._session, ds_browser,

                                            ds_path)

        return {'unexplained_images': [],

                'originals': originals}

**** CubicPower OpenStack Study ****

    def _age_cached_images(self, context, datastore, dc_info,

                           ds_path):

        """Ages cached images."""

        age_seconds = CONF.remove_unused_original_minimum_age_seconds

        unused_images = self.originals - self.used_images

        ds_browser = self._get_ds_browser(datastore['ref'])

        for image in unused_images:

            path = self.timestamp_folder_get(ds_path, image)

            # Lock to ensure that the spawn will not try and access a image

            # that is currently being deleted on the datastore.

            with lockutils.lock(path, lock_file_prefix='nova-vmware-ts',

                                external=True):

                ts = self._get_timestamp(ds_browser, path)

                if not ts:

                    ts_path = '%s/%s' % (path,

                                         self._get_timestamp_filename())

                    try:

                        ds_util.mkdir(self._session, ts_path, dc_info.ref)

                    except error_util.FileAlreadyExistsException:

                        LOG.debug(_("Timestamp already exists."))

                    LOG.info(_("Image %s is no longer used by this node. "

                               "Pending deletion!"), image)

                else:

                    dt = self._get_datetime_from_filename(ts)

                    if timeutils.is_older_than(dt, age_seconds):

                        LOG.info(_("Image %s is no longer used. "

                                   "Deleting!"), path)

                        # Image has aged - delete the image ID folder

                        self._folder_delete(path, dc_info.ref)

        # If the image is used and the timestamp file exists then we delete

        # the timestamp.

        for image in self.used_images:

            path = self.timestamp_folder_get(ds_path, image)

            with lockutils.lock(path, lock_file_prefix='nova-vmware-ts',

                                external=True):

                self.timestamp_cleanup(dc_info.ref, ds_browser,

                                       datastore['ref'], datastore['name'],

                                       path)

**** CubicPower OpenStack Study ****

    def update(self, context, instances, datastores_info):

        """The cache manager entry point.

        This will invoke the cache manager. This will update the cache

        according to the defined cache management scheme. The information

        populated in the cached stats will be used for the cache management.

        """

        # read running instances data

        running = self._list_running_instances(context, instances)

        self.used_images = set(running['used_images'].keys())

        # perform the aging and image verification per datastore

        for (datastore, dc_info) in datastores_info:

            ds_path = ds_util.build_datastore_path(datastore['name'],

                                                   self._base_folder)

            images = self._list_datastore_images(ds_path, datastore)

            self.originals = images['originals']

            self._age_cached_images(context, datastore, dc_info, ds_path)