take down the -s (startingup) option
[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             # check leases and adjust the 'alive' field in slivers
99             self.adjustReservedSlivers (data)
100             # dump it too, so it can be retrieved later in case of comm. failure
101             self.dumpSlivers(data)
102             # log it for debug purposes, no matter what verbose is
103             logger.log_slivers(data)
104             logger.verbose("nodemanager: Sync w/ PLC done")
105             last_data=data
106         except:
107             logger.log_exc("nodemanager: failed in GetSlivers")
108             #  XXX So some modules can at least boostrap.
109             logger.log("nodemanager:  Can't contact PLC to GetSlivers().  Continuing.")
110             data = {}
111             # for modules that request it though the 'persistent_data' property
112             last_data=self.loadSlivers()
113             # adjust again with current time
114             self.adjustReservedSlivers (data)
115         #  Invoke GetSlivers() functions from the callback modules
116         for module in self.loaded_modules:
117             logger.verbose('nodemanager: triggering %s.GetSlivers'%module.__name__)
118             try:
119                 callback = getattr(module, 'GetSlivers')
120                 module_data=data
121                 if getattr(module,'persistent_data',False):
122                     module_data=last_data
123                 callback(data, config, plc)
124             except:
125                 logger.log_exc("nodemanager: GetSlivers failed to run callback for module %r"%module)
126
127
128     def getPLCDefaults(self, data, config):
129         """
130         Get PLC wide defaults from _default system slice.  Adds them to config class.
131         """
132         for slice in data.get('slivers'):
133             if slice['name'] == config.PLC_SLICE_PREFIX+"_default":
134                 attr_dict = {}
135                 for attr in slice.get('attributes'): attr_dict[attr['tagname']] = attr['value']
136                 if len(attr_dict):
137                     logger.verbose("nodemanager: Found default slice overrides.\n %s" % attr_dict)
138                     config.OVERRIDES = attr_dict
139                     return
140         # NOTE: if an _default slice existed, it would have been found above and
141         #           the routine would return.  Thus, if we've gotten here, then no default
142         #           slice is bound to this node.
143         if 'OVERRIDES' in dir(config): del config.OVERRIDES
144
145
146     def setSliversVref (self, data):
147         """
148         Tweak the 'vref' attribute in all slivers based on the 'GetSliceFamily' key
149         """
150         # GetSlivers exposes the result of GetSliceFamily() as an separate key in data
151         # It is safe to override the attributes with this, as this method has the right logic
152         for sliver in data.get('slivers'):
153             try:
154                 slicefamily=sliver.get('GetSliceFamily')
155                 for att in sliver['attributes']:
156                     if att['tagname']=='vref':
157                         att['value']=slicefamily
158                         continue
159                 sliver['attributes'].append({ 'tagname':'vref','value':slicefamily})
160             except:
161                 logger.log_exc("nodemanager: Could not overwrite 'vref' attribute from 'GetSliceFamily'",name=sliver['name'])
162
163     def adjustReservedSlivers (self, data):
164         """
165         On reservable nodes, tweak the 'alive' field to instruct cyclic loop
166         about what to do with slivers.
167         """
168         pass
169
170     def dumpSlivers (self, slivers):
171         f = open(NodeManager.DB_FILE, "w")
172         logger.log ("nodemanager: saving successfully fetched GetSlivers in %s" % NodeManager.DB_FILE)
173         pickle.dump(slivers, f)
174         f.close()
175
176     def loadSlivers (self):
177         try:
178             f = open(NodeManager.DB_FILE, "r+")
179             logger.log("nodemanager: restoring latest known GetSlivers from %s" % NodeManager.DB_FILE)
180             slivers = pickle.load(f)
181             f.close()
182             return slivers
183         except:
184             logger.log("Could not restore GetSlivers from %s" % NodeManager.DB_FILE)
185             return {}
186
187     def run(self):
188         try:
189             if self.options.daemon: tools.daemon()
190
191             # set log level
192             if (self.options.verbose):
193                 logger.set_level(logger.LOG_VERBOSE)
194
195             # Load /etc/planetlab/plc_config
196             config = Config(self.options.config)
197
198             try:
199                 other_pid = tools.pid_file()
200                 if other_pid != None:
201                     print """There might be another instance of the node manager running as pid %d.
202 If this is not the case, please remove the pid file %s. -- exiting""" % (other_pid, tools.PID_FILE)
203                     return
204             except OSError, err:
205                 print "Warning while writing PID file:", err
206
207             # load modules
208             self.loaded_modules = []
209             for module in self.modules:
210                 try:
211                     m = __import__(module)
212                     logger.verbose("nodemanager: triggering %s.start"%m.__name__)
213                     m.start(self.options, config)
214                     self.loaded_modules.append(m)
215                 except ImportError, err:
216                     print "Warning while loading module %s:" % module, err
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                 logger.log('nodemanager: mainloop - calling GetSlivers - period=%d random=%d'%(iperiod,irandom))
256                 self.GetSlivers(config, plc)
257                 delay=iperiod + random.randrange(0,irandom)
258                 logger.log('nodemanager: mainloop - sleeping for %d s'%delay)
259                 time.sleep(delay)
260         except: logger.log_exc("nodemanager: failed in run")
261
262 def run():
263     logger.log("======================================== Entering nodemanager.py")
264     NodeManager().run()
265
266 if __name__ == '__main__':
267     run()
268 else:
269     # This is for debugging purposes.  Open a copy of Python and import nodemanager
270     tools.as_daemon_thread(run)