153 lines
4.0 KiB
Python
Executable File
153 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import logging, sys, os, shutil, time, datetime, yaml, socket
|
|
from weir import zfs, process
|
|
|
|
HOLD_SYNC_TAG = 'sync'
|
|
|
|
def setup_logging():
|
|
logging.basicConfig(level=logging.DEBUG, format="%(asctime)-15s %(message)s")
|
|
|
|
class Project:
|
|
def __init__(self, pool, name, host=None):
|
|
self.host = host
|
|
self.pool = pool
|
|
self.name = name
|
|
|
|
self.fs = zfs.open(self.path(), 'filesystem')
|
|
|
|
# Check that the filesystem exists
|
|
try:
|
|
self.fs.getprops()
|
|
except process.DatasetNotFoundError:
|
|
self.fs = zfs.create(self.path(), 'filesystem')
|
|
|
|
|
|
def path(self):
|
|
if self.host:
|
|
return 'zfs://{}/{}/{}'.format(self.host, self.pool, self.name)
|
|
|
|
return '{}/{}'.format(self.pool, self.name)
|
|
|
|
def snapshots(self):
|
|
return self.fs.snapshots()
|
|
|
|
def snapshot(self, name=None, create=True):
|
|
if name is None:
|
|
name = 'test'
|
|
|
|
snap = list(filter(lambda s: s.snapname() == name, self.snapshots()))
|
|
if len(snap) == 0 and create:
|
|
return self.fs.snapshot(name)
|
|
elif len(snap) == 0:
|
|
return None
|
|
|
|
return snap[0]
|
|
|
|
|
|
|
|
def sync(src, dest):
|
|
# Find the last snapshot in common between src and dest
|
|
src_snapshots = map(lambda s: s.snapname(), src.snapshots())
|
|
dest_snapshots = map(lambda s: s.snapname(), dest.snapshots())
|
|
common_snapshots = list(set(src_snapshots) & set(dest_snapshots))
|
|
common_snapshots.sort()
|
|
|
|
previous_snapshot = None
|
|
if len(common_snapshots):
|
|
previous_snapshot = dict([
|
|
("src", src.snapshot(common_snapshots[-1], create=False)),
|
|
("dest", dest.snapshot(common_snapshots[-1], create=False))
|
|
])
|
|
|
|
# Create the new snapshot
|
|
new_snapshot = src.snapshot(datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S"))
|
|
|
|
# Hold the source snapshot
|
|
try:
|
|
new_snapshot.hold(HOLD_SYNC_TAG)
|
|
except process.HoldTagExistsError:
|
|
# We can ignore this error.
|
|
None
|
|
|
|
if previous_snapshot:
|
|
s = new_snapshot.send(base=previous_snapshot["src"].__str__(), replicate=True)
|
|
else:
|
|
s = new_snapshot.send(replicate=True)
|
|
|
|
try:
|
|
r = zfs.receive(dest.path(), force=True)
|
|
shutil.copyfileobj(s, r)
|
|
|
|
s.close()
|
|
r.close()
|
|
except:
|
|
try:
|
|
s.close()
|
|
except:
|
|
None
|
|
|
|
try:
|
|
r.close()
|
|
except:
|
|
None
|
|
|
|
new_snapshot.release(HOLD_SYNC_TAG)
|
|
new_snapshot.destroy()
|
|
return False
|
|
|
|
# Hold the dest tag
|
|
dest_snapshot = dest.snapshot(new_snapshot.snapname(), create=False)
|
|
dest_snapshot.hold(HOLD_SYNC_TAG)
|
|
|
|
# Release the hold
|
|
if previous_snapshot:
|
|
for _, snap in previous_snapshot.items():
|
|
snap.release(HOLD_SYNC_TAG)
|
|
snap.destroy()
|
|
|
|
return True
|
|
|
|
def main():
|
|
if os.geteuid():
|
|
print("zfssync must be run as root")
|
|
sys.exit(1)
|
|
|
|
# Configure logging
|
|
setup_logging()
|
|
|
|
if len(sys.argv) != 2:
|
|
logging.error("Project argument is required")
|
|
sys.exit(2)
|
|
|
|
# Setup zfssync
|
|
logging.info('Starting zfssync')
|
|
|
|
with open('/home/mirror/merlin/zfssync.yml') as f:
|
|
conf = yaml.load(f)
|
|
|
|
project_name = sys.argv[1]
|
|
if project_name in conf['projects']:
|
|
project = conf['projects'][project_name]
|
|
else:
|
|
print('No project "{}" in config'.format(project_name))
|
|
sys.exit(3)
|
|
|
|
hostname = socket.gethostname()
|
|
dest_hostname = 'phys-1002-201.cloud.cs.uwaterloo.ca' if hostname == 'potassium-benzoate' else 'potassium-benzoate'
|
|
|
|
logging.info('Sending {} from {} to {}'.format(project_name, hostname, dest_hostname))
|
|
|
|
src = Project(
|
|
project['hosts'][hostname]['pool'],
|
|
project['hosts'][hostname]['dataset'])
|
|
dest = Project(
|
|
project['hosts'][dest_hostname]['pool'],
|
|
project['hosts'][dest_hostname]['dataset'],
|
|
host=dest_hostname)
|
|
|
|
sync(src, dest)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|