reguire gnupg1 on f>=31; sense the system to use gpg1 when installed
[nodemanager.git] / nodemanager.py
1 #!/usr/bin/python3
2 #
3 # Useful information can be found at https://svn.planet-lab.org/wiki/NodeManager
4 #
5
6 # Faiyaz Ahmed <faiyaza at cs dot princeton dot edu>
7 # Copyright (C) 2008 The Trustees of Princeton University
8
9
10 """
11 Node Manager
12 """
13
14 import time
15 import os
16 import sys
17 import glob
18 import pickle
19 import random
20 from argparse import ArgumentParser
21
22 import logger
23 import tools
24
25 from config import Config
26 from plcapi import PLCAPI
27
28
29 class NodeManager:
30
31     PLUGIN_PATH = "/usr/share/NodeManager/plugins"
32
33     DB_FILE = "/var/lib/nodemanager/getslivers.pickle"
34
35     # the modules in this directory that need to be run
36     # NOTE: modules listed here will also be loaded in this order
37     # once loaded, they get re-ordered after their priority (lower comes first)
38     # for determining the runtime order
39     core_modules = ['net', 'conf_files', 'slivermanager', 'bwmon']
40
41     default_period = 600
42     default_random = 301
43     default_priority = 100
44
45     def __init__ (self):
46
47         parser = ArgumentParser()
48         parser.add_argument(
49             '-d', '--daemon', action='store_true', dest='daemon',
50             default=False,
51             help='run daemonized')
52         parser.add_argument(
53             '-f', '--config', action='store', dest='config',
54             default='/etc/planetlab/plc_config',
55             help='PLC configuration file')
56         parser.add_argument(
57             '-k', '--session', action='store', dest='session',
58             default='/etc/planetlab/session',
59             help='API session key (or file)')
60         parser.add_argument(
61             '-p', '--period', action='store', dest='period',
62             default=NodeManager.default_period,
63             help='Polling interval (sec) - default {}'
64                  .format(NodeManager.default_period))
65         parser.add_argument(
66             '-r', '--random', action='store', dest='random',
67             default=NodeManager.default_random,
68             help='Range for additional random polling interval (sec) -- default {}'
69                  .format(NodeManager.default_random))
70         parser.add_argument(
71             '-v', '--verbose', action='store_true', dest='verbose',
72             default=False,
73             help='more verbose log')
74         parser.add_argument(
75             '-P', '--path', action='store', dest='path',
76             default=NodeManager.PLUGIN_PATH,
77             help='Path to plugins directory')
78
79         parser.add_argument(
80             '-m', '--module', action='store', dest='user_module',
81             default='',
82             help='run a single module')
83         self.options = parser.parse_args()
84
85         # determine the modules to be run
86         self.modules = NodeManager.core_modules
87         # Deal with plugins directory
88         if os.path.exists(self.options.path):
89             sys.path.append(self.options.path)
90             plugins = [
91                 os.path.split(os.path.splitext(x)[0])[1]
92                 for x in glob.glob( os.path.join(self.options.path, '*.py') )
93                 if not x.endswith("/__init__.py")
94                 ]
95             self.modules += plugins
96         if self.options.user_module:
97             assert self.options.user_module in self.modules
98             self.modules = [self.options.user_module]
99             logger.verbose('nodemanager: Running single module {}'.format(self.options.user_module))
100
101
102     def GetSlivers(self, config, plc):
103         """
104         Retrieve GetSlivers at PLC and trigger callbacks defined in modules/plugins
105         """
106         try:
107             logger.log("nodemanager: Syncing w/ PLC")
108             # retrieve GetSlivers from PLC
109             data = plc.GetSlivers()
110             # use the magic 'default' slice to retrieve system-wide defaults
111             self.getPLCDefaults(data, config)
112             # tweak the 'vref' attribute from GetSliceFamily
113             self.setSliversVref(data)
114             # dump it too, so it can be retrieved later in case of comm. failure
115             self.dumpSlivers(data)
116             # log it for debug purposes, no matter what verbose is
117             logger.log_slivers(data)
118             logger.verbose("nodemanager: Sync w/ PLC done")
119             last_data = data
120         except:
121             logger.log_exc("nodemanager: failed in GetSlivers")
122             # XXX So some modules can at least boostrap.
123             logger.log("nodemanager:  Can't contact PLC to GetSlivers().  Continuing.")
124             data = {}
125             # for modules that request it though the 'persistent_data' property
126             last_data = self.loadSlivers()
127
128         #  Invoke GetSlivers() functions from the callback modules
129         for module in self.loaded_modules:
130             logger.verbose('nodemanager: triggering {}.GetSlivers'.format(module.__name__))
131             try:
132                 callback = getattr(module, 'GetSlivers')
133                 module_data = data
134                 if getattr(module, 'persistent_data', False):
135                     module_data = last_data
136                 callback(data, config, plc)
137             except SystemExit as e:
138                 sys.exit(e)
139             except:
140                 logger.log_exc("nodemanager: GetSlivers failed to run callback for module {}"
141                                .format(module))
142
143
144     def getPLCDefaults(self, data, config):
145         """
146         Get PLC wide defaults from _default system slice.  Adds them to config class.
147         """
148         for slice in data.get('slivers'):
149             if slice['name'] == config.PLC_SLICE_PREFIX + "_default":
150                 attr_dict = {}
151                 for attr in slice.get('attributes'):
152                     attr_dict[attr['tagname']] = attr['value']
153                 if attr_dict:
154                     logger.verbose("nodemanager: Found default slice overrides.\n {}"
155                                    .format(attr_dict))
156                     config.OVERRIDES = attr_dict
157                     return
158         # NOTE: if an _default slice existed, it would have been found above and
159         #           the routine would return.  Thus, if we've gotten here, then no default
160         #           slice is bound to this node.
161         if 'OVERRIDES' in dir(config):
162             del config.OVERRIDES
163
164
165     def setSliversVref (self, data):
166         """
167         Tweak the 'vref' attribute in all slivers based on the 'GetSliceFamily' key
168         """
169         # GetSlivers exposes the result of GetSliceFamily() as an separate key in data
170         # It is safe to override the attributes with this, as this method has the right logic
171         for sliver in data.get('slivers'):
172             try:
173                 slicefamily = sliver.get('GetSliceFamily')
174                 for att in sliver['attributes']:
175                     if att['tagname'] == 'vref':
176                         att['value'] = slicefamily
177                         continue
178                 sliver['attributes'].append(
179                     {'tagname': 'vref', 'value': slicefamily})
180             except Exception:
181                 logger.log_exc(
182                     "nodemanager: Could not overwrite 'vref' attribute from 'GetSliceFamily'",
183                     name=sliver['name'])
184
185
186     def dumpSlivers (self, slivers):
187         with open(NodeManager.DB_FILE, "wb") as feed:
188             logger.log ("nodemanager: saving successfully fetched GetSlivers in {}"
189                         .format(NodeManager.DB_FILE))
190             pickle.dump(slivers, feed)
191
192
193     def loadSlivers (self):
194         try:
195             with open(NodeManager.DB_FILE, "rb+") as feed:
196                 logger.log("nodemanager: restoring latest known GetSlivers from {}"
197                            .format(NodeManager.DB_FILE))
198                 slivers = pickle.load(feed)
199             return slivers
200         except:
201             logger.log("Could not restore GetSlivers from {}"
202                        .format(NodeManager.DB_FILE))
203             return {}
204
205     def run(self):
206         # make sure to create /etc/planetlab/virt so others can read that
207         # used e.g. in vsys-scripts's sliceip
208         tools.get_node_virt()
209         try:
210             if self.options.daemon:
211                 tools.daemon()
212
213             # set log level
214             if self.options.verbose:
215                 logger.set_level(logger.LOG_VERBOSE)
216             tools.init_signals()
217
218             # Load /etc/planetlab/plc_config
219             config = Config(self.options.config)
220
221             try:
222                 other_pid = tools.pid_file()
223                 if other_pid is not None:
224                     print("""There might be another instance of the node manager running as pid {}.
225 If this is not the case, please remove the pid file {}. -- exiting"""
226                           .format(other_pid, tools.PID_FILE))
227                     return
228             except OSError as err:
229                 print("Warning while writing PID file:", err)
230
231             # load modules
232             self.loaded_modules = []
233             for module in self.modules:
234                 try:
235                     m = __import__(module)
236                     logger.verbose("nodemanager: triggering {}.start".format(m.__name__))
237                     try:
238                         m.start()
239                     except Exception:
240                         logger.log("WARNING: module {} did not start".format(m.__name__))
241                     self.loaded_modules.append(m)
242                 except Exception:
243                     if module not in NodeManager.core_modules:
244                         logger.log_exc("ERROR while loading module {} - skipped"
245                                        .format(module))
246                     else:
247                         logger.log("FATAL : failed to start core module {}".format(module))
248                         sys.exit(1)
249
250             # sort on priority (lower first)
251             def module_priority(module):
252                 return getattr(module, 'priority', NodeManager.default_priority)
253             self.loaded_modules.sort(key=module_priority)
254
255             logger.log('ordered modules:')
256             for module in self.loaded_modules:
257                 logger.log('{}: {}'
258                            .format(getattr(module, 'priority',
259                                            NodeManager.default_priority),
260                                    module.__name__))
261
262             # Load /etc/planetlab/session
263             if os.path.exists(self.options.session):
264                 with open(self.options.session) as feed:
265                     session = feed.read().strip()
266             else:
267                 session = None
268
269
270             # get random periods
271             iperiod = int(self.options.period)
272             irandom = int(self.options.random)
273
274             # Initialize XML-RPC client
275             plc = PLCAPI(config.plc_api_uri, config.cacert, session, timeout=iperiod/2)
276
277             #check auth
278             logger.log("nodemanager: Checking Auth.")
279             while not plc.check_authentication():
280                 try:
281                     plc.update_session()
282                     logger.log("nodemanager: Authentication Failure. Retrying")
283                 except Exception as exc:
284                     logger.log("nodemanager: Retry Failed. ({}); Waiting.."
285                                .format(exc))
286                 time.sleep(iperiod)
287             logger.log("nodemanager: Authentication Succeeded!")
288
289
290             while True:
291             # Main nodemanager Loop
292                 work_beg = time.time()
293                 logger.log('nodemanager: mainloop - calling GetSlivers - period={} random={}'
294                            .format(iperiod, irandom))
295                 self.GetSlivers(config, plc)
296                 delay = iperiod + random.randrange(0, irandom)
297                 work_end = time.time()
298                 work_duration = int(work_end-work_beg)
299                 logger.log('nodemanager: mainloop has worked for {} s - sleeping for {} s'
300                            .format(work_duration, delay))
301                 time.sleep(delay)
302         except SystemExit:
303             pass
304         except:
305             logger.log_exc("nodemanager: failed in run")
306
307 def run():
308     logger.log("======================================== Entering nodemanager.py")
309     NodeManager().run()
310
311 if __name__ == '__main__':
312     run()
313 else:
314     # This is for debugging purposes.  Open a copy of Python and import nodemanager
315     tools.as_daemon_thread(run)