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