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