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