You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.2 KiB
129 lines
4.2 KiB
# pylint: skip-file
|
|
# type: ignore
|
|
|
|
from subprocess import run, PIPE
|
|
import asyncio
|
|
import locale
|
|
from time import time
|
|
import re
|
|
|
|
from zasd.config import config
|
|
from zasd.filesystem import FilesystemBase
|
|
|
|
#
|
|
# Constants
|
|
|
|
_DATASET_COLS = ['type', 'name', 'creation', 'mountpoint']
|
|
|
|
class _ZFSFilesystem(FilesystemBase):
|
|
_filesystems = {}
|
|
|
|
async def snapshots(self):
|
|
datasets = (ds for ds in await self._datasets('snapshot') if
|
|
_is_managed_name(ds['name']))
|
|
|
|
@classmethod
|
|
async def filesystems(cls):
|
|
datasets = (ds for ds in cls._datasets('filesystem') if
|
|
ds['name'] in cls._filesystems)
|
|
|
|
@classmethod
|
|
def _datasets(cls, dataset_type):
|
|
'''Get list of datasets'''
|
|
return _run_for_dicts(
|
|
[config['zfs_path'],
|
|
'list', '-Hp',
|
|
'-t', dataset_type,
|
|
'-o', ','.join(_DATASET_COLS)], _DATASET_COLS)
|
|
|
|
class _ZFSSnapshot():
|
|
def initialise(self, props):
|
|
name = props.get('name')
|
|
if name:
|
|
# Split name string into field list
|
|
fields = _split_snapshot_name(name)
|
|
|
|
# Get any missing properties from field list
|
|
if not props.get('dataset'):
|
|
props['dataset'] = fields[0]
|
|
if not props.get('tag'):
|
|
props['tag'] = fields[1]
|
|
if not props.get('ordinal'):
|
|
props['ordinal'] = fields[2]
|
|
|
|
# Assign properties to object
|
|
self._dataset = props['dataset']
|
|
self._tag = props['tag']
|
|
self._ordinal = props.get('ordinal')
|
|
|
|
def tag(self):
|
|
return self._tag
|
|
|
|
def ordinal(self):
|
|
return self._ordinal
|
|
|
|
async def take(self):
|
|
args = [config['zfs']['executable'], 'snapshot', self._zfs_name()]
|
|
await asyncio.create_subprocess_exec(*args)
|
|
# pylint: disable=attribute-defined-outside-init
|
|
self._ordinal = _generate_ordinal()
|
|
|
|
async def delete(self):
|
|
args = [config['zfs']['executable'], 'destroy', self._zfs_name()]
|
|
await asyncio.create_subprocess_exec(*args)
|
|
|
|
def _zfs_name(self):
|
|
''' Get ZFS name of snapshot '''
|
|
return '{}@{}{}{}'.format(
|
|
self._dataset, self._tag, config['separator'], self._ordinal)
|
|
|
|
def _is_managed_name(name):
|
|
return (_is_snapshot_name(name) and
|
|
_split_snapshot_name(name)[0] in _ZFSFilesystem.filesystems())
|
|
|
|
def _is_snapshot_name(name):
|
|
'''
|
|
Check if 'name' looks like a managed snapshot name, meaning a string
|
|
consisting of a character sequence as follows:
|
|
|
|
1. At least one character that isn't '@'
|
|
2. Character '@'
|
|
3. At least one character that isn't a separator
|
|
4. Separator character
|
|
5. At least one character that isn't a separator
|
|
6. Go to #4 and repeat until the string ends
|
|
|
|
NOTE: The validity of 'name' as a ZFS snapshot name is not checked!
|
|
'''
|
|
sep = re.escape(config['separator'])
|
|
return isinstance(name, str) and bool(
|
|
re.match('^[^@]+[^%s]+(%s[^%s]+)+$' % (sep, sep, sep), name))
|
|
|
|
def _split_snapshot_name(name):
|
|
return name.split('@' + config['separator'])
|
|
|
|
def _generate_ordinal():
|
|
return ('%x' % int(time()))[-8:]
|
|
|
|
def _run_for_dicts(args, column_list):
|
|
''' Run program and convert tabulated output to list
|
|
of dictionaries with given column names as keys '''
|
|
return _table_to_dicts(_run_for_table(args), column_list)
|
|
|
|
def _run_for_table(args):
|
|
'''Run program and convert tabulated output to nested lists'''
|
|
result = run(args, check=True, stdout=PIPE,
|
|
encoding=locale.getpreferredencoding())
|
|
return _str_to_table(result.stdout)
|
|
|
|
def _str_to_table(string, sep='\t'):
|
|
'''Convert tabulated multi-line string to nested lists'''
|
|
return (line.split(sep) for line in string.splitlines())
|
|
|
|
def _table_to_dicts(table, column_list):
|
|
'''Convert table to list of dictionaries with given column names as keys'''
|
|
return (_row_to_dict(row, column_list) for row in table)
|
|
|
|
def _row_to_dict(row, column_list):
|
|
'''Convert table row to dictionary with given column names as keys'''
|
|
return {column_list[i]: row[i] for i in range(len(row))}
|
|
|