7286: Add drain* and fail* to SLURM_END_STATES, because the '*' means the node
[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     if arvados_node["last_ping_at"] is None:
35         return None
36     else:
37         return not timestamp_fresh(arvados_timestamp(arvados_node["last_ping_at"]), fresh_time)
38
39 class ShutdownTimer(object):
40     """Keep track of a cloud node's shutdown windows.
41
42     Instantiate this class with a timestamp of when a cloud node started,
43     and a list of durations (in minutes) of when the node must not and may
44     be shut down, alternating.  The class will tell you when a shutdown
45     window is open, and when the next open window will start.
46     """
47     def __init__(self, start_time, shutdown_windows):
48         # The implementation is easiest if we have an even number of windows,
49         # because then windows always alternate between open and closed.
50         # Rig that up: calculate the first shutdown window based on what's
51         # passed in.  Then, if we were given an odd number of windows, merge
52         # that first window into the last one, since they both# represent
53         # closed state.
54         first_window = shutdown_windows[0]
55         shutdown_windows = list(shutdown_windows[1:])
56         self._next_opening = start_time + (60 * first_window)
57         if len(shutdown_windows) % 2:
58             shutdown_windows.append(first_window)
59         else:
60             shutdown_windows[-1] += first_window
61         self.shutdown_windows = itertools.cycle([60 * n
62                                                  for n in shutdown_windows])
63         self._open_start = self._next_opening
64         self._open_for = next(self.shutdown_windows)
65
66     def _advance_opening(self):
67         while self._next_opening < time.time():
68             self._open_start = self._next_opening
69             self._next_opening += self._open_for + next(self.shutdown_windows)
70             self._open_for = next(self.shutdown_windows)
71
72     def next_opening(self):
73         self._advance_opening()
74         return self._next_opening
75
76     def window_open(self):
77         self._advance_opening()
78         return 0 < (time.time() - self._open_start) < self._open_for