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

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. 

14 

15""" 

16Image cache class 

17 

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. 

31 

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""" 

37 

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 

44 

45from nova.virt import imagecache 

46from nova.virt.vmwareapi import ds_util 

47 

48LOG = logging.getLogger(__name__) 

49 

50CONF = cfg.CONF 

51 

52TIMESTAMP_PREFIX = 'ts-' 

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

54 

55 

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 = {} 

62 

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) 

75 

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) 

79 

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) 

84 

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) 

90 

91 def timestamp_folder_get(self, ds_path, image_id): 

92 """Returns the timestamp folder.""" 

93 return ds_path.join(image_id) 

94 

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) 

102 

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 

109 

110 def _get_timestamp_filename(self): 

111 return '%s%s' % (TIMESTAMP_PREFIX, 

112 timeutils.utcnow().strftime(TIMESTAMP_FORMAT)) 

113 

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) 

117 

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 

126 

127 def _list_datastore_images(self, ds_path, datastore): 

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

129 

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} 

139 

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) 

168 

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) 

177 

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

179 """The cache manager entry point. 

180 

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) 

194 

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)