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