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