remove config and options parameters from start function as nobody uses them
[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('-f', '--config', action='store', dest='config', default='/etc/planetlab/plc_config',
55                           help='PLC configuration file')
56         parser.add_option('-k', '--session', action='store', dest='session', default='/etc/planetlab/session',
57                           help='API session key (or file)')
58         parser.add_option('-p', '--period', action='store', dest='period', default=NodeManager.default_period,
59                           help='Polling interval (sec) - default %d'%NodeManager.default_period)
60         parser.add_option('-r', '--random', action='store', dest='random', default=NodeManager.default_random,
61                           help='Range for additional random polling interval (sec) -- default %d'%NodeManager.default_random)
62         parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
63                           help='more verbose log')
64         parser.add_option('-P', '--path', action='store', dest='path', default=NodeManager.PLUGIN_PATH,
65                           help='Path to plugins directory')
66
67         # NOTE: BUG the 'help' for this parser.add_option() wont list plugins from the --path argument
68         parser.add_option('-m', '--module', action='store', dest='user_module', default='', help='run a single module')
69         (self.options, args) = parser.parse_args()
70
71         if len(args) != 0:
72             parser.print_help()
73             sys.exit(1)
74
75         # determine the modules to be run
76         self.modules = NodeManager.core_modules
77         # Deal with plugins directory
78         if os.path.exists(self.options.path):
79             sys.path.append(self.options.path)
80             plugins = [ os.path.split(os.path.splitext(x)[0])[1] for x in glob.glob( os.path.join(self.options.path,'*.py') ) ]
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:
121                 logger.log_exc("nodemanager: GetSlivers failed to run callback for module %r"%module)
122
123
124     def getPLCDefaults(self, data, config):
125         """
126         Get PLC wide defaults from _default system slice.  Adds them to config class.
127         """
128         for slice in data.get('slivers'):
129             if slice['name'] == config.PLC_SLICE_PREFIX+"_default":
130                 attr_dict = {}
131                 for attr in slice.get('attributes'): attr_dict[attr['tagname']] = attr['value']
132                 if len(attr_dict):
133                     logger.verbose("nodemanager: Found default slice overrides.\n %s" % attr_dict)
134                     config.OVERRIDES = attr_dict
135                     return
136         # NOTE: if an _default slice existed, it would have been found above and
137         #           the routine would return.  Thus, if we've gotten here, then no default
138         #           slice is bound to this node.
139         if 'OVERRIDES' in dir(config): del config.OVERRIDES
140
141
142     def setSliversVref (self, data):
143         """
144         Tweak the 'vref' attribute in all slivers based on the 'GetSliceFamily' key
145         """
146         # GetSlivers exposes the result of GetSliceFamily() as an separate key in data
147         # It is safe to override the attributes with this, as this method has the right logic
148         for sliver in data.get('slivers'):
149             try:
150                 slicefamily=sliver.get('GetSliceFamily')
151                 for att in sliver['attributes']:
152                     if att['tagname']=='vref':
153                         att['value']=slicefamily
154                         continue
155                 sliver['attributes'].append({ 'tagname':'vref','value':slicefamily})
156             except:
157                 logger.log_exc("nodemanager: Could not overwrite 'vref' attribute from 'GetSliceFamily'",name=sliver['name'])
158
159     def dumpSlivers (self, slivers):
160         f = open(NodeManager.DB_FILE, "w")
161         logger.log ("nodemanager: saving successfully fetched GetSlivers in %s" % NodeManager.DB_FILE)
162         pickle.dump(slivers, f)
163         f.close()
164
165     def loadSlivers (self):
166         try:
167             f = open(NodeManager.DB_FILE, "r+")
168             logger.log("nodemanager: restoring latest known GetSlivers from %s" % NodeManager.DB_FILE)
169             slivers = pickle.load(f)
170             f.close()
171             return slivers
172         except:
173             logger.log("Could not restore GetSlivers from %s" % NodeManager.DB_FILE)
174             return {}
175
176     def run(self):
177         try:
178             if self.options.daemon: tools.daemon()
179
180             # set log level
181             if (self.options.verbose):
182                 logger.set_level(logger.LOG_VERBOSE)
183
184             # Load /etc/planetlab/plc_config
185             config = Config(self.options.config)
186
187             try:
188                 other_pid = tools.pid_file()
189                 if other_pid != None:
190                     print """There might be another instance of the node manager running as pid %d.
191 If this is not the case, please remove the pid file %s. -- exiting""" % (other_pid, tools.PID_FILE)
192                     return
193             except OSError, err:
194                 print "Warning while writing PID file:", err
195
196             # load modules
197             self.loaded_modules = []
198             for module in self.modules:
199                 try:
200                     m = __import__(module)
201                     logger.verbose("nodemanager: triggering %s.start"%m.__name__)
202                     m.start()
203                     self.loaded_modules.append(m)
204                 except ImportError, err:
205                     print "Warning while loading module %s:" % module, err
206
207             # sort on priority (lower first)
208             def sort_module_priority (m1,m2):
209                 return getattr(m1,'priority',NodeManager.default_priority) - getattr(m2,'priority',NodeManager.default_priority)
210             self.loaded_modules.sort(sort_module_priority)
211
212             logger.log('ordered modules:')
213             for module in self.loaded_modules:
214                 logger.log ('%s: %s'%(getattr(module,'priority',NodeManager.default_priority),module.__name__))
215
216             # Load /etc/planetlab/session
217             if os.path.exists(self.options.session):
218                 session = file(self.options.session).read().strip()
219             else:
220                 session = None
221
222
223             # get random periods
224             iperiod=int(self.options.period)
225             irandom=int(self.options.random)
226
227             # Initialize XML-RPC client
228             plc = PLCAPI(config.plc_api_uri, config.cacert, session, timeout=iperiod/2)
229
230             #check auth
231             logger.log("nodemanager: Checking Auth.")
232             while plc.check_authentication() != True:
233                 try:
234                     plc.update_session()
235                     logger.log("nodemanager: Authentication Failure. Retrying")
236                 except Exception,e:
237                     logger.log("nodemanager: Retry Failed. (%r); Waiting.."%e)
238                 time.sleep(iperiod)
239             logger.log("nodemanager: Authentication Succeeded!")
240
241
242             while True:
243             # Main nodemanager Loop
244                 logger.log('nodemanager: mainloop - calling GetSlivers - period=%d random=%d'%(iperiod,irandom))
245                 self.GetSlivers(config, plc)
246                 delay=iperiod + random.randrange(0,irandom)
247                 logger.log('nodemanager: mainloop - sleeping for %d s'%delay)
248                 time.sleep(delay)
249         except: logger.log_exc("nodemanager: failed in run")
250
251 def run():
252     logger.log("======================================== Entering nodemanager.py")
253     NodeManager().run()
254
255 if __name__ == '__main__':
256     run()
257 else:
258     # This is for debugging purposes.  Open a copy of Python and import nodemanager
259     tools.as_daemon_thread(run)