blob: d95165a2c4ba858b0194aee814c6377a7d211bd8 [file] [log] [blame]
Max Rasskazovc018e382015-06-03 15:22:04 +03001#-*- coding: utf-8 -*-
2
3
Max Rasskazov55e1bbc2015-06-05 03:29:29 +03004import datetime
Max Rasskazovc018e382015-06-03 15:22:04 +03005import logging
6import os
7import time
8
9
Max Rasskazov55e1bbc2015-06-05 03:29:29 +030010def singleton(class_):
11 instances = {}
Max Rasskazov3eb11982015-07-16 19:45:17 +030012
Max Rasskazov55e1bbc2015-06-05 03:29:29 +030013 def getinstance(*args, **kwargs):
14 if class_ not in instances:
15 instances[class_] = class_(*args, **kwargs)
Max Rasskazovd77a5e62015-06-19 17:47:30 +030016 elif hasattr(class_, 'reinit'):
17 instances[class_].reinit(*args, **kwargs)
Max Rasskazov55e1bbc2015-06-05 03:29:29 +030018 return instances[class_]
19 return getinstance
20
21
Max Rasskazovc018e382015-06-03 15:22:04 +030022logging.basicConfig()
23logger = logging.getLogger('safe_rsync')
24
25loglevel = os.environ.get('LOGLEVEL', 'INFO')
26if loglevel not in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
27 logger.warn('LOGLEVEL environment variable has wrong value=={}. Using '
28 '"INFO" by default.'.format(loglevel))
29 loglevel = 'INFO'
30logger.setLevel(loglevel)
31
32
33def logged(logger=None):
34 if logger is None:
35 logger = globals().get('logger')
36
37 def wrap(f):
38 def wrapped_f(*args, **kwargs):
39 logger.debug('Starting {}({}, {}) (defaults: {})'
40 ''.format(f.__name__,
41 str(args),
42 str(kwargs),
43 str(f.__defaults__))
44 )
45 r = f(*args, **kwargs)
46 logger.debug('{} done with result "{}".'.
47 format(f.__name__, str(r)))
48 return r
49 return wrapped_f
50 return wrap
51
52
Max Rasskazov3eb11982015-07-16 19:45:17 +030053@singleton
54class TimeStamp(object):
55 def __init__(self, now=None):
56 # now='2015-06-18-104259'
57 self.snapshot_stamp_format = r'%Y-%m-%d-%H%M%S'
58 self.snapshot_stamp_regexp = r'[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}'
59
60 if now is None:
61 self.now = datetime.datetime.utcnow()
62 else:
63 self.now = datetime.datetime.strptime(now,
64 self.snapshot_stamp_format)
65 self.snapshot_stamp = self.now.strftime(self.snapshot_stamp_format)
66
67 def __str__(self):
68 return self.snapshot_stamp
69
70 def reinit(self, *args, **kwagrs):
71 self.__init__(*args, **kwagrs)
72
73
Max Rasskazovc018e382015-06-03 15:22:04 +030074class ResultNotProduced(Exception):
75 def __init__(self, value):
76 self.value = value
77
78 def __str__(self):
79 return repr(self.value)
80
81
82class Retry(object):
83 """
84 Waits while the function reaches the specified status.
85
86 :param function: function that returns some status
87 :param expected_status: status the machine should turn to
88 :param attempts: how many times to check status
89 :param timeout: timeout in seconds before attempts
90 :return: True if node moves to the specified status, False otherwise
91 :Examples:
92 Retry(timeout=3, attempts=10).wait(function, result, param1, param2)
93 Retry().wait_result(function, result, param1, param2)
94 """
95
96 def __init__(self, timeout=5, attempts=10):
97 self.timeout = timeout
98 self.attempts = attempts
99 self.logger = globals().get('logger').getChild('Retry')
100
101 def wait_result(self, function, expected_result, *args, **kwargs):
102
103 self.logger.debug('Wait for {}() == {}...'
104 ''.format(function.__name__, str(expected_result)))
105
106 @logged(self.logger)
107 def f():
108 return function(*args, **kwargs)
109
110 attempt = 1
111 while attempt <= self.attempts:
112 try:
113 result = f()
114 except Exception as e:
115 self.logger.error('Exception on function {}: {}'
116 ''.format(function.__name__, str(e)))
117 raise
118 else:
119 if result == expected_result:
120 self.logger.debug('Got on attempt #{}:'.format(attempt))
121 return result
122 attempt += 1
123 time.sleep(self.timeout)
124
125 raise ResultNotProduced('Result "{}" was not produced during '
126 '{} attempts.'
127 ''.format(expected_result, attempt - 1))