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