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