log subprocess calls.
[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 import net
28
29 id="$Id$"
30 savedargv = sys.argv[:]
31
32 known_modules=['conf_files', 'sm', 'bwmon', 'vsys', 'codemux']
33
34 parser = optparse.OptionParser()
35 parser.add_option('-d', '--daemon', action='store_true', dest='daemon', default=False, help='run daemonized')
36 parser.add_option('-s', '--startup', action='store_true', dest='startup', default=False, help='run all sliver startup scripts')
37 parser.add_option('-f', '--config', action='store', dest='config', default='/etc/planetlab/plc_config', help='PLC configuration file')
38 parser.add_option('-k', '--session', action='store', dest='session', default='/etc/planetlab/session', help='API session key (or file)')
39 parser.add_option('-p', '--period', action='store', dest='period', default=600, help='Polling interval (sec)')
40 parser.add_option('-r', '--random', action='store', dest='random', default=301, help='Range for additional random polling interval (sec)')
41 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False, help='more verbose log')
42 parser.add_option('-P', '--path', action='store', dest='path', default='/usr/share/NodeManager/plugins', help='Path to plugins directory')
43 parser.add_option('-m', '--module', action='store', dest='module', default='', help='run a single module among '+' '.join(known_modules))
44 (options, args) = parser.parse_args()
45
46 # Deal with plugins directory
47 if os.path.exists(options.path):
48     sys.path.append(options.path)
49     known_modules += [i[:-3] for i in os.listdir(options.path) if i.endswith(".py") and (i[:-3] not in known_modules)]
50
51 modules = []
52
53 def GetSlivers(plc, config):
54     '''Run call backs defined in modules'''
55     try: 
56         logger.log("Syncing w/ PLC")
57         data = plc.GetSlivers()
58         getPLCDefaults(data, config)
59         if (options.verbose): logger.log_slivers(data)
60     except: 
61         logger.log_exc()
62         #  XXX So some modules can at least boostrap.
63         logger.log("nm:  Can't contact PLC to GetSlivers().  Continuing.")
64         data = {}
65     # Set i2 ip list for nodes in I2 nodegroup
66     # and init network interfaces (unless overridden)
67     try: net.GetSlivers(plc, data, config) # TODO - num of args needs to be unified across mods.
68     except: logger.log_exc()
69     #  All other callback modules
70     for module in modules:
71         try:        
72             callback = getattr(module, 'GetSlivers')
73             callback(data)
74         except: logger.log_exc()
75
76
77 def getPLCDefaults(data, config):
78     '''
79     Get PLC wide defaults from _default system slice.  Adds them to config class.
80     '''
81     for slice in data.get('slivers'): 
82         if slice['name'] == config.PLC_SLICE_PREFIX+"_default":
83             attr_dict = {}
84             for attr in slice.get('attributes'): attr_dict[attr['name']] = attr['value'] 
85             if len(attr_dict):
86                 logger.verbose("Found default slice overrides.\n %s" % attr_dict)
87                 config.OVERRIDES = attr_dict
88             return 
89     if 'OVERRIDES' in dir(config): del config.OVERRIDES
90
91
92 def run():
93     try:
94         if options.daemon: tools.daemon()
95
96         # set log level
97         if (options.verbose):
98             logger.set_level(logger.LOG_VERBOSE)
99
100         # Load /etc/planetlab/plc_config
101         config = Config(options.config)
102
103         try:
104             other_pid = tools.pid_file()
105             if other_pid != None:
106                 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)
107                 return
108         except OSError, err:
109             print "Warning while writing PID file:", err
110
111         # Load and start modules
112         if options.module:
113             assert options.module in known_modules
114             running_modules=[options.module]
115             logger.verbose('Running single module %s'%options.module)
116         else:
117             running_modules=known_modules
118         for module in running_modules:
119             try:
120                 m = __import__(module)
121                 m.start(options, config)
122                 modules.append(m)
123             except ImportError, err:
124                 print "Warning while loading module %s:" % module, err
125
126         # Load /etc/planetlab/session
127         if os.path.exists(options.session):
128             session = file(options.session).read().strip()
129         else:
130             session = options.session
131
132         # Initialize XML-RPC client
133         iperiod=int(options.period)
134         irandom=int(options.random)
135         plc = PLCAPI(config.plc_api_uri, config.cacert, session, timeout=iperiod/2)
136
137         while True:
138         # Main NM Loop
139             logger.verbose('mainloop - nm:getSlivers - period=%d random=%d'%(iperiod,irandom))
140             GetSlivers(plc, config)
141             delay=iperiod + random.randrange(0,irandom)
142             logger.verbose('mainloop - sleeping for %d s'%delay)
143             time.sleep(delay)
144     except: logger.log_exc()
145
146
147 if __name__ == '__main__':
148     logger.log("Entering nm.py "+id)
149     stacklim = 512*1024  # 0.5 MiB
150     curlim = resource.getrlimit(resource.RLIMIT_STACK)[0]  # soft limit
151     if curlim > stacklim:
152         resource.setrlimit(resource.RLIMIT_STACK, (stacklim, stacklim))
153         # for some reason, doesn't take effect properly without the exec()
154         python = '/usr/bin/python'
155         os.execv(python, [python] + savedargv)
156     run()
157 else:
158     # This is for debugging purposes.  Open a copy of Python and import nm
159     tools.as_daemon_thread(run)