more renamings
[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
24 import logger
25 import tools
26
27 from config import Config
28 from plcapi import PLCAPI 
29 import random
30
31 id="$Id$"
32 savedargv = sys.argv[:]
33
34 # NOTE: modules listed here should also be loaded in this order
35 # see the priority set in each module - lower comes first
36 known_modules=['net','conf_files', 'sm', 'bwmon']
37
38 plugin_path = "/usr/share/NodeManager/plugins"
39
40 default_period=600
41 default_random=301
42
43 parser = optparse.OptionParser()
44 parser.add_option('-d', '--daemon', action='store_true', dest='daemon', default=False, help='run daemonized')
45 parser.add_option('-s', '--startup', action='store_true', dest='startup', default=False, help='run all sliver startup scripts')
46 parser.add_option('-f', '--config', action='store', dest='config', default='/etc/planetlab/plc_config', help='PLC configuration file')
47 parser.add_option('-k', '--session', action='store', dest='session', default='/etc/planetlab/session', help='API session key (or file)')
48 parser.add_option('-p', '--period', action='store', dest='period', default=default_period, 
49                   help='Polling interval (sec) - default %d'%default_period)
50 parser.add_option('-r', '--random', action='store', dest='random', default=default_random, 
51                   help='Range for additional random polling interval (sec) -- default %d'%default_random)
52 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False, help='more verbose log')
53 parser.add_option('-P', '--path', action='store', dest='path', default=plugin_path, help='Path to plugins directory')
54
55 # NOTE: BUG the 'help' for this parser.add_option() wont list plugins from the --path argument
56 parser.add_option('-m', '--module', action='store', dest='module', default='', help='run a single module among '+' '.join(known_modules))
57 (options, args) = parser.parse_args()
58
59 # Deal with plugins directory
60 if os.path.exists(options.path):
61     sys.path.append(options.path)
62     plugins = [ os.path.split(os.path.splitext(x)[0])[1] for x in glob.glob( os.path.join(options.path,'*.py') ) ]
63     known_modules += plugins
64
65 modules = []
66
67 def GetSlivers(config, plc):
68     '''Run call backs defined in modules'''
69     try: 
70         logger.log("nodemanager: Syncing w/ PLC")
71         # retrieve GetSlivers from PLC
72         data = plc.GetSlivers()
73         # use the magic 'default' slice to retrieve system-wide defaults
74         getPLCDefaults(data, config)
75         # tweak the 'vref' attribute from GetSliceFamily
76         setSliversVref (data)
77         # always dump it for debug purposes
78         # used to be done only in verbose; very helpful though, and tedious to obtain,
79         # so let's dump this unconditionnally
80         logger.log_slivers(data)
81         logger.verbose("nodemanager: Sync w/ PLC done")
82     except: 
83         logger.log_exc("nodemanager: failed in GetSlivers")
84         #  XXX So some modules can at least boostrap.
85         logger.log("nodemanager:  Can't contact PLC to GetSlivers().  Continuing.")
86         data = {}
87     #  Invoke GetSlivers() functions from the callback modules
88     for module in modules:
89         logger.verbose('trigerring GetSlivers callback for module %s'%module.__name__)
90         try:        
91             callback = getattr(module, 'GetSlivers')
92             callback(data, config, plc)
93         except: 
94             logger.log_exc("nodemanager: GetSlivers failed to run callback for module %r"%module)
95
96
97 def getPLCDefaults(data, config):
98     '''
99     Get PLC wide defaults from _default system slice.  Adds them to config class.
100     '''
101     for slice in data.get('slivers'): 
102         if slice['name'] == config.PLC_SLICE_PREFIX+"_default":
103             attr_dict = {}
104             for attr in slice.get('attributes'): attr_dict[attr['tagname']] = attr['value'] 
105             if len(attr_dict):
106                 logger.verbose("nodemanager: Found default slice overrides.\n %s" % attr_dict)
107                 config.OVERRIDES = attr_dict
108                 return
109     # NOTE: if an _default slice existed, it would have been found above and
110     #       the routine would return.  Thus, if we've gotten here, then no default
111     #       slice is bound to this node.
112     if 'OVERRIDES' in dir(config): del config.OVERRIDES
113
114
115 def setSliversVref (data):
116     '''
117     Tweak the 'vref' attribute in all slivers based on the 'GetSliceFamily' key
118     '''
119     # GetSlivers exposes the result of GetSliceFamily() as an separate key in data
120     # It is safe to override the attributes with this, as this method has the right logic
121     for sliver in data.get('slivers'): 
122         try:
123             slicefamily=sliver.get('GetSliceFamily')
124             for att in sliver['attributes']:
125                 if att['tagname']=='vref': 
126                     att['value']=slicefamily
127                     continue
128             sliver['attributes'].append({ 'tagname':'vref','value':slicefamily})
129         except:
130             logger.log_exc("nodemanager: Could not overwrite 'vref' attribute from 'GetSliceFamily'",name=sliver['name'])
131     
132
133 def run():
134     try:
135         if options.daemon: tools.daemon()
136
137         # set log level
138         if (options.verbose):
139             logger.set_level(logger.LOG_VERBOSE)
140
141         # Load /etc/planetlab/plc_config
142         config = Config(options.config)
143
144         try:
145             other_pid = tools.pid_file()
146             if other_pid != None:
147                 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)
148                 return
149         except OSError, err:
150             print "Warning while writing PID file:", err
151
152         # Load and start modules
153         if options.module:
154             assert options.module in known_modules
155             running_modules=[options.module]
156             logger.verbose('nodemanager: Running single module %s'%options.module)
157         else:
158             running_modules=known_modules
159         for module in running_modules:
160             try:
161                 m = __import__(module)
162                 m.start(options, config)
163                 modules.append(m)
164             except ImportError, err:
165                 print "Warning while loading module %s:" % module, err
166
167         default_priority=100
168         # sort on priority (lower first)
169         def sort_module_priority (m1,m2):
170             return getattr(m1,'priority',default_priority) - getattr(m2,'priority',default_priority)
171         modules.sort(sort_module_priority)
172
173         logger.log('ordered modules:')
174         for module in modules: logger.log ('%s: %s'%(getattr(module,'priority',default_priority),module.__name__))
175
176         # Load /etc/planetlab/session
177         if os.path.exists(options.session):
178             session = file(options.session).read().strip()
179         else:
180             session = None
181
182         # Initialize XML-RPC client
183         iperiod=int(options.period)
184         irandom=int(options.random)
185         plc = PLCAPI(config.plc_api_uri, config.cacert, session, timeout=iperiod/2)
186
187         #check auth
188         logger.log("nodemanager: Checking Auth.")
189         while plc.check_authentication() != True:
190             try:
191 #                import pdb
192 #               pdb.set_trace()
193                 plc.update_session()
194                 logger.log("nodemanager: Authentication Failure. Retrying")
195             except Exception,e:
196                 logger.log("nodemanager: Retry Failed. (%r); Waiting.."%e)
197             time.sleep(iperiod)
198         logger.log("nodemanager: Authentication Succeeded!")
199
200
201         while True:
202         # Main nodemanager Loop
203             logger.log('nodemanager: mainloop - calling GetSlivers - period=%d random=%d'%(iperiod,irandom))
204             GetSlivers(config, plc)
205             delay=iperiod + random.randrange(0,irandom)
206             logger.log('nodemanager: mainloop - sleeping for %d s'%delay)
207             time.sleep(delay)
208     except: logger.log_exc("nodemanager: failed in run")
209
210
211 if __name__ == '__main__':
212     logger.log("======================================== Entering nodemanager.py "+id)
213     run()
214 else:
215     # This is for debugging purposes.  Open a copy of Python and import nm
216     tools.as_daemon_thread(run)