Merge "RemoteFS: enable image volume cache"

This commit is contained in:
Jenkins 2017-06-15 00:22:52 +00:00 committed by Gerrit Code Review
commit 673269858c
2 changed files with 111 additions and 57 deletions

View File

@ -52,9 +52,26 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
self._fake_snapshot.id) self._fake_snapshot.id)
self._fake_snapshot.volume = self._fake_volume self._fake_snapshot.volume = self._fake_volume
@ddt.data({'current_state': 'in-use',
'acceptable_states': ['available', 'in-use']},
{'current_state': 'in-use',
'acceptable_states': ['available'],
'expected_exception': exception.InvalidVolume})
@ddt.unpack
def test_validate_state(self, current_state, acceptable_states,
expected_exception=None):
if expected_exception:
self.assertRaises(expected_exception,
self._driver._validate_state,
current_state,
acceptable_states)
else:
self._driver._validate_state(current_state, acceptable_states)
def _test_delete_snapshot(self, volume_in_use=False, def _test_delete_snapshot(self, volume_in_use=False,
stale_snapshot=False, stale_snapshot=False,
is_active_image=True): is_active_image=True,
is_tmp_snap=False):
# If the snapshot is not the active image, it is guaranteed that # If the snapshot is not the active image, it is guaranteed that
# another snapshot exists having it as backing file. # another snapshot exists having it as backing file.
@ -78,6 +95,7 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
self._driver._local_volume_dir = mock.Mock( self._driver._local_volume_dir = mock.Mock(
return_value=self._FAKE_MNT_POINT) return_value=self._FAKE_MNT_POINT)
self._driver._validate_state = mock.Mock()
self._driver._read_info_file = mock.Mock() self._driver._read_info_file = mock.Mock()
self._driver._write_info_file = mock.Mock() self._driver._write_info_file = mock.Mock()
self._driver._img_commit = mock.Mock() self._driver._img_commit = mock.Mock()
@ -91,12 +109,18 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
self._fake_snapshot.id: fake_snapshot_name self._fake_snapshot.id: fake_snapshot_name
} }
exp_acceptable_states = ['available', 'in-use', 'backing-up',
'deleting', 'downloading']
if volume_in_use: if volume_in_use:
self._fake_snapshot.volume.status = 'in-use' self._fake_snapshot.volume.status = 'in-use'
self._driver._read_info_file.return_value = fake_info self._driver._read_info_file.return_value = fake_info
self._driver._delete_snapshot(self._fake_snapshot) self._driver._delete_snapshot(self._fake_snapshot)
self._driver._validate_state.assert_called_once_with(
self._fake_snapshot.volume.status,
exp_acceptable_states)
if stale_snapshot: if stale_snapshot:
self._driver._delete_stale_snapshot.assert_called_once_with( self._driver._delete_stale_snapshot.assert_called_once_with(
self._fake_snapshot) self._fake_snapshot)
@ -228,7 +252,7 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
mock.call(*command3, run_as_root=True)] mock.call(*command3, run_as_root=True)]
self._driver._execute.assert_has_calls(calls) self._driver._execute.assert_has_calls(calls)
def _test_create_snapshot(self, volume_in_use=False): def _test_create_snapshot(self, volume_in_use=False, tmp_snap=False):
fake_snapshot_info = {} fake_snapshot_info = {}
fake_snapshot_file_name = os.path.basename(self._fake_snapshot_path) fake_snapshot_file_name = os.path.basename(self._fake_snapshot_path)
@ -243,11 +267,16 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
return_value=self._fake_volume.name) return_value=self._fake_volume.name)
self._driver._get_new_snap_path = mock.Mock( self._driver._get_new_snap_path = mock.Mock(
return_value=self._fake_snapshot_path) return_value=self._fake_snapshot_path)
self._driver._validate_state = mock.Mock()
expected_snapshot_info = { expected_snapshot_info = {
'active': fake_snapshot_file_name, 'active': fake_snapshot_file_name,
self._fake_snapshot.id: fake_snapshot_file_name self._fake_snapshot.id: fake_snapshot_file_name
} }
exp_acceptable_states = ['available', 'in-use', 'backing-up']
if tmp_snap:
exp_acceptable_states.append('downloading')
self._fake_snapshot.id = 'tmp-snap-%s' % self._fake_snapshot.id
if volume_in_use: if volume_in_use:
self._fake_snapshot.volume.status = 'in-use' self._fake_snapshot.volume.status = 'in-use'
@ -258,6 +287,9 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
self._driver._create_snapshot(self._fake_snapshot) self._driver._create_snapshot(self._fake_snapshot)
self._driver._validate_state.assert_called_once_with(
self._fake_snapshot.volume.status,
exp_acceptable_states)
fake_method = getattr(self._driver, expected_method_called) fake_method = getattr(self._driver, expected_method_called)
fake_method.assert_called_with( fake_method.assert_called_with(
self._fake_snapshot, self._fake_volume.name, self._fake_snapshot, self._fake_volume.name,
@ -428,52 +460,59 @@ class RemoteFsSnapDriverTestCase(test.TestCase):
basedir=basedir, basedir=basedir,
valid_backing_file=False) valid_backing_file=False)
def test_create_cloned_volume(self): @mock.patch.object(remotefs.RemoteFSSnapDriver,
'_validate_state')
@mock.patch.object(remotefs.RemoteFSSnapDriver, '_create_snapshot')
@mock.patch.object(remotefs.RemoteFSSnapDriver, '_delete_snapshot')
@mock.patch.object(remotefs.RemoteFSSnapDriver,
'_copy_volume_from_snapshot')
def test_create_cloned_volume(self, mock_copy_volume_from_snapshot,
mock_delete_snapshot,
mock_create_snapshot,
mock_validate_state):
drv = self._driver drv = self._driver
with mock.patch.object(drv, '_create_snapshot') as \ volume = fake_volume.fake_volume_obj(self.context)
mock_create_snapshot,\ src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1'
mock.patch.object(drv, '_delete_snapshot') as \ src_vref = fake_volume.fake_volume_obj(
mock_delete_snapshot,\ self.context,
mock.patch.object(drv, '_copy_volume_from_snapshot') as \ id=src_vref_id,
mock_copy_volume_from_snapshot: name='volume-%s' % src_vref_id)
volume = fake_volume.fake_volume_obj(self.context) vol_attrs = ['provider_location', 'size', 'id', 'name', 'status',
src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1' 'volume_type', 'metadata']
src_vref = fake_volume.fake_volume_obj( Volume = collections.namedtuple('Volume', vol_attrs)
self.context,
id=src_vref_id,
name='volume-%s' % src_vref_id)
vol_attrs = ['provider_location', 'size', 'id', 'name', 'status', snap_attrs = ['volume_name', 'volume_size', 'name',
'volume_type', 'metadata'] 'volume_id', 'id', 'volume']
Volume = collections.namedtuple('Volume', vol_attrs) Snapshot = collections.namedtuple('Snapshot', snap_attrs)
snap_attrs = ['volume_name', 'volume_size', 'name', volume_ref = Volume(id=volume.id,
'volume_id', 'id', 'volume'] name=volume.name,
Snapshot = collections.namedtuple('Snapshot', snap_attrs) status=volume.status,
provider_location=volume.provider_location,
size=volume.size,
volume_type=volume.volume_type,
metadata=volume.metadata)
volume_ref = Volume(id=volume.id, snap_ref = Snapshot(volume_name=volume.name,
name=volume.name, name='clone-snap-%s' % src_vref.id,
status=volume.status, volume_size=src_vref.size,
provider_location=volume.provider_location, volume_id=src_vref.id,
size=volume.size, id='tmp-snap-%s' % src_vref.id,
volume_type=volume.volume_type, volume=src_vref)
metadata=volume.metadata)
snap_ref = Snapshot(volume_name=volume.name, drv.create_cloned_volume(volume, src_vref)
name='clone-snap-%s' % src_vref.id,
volume_size=src_vref.size,
volume_id=src_vref.id,
id='tmp-snap-%s' % src_vref.id,
volume=src_vref)
drv.create_cloned_volume(volume, src_vref) exp_acceptable_states = ['available', 'backing-up', 'downloading']
mock_validate_state.assert_called_once_with(
mock_create_snapshot.assert_called_once_with(snap_ref) src_vref.status,
mock_copy_volume_from_snapshot.assert_called_once_with( exp_acceptable_states,
snap_ref, volume_ref, volume['size']) obj_description='source volume')
self.assertTrue(mock_delete_snapshot.called) mock_create_snapshot.assert_called_once_with(snap_ref)
mock_copy_volume_from_snapshot.assert_called_once_with(
snap_ref, volume_ref, volume['size'])
self.assertTrue(mock_delete_snapshot.called)
def test_create_regular_file(self): def test_create_regular_file(self):
self._driver._create_regular_file('/path', 1) self._driver._create_regular_file('/path', 1)

View File

@ -233,6 +233,23 @@ class RemoteFSDriver(driver.BaseVD):
" mount_point_base.") " mount_point_base.")
return None return None
@staticmethod
def _validate_state(current_state,
acceptable_states,
obj_description='volume',
invalid_exc=exception.InvalidVolume):
if current_state not in acceptable_states:
message = _('Invalid %(obj_description)s state. '
'Acceptable states for this operation: '
'%(acceptable_states)s. '
'Current %(obj_description)s state: '
'%(current_state)s.')
raise invalid_exc(
message=message %
dict(obj_description=obj_description,
acceptable_states=acceptable_states,
current_state=current_state))
@utils.trace @utils.trace
def create_volume(self, volume): def create_volume(self, volume):
"""Creates a volume. """Creates a volume.
@ -941,11 +958,10 @@ class RemoteFSSnapDriverBase(RemoteFSDriver):
{'src': src_vref.id, {'src': src_vref.id,
'dst': volume.id}) 'dst': volume.id})
if src_vref.status not in ['available', 'backing-up']: acceptable_states = ['available', 'backing-up', 'downloading']
msg = _("Source volume status must be 'available', or " self._validate_state(src_vref.status,
"'backing-up' but is: " acceptable_states,
"%(status)s.") % {'status': src_vref.status} obj_description='source volume')
raise exception.InvalidVolume(msg)
volume_name = CONF.volume_name_template % volume.id volume_name = CONF.volume_name_template % volume.id
@ -1021,13 +1037,9 @@ class RemoteFSSnapDriverBase(RemoteFSDriver):
else 'offline')}) else 'offline')})
volume_status = snapshot.volume.status volume_status = snapshot.volume.status
if volume_status not in ['available', 'in-use', acceptable_states = ['available', 'in-use', 'backing-up', 'deleting',
'backing-up', 'deleting']: 'downloading']
msg = _("Volume status must be 'available', 'in-use', " self._validate_state(volume_status, acceptable_states)
"'backing-up' or 'deleting' but is: "
"%(status)s.") % {'status': volume_status}
raise exception.InvalidVolume(msg)
vol_path = self._local_volume_dir(snapshot.volume) vol_path = self._local_volume_dir(snapshot.volume)
self._ensure_share_writable(vol_path) self._ensure_share_writable(vol_path)
@ -1332,12 +1344,15 @@ class RemoteFSSnapDriverBase(RemoteFSDriver):
else 'offline')}) else 'offline')})
status = snapshot.volume.status status = snapshot.volume.status
if status not in ['available', 'in-use', 'backing-up']:
msg = _("Volume status must be 'available', 'in-use' or "
"'backing-up' but is: "
"%(status)s.") % {'status': status}
raise exception.InvalidVolume(msg) acceptable_states = ['available', 'in-use', 'backing-up']
if snapshot.id.startswith('tmp-snap-'):
# This is an internal volume snapshot. In order to support
# image caching, we'll allow creating/deleting such snapshots
# while having volumes in 'downloading' state.
acceptable_states.append('downloading')
self._validate_state(status, acceptable_states)
info_path = self._local_path_volume_info(snapshot.volume) info_path = self._local_path_volume_info(snapshot.volume)
snap_info = self._read_info_file(info_path, empty_if_missing=True) snap_info = self._read_info_file(info_path, empty_if_missing=True)