xenapi: cached images should be cleaned up by time
For xenapi driver, there needs to be some way to delete cached images based on when they were created. add an optional arg to control delete operation. Change-Id: I24fc45e989aa951aab55a261fce77f7e3667d988 Closes-bug: 1481689
This commit is contained in:
parent
a067f8c646
commit
693ace79fb
@ -46,6 +46,7 @@ from nova.virt import hardware
|
|||||||
from nova.virt.xenapi import driver as xenapi_conn
|
from nova.virt.xenapi import driver as xenapi_conn
|
||||||
from nova.virt.xenapi import fake
|
from nova.virt.xenapi import fake
|
||||||
from nova.virt.xenapi import vm_utils
|
from nova.virt.xenapi import vm_utils
|
||||||
|
import time
|
||||||
|
|
||||||
CONF = nova.conf.CONF
|
CONF = nova.conf.CONF
|
||||||
XENSM_TYPE = 'xensm'
|
XENSM_TYPE = 'xensm'
|
||||||
@ -628,13 +629,119 @@ class CreateCachedImageTestCase(VMUtilsTestBase):
|
|||||||
mock_safe_find_sr):
|
mock_safe_find_sr):
|
||||||
self.session.call_xenapi.side_effect = ['ext', {}, 'cache_vdi_ref',
|
self.session.call_xenapi.side_effect = ['ext', {}, 'cache_vdi_ref',
|
||||||
None, None, None, None, None,
|
None, None, None, None, None,
|
||||||
None, 'vdi_uuid']
|
None, None, 'vdi_uuid']
|
||||||
self.assertEqual((True, {'root': {'uuid': 'vdi_uuid', 'file': None}}),
|
self.assertEqual((True, {'root': {'uuid': 'vdi_uuid', 'file': None}}),
|
||||||
vm_utils._create_cached_image('context', self.session,
|
vm_utils._create_cached_image('context', self.session,
|
||||||
'instance', 'name', 'uuid',
|
'instance', 'name', 'uuid',
|
||||||
vm_utils.ImageType.DISK_VHD))
|
vm_utils.ImageType.DISK_VHD))
|
||||||
|
|
||||||
|
|
||||||
|
class DestroyCachedImageTestCase(VMUtilsTestBase):
|
||||||
|
def setUp(self):
|
||||||
|
super(DestroyCachedImageTestCase, self).setUp()
|
||||||
|
self.session = stubs.get_fake_session()
|
||||||
|
|
||||||
|
@mock.patch.object(vm_utils, '_find_cached_images')
|
||||||
|
@mock.patch.object(vm_utils, 'destroy_vdi')
|
||||||
|
@mock.patch.object(vm_utils, '_walk_vdi_chain')
|
||||||
|
@mock.patch.object(time, 'time')
|
||||||
|
def test_destroy_cached_image_out_of_keep_days(self,
|
||||||
|
mock_time,
|
||||||
|
mock_walk_vdi_chain,
|
||||||
|
mock_destroy_vdi,
|
||||||
|
mock_find_cached_images):
|
||||||
|
fake_cached_time = '0'
|
||||||
|
mock_find_cached_images.return_value = {'fake_image_id': {
|
||||||
|
'vdi_ref': 'fake_vdi_ref', 'cached_time': fake_cached_time}}
|
||||||
|
self.session.call_xenapi.return_value = 'fake_uuid'
|
||||||
|
mock_walk_vdi_chain.return_value = ('just_one',)
|
||||||
|
|
||||||
|
mock_time.return_value = 2 * 3600 * 24
|
||||||
|
|
||||||
|
fake_keep_days = 1
|
||||||
|
expected_return = set()
|
||||||
|
expected_return.add('fake_uuid')
|
||||||
|
|
||||||
|
uuid_return = vm_utils.destroy_cached_images(self.session,
|
||||||
|
'fake_sr_ref', False, False, fake_keep_days)
|
||||||
|
mock_find_cached_images.assert_called_once()
|
||||||
|
mock_walk_vdi_chain.assert_called_once()
|
||||||
|
mock_time.assert_called()
|
||||||
|
mock_destroy_vdi.assert_called_once()
|
||||||
|
self.assertEqual(expected_return, uuid_return)
|
||||||
|
|
||||||
|
@mock.patch.object(vm_utils, '_find_cached_images')
|
||||||
|
@mock.patch.object(vm_utils, 'destroy_vdi')
|
||||||
|
@mock.patch.object(vm_utils, '_walk_vdi_chain')
|
||||||
|
@mock.patch.object(time, 'time')
|
||||||
|
def test_destroy_cached_image(self, mock_time, mock_walk_vdi_chain,
|
||||||
|
mock_destroy_vdi, mock_find_cached_images):
|
||||||
|
fake_cached_time = '0'
|
||||||
|
mock_find_cached_images.return_value = {'fake_image_id': {
|
||||||
|
'vdi_ref': 'fake_vdi_ref', 'cached_time': fake_cached_time}}
|
||||||
|
self.session.call_xenapi.return_value = 'fake_uuid'
|
||||||
|
mock_walk_vdi_chain.return_value = ('just_one',)
|
||||||
|
|
||||||
|
mock_time.return_value = 2 * 3600 * 24
|
||||||
|
|
||||||
|
fake_keep_days = 1
|
||||||
|
expected_return = set()
|
||||||
|
expected_return.add('fake_uuid')
|
||||||
|
|
||||||
|
uuid_return = vm_utils.destroy_cached_images(self.session,
|
||||||
|
'fake_sr_ref', False, False, fake_keep_days)
|
||||||
|
mock_find_cached_images.assert_called_once()
|
||||||
|
mock_walk_vdi_chain.assert_called_once()
|
||||||
|
mock_destroy_vdi.assert_called_once()
|
||||||
|
self.assertEqual(expected_return, uuid_return)
|
||||||
|
|
||||||
|
@mock.patch.object(vm_utils, '_find_cached_images')
|
||||||
|
@mock.patch.object(vm_utils, 'destroy_vdi')
|
||||||
|
@mock.patch.object(vm_utils, '_walk_vdi_chain')
|
||||||
|
@mock.patch.object(time, 'time')
|
||||||
|
def test_destroy_cached_image_cached_time_not_exceed(
|
||||||
|
self, mock_time, mock_walk_vdi_chain,
|
||||||
|
mock_destroy_vdi, mock_find_cached_images):
|
||||||
|
fake_cached_time = '0'
|
||||||
|
mock_find_cached_images.return_value = {'fake_image_id': {
|
||||||
|
'vdi_ref': 'fake_vdi_ref', 'cached_time': fake_cached_time}}
|
||||||
|
self.session.call_xenapi.return_value = 'fake_uuid'
|
||||||
|
mock_walk_vdi_chain.return_value = ('just_one',)
|
||||||
|
|
||||||
|
mock_time.return_value = 1 * 3600 * 24
|
||||||
|
|
||||||
|
fake_keep_days = 2
|
||||||
|
expected_return = set()
|
||||||
|
|
||||||
|
uuid_return = vm_utils.destroy_cached_images(self.session,
|
||||||
|
'fake_sr_ref', False, False, fake_keep_days)
|
||||||
|
mock_find_cached_images.assert_called_once()
|
||||||
|
mock_walk_vdi_chain.assert_called_once()
|
||||||
|
mock_destroy_vdi.assert_not_called()
|
||||||
|
self.assertEqual(expected_return, uuid_return)
|
||||||
|
|
||||||
|
@mock.patch.object(vm_utils, '_find_cached_images')
|
||||||
|
@mock.patch.object(vm_utils, 'destroy_vdi')
|
||||||
|
@mock.patch.object(vm_utils, '_walk_vdi_chain')
|
||||||
|
@mock.patch.object(time, 'time')
|
||||||
|
def test_destroy_cached_image_no_cached_time(
|
||||||
|
self, mock_time, mock_walk_vdi_chain,
|
||||||
|
mock_destroy_vdi, mock_find_cached_images):
|
||||||
|
mock_find_cached_images.return_value = {'fake_image_id': {
|
||||||
|
'vdi_ref': 'fake_vdi_ref', 'cached_time': None}}
|
||||||
|
self.session.call_xenapi.return_value = 'fake_uuid'
|
||||||
|
mock_walk_vdi_chain.return_value = ('just_one',)
|
||||||
|
fake_keep_days = 2
|
||||||
|
expected_return = set()
|
||||||
|
|
||||||
|
uuid_return = vm_utils.destroy_cached_images(self.session,
|
||||||
|
'fake_sr_ref', False, False, fake_keep_days)
|
||||||
|
mock_find_cached_images.assert_called_once()
|
||||||
|
mock_walk_vdi_chain.assert_called_once()
|
||||||
|
mock_destroy_vdi.assert_not_called()
|
||||||
|
self.assertEqual(expected_return, uuid_return)
|
||||||
|
|
||||||
|
|
||||||
class ShutdownTestCase(VMUtilsTestBase):
|
class ShutdownTestCase(VMUtilsTestBase):
|
||||||
|
|
||||||
def test_hardshutdown_should_return_true_when_vm_is_shutdown(self):
|
def test_hardshutdown_should_return_true_when_vm_is_shutdown(self):
|
||||||
|
@ -716,7 +716,8 @@ def get_sr_path(session, sr_ref=None):
|
|||||||
return os.path.join(CONF.xenserver.sr_base_path, sr_uuid)
|
return os.path.join(CONF.xenserver.sr_base_path, sr_uuid)
|
||||||
|
|
||||||
|
|
||||||
def destroy_cached_images(session, sr_ref, all_cached=False, dry_run=False):
|
def destroy_cached_images(session, sr_ref, all_cached=False, dry_run=False,
|
||||||
|
keep_days=0):
|
||||||
"""Destroy used or unused cached images.
|
"""Destroy used or unused cached images.
|
||||||
|
|
||||||
A cached image that is being used by at least one VM is said to be 'used'.
|
A cached image that is being used by at least one VM is said to be 'used'.
|
||||||
@ -728,6 +729,10 @@ def destroy_cached_images(session, sr_ref, all_cached=False, dry_run=False):
|
|||||||
|
|
||||||
The default behavior of this function is to destroy only 'unused' cached
|
The default behavior of this function is to destroy only 'unused' cached
|
||||||
images. To destroy all cached images, use the `all_cached=True` kwarg.
|
images. To destroy all cached images, use the `all_cached=True` kwarg.
|
||||||
|
|
||||||
|
`keep_days` is used to destroy images based on when they were created.
|
||||||
|
Only the images which were created `keep_days` ago will be deleted if the
|
||||||
|
argument has been set.
|
||||||
"""
|
"""
|
||||||
cached_images = _find_cached_images(session, sr_ref)
|
cached_images = _find_cached_images(session, sr_ref)
|
||||||
destroyed = set()
|
destroyed = set()
|
||||||
@ -738,7 +743,8 @@ def destroy_cached_images(session, sr_ref, all_cached=False, dry_run=False):
|
|||||||
destroy_vdi(session, vdi_ref)
|
destroy_vdi(session, vdi_ref)
|
||||||
destroyed.add(vdi_uuid)
|
destroyed.add(vdi_uuid)
|
||||||
|
|
||||||
for vdi_ref in cached_images.values():
|
for vdi_dict in cached_images.values():
|
||||||
|
vdi_ref = vdi_dict['vdi_ref']
|
||||||
vdi_uuid = session.call_xenapi('VDI.get_uuid', vdi_ref)
|
vdi_uuid = session.call_xenapi('VDI.get_uuid', vdi_ref)
|
||||||
|
|
||||||
if all_cached:
|
if all_cached:
|
||||||
@ -760,13 +766,22 @@ def destroy_cached_images(session, sr_ref, all_cached=False, dry_run=False):
|
|||||||
if len(children) > 1:
|
if len(children) > 1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
destroy_cached_vdi(vdi_uuid, vdi_ref)
|
cached_time = vdi_dict.get('cached_time')
|
||||||
|
if cached_time is not None:
|
||||||
|
if (int(time.time()) - int(cached_time)) / (3600 * 24) \
|
||||||
|
>= keep_days:
|
||||||
|
destroy_cached_vdi(vdi_uuid, vdi_ref)
|
||||||
|
else:
|
||||||
|
LOG.debug("vdi %s can't be destroyed because the cached time is"
|
||||||
|
" not specified", vdi_uuid)
|
||||||
|
|
||||||
return destroyed
|
return destroyed
|
||||||
|
|
||||||
|
|
||||||
def _find_cached_images(session, sr_ref):
|
def _find_cached_images(session, sr_ref):
|
||||||
"""Return a dict(uuid=vdi_ref) representing all cached images."""
|
"""Return a dict {image_id: {'vdi_ref': vdi_ref, 'cached_time':
|
||||||
|
cached_time}} representing all cached images.
|
||||||
|
"""
|
||||||
cached_images = {}
|
cached_images = {}
|
||||||
for vdi_ref, vdi_rec in _get_all_vdis_in_sr(session, sr_ref):
|
for vdi_ref, vdi_rec in _get_all_vdis_in_sr(session, sr_ref):
|
||||||
try:
|
try:
|
||||||
@ -774,7 +789,9 @@ def _find_cached_images(session, sr_ref):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
cached_images[image_id] = vdi_ref
|
cached_time = vdi_rec['other_config'].get('cached-time')
|
||||||
|
cached_images[image_id] = {'vdi_ref': vdi_ref,
|
||||||
|
'cached_time': cached_time}
|
||||||
|
|
||||||
return cached_images
|
return cached_images
|
||||||
|
|
||||||
@ -1226,6 +1243,10 @@ def _create_cached_image(context, session, instance, name_label,
|
|||||||
'root')
|
'root')
|
||||||
session.call_xenapi('VDI.add_to_other_config',
|
session.call_xenapi('VDI.add_to_other_config',
|
||||||
cache_vdi_ref, 'image-id', str(image_id))
|
cache_vdi_ref, 'image-id', str(image_id))
|
||||||
|
session.call_xenapi('VDI.add_to_other_config',
|
||||||
|
cache_vdi_ref,
|
||||||
|
'cached-time',
|
||||||
|
str(int(time.time())))
|
||||||
|
|
||||||
if CONF.use_cow_images:
|
if CONF.use_cow_images:
|
||||||
new_vdi_ref = _clone_vdi(session, cache_vdi_ref)
|
new_vdi_ref = _clone_vdi(session, cache_vdi_ref)
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
For the XenAPI driver, in order to delete cached images based on when they
|
||||||
|
were created, a new ``--keep-days DAYS`` option is added to the
|
||||||
|
``destroy_cached_images`` script to delete cached images which were created
|
||||||
|
at least ``DAYS`` days ago. By default, all unused cached images will be
|
||||||
|
deleted when the script is run if they have ``cached_time``.
|
@ -21,6 +21,8 @@ Options:
|
|||||||
--dry_run - Don't actually destroy the VDIs
|
--dry_run - Don't actually destroy the VDIs
|
||||||
--all_cached - Destroy all cached images instead of just unused cached
|
--all_cached - Destroy all cached images instead of just unused cached
|
||||||
images.
|
images.
|
||||||
|
--keep_days - N - Only remove those cached images which were created
|
||||||
|
more than N days ago.
|
||||||
"""
|
"""
|
||||||
import eventlet
|
import eventlet
|
||||||
eventlet.monkey_patch()
|
eventlet.monkey_patch()
|
||||||
@ -52,7 +54,11 @@ destroy_opts = [
|
|||||||
' images.'),
|
' images.'),
|
||||||
cfg.BoolOpt('dry_run',
|
cfg.BoolOpt('dry_run',
|
||||||
default=False,
|
default=False,
|
||||||
help='Don\'t actually delete the VDIs.')
|
help='Don\'t actually delete the VDIs.'),
|
||||||
|
cfg.IntOpt('keep_days',
|
||||||
|
default=0,
|
||||||
|
help='Destroy cached images which were'
|
||||||
|
' created over keep_days.')
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = nova.conf.CONF
|
CONF = nova.conf.CONF
|
||||||
@ -69,7 +75,7 @@ def main():
|
|||||||
sr_ref = vm_utils.safe_find_sr(_session)
|
sr_ref = vm_utils.safe_find_sr(_session)
|
||||||
destroyed = vm_utils.destroy_cached_images(
|
destroyed = vm_utils.destroy_cached_images(
|
||||||
_session, sr_ref, all_cached=CONF.all_cached,
|
_session, sr_ref, all_cached=CONF.all_cached,
|
||||||
dry_run=CONF.dry_run)
|
dry_run=CONF.dry_run, keep_days=CONF.keep_days)
|
||||||
|
|
||||||
if '--verbose' in sys.argv:
|
if '--verbose' in sys.argv:
|
||||||
print('\n'.join(destroyed))
|
print('\n'.join(destroyed))
|
||||||
|
Loading…
Reference in New Issue
Block a user