Coverage for nova/virt/vmwareapi/imagecache.py: 0%
93 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright (c) 2014 VMware, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15"""
16Image cache class
18Images that are stored in the cache folder will be stored in a folder whose
19name is the image ID. In the event that an image is discovered to be no longer
20used then a timestamp will be added to the image folder.
21The timestamp will be a folder - this is due to the fact that we can use the
22VMware API's for creating and deleting of folders (it really simplifies
23things). The timestamp will contain the time, on the compute node, when the
24image was first seen to be unused.
25At each aging iteration we check if the image can be aged.
26This is done by comparing the current nova compute time to the time embedded
27in the timestamp. If the time exceeds the configured aging time then
28the parent folder, that is the image ID folder, will be deleted.
29That effectively ages the cached image.
30If an image is used then the timestamps will be deleted.
32When accessing a timestamp we make use of locking. This ensure that aging
33will not delete an image during the spawn operation. When spawning
34the timestamp folder will be locked and the timestamps will be purged.
35This will ensure that an image is not deleted during the spawn.
36"""
38from oslo_concurrency import lockutils
39from oslo_config import cfg
40from oslo_log import log as logging
41from oslo_utils import timeutils
42from oslo_vmware import exceptions as vexc
43from oslo_vmware import vim_util as vutil
45from nova.virt import imagecache
46from nova.virt.vmwareapi import ds_util
48LOG = logging.getLogger(__name__)
50CONF = cfg.CONF
52TIMESTAMP_PREFIX = 'ts-'
53TIMESTAMP_FORMAT = '%Y-%m-%d-%H-%M-%S'
56class ImageCacheManager(imagecache.ImageCacheManager):
57 def __init__(self, session, base_folder):
58 super(ImageCacheManager, self).__init__()
59 self._session = session
60 self._base_folder = base_folder
61 self._ds_browser = {}
63 def _folder_delete(self, ds_path, dc_ref):
64 try:
65 ds_util.file_delete(self._session, ds_path, dc_ref)
66 except (vexc.CannotDeleteFileException,
67 vexc.FileFaultException,
68 vexc.FileLockedException) as e:
69 # There may be more than one process or thread that tries
70 # to delete the file.
71 LOG.warning("Unable to delete %(file)s. Exception: %(ex)s",
72 {'file': ds_path, 'ex': e})
73 except vexc.FileNotFoundException:
74 LOG.debug("File not found: %s", ds_path)
76 def enlist_image(self, image_id, datastore, dc_ref):
77 ds_browser = self._get_ds_browser(datastore.ref)
78 cache_root_folder = datastore.build_path(self._base_folder)
80 # Check if the timestamp file exists - if so then delete it. This
81 # will ensure that the aging will not delete a cache image if it
82 # is going to be used now.
83 path = self.timestamp_folder_get(cache_root_folder, image_id)
85 # Lock to ensure that the spawn will not try and access an image
86 # that is currently being deleted on the datastore.
87 with lockutils.lock(str(path), lock_file_prefix='nova-vmware-ts',
88 external=True):
89 self.timestamp_cleanup(dc_ref, ds_browser, path)
91 def timestamp_folder_get(self, ds_path, image_id):
92 """Returns the timestamp folder."""
93 return ds_path.join(image_id)
95 def timestamp_cleanup(self, dc_ref, ds_browser, ds_path):
96 ts = self._get_timestamp(ds_browser, ds_path)
97 if ts:
98 ts_path = ds_path.join(ts)
99 LOG.debug("Timestamp path %s exists. Deleting!", ts_path)
100 # Image is used - no longer need timestamp folder
101 self._folder_delete(ts_path, dc_ref)
103 def _get_timestamp(self, ds_browser, ds_path):
104 files = ds_util.get_sub_folders(self._session, ds_browser, ds_path)
105 if files:
106 for file in files:
107 if file.startswith(TIMESTAMP_PREFIX):
108 return file
110 def _get_timestamp_filename(self):
111 return '%s%s' % (TIMESTAMP_PREFIX,
112 timeutils.utcnow().strftime(TIMESTAMP_FORMAT))
114 def _get_datetime_from_filename(self, timestamp_filename):
115 ts = timestamp_filename.lstrip(TIMESTAMP_PREFIX)
116 return timeutils.parse_strtime(ts, fmt=TIMESTAMP_FORMAT)
118 def _get_ds_browser(self, ds_ref):
119 ds_browser = self._ds_browser.get(vutil.get_moref_value(ds_ref))
120 if not ds_browser:
121 ds_browser = vutil.get_object_property(self._session.vim,
122 ds_ref,
123 "browser")
124 self._ds_browser[vutil.get_moref_value(ds_ref)] = ds_browser
125 return ds_browser
127 def _list_datastore_images(self, ds_path, datastore):
128 """Return a list of the images present in _base.
130 This method returns a dictionary with the following keys:
131 - unexplained_images
132 - originals
133 """
134 ds_browser = self._get_ds_browser(datastore.ref)
135 originals = ds_util.get_sub_folders(self._session, ds_browser,
136 ds_path)
137 return {'unexplained_images': [],
138 'originals': originals}
140 def _age_cached_images(self, context, datastore, dc_info,
141 ds_path):
142 """Ages cached images."""
143 age_seconds = (
144 CONF.image_cache.remove_unused_original_minimum_age_seconds)
145 unused_images = self.originals - self.used_images
146 ds_browser = self._get_ds_browser(datastore.ref)
147 for image in unused_images:
148 path = self.timestamp_folder_get(ds_path, image)
149 # Lock to ensure that the spawn will not try and access an image
150 # that is currently being deleted on the datastore.
151 with lockutils.lock(str(path), lock_file_prefix='nova-vmware-ts',
152 external=True):
153 ts = self._get_timestamp(ds_browser, path)
154 if not ts:
155 ts_path = path.join(self._get_timestamp_filename())
156 try:
157 ds_util.mkdir(self._session, ts_path, dc_info.ref)
158 except vexc.FileAlreadyExistsException:
159 LOG.debug("Timestamp already exists.")
160 LOG.info("Image %s is no longer used by this node. "
161 "Pending deletion!", image)
162 else:
163 dt = self._get_datetime_from_filename(str(ts))
164 if timeutils.is_older_than(dt, age_seconds):
165 LOG.info("Image %s is no longer used. Deleting!", path)
166 # Image has aged - delete the image ID folder
167 self._folder_delete(path, dc_info.ref)
169 # If the image is used and the timestamp file exists then we delete
170 # the timestamp.
171 for image in self.used_images:
172 path = self.timestamp_folder_get(ds_path, image)
173 with lockutils.lock(str(path), lock_file_prefix='nova-vmware-ts',
174 external=True):
175 self.timestamp_cleanup(dc_info.ref, ds_browser,
176 path)
178 def update(self, context, instances, datastores_info):
179 """The cache manager entry point.
181 This will invoke the cache manager. This will update the cache
182 according to the defined cache management scheme. The information
183 populated in the cached stats will be used for the cache management.
184 """
185 # read running instances data
186 running = self._list_running_instances(context, instances)
187 self.used_images = set(running['used_images'].keys())
188 # perform the aging and image verification per datastore
189 for (datastore, dc_info) in datastores_info:
190 ds_path = datastore.build_path(self._base_folder)
191 images = self._list_datastore_images(ds_path, datastore)
192 self.originals = images['originals']
193 self._age_cached_images(context, datastore, dc_info, ds_path)
195 def get_image_cache_folder(self, datastore, image_id):
196 """Returns datastore path of folder containing the image."""
197 return datastore.build_path(self._base_folder, image_id)