storage: move @locked from lvm to Volume base class
And use it in reflink, instead of a synchronous lock.
This commit is contained in:
parent
a1b5262426
commit
e188b93c95
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
""" Qubes storage system"""
|
""" Qubes storage system"""
|
||||||
|
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
@ -133,6 +134,8 @@ class Volume:
|
|||||||
self.source = source
|
self.source = source
|
||||||
#: Volume unique (inside given pool) identifier
|
#: Volume unique (inside given pool) identifier
|
||||||
self.vid = vid
|
self.vid = vid
|
||||||
|
#: Asynchronous lock for @Volume.locked decorator
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, Volume):
|
if isinstance(other, Volume):
|
||||||
@ -155,6 +158,23 @@ class Volume:
|
|||||||
config = _sanitize_config(self.config)
|
config = _sanitize_config(self.config)
|
||||||
return lxml.etree.Element('volume', **config)
|
return lxml.etree.Element('volume', **config)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def locked(method):
|
||||||
|
'''Decorator running given Volume's coroutine under a lock.
|
||||||
|
Needs to be added after wrapping with @asyncio.coroutine, for example:
|
||||||
|
|
||||||
|
>>>@Volume.locked
|
||||||
|
>>>@asyncio.coroutine
|
||||||
|
>>>def start(self):
|
||||||
|
>>> pass
|
||||||
|
'''
|
||||||
|
@asyncio.coroutine
|
||||||
|
@functools.wraps(method)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
with (yield from self._lock): # pylint: disable=protected-access
|
||||||
|
return (yield from method(self, *args, **kwargs))
|
||||||
|
return wrapper
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
''' Create the given volume on disk.
|
''' Create the given volume on disk.
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
''' Driver for storing vm images in a LVM thin pool '''
|
''' Driver for storing vm images in a LVM thin pool '''
|
||||||
import functools
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -292,22 +291,6 @@ def _revision_sort_key(revision):
|
|||||||
revision = revision.split('-')[0]
|
revision = revision.split('-')[0]
|
||||||
return int(revision)
|
return int(revision)
|
||||||
|
|
||||||
def locked(method):
|
|
||||||
'''Decorator running given Volume's coroutine under a lock.
|
|
||||||
Needs to be added after wrapping with @asyncio.coroutine, for example:
|
|
||||||
|
|
||||||
>>>@locked
|
|
||||||
>>>@asyncio.coroutine
|
|
||||||
>>>def start(self):
|
|
||||||
>>> pass
|
|
||||||
'''
|
|
||||||
@asyncio.coroutine
|
|
||||||
@functools.wraps(method)
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
with (yield from self._lock): # pylint: disable=protected-access
|
|
||||||
return (yield from method(self, *args, **kwargs))
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
class ThinVolume(qubes.storage.Volume):
|
class ThinVolume(qubes.storage.Volume):
|
||||||
''' Default LVM thin volume implementation
|
''' Default LVM thin volume implementation
|
||||||
''' # pylint: disable=too-few-public-methods
|
''' # pylint: disable=too-few-public-methods
|
||||||
@ -323,8 +306,6 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
if self.save_on_stop:
|
if self.save_on_stop:
|
||||||
self._vid_import = self.vid + '-import'
|
self._vid_import = self.vid + '-import'
|
||||||
|
|
||||||
self._lock = asyncio.Lock()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self):
|
def path(self):
|
||||||
return '/dev/' + self._vid_current
|
return '/dev/' + self._vid_current
|
||||||
@ -461,7 +442,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
# and remove old snapshots, if needed
|
# and remove old snapshots, if needed
|
||||||
yield from self._remove_revisions()
|
yield from self._remove_revisions()
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def create(self):
|
def create(self):
|
||||||
assert self.vid
|
assert self.vid
|
||||||
@ -480,7 +461,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
yield from reset_cache_coro()
|
yield from reset_cache_coro()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def remove(self):
|
def remove(self):
|
||||||
assert self.vid
|
assert self.vid
|
||||||
@ -514,7 +495,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
devpath = self.path
|
devpath = self.path
|
||||||
return devpath
|
return devpath
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def import_volume(self, src_volume):
|
def import_volume(self, src_volume):
|
||||||
if not src_volume.save_on_stop:
|
if not src_volume.save_on_stop:
|
||||||
@ -556,7 +537,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def import_data(self, size):
|
def import_data(self, size):
|
||||||
''' Returns an object that can be `open()`. '''
|
''' Returns an object that can be `open()`. '''
|
||||||
@ -573,7 +554,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
devpath = '/dev/' + self._vid_import
|
devpath = '/dev/' + self._vid_import
|
||||||
return devpath
|
return devpath
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def import_data_end(self, success):
|
def import_data_end(self, success):
|
||||||
'''Either commit imported data, or discard temporary volume'''
|
'''Either commit imported data, or discard temporary volume'''
|
||||||
@ -609,7 +590,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
return (size_cache[self._vid_snap]['origin'] !=
|
return (size_cache[self._vid_snap]['origin'] !=
|
||||||
self.source.path.split('/')[-1])
|
self.source.path.split('/')[-1])
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def revert(self, revision=None):
|
def revert(self, revision=None):
|
||||||
if self.is_dirty():
|
if self.is_dirty():
|
||||||
@ -633,7 +614,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
yield from reset_cache_coro()
|
yield from reset_cache_coro()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def resize(self, size):
|
def resize(self, size):
|
||||||
''' Expands volume, throws
|
''' Expands volume, throws
|
||||||
@ -682,7 +663,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
cmd = ['clone', self.source.path, self._vid_snap]
|
cmd = ['clone', self.source.path, self._vid_snap]
|
||||||
yield from qubes_lvm_coro(cmd, self.log)
|
yield from qubes_lvm_coro(cmd, self.log)
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def start(self):
|
def start(self):
|
||||||
self.abort_if_import_in_progress()
|
self.abort_if_import_in_progress()
|
||||||
@ -696,7 +677,7 @@ class ThinVolume(qubes.storage.Volume):
|
|||||||
yield from reset_cache_coro()
|
yield from reset_cache_coro()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@locked
|
@qubes.storage.Volume.locked
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def stop(self):
|
def stop(self):
|
||||||
try:
|
try:
|
||||||
|
@ -32,7 +32,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
|
||||||
from contextlib import contextmanager, suppress
|
from contextlib import contextmanager, suppress
|
||||||
|
|
||||||
import qubes.storage
|
import qubes.storage
|
||||||
@ -131,28 +130,17 @@ class ReflinkPool(qubes.storage.Pool):
|
|||||||
self.dir_path)
|
self.dir_path)
|
||||||
|
|
||||||
|
|
||||||
def _locked(method):
|
|
||||||
''' Decorator transforming a synchronous volume method to run
|
|
||||||
under the volume lock.
|
|
||||||
'''
|
|
||||||
@functools.wraps(method)
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
with self._lock: # pylint: disable=protected-access
|
|
||||||
return method(self, *args, **kwargs)
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
class ReflinkVolume(qubes.storage.Volume):
|
class ReflinkVolume(qubes.storage.Volume):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._lock = threading.Lock()
|
|
||||||
self._path_vid = os.path.join(self.pool.dir_path, self.vid)
|
self._path_vid = os.path.join(self.pool.dir_path, self.vid)
|
||||||
self._path_clean = self._path_vid + '.img'
|
self._path_clean = self._path_vid + '.img'
|
||||||
self._path_dirty = self._path_vid + '-dirty.img'
|
self._path_dirty = self._path_vid + '-dirty.img'
|
||||||
self._path_import = self._path_vid + '-import.img'
|
self._path_import = self._path_vid + '-import.img'
|
||||||
self.path = self._path_dirty
|
self.path = self._path_dirty
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def create(self):
|
def create(self):
|
||||||
self._remove_all_images()
|
self._remove_all_images()
|
||||||
if self.save_on_stop and not self.snap_on_start:
|
if self.save_on_stop and not self.snap_on_start:
|
||||||
@ -173,8 +161,8 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
raise qubes.storage.StoragePoolException(
|
raise qubes.storage.StoragePoolException(
|
||||||
'Missing image file {!r} for volume {}'.format(img, self.vid))
|
'Missing image file {!r} for volume {}'.format(img, self.vid))
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
self.pool._volumes.pop(self, None) # pylint: disable=protected-access
|
self.pool._volumes.pop(self, None) # pylint: disable=protected-access
|
||||||
self._remove_all_images()
|
self._remove_all_images()
|
||||||
@ -203,8 +191,8 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
def is_dirty(self):
|
def is_dirty(self):
|
||||||
return self.save_on_stop and os.path.exists(self._path_dirty)
|
return self.save_on_stop and os.path.exists(self._path_dirty)
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self._remove_incomplete_images()
|
self._remove_incomplete_images()
|
||||||
if not self.is_dirty():
|
if not self.is_dirty():
|
||||||
@ -220,8 +208,8 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
_create_sparse_file(self._path_dirty, self.size)
|
_create_sparse_file(self._path_dirty, self.size)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self.save_on_stop:
|
if self.save_on_stop:
|
||||||
self._commit(self._path_dirty)
|
self._commit(self._path_dirty)
|
||||||
@ -253,8 +241,8 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
for number, timestamp in list(self.revisions.items())[:-keep or None]:
|
for number, timestamp in list(self.revisions.items())[:-keep or None]:
|
||||||
_remove_file(self._path_revision(number, timestamp))
|
_remove_file(self._path_revision(number, timestamp))
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def revert(self, revision=None):
|
def revert(self, revision=None):
|
||||||
if self.is_dirty():
|
if self.is_dirty():
|
||||||
raise qubes.storage.StoragePoolException(
|
raise qubes.storage.StoragePoolException(
|
||||||
@ -268,8 +256,8 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
_rename_file(path_revision, self._path_clean)
|
_rename_file(path_revision, self._path_clean)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def resize(self, size):
|
def resize(self, size):
|
||||||
''' Resize a read-write volume; notify any corresponding loop
|
''' Resize a read-write volume; notify any corresponding loop
|
||||||
devices of the size change.
|
devices of the size change.
|
||||||
@ -292,8 +280,8 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
'Cannot export: {} is not save_on_stop'.format(self.vid))
|
'Cannot export: {} is not save_on_stop'.format(self.vid))
|
||||||
return self._path_clean
|
return self._path_clean
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def import_data(self, size):
|
def import_data(self, size):
|
||||||
if not self.save_on_stop:
|
if not self.save_on_stop:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
@ -305,10 +293,11 @@ class ReflinkVolume(qubes.storage.Volume):
|
|||||||
(self._commit if success else _remove_file)(self._path_import)
|
(self._commit if success else _remove_file)(self._path_import)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
import_data_end = _coroutinized(_locked(_import_data_end))
|
import_data_end = qubes.storage.Volume.locked(_coroutinized(
|
||||||
|
_import_data_end))
|
||||||
|
|
||||||
|
@qubes.storage.Volume.locked
|
||||||
@_coroutinized
|
@_coroutinized
|
||||||
@_locked
|
|
||||||
def import_volume(self, src_volume):
|
def import_volume(self, src_volume):
|
||||||
if self.save_on_stop:
|
if self.save_on_stop:
|
||||||
try:
|
try:
|
||||||
|
Loading…
Reference in New Issue
Block a user