Merge "mypy: image cache"
This commit is contained in:
commit
799182036f
@ -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'],
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user