tweak privatebridge to remove exception message when ovs is not installed - prints...
[nodemanager.git] / nodemanager.py
1 #!/usr/bin/python
2 #
3 # Useful information can be found at https://svn.planet-lab.org/wiki/NodeManager
4 #
5
6 # Faiyaz Ahmed <faiyaza at cs dot princeton dot edu>
7 # Copyright (C) 2008 The Trustees of Princeton University
8
9
10 """Node Manager"""
11
12 import optparse
13 import time
14 import xmlrpclib
15 import socket
16 import os
17 import sys
18 import glob
19 import pickle
20 import random
21 import resource
22
23 import logger
24 import tools
25
26 from config import Config
27 from plcapi import PLCAPI
28
29
30 class NodeManager:
31
32     PLUGIN_PATH = "/usr/share/NodeManager/plugins"
33
34     DB_FILE = "/var/lib/nodemanager/getslivers.pickle"
35
36     # the modules in this directory that need to be run
37     # NOTE: modules listed here will also be loaded in this order
38     # once loaded, they get re-ordered after their priority (lower comes first)
39     # for determining the runtime order
40     core_modules=['net', 'conf_files', 'slivermanager', 'bwmon']
41
42     default_period=600
43     default_random=301
44     default_priority=100
45
46     def __init__ (self):
47
48         parser = optparse.OptionParser()
49         parser.add_option('-d', '--daemon', action='store_true', dest='daemon', default=False,
50                           help='run daemonized')
51         parser.add_option('-f', '--config', action='store', dest='config', default='/etc/planetlab/plc_config',
52                           help='PLC configuration file')
53         parser.add_option('-k', '--session', action='store', dest='session', default='/etc/planetlab/session',
54                           help='API session key (or file)')
55         parser.add_option('-p', '--period', action='store', dest='period', default=NodeManager.default_period,
56                           help='Polling interval (sec) - default %d'%NodeManager.default_period)
57         parser.add_option('-r', '--random', action='store', dest='random', default=NodeManager.default_random,
58                           help='Range for additional random polling interval (sec) -- default %d'%NodeManager.default_random)
59         parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
60                           help='more verbose log')
61         parser.add_option('-P', '--path', action='store', dest='path', default=NodeManager.PLUGIN_PATH,
62                           help='Path to plugins directory')
63
64         # NOTE: BUG the 'help' for this parser.add_option() wont list plugins from the --path argument
65         parser.add_option('-m', '--module', action='store', dest='user_module', default='', help='run a single module')
66         (self.options, args) = parser.parse_args()
67
68         if len(args) != 0:
69             parser.print_help()
70             sys.exit(1)
71
72         # determine the modules to be run
73         self.modules = NodeManager.core_modules
74         # Deal with plugins directory
75         if os.path.exists(self.options.path):
76             sys.path.append(self.options.path)
77             plugins = [ os.path.split(os.path.splitext(x)[0])[1]
78                         for x in glob.glob( os.path.join(self.options.path,'*.py') )
79                         if not x.endswith("/__init__.py")
80                         ]
81             self.modules += plugins
82         if self.options.user_module:
83             assert self.options.user_module in self.modules
84             self.modules=[self.options.user_module]
85             logger.verbose('nodemanager: Running single module %s'%self.options.user_module)
86
87
88     def GetSlivers(self, config, plc):
89         """Retrieves GetSlivers at PLC and triggers callbacks defined in modules/plugins"""
90         try:
91             logger.log("nodemanager: Syncing w/ PLC")
92             # retrieve GetSlivers from PLC
93             data = plc.GetSlivers()
94             # use the magic 'default' slice to retrieve system-wide defaults
95             self.getPLCDefaults(data, config)
96             # tweak the 'vref' attribute from GetSliceFamily
97             self.setSliversVref (data)
98             # dump it too, so it can be retrieved later in case of comm. failure
99             self.dumpSlivers(data)
100             # log it for debug purposes, no matter what verbose is
101             logger.log_slivers(data)
102             logger.verbose("nodemanager: Sync w/ PLC done")
103             last_data=data
104         except:
105             logger.log_exc("nodemanager: failed in GetSlivers")
106             #  XXX So some modules can at least boostrap.
107             logger.log("nodemanager:  Can't contact PLC to GetSlivers().  Continuing.")
108             data = {}
109             # for modules that request it though the 'persistent_data' property
110             last_data=self.loadSlivers()
111         #  Invoke GetSlivers() functions from the callback modules
112         for module in self.loaded_modules:
113             logger.verbose('nodemanager: triggering %s.GetSlivers'%module.__name__)
114             try:
115                 callback = getattr(module, 'GetSlivers')
116                 module_data=data
117                 if getattr(module,'persistent_data',False):
118                     module_data=last_data
119                 callback(data, config, plc)
120             except:
121                 logger.log_exc("nodemanager: GetSlivers failed to run callback for module %r"%module)
122
123
124     def getPLCDefaults(self, data, config):
125         """
126         Get PLC wide defaults from _default system slice.  Adds them to config class.
127         """
128         for slice in data.get('slivers'):
129             if slice['name'] == config.PLC_SLICE_PREFIX+"_default":
130                 attr_dict = {}
131                 for attr in slice.get('attributes'): attr_dict[attr['tagname']] = attr['value']
132                 if len(attr_dict):
133                     logger.verbose("nodemanager: Found default slice overrides.\n %s" % attr_dict)
134                     config.OVERRIDES = attr_dict
135                     return
136         # NOTE: if an _default slice existed, it would have been found above and
137         #           the routine would return.  Thus, if we've gotten here, then no default
138         #           slice is bound to this node.
139         if 'OVERRIDES' in dir(config): del config.OVERRIDES
140
141
142     def setSliversVref (self, data):
143         """
144         Tweak the 'vref' attribute in all slivers based on the 'GetSliceFamily' key
145         """
146         # GetSlivers exposes the result of GetSliceFamily() as an separate key in data
147         # It is safe to override the attributes with this, as this method has the right logic
148         for sliver in data.get('slivers'):
149             try:
150                 slicefamily=sliver.get('GetSliceFamily')
151                 for att in sliver['attributes']:
152                     if att['tagname']=='vref':
153                         att['value']=slicefamily
154                         continue
155                 sliver['attributes'].append({ 'tagname':'vref','value':slicefamily})
156             except:
157                 logger.log_exc("nodemanager: Could not overwrite 'vref' attribute from 'GetSliceFamily'",name=sliver['name'])
158
159     def dumpSlivers (self, slivers):
160         f = open(NodeManager.DB_FILE, "w")
161         logger.log ("nodemanager: saving successfully fetched GetSlivers in %s" % NodeManager.DB_FILE)
162         pickle.dump(slivers, f)
163         f.close()
164
165     def loadSlivers (self):
166         try:
167             f = open(NodeManager.DB_FILE, "r+")
168             logger.log("nodemanager: restoring latest known GetSlivers from %s" % NodeManager.DB_FILE)
169             slivers = pickle.load(f)
170             f.close()
171             return slivers
172         except:
173             logger.log("Could not restore GetSlivers from %s" % NodeManager.DB_FILE)
174             return {}
175
176     def run(self):
177         # make sure to create /etc/planetlab/virt so others can read that
178         # used e.g. in vsys-scripts's sliceip
179         tools.get_node_virt()
180         try:
181             if self.options.daemon: tools.daemon()
182
183             # set log level
184             if (self.options.verbose):
185                 logger.set_level(logger.LOG_VERBOSE)
186             tools.init_signals()
187
188             # Load /etc/planetlab/plc_config
189             config = Config(self.options.config)
190
191             try:
192                 other_pid = tools.pid_file()
193                 if other_pid != None:
194                     print """There might be another instance of the node manager running as pid %d.
195 If this is not the case, please remove the pid file %s. -- exiting""" % (other_pid, tools.PID_FILE)
196                     return
197             except OSError, err:
198                 print "Warning while writing PID file:", err
199
200             # load modules
201             self.loaded_modules = []
202             for module in self.modules:
203                 try:
204                     m = __import__(module)
205                     logger.verbose("nodemanager: triggering %s.start"%m.__name__)
206                     try:        m.start()
207                     except:     logger.log("WARNING: module %s did not start")
208                     self.loaded_modules.append(m)
209                 except:
210                     if module not in NodeManager.core_modules:
211                         logger.log_exc ("ERROR while loading module %s - skipped" % module)
212                     else:
213                         logger.log("FATAL : failed to start core module %s"%module)
214                         sys.exit(1)
215
216             # sort on priority (lower first)
217             def sort_module_priority (m1,m2):
218                 return getattr(m1,'priority',NodeManager.default_priority) - getattr(m2,'priority',NodeManager.default_priority)
219             self.loaded_modules.sort(sort_module_priority)
220
221             logger.log('ordered modules:')
222             for module in self.loaded_modules:
223                 logger.log ('%s: %s'%(getattr(module,'priority',NodeManager.default_priority),module.__name__))
224
225             # Load /etc/planetlab/session
226             if os.path.exists(self.options.session):
227                 session = file(self.options.session).read().strip()
228             else:
229                 session = None
230
231
232             # get random periods
233             iperiod=int(self.options.period)
234             irandom=int(self.options.random)
235
236             # Initialize XML-RPC client
237             plc = PLCAPI(config.plc_api_uri, config.cacert, session, timeout=iperiod/2)
238
239             #check auth
240             logger.log("nodemanager: Checking Auth.")
241             while plc.check_authentication() != True:
242                 try:
243                     plc.update_session()
244                     logger.log("nodemanager: Authentication Failure. Retrying")
245                 except Exception,e:
246                     logger.log("nodemanager: Retry Failed. (%r); Waiting.."%e)
247                 time.sleep(iperiod)
248             logger.log("nodemanager: Authentication Succeeded!")
249
250
251             while True:
252             # Main nodemanager Loop
253                 work_beg=time.time()
254                 logger.log('nodemanager: mainloop - calling GetSlivers - period=%d random=%d'%(iperiod,irandom))
255                 self.GetSlivers(config, plc)
256                 delay=iperiod + random.randrange(0,irandom)
257                 work_end=time.time()
258                 work_duration=int(work_end-work_beg)
259                 logger.log('nodemanager: mainloop has worked for %s s - sleeping for %d s'%(work_duration,delay))
260                 time.sleep(delay)
261         except SystemExit:
262             pass
263         except: 
264             logger.log_exc("nodemanager: failed in run")
265
266 def run():
267     logger.log("======================================== Entering nodemanager.py")
268     NodeManager().run()
269
270 if __name__ == '__main__':
271     run()
272 else:
273     # This is for debugging purposes.  Open a copy of Python and import nodemanager
274     tools.as_daemon_thread(run)