6321: Add test that OSError is caught from slurm subprocess invocations.
[arvados.git] / services / nodemanager / arvnodeman / computenode / __init__.py
1 #!/usr/bin/env python
2
3 from __future__ import absolute_import, print_function
4
5 import calendar
6 import itertools
7 import re
8 import time
9
10 ARVADOS_TIMEFMT = '%Y-%m-%dT%H:%M:%SZ'
11 ARVADOS_TIMESUBSEC_RE = re.compile(r'(\.\d+)Z$')
12
13 def arvados_node_fqdn(arvados_node, default_hostname='dynamic.compute'):
14     hostname = arvados_node.get('hostname') or default_hostname
15     return '{}.{}'.format(hostname, arvados_node['domain'])
16
17 def arvados_node_mtime(node):
18     return arvados_timestamp(node['modified_at'])
19
20 def arvados_timestamp(timestr):
21     subsec_match = ARVADOS_TIMESUBSEC_RE.search(timestr)
22     if subsec_match is None:
23         subsecs = .0
24     else:
25         subsecs = float(subsec_match.group(1))
26         timestr = timestr[:subsec_match.start()] + 'Z'
27     return calendar.timegm(time.strptime(timestr + 'UTC',
28                                          ARVADOS_TIMEFMT + '%Z'))
29
30 def timestamp_fresh(timestamp, fresh_time):
31     return (time.time() - timestamp) < fresh_time
32
33 def arvados_node_missing(arvados_node, fresh_time):
34     """Indicate if cloud node corresponding to the arvados
35     node is "missing".
36
37     If True, this means the node has not pinged the API server within the timeout
38     period.  If False, the ping is up to date.  If the node has never pinged,
39     returns None.
40     """
41     if arvados_node["last_ping_at"] is None:
42         return None
43     else:
44         return not timestamp_fresh(arvados_timestamp(arvados_node["last_ping_at"]), fresh_time)
45
46 class ShutdownTimer(object):
47     """Keep track of a cloud node's shutdown windows.
48
49     Instantiate this class with a timestamp of when a cloud node started,
50     and a list of durations (in minutes) of when the node must not and may
51     be shut down, alternating.  The class will tell you when a shutdown
52     window is open, and when the next open window will start.
53     """
54     def __init__(self, start_time, shutdown_windows):
55         # The implementation is easiest if we have an even number of windows,
56         # because then windows always alternate between open and closed.
57         # Rig that up: calculate the first shutdown window based on what's
58         # passed in.  Then, if we were given an odd number of windows, merge
59         # that first window into the last one, since they both# represent
60         # closed state.
61         first_window = shutdown_windows[0]
62         shutdown_windows = list(shutdown_windows[1:])
63         self._next_opening = start_time + (60 * first_window)
64         if len(shutdown_windows) % 2:
65             shutdown_windows.append(first_window)
66         else:
67             shutdown_windows[-1] += first_window
68         self.shutdown_windows = itertools.cycle([60 * n
69                                                  for n in shutdown_windows])
70         self._open_start = self._next_opening
71         self._open_for = next(self.shutdown_windows)
72
73     def _advance_opening(self):
74         while self._next_opening < time.time():
75             self._open_start = self._next_opening
76             self._next_opening += self._open_for + next(self.shutdown_windows)
77             self._open_for = next(self.shutdown_windows)
78
79     def next_opening(self):
80         self._advance_opening()
81         return self._next_opening
82
83     def window_open(self):
84         self._advance_opening()
85         return 0 < (time.time() - self._open_start) < self._open_for