117 lines
4.0 KiB
Python
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() |