Merge "mypy: image cache"

This commit is contained in:
Zuul 2021-08-18 22:29:41 +00:00 committed by Gerrit Code Review
commit 799182036f
4 changed files with 67 additions and 20 deletions

View File

@ -12,11 +12,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from typing import Optional
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import timeutils from oslo_utils import timeutils
from pytz import timezone from pytz import timezone
from cinder import context
from cinder import objects from cinder import objects
from cinder import rpc from cinder import rpc
from cinder import utils from cinder import utils
@ -27,18 +30,25 @@ LOG = logging.getLogger(__name__)
class ImageVolumeCache(object): class ImageVolumeCache(object):
def __init__(self, db, volume_api, max_cache_size_gb=0, def __init__(self,
max_cache_size_count=0): db,
volume_api,
max_cache_size_gb: int = 0,
max_cache_size_count: int = 0):
self.db = db self.db = db
self.volume_api = volume_api self.volume_api = volume_api
self.max_cache_size_gb = int(max_cache_size_gb) self.max_cache_size_gb = int(max_cache_size_gb)
self.max_cache_size_count = int(max_cache_size_count) self.max_cache_size_count = int(max_cache_size_count)
self.notifier = rpc.get_notifier('volume', CONF.host) self.notifier = rpc.get_notifier('volume', CONF.host)
def get_by_image_volume(self, context, volume_id): def get_by_image_volume(self,
context: context.RequestContext,
volume_id: str):
return self.db.image_volume_cache_get_by_volume_id(context, volume_id) return self.db.image_volume_cache_get_by_volume_id(context, volume_id)
def evict(self, context, cache_entry): def evict(self,
context: context.RequestContext,
cache_entry: dict) -> None:
LOG.debug('Evicting image cache entry: %(entry)s.', LOG.debug('Evicting image cache entry: %(entry)s.',
{'entry': self._entry_to_str(cache_entry)}) {'entry': self._entry_to_str(cache_entry)})
self.db.image_volume_cache_delete(context, cache_entry['volume_id']) self.db.image_volume_cache_delete(context, cache_entry['volume_id'])
@ -46,12 +56,16 @@ class ImageVolumeCache(object):
cache_entry['host']) cache_entry['host'])
@staticmethod @staticmethod
def _get_query_filters(volume_ref): def _get_query_filters(volume_ref: objects.Volume) -> dict:
if volume_ref.is_clustered: if volume_ref.is_clustered:
return {'cluster_name': volume_ref.cluster_name} return {'cluster_name': volume_ref.cluster_name}
return {'host': volume_ref.host} return {'host': volume_ref.host}
def get_entry(self, context, volume_ref, image_id, image_meta): def get_entry(self,
context: context.RequestContext,
volume_ref: objects.Volume,
image_id: str,
image_meta: dict) -> Optional[dict]:
cache_entry = self.db.image_volume_cache_get_and_update_last_used( cache_entry = self.db.image_volume_cache_get_and_update_last_used(
context, context,
image_id, image_id,
@ -77,7 +91,11 @@ class ImageVolumeCache(object):
volume_ref['host']) volume_ref['host'])
return cache_entry return cache_entry
def create_cache_entry(self, context, volume_ref, image_id, image_meta): def create_cache_entry(self,
context: context.RequestContext,
volume_ref: objects.Volume,
image_id: str,
image_meta: dict) -> dict:
"""Create a new cache entry for an image. """Create a new cache entry for an image.
This assumes that the volume described by volume_ref has already been This assumes that the volume described by volume_ref has already been
@ -112,7 +130,9 @@ class ImageVolumeCache(object):
{'entry': self._entry_to_str(cache_entry)}) {'entry': self._entry_to_str(cache_entry)})
return cache_entry return cache_entry
def ensure_space(self, context, volume): def ensure_space(self,
context: context.RequestContext,
volume: objects.Volume) -> bool:
"""Makes room for a volume cache entry. """Makes room for a volume cache entry.
Returns True if successful, false otherwise. Returns True if successful, false otherwise.
@ -184,19 +204,32 @@ class ImageVolumeCache(object):
return True return True
@utils.if_notifications_enabled @utils.if_notifications_enabled
def _notify_cache_hit(self, context, image_id, host): def _notify_cache_hit(self,
context: context.RequestContext,
image_id: str,
host: str) -> None:
self._notify_cache_action(context, image_id, host, 'hit') self._notify_cache_action(context, image_id, host, 'hit')
@utils.if_notifications_enabled @utils.if_notifications_enabled
def _notify_cache_miss(self, context, image_id, host): def _notify_cache_miss(self,
context: context.RequestContext,
image_id: str,
host: str) -> None:
self._notify_cache_action(context, image_id, host, 'miss') self._notify_cache_action(context, image_id, host, 'miss')
@utils.if_notifications_enabled @utils.if_notifications_enabled
def _notify_cache_eviction(self, context, image_id, host): def _notify_cache_eviction(self,
context: context.RequestContext,
image_id: str,
host: str) -> None:
self._notify_cache_action(context, image_id, host, 'evict') self._notify_cache_action(context, image_id, host, 'evict')
@utils.if_notifications_enabled @utils.if_notifications_enabled
def _notify_cache_action(self, context, image_id, host, action): def _notify_cache_action(self,
context: context.RequestContext,
image_id: str,
host: str,
action: str) -> None:
data = { data = {
'image_id': image_id, 'image_id': image_id,
'host': host, 'host': host,
@ -205,14 +238,18 @@ class ImageVolumeCache(object):
' data=%(data)s.', {'action': action, 'data': data}) ' data=%(data)s.', {'action': action, 'data': data})
self.notifier.info(context, 'image_volume_cache.%s' % action, data) self.notifier.info(context, 'image_volume_cache.%s' % action, data)
def _delete_image_volume(self, context, cache_entry): def _delete_image_volume(self,
context: context.RequestContext,
cache_entry: dict) -> None:
"""Delete a volume and remove cache entry.""" """Delete a volume and remove cache entry."""
volume = objects.Volume.get_by_id(context, cache_entry['volume_id']) volume = objects.Volume.get_by_id(context, cache_entry['volume_id'])
# Delete will evict the cache entry. # Delete will evict the cache entry.
self.volume_api.delete(context, volume) self.volume_api.delete(context, volume)
def _should_update_entry(self, cache_entry, image_meta): def _should_update_entry(self,
cache_entry: dict,
image_meta: dict) -> bool:
"""Ensure that the cache entry image data is still valid.""" """Ensure that the cache entry image data is still valid."""
image_updated_utc = (image_meta['updated_at'] image_updated_utc = (image_meta['updated_at']
.astimezone(timezone('UTC'))) .astimezone(timezone('UTC')))
@ -226,7 +263,7 @@ class ImageVolumeCache(object):
return image_updated_utc != cache_updated_utc return image_updated_utc != cache_updated_utc
def _entry_to_str(self, cache_entry): def _entry_to_str(self, cache_entry: dict) -> str:
return str({ return str({
'id': cache_entry['id'], 'id': cache_entry['id'],
'image_id': cache_entry['image_id'], 'image_id': cache_entry['image_id'],

View File

@ -23,8 +23,10 @@ import shutil
import sys import sys
import textwrap import textwrap
import time import time
import typing as ty import typing
from typing import Any, Dict, Tuple # noqa: H301
import urllib import urllib
import urllib.parse
import glanceclient.exc import glanceclient.exc
from keystoneauth1.loading import session as ks_session from keystoneauth1.loading import session as ks_session
@ -304,7 +306,9 @@ class GlanceImageService(object):
except Exception: except Exception:
_reraise_translated_exception() _reraise_translated_exception()
def show(self, context, image_id): def show(self,
context: context.RequestContext,
image_id: str) -> Dict[str, Any]:
"""Returns a dict with image data for the given opaque image id.""" """Returns a dict with image data for the given opaque image id."""
try: try:
image = self._client.call(context, 'get', image_id) image = self._client.call(context, 'get', image_id)
@ -352,6 +356,7 @@ class GlanceImageService(object):
except Exception: except Exception:
_reraise_translated_image_exception(image_id) _reraise_translated_image_exception(image_id)
@typing.no_type_check
def download(self, context, image_id, data=None): def download(self, context, image_id, data=None):
"""Calls out to Glance for data and writes data.""" """Calls out to Glance for data and writes data."""
if data and 'file' in CONF.allowed_direct_url_schemes: if data and 'file' in CONF.allowed_direct_url_schemes:
@ -452,7 +457,7 @@ class GlanceImageService(object):
raise exception.ImageNotFound(image_id=image_id) raise exception.ImageNotFound(image_id=image_id)
return True return True
def _translate_from_glance(self, context, image): def _translate_from_glance(self, context, image) -> dict:
"""Get image metadata from glance image. """Get image metadata from glance image.
Extract metadata from image and convert it's properties Extract metadata from image and convert it's properties
@ -595,7 +600,7 @@ def _extract_attributes(image):
'visibility', 'visibility',
'cinder_encryption_key_id'] 'cinder_encryption_key_id']
output = {} output: Dict[str, Any] = {}
for attr in IMAGE_ATTRIBUTES: for attr in IMAGE_ATTRIBUTES:
if attr == 'deleted_at' and not output['deleted']: if attr == 'deleted_at' and not output['deleted']:
@ -656,7 +661,7 @@ def _translate_plain_exception(exc_value):
def get_remote_image_service(context: context.RequestContext, def get_remote_image_service(context: context.RequestContext,
image_href) -> ty.Tuple[GlanceImageService, str]: image_href) -> Tuple[GlanceImageService, str]:
"""Create an image_service and parse the id from the given image_href. """Create an image_service and parse the id from the given image_href.
The image_href param can be an href of the form The image_href param can be an href of the form

View File

@ -38,6 +38,7 @@ intact.
import functools import functools
import time import time
import typing as ty import typing as ty
from typing import Optional
from castellan import key_manager from castellan import key_manager
from oslo_config import cfg from oslo_config import cfg
@ -261,6 +262,7 @@ class VolumeManager(manager.CleanableManager,
self.service_uuid = None self.service_uuid = None
self.cluster: str self.cluster: str
self.image_volume_cache: Optional[image_cache.ImageVolumeCache]
if not volume_driver: if not volume_driver:
# Get from configuration, which will get the default # Get from configuration, which will get the default
@ -1548,6 +1550,7 @@ class VolumeManager(manager.CleanableManager,
This assumes that the image has already been downloaded and stored This assumes that the image has already been downloaded and stored
in the volume described by the volume_ref. in the volume described by the volume_ref.
""" """
assert self.image_volume_cache is not None
cache_entry = self.image_volume_cache.get_entry(ctx, cache_entry = self.image_volume_cache.get_entry(ctx,
volume_ref, volume_ref,
image_id, image_id,

View File

@ -1,5 +1,7 @@
cinder/context.py cinder/context.py
cinder/i18n.py cinder/i18n.py
cinder/image/cache.py
cinder/image/glance.py
cinder/image/image_utils.py cinder/image/image_utils.py
cinder/exception.py cinder/exception.py
cinder/manager.py cinder/manager.py