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