|
@@ -60,9 +60,11 @@ class Job(object):
|
|
|
logTransitions = False
|
|
|
READY = 0
|
|
|
QUEUED = 1
|
|
|
- RUNNING = 2
|
|
|
- FAILED = 3
|
|
|
- SUCCESSFUL = 4
|
|
|
+ STARTING = 2
|
|
|
+ RUNNING = 3
|
|
|
+ ENDING = 4
|
|
|
+ FAILED = 5
|
|
|
+ SUCCESSFUL = 6
|
|
|
|
|
|
@staticmethod
|
|
|
def initPool():
|
|
@@ -91,7 +93,7 @@ class Job(object):
|
|
|
@staticmethod
|
|
|
def running():
|
|
|
return [x for x in Job.pool
|
|
|
- if x.state is Job.RUNNING]
|
|
|
+ if x.state in (Job.STARTING, Job.RUNNING, Job.ENDING)]
|
|
|
|
|
|
@staticmethod
|
|
|
def queued():
|
|
@@ -115,14 +117,22 @@ class Job(object):
|
|
|
if self._state is Job.READY and self in Job.pool:
|
|
|
self.setState(Job.QUEUED)
|
|
|
|
|
|
- elif self._state is Job.RUNNING:
|
|
|
+ elif self._state in (Job.STARTING, Job.RUNNING, Job.ENDING):
|
|
|
code = self._process.poll() or self._process.returncode
|
|
|
if code is None and not psutil.pid_exists(self._process.pid):
|
|
|
code = -1
|
|
|
|
|
|
if code is not None:
|
|
|
- self.setState(Job.FAILED if code else Job.SUCCESSFUL)
|
|
|
- Job.initPool()
|
|
|
+ if code:
|
|
|
+ self.setState(Job.FAILED)
|
|
|
+ Job.initPool()
|
|
|
+
|
|
|
+ elif self._state is Job.ENDING:
|
|
|
+ self.setState(Job.SUCCESSFUL)
|
|
|
+ Job.initPool()
|
|
|
+
|
|
|
+ else:
|
|
|
+ self.start()
|
|
|
|
|
|
return self._state
|
|
|
|
|
@@ -138,7 +148,10 @@ class Job(object):
|
|
|
|
|
|
@property
|
|
|
def args(self):
|
|
|
- if not hasattr(self, '_args'):
|
|
|
+ if self._state is Job.STARTING:
|
|
|
+ return shlex.split(self.pre)
|
|
|
+
|
|
|
+ elif self._state is Job.RUNNING:
|
|
|
if Backup.engine == "rdiff-backup":
|
|
|
args = ['rdiff-backup', '-v{}'.format(Backup.verbosity)]
|
|
|
if 'filters' in self.config:
|
|
@@ -153,13 +166,17 @@ class Job(object):
|
|
|
raise BackupException(
|
|
|
'{0} has an invalid filter {1}'.format(self, item))
|
|
|
|
|
|
- self._args = args + [self.fromPath, self.toPath]
|
|
|
+ return args + [self.fromPath, self.toPath]
|
|
|
|
|
|
else:
|
|
|
raise StateException(
|
|
|
'Invalid backup engine {}'.format(Backup.engine))
|
|
|
|
|
|
- return self._args
|
|
|
+ elif self._state is Job.ENDING:
|
|
|
+ return shlex.split(self.post)
|
|
|
+
|
|
|
+ else:
|
|
|
+ raise StateException('Invalid state {}'.format(self.getState()))
|
|
|
|
|
|
@property
|
|
|
def logfile(self):
|
|
@@ -233,6 +250,20 @@ class Job(object):
|
|
|
|
|
|
return self._index
|
|
|
|
|
|
+ @property
|
|
|
+ def pre(self):
|
|
|
+ if not hasattr(self, '_pre'):
|
|
|
+ self._pre = self.config['pre'] if 'pre' in self.config else None
|
|
|
+
|
|
|
+ return self._pre
|
|
|
+
|
|
|
+ @property
|
|
|
+ def post(self):
|
|
|
+ if not hasattr(self, '_post'):
|
|
|
+ self._post = self.config['post'] if 'post' in self.config else None
|
|
|
+
|
|
|
+ return self._post
|
|
|
+
|
|
|
def log(self, text):
|
|
|
text = '[Backup {0} Job #{1}] {2}\n'.format(
|
|
|
self._backup.name, self.index, text)
|
|
@@ -241,15 +272,29 @@ class Job(object):
|
|
|
self._backup.logfile.flush()
|
|
|
|
|
|
def start(self):
|
|
|
- if self.state is not Job.QUEUED:
|
|
|
+ if self._state is self.QUEUED:
|
|
|
+ self._backup.setStatus(Backup.RUNNING)
|
|
|
+ self.setState(Job.STARTING)
|
|
|
+ if self.pre is None:
|
|
|
+ self.setState(Job.RUNNING)
|
|
|
+
|
|
|
+ elif self._state is self.STARTING:
|
|
|
+ self.setState(Job.RUNNING)
|
|
|
+
|
|
|
+ elif self._state is self.RUNNING:
|
|
|
+ self.setState(Job.ENDING)
|
|
|
+ if self.post is None:
|
|
|
+ self.setState(Job.SUCCESSFUL)
|
|
|
+ return
|
|
|
+
|
|
|
+ else:
|
|
|
raise StateException('Invalid state to start {}'.format(self))
|
|
|
|
|
|
- self._backup.setStatus(Backup.RUNNING)
|
|
|
- self.setState(Job.RUNNING)
|
|
|
- self.logfile.write(' '.join([shlex.quote(x) for x in self.args]) + '\n')
|
|
|
+ args = self.args
|
|
|
+ self.logfile.write(' '.join([shlex.quote(x) for x in args]) + '\n')
|
|
|
self.logfile.flush()
|
|
|
self._process = subprocess.Popen(
|
|
|
- self.args, stdout=self.logfile, stderr=subprocess.STDOUT,
|
|
|
+ args, stdout=self.logfile, stderr=subprocess.STDOUT,
|
|
|
stdin=subprocess.DEVNULL, universal_newlines=True, bufsize=1)
|
|
|
|
|
|
def setState(self, state):
|
|
@@ -264,7 +309,9 @@ class Job(object):
|
|
|
return {
|
|
|
Job.READY: 'ready',
|
|
|
Job.QUEUED: 'queued',
|
|
|
+ Job.STARTING: 'starting',
|
|
|
Job.RUNNING: 'running',
|
|
|
+ Job.ENDING: 'ending',
|
|
|
Job.FAILED: 'failed',
|
|
|
Job.SUCCESSFUL: 'successful',
|
|
|
}[self.state if state is None else state]
|
|
@@ -520,43 +567,38 @@ def sources():
|
|
|
|
|
|
|
|
|
def main(args):
|
|
|
- config._root = args[0] if len(args) else '/etc/backup.d'
|
|
|
- if not os.path.exists(config._root):
|
|
|
- raise BackupException(
|
|
|
- 'Configuration files missing from {}'.format(config._root))
|
|
|
-
|
|
|
- if 'engine' in config():
|
|
|
- engine = config()["engine"]
|
|
|
- if engine not in ("rdiff-backup"):
|
|
|
- raise BackupException('Unknown backup engine: {}'.format(engine))
|
|
|
-
|
|
|
- Backup.engine = engine
|
|
|
-
|
|
|
- if 'logdir' in config():
|
|
|
- logdir = config()['logdir']
|
|
|
- os.makedirs(logdir, exist_ok=True)
|
|
|
- if not os.path.exists(logdir):
|
|
|
+ try:
|
|
|
+ config._root = args[0] if len(args) else '/etc/backup.d'
|
|
|
+ if not os.path.exists(config._root):
|
|
|
raise BackupException(
|
|
|
- 'Unable to create logging directory: {}'.format(logdir))
|
|
|
+ 'Configuration files missing from {}'.format(config._root))
|
|
|
|
|
|
- Backup.logdir = logdir
|
|
|
+ if 'engine' in config():
|
|
|
+ engine = config()["engine"]
|
|
|
+ if engine not in ("rdiff-backup"):
|
|
|
+ raise BackupException('Unknown backup engine: {}'.format(engine))
|
|
|
|
|
|
- if 'maxthreads' in config():
|
|
|
- Job.maxthreads = config()['maxthreads']
|
|
|
+ Backup.engine = engine
|
|
|
|
|
|
- if 'verbosity' in config():
|
|
|
- Backup.verbosity = config()['verbosity']
|
|
|
+ if 'logdir' in config():
|
|
|
+ logdir = config()['logdir']
|
|
|
+ os.makedirs(logdir, exist_ok=True)
|
|
|
+ if not os.path.exists(logdir):
|
|
|
+ raise BackupException(
|
|
|
+ 'Unable to create logging directory: {}'.format(logdir))
|
|
|
|
|
|
- Backup.logTransitions = Job.logTransitions = True
|
|
|
- Backup.load(sources().keys())
|
|
|
- Backup.start()
|
|
|
- Backup.wait()
|
|
|
+ Backup.logdir = logdir
|
|
|
|
|
|
+ if 'maxthreads' in config():
|
|
|
+ Job.maxthreads = config()['maxthreads']
|
|
|
|
|
|
-if __name__ == '__main__':
|
|
|
- try:
|
|
|
- main(sys.argv[1:])
|
|
|
+ if 'verbosity' in config():
|
|
|
+ Backup.verbosity = config()['verbosity']
|
|
|
|
|
|
+ Backup.logTransitions = Job.logTransitions = True
|
|
|
+ Backup.load(sources().keys())
|
|
|
+ Backup.start()
|
|
|
+ Backup.wait()
|
|
|
except BackupException as ex:
|
|
|
print(ex)
|
|
|
sys.exit(1)
|
|
@@ -566,3 +608,7 @@ if __name__ == '__main__':
|
|
|
msg = "Error encountered:\n" + format_exc().strip()
|
|
|
print(msg)
|
|
|
sys.exit(1)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main(sys.argv[1:])
|