8dd3474e36459ec942d1ec03c7e2b57d10e43b61
[bootmanager.git] / source / RunlevelAgent.py
1 #!/usr/bin/python
2 #
3 # RunlevelAgent - acts as a heartbeat back to myplc reporting that the node is
4 #     online and whether it is in boot or pre-boot run-level.
5 #   This is useful to identify nodes that are behind a firewall, as well as to
6 #   have the machine report run-time status both in safeboot and boot modes,
7 #   so that it is immediately visible at myplc (gui or api).
8
9
10 from __future__ import print_function
11
12 import xml, xmlrpclib
13 import logging
14 import time
15 import traceback
16 import sys
17 import os
18 import string
19 import ssl
20
21 CONFIG_FILE = "/tmp/source/configuration"
22 SESSION_FILE = "/etc/planetlab/session"
23 RLA_PID_FILE = "/var/run/rla.pid"
24
25 def read_config_file(filename):
26     ## NOTE: text copied from BootManager.py 
27     # TODO: unify this code to make it common. i.e. use ConfigParser module
28     vars = {}
29     vars_file = file(filename,'r')
30     validConfFile = True
31     for line in vars_file:
32         # if its a comment or a whitespace line, ignore
33         if line[:1] == "#" or string.strip(line) == "":
34             continue
35
36         parts = string.split(line, "=")
37         if len(parts) != 2:
38             print("Invalid line in vars file: {}".format(line))
39             validConfFile = False
40             break
41
42         name = string.strip(parts[0])
43         value = string.strip(parts[1])
44         vars[name] = value
45
46     vars_file.close()
47     if not validConfFile:
48         print("Unable to read configuration vars.")
49
50     return vars
51
52 try:
53     sys.path = ['/etc/planetlab'] + sys.path
54     import plc_config
55     api_server_url = "https://" + plc_config.PLC_API_HOST + plc_config.PLC_API_PATH
56 except:
57     filename  = CONFIG_FILE
58     vars = read_config_file(filename)
59     api_server_url = vars['BOOT_API_SERVER']
60
61
62 class Auth:
63     def __init__(self, username=None, password=None, **kwargs):
64         if 'session' in kwargs:
65             self.auth = { 'AuthMethod' : 'session',
66                           'session' : kwargs['session'] }
67         else:
68             if username is None and password is None:
69                 self.auth = {'AuthMethod': "anonymous"}
70             else:
71                 self.auth = {'Username' : username,
72                              'AuthMethod' : 'password',
73                              'AuthString' : password}
74 class PLC:
75     def __init__(self, auth, url):
76         self.auth = auth
77         self.url = url
78         # Using a self signed certificate
79         # https://www.python.org/dev/peps/pep-0476/
80         if hasattr(ssl, '_create_unverified_context'):
81             self.api = xmlrpclib.Server(self.url, verbose=False, allow_none=True,
82                                            context=ssl._create_unverified_context())
83         else :
84             self.api = xmlrpclib.Server(self.url, verbose=False, allow_none=True)
85
86     def __getattr__(self, name):
87         method = getattr(self.api, name)
88         if method is None:
89             raise AssertionError("method does not exist")
90
91         return lambda *params : method(self.auth.auth, *params)
92
93     def __repr__(self):
94         return self.api.__repr__()
95
96 def extract_from(filename, pattern):
97     f = os.popen("grep -E {} {}".format(pattern, filename))
98     val = f.read().strip()
99     return val
100
101 def check_running(commandname):
102     f = os.popen("ps ax | grep -E {} | grep -v grep".format(commandname))
103     val = f.read().strip()
104     return val
105
106
107 def save_pid():
108     # save PID
109     try:
110         pid = os.getpid()
111         f = open(RLA_PID_FILE, 'w')
112         f.write("{}\n".format(pid))
113         f.close()
114     except:
115         print("Uuuhhh.... this should not occur.")
116         sys.exit(1)
117
118 def start_and_run():
119
120     save_pid()
121
122     # Keep trying to authenticate session, waiting for NM to re-write the
123     # session file, or DNS to succeed, until AuthCheck succeeds.
124     while True:
125         try:
126             f = open(SESSION_FILE, 'r')
127             session_str = f.read().strip()
128             api = PLC(Auth(session=session_str), api_server_url)
129             # NOTE: What should we do if this call fails?
130             # TODO: handle dns failure here.
131             api.AuthCheck()
132             break
133         except:
134             print("Retry in 30 seconds: ", os.popen("uptime").read().strip())
135             traceback.print_exc(limit=5)
136             time.sleep(30)
137
138     try:
139         env = 'production'
140         if len(sys.argv) > 2:
141             env = sys.argv[2]
142     except:
143         traceback.print_exc()
144
145     while True:
146         try:
147             # NOTE: here we are inferring the runlevel by environmental
148             #         observations.  We know how this process was started by the
149             #         given command line argument.  Then in bootmanager
150             #         runlevel, the bm.log gives information about the current
151             #         activity.
152             # other options:
153             #   call plc for current boot state?
154             #   how long have we been running?
155             if env == "bootmanager":
156                 bs_val = extract_from('/tmp/bm.log', "'Current boot state:'")
157                 if len(bs_val) > 0: bs_val = bs_val.split()[-1]
158                 ex_val = extract_from('/tmp/bm.log', 'Exception')
159                 fs_val = extract_from('/tmp/bm.log', 'mke2fs')
160                 bm_val = check_running("BootManager.py")
161
162                 if bs_val in ['diag', 'diagnose', 'safeboot', 'disabled', 'disable']:
163                     api.ReportRunlevel({'run_level' : 'safeboot'})
164
165                 elif len(ex_val) > len("Exception"):
166                     api.ReportRunlevel({'run_level' : 'failboot'})
167
168                 elif len(fs_val) > 0 and len(bm_val) > 0:
169                     api.ReportRunlevel({'run_level' : 'reinstall'})
170
171                 else:
172                     api.ReportRunlevel({'run_level' : 'failboot'})
173
174             elif env == "production":
175                 api.ReportRunlevel({'run_level' : 'boot'})
176             else:
177                 api.ReportRunlevel({'run_level' : 'failboot'})
178                 
179         except:
180             print("reporting error: ", os.popen("uptime").read().strip())
181             traceback.print_exc()
182
183         sys.stdout.flush()
184         # TODO: change to a configurable value
185         time.sleep(60*15)
186
187 def agent_running():
188     try:
189         os.stat(RLA_PID_FILE)
190         f = os.popen("ps ax | grep RunlevelAgent | grep -Ev 'grep|vim' | awk '{print $1}' | wc -l")
191         l = f.read().strip()
192         if int(l) >= 2:
193             return True
194         else:
195             try:
196                 os.unlink(RLA_PID_FILE)
197             except:
198                 pass
199             return False
200     except:
201         return False
202         
203
204 def shutdown():
205     import signal
206
207     pid = open(RLA_PID_FILE, 'r').read().strip()
208
209     # Try three different ways to kill the process.  Just to be sure.
210     os.kill(int(pid), signal.SIGKILL)
211     os.system("pkill RunlevelAgent.py")
212     os.system("ps ax | grep RunlevelAgent | grep -v grep | awk '{print $1}' | xargs kill -9 ")
213
214 if __name__ == "__main__":
215     if "start" in sys.argv and not agent_running():
216         start_and_run()
217
218     if "stop" in sys.argv and agent_running():
219         shutdown()