ZFS_Snapshot_Manager/zfssmd.py

117 lines
4.0 KiB
Python

#!/usr/bin/python3
import subprocess
import Pyro4
from pathlib import Path
import logging as log
import signal
import time
from os import getpid
from zfssmd_worker import ZFSSMDaemonRefresh
ENCODING = 'utf-8'
UNIX_SOCK=Path('/run/zfssmd.sock')
UNIX_PID=Path('/run/zfssmd.pid')
CONN_ID='058b7dde9ec53de9235cfc57a07ce17a9eabfce3'
class ZfsSnapshotManagerDaemon(object):
"""Pyro4 Daemon we create to speed up listing of snapshots by caching it in memory
When we start daemon we refresh the list by calling zfs list command.
We keep listening on unix socket for refresh requests or client requests.
"""
snapshots = list()
last_refreshed=99999999 # We use arbitrarily large number to make sure we are not interfering with ZFSSMDClient.check_old_timestamp()
def __init__(self):
"""
Refreshes snapshot list on daemon launch
:param self.last_refreshed keeps tabs on when was the last cache refresh
"""
self.last_refreshed = self.call_zfs_list_snapshots()
@Pyro4.expose
def call_zfs_list_snapshots(self):
"""
Calls zfs list command and mutates the self.snapshots variable to contain latest list of snapshots.
:return: refresh timestamp
"""
command = 'zfs list -Hp -t snapshot -o name,creation,used,referenced -s name'
try:
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception as e:
log.error("Couldn't run ZFS list command. " + e)
snapshot_list = [snapshot.decode(ENCODING) for snapshot in p.stdout]
last_refreshed = time.time()
self.snapshots = snapshot_list
self.last_refreshed = last_refreshed
return last_refreshed
@Pyro4.expose
def get_current_list(self):
"""
Getter exposed for client connection
:return: dict with key being last refresh time and value being list of snapshots
"""
return {self.last_refreshed:self.snapshots}
def check_start_conditions():
"""
Makes sure daemon can launch by checking for PID and sock files. If they exist it logs warning, deletes temp files,
creates new PID file. At this point if we detect error we kill the daemon immediately.
:return: False as intended
"""
if UNIX_PID.is_file():
log.warning("Daemon already running or didn't exit gracefully. Trying to clean up.")
try:
UNIX_PID.unlink()
except Exception as e:
log.error("Couldn't delete PID file" + str(UNIX_PID))
log.error(e)
raise SystemExit(1)
try:
UNIX_PID.touch()
except Exception as e:
log.error("Couldn't create PID file. " + UNIX_PID + " Check Permissions")
log.error(e)
raise SystemExit(1)
if UNIX_SOCK.is_socket() or UNIX_SOCK.is_file():
log.warning("Daemon already running or didn't exit gracefully. Trying to clean up.")
try:
UNIX_SOCK.unlink()
except Exception as e:
log.error("Couldn't delete sock file" + str(UNIX_SOCK))
log.error(e)
raise SystemExit(1)
else:
log.debug("Socket not set. Continuing.")
def sigterm_handler(_signo, _stack_frame):
"""
Handles SIGINT and SIGTERM. Same action no point in creating two functions.
Deletes PID and sock file on exit
"""
log.warning("Received Termination signal. Cleaning up.")
UNIX_SOCK.unlink()
UNIX_PID.unlink()
raise SystemExit(0)
def start_daemon():
"""
Handles starting all the intended actions with starting this program.
Spawns signal handler, checks start conditions, registers new daemon with URI
and launches scheduled interval refresh thread.
:return: False
"""
signal.signal(signal.SIGTERM, sigterm_handler)
signal.signal(signal.SIGINT, sigterm_handler)
check_start_conditions()
daemon = Pyro4.Daemon(port=None,unixsocket=str(UNIX_SOCK))
uri = daemon.register(ZfsSnapshotManagerDaemon(), CONN_ID)
log.info(uri)
ZFSSMDaemonRefresh()
daemon.requestLoop()
start_daemon()