#!/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()