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