abc05bbff85ef7a556ef935274596a8bc533fb2d
[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("nm: Syncing w/ PLC")
68         # retrieve GetSlivers from PLC
69         data = plc.GetSlivers()
70         # use the magic 'default' slice to retrieve system-wide defaults
71         getPLCDefaults(data, config)
72         # tweak the 'vref' attribute from GetSliceFamily
73         setSliversVref (data)
74         # always dump it for debug purposes
75         # used to be done only in verbose; very helpful though, and tedious to obtain,
76         # so let's dump this unconditionnally
77         logger.log_slivers(data)
78         logger.verbose("nm: Sync w/ PLC done")
79     except: 
80         logger.log_exc("nm: failed in GetSlivers")
81         #  XXX So some modules can at least boostrap.
82         logger.log("nm:  Can't contact PLC to GetSlivers().  Continuing.")
83         data = {}
84     #  Invoke GetSlivers() functions from the callback modules
85     for module in modules:
86         try:        
87             callback = getattr(module, 'GetSlivers')
88             callback(data, config, plc)
89         except: logger.log_exc("nm: GetSlivers failed to run callback for module %r"%module)
90
91
92 def getPLCDefaults(data, config):
93     '''
94     Get PLC wide defaults from _default system slice.  Adds them to config class.
95     '''
96     for slice in data.get('slivers'): 
97         if slice['name'] == config.PLC_SLICE_PREFIX+"_default":
98             attr_dict = {}
99             for attr in slice.get('attributes'): attr_dict[attr['tagname']] = attr['value'] 
100             if len(attr_dict):
101                 logger.verbose("nm: 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 setSliversVref (data):
111     '''
112     Tweak the 'vref' attribute in all slivers based on the 'GetSliceFamily' key
113     '''
114     # GetSlivers exposes the result of GetSliceFamily() as an separate key in data
115     # It is safe to override the attributes with this, as this method has the right logic
116     for sliver in data.get('slivers'): 
117         try:
118             slicefamily=sliver.get('GetSliceFamily')
119             for att in sliver['attributes']:
120                 if att['tagname']=='vref': 
121                     att['value']=slicefamily
122                     continue
123             sliver['attributes'].append({ 'tagname':'vref','value':slicefamily})
124         except:
125             logger.log_exc("nm: Could not overwrite 'vref' attribute from 'GetSliceFamily'",name=sliver['name'])
126     
127
128 def run():
129     try:
130         if options.daemon: tools.daemon()
131
132         # set log level
133         if (options.verbose):
134             logger.set_level(logger.LOG_VERBOSE)
135
136         # Load /etc/planetlab/plc_config
137         config = Config(options.config)
138
139         try:
140             other_pid = tools.pid_file()
141             if other_pid != None:
142                 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)
143                 return
144         except OSError, err:
145             print "Warning while writing PID file:", err
146
147         # Load and start modules
148         if options.module:
149             assert options.module in known_modules
150             running_modules=[options.module]
151             logger.verbose('nm: Running single module %s'%options.module)
152         else:
153             running_modules=known_modules
154         for module in running_modules:
155             try:
156                 m = __import__(module)
157                 m.start(options, config)
158                 modules.append(m)
159             except ImportError, err:
160                 print "Warning while loading module %s:" % module, err
161
162         # Load /etc/planetlab/session
163         if os.path.exists(options.session):
164             session = file(options.session).read().strip()
165         else:
166             session = None
167
168         # Initialize XML-RPC client
169         iperiod=int(options.period)
170         irandom=int(options.random)
171         plc = PLCAPI(config.plc_api_uri, config.cacert, session, timeout=iperiod/2)
172
173         #check auth
174         logger.log("nm: Checking Auth.")
175         while plc.check_authentication() != True:
176             try:
177                 plc.update_session()
178                 logger.log("nm: Authentication Failure. Retrying")
179             except:
180                 logger.log("nm: Retry Failed. Waiting")
181             time.sleep(iperiod)
182         logger.log("nm: Authentication Succeeded!")
183
184
185         while True:
186         # Main NM Loop
187             logger.verbose('nm: mainloop - calling GetSlivers - period=%d random=%d'%(iperiod,irandom))
188             GetSlivers(config, plc)
189             delay=iperiod + random.randrange(0,irandom)
190             logger.verbose('nm: mainloop - sleeping for %d s'%delay)
191             time.sleep(delay)
192     except: logger.log_exc("nm: failed in run")
193
194
195 if __name__ == '__main__':
196     logger.log("======================================== Entering nm.py "+id)
197     run()
198 else:
199     # This is for debugging purposes.  Open a copy of Python and import nm
200     tools.as_daemon_thread(run)