# increment this every time the db schema changes and provide upgrade code
SNAP_DB_VERSION = '0'
SNAP_DB_OBJECT_NAME = f'{SNAP_DB_PREFIX}_v{SNAP_DB_VERSION}'
+# scheduled snapshots are tz suffixed
+SNAPSHOT_TS_FORMAT_TZ = '%Y-%m-%d-%H_%M_%S_%Z'
+# for backward compat snapshot name parsing
SNAPSHOT_TS_FORMAT = '%Y-%m-%d-%H_%M_%S'
+# length of timestamp format (without tz suffix)
+# e.g.: scheduled-2022-04-19-05_39_00_UTC (len = "2022-04-19-05_39_00")
+SNAPSHOT_TS_FORMAT_LEN = 19
SNAPSHOT_PREFIX = 'scheduled'
log = logging.getLogger(__name__)
PRUNING_PATTERNS = OrderedDict([
# n is for keep last n snapshots, uses the snapshot name timestamp
# format for lowest granularity
+ # NOTE: prune set has tz suffix stripped out.
("n", SNAPSHOT_TS_FORMAT),
# TODO remove M for release
("M", '%Y-%m-%d-%H_%M'),
keep = keep[:MAX_SNAPS_PER_PATH]
return candidates - set(keep)
+def snap_name_to_timestamp(scheduled_snap_name: str) -> str:
+ """ extract timestamp from a schedule snapshot with tz suffix stripped out """
+ ts = scheduled_snap_name.lstrip(f'{SNAPSHOT_PREFIX}-')
+ return ts[0:SNAPSHOT_TS_FORMAT_LEN]
class DBInfo():
def __init__(self, fs: str, db: sqlite3.Connection):
size, _mtime = ioctx.stat(SNAP_DB_OBJECT_NAME)
dump = ioctx.read(SNAP_DB_OBJECT_NAME, size).decode('utf-8')
db.executescript(dump)
- ioctx.remove(SNAP_DB_OBJECT_NAME)
+ ioctx.remove_object(SNAP_DB_OBJECT_NAME)
except rados.ObjectNotFound:
log.debug(f'No legacy schedule DB found in {fs}')
db.executescript(Schedule.CREATE_TABLES)
start=start)[0]
time = datetime.now(timezone.utc)
with open_filesystem(self, fs_name) as fs_handle:
- snap_ts = time.strftime(SNAPSHOT_TS_FORMAT)
- snap_name = f'{path}/.snap/{SNAPSHOT_PREFIX}-{snap_ts}'
+ snap_ts = time.strftime(SNAPSHOT_TS_FORMAT_TZ)
+ snap_dir = self.mgr.rados.conf_get('client_snapdir')
+ snap_name = f'{path}/{snap_dir}/{SNAPSHOT_PREFIX}-{snap_ts}'
fs_handle.mkdir(snap_name, 0o755)
log.info(f'created scheduled snapshot of {path}')
log.debug(f'created scheduled snapshot {snap_name}')
prune_candidates = set()
time = datetime.now(timezone.utc)
with open_filesystem(self, sched.fs) as fs_handle:
- with fs_handle.opendir(f'{path}/.snap') as d_handle:
+ snap_dir = self.mgr.rados.conf_get('client_snapdir')
+ with fs_handle.opendir(f'{path}/{snap_dir}') as d_handle:
dir_ = fs_handle.readdir(d_handle)
while dir_:
if dir_.d_name.decode('utf-8').startswith(f'{SNAPSHOT_PREFIX}-'):
log.debug(f'add {dir_.d_name} to pruning')
ts = datetime.strptime(
- dir_.d_name.decode('utf-8').lstrip(f'{SNAPSHOT_PREFIX}-'),
- SNAPSHOT_TS_FORMAT)
+ snap_name_to_timestamp(dir_.d_name.decode('utf-8')), SNAPSHOT_TS_FORMAT)
prune_candidates.add((dir_, ts))
else:
log.debug(f'skipping dir entry {dir_.d_name}')
for k in to_prune:
dirname = k[0].d_name.decode('utf-8')
log.debug(f'rmdir on {dirname}')
- fs_handle.rmdir(f'{path}/.snap/{dirname}')
+ fs_handle.rmdir(f'{path}/{snap_dir}/{dirname}')
if to_prune:
with self.get_schedule_db(sched.fs) as conn_mgr:
db = conn_mgr.dbinfo.db