blob: 8b31e7acad844b070eb2f7ee5413f9dab54b3868 [file] [log] [blame]
adminbae64d82013-08-01 10:50:15 -07001# Copyright 2011 James McCauley
2#
3# This file is part of POX.
4#
5# POX is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# POX is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with POX. If not, see <http://www.gnu.org/licenses/>.
17
18"""
19Some of POX's core API and functionality is here, largely in the POXCore
20class (an instance of which is available as pox.core.core).
21
22This includes things like component rendezvous, logging, system status
23(up and down events), etc.
24"""
25
26# Set up initial log state
27import logging
28
29import inspect
30import time
31import os
32
33_path = inspect.stack()[0][1]
34_ext_path = _path[0:_path.rindex(os.sep)]
35_ext_path = os.path.dirname(_ext_path) + os.sep
36_path = os.path.dirname(_path) + os.sep
37
38SQUELCH_TIME = 5
39
40_squelch = ''
41_squelchTime = 0
42_squelchCount = 0
43
44def getLogger (name=None, moreFrames=0):
45 """
46 In general, you don't need to call this directly, and will use
47 core.getLogger() instead.
48 """
49 if name is None:
50 s = inspect.stack()[1+moreFrames]
51 name = s[1]
52 if name.endswith('.py'):
53 name = name[0:-3]
54 elif name.endswith('.pyc'):
55 name = name[0:-4]
56 if name.startswith(_path):
57 name = name[len(_path):]
58 elif name.startswith(_ext_path):
59 name = name[len(_ext_path):]
60 name = name.replace('/', '.').replace('\\', '.') #FIXME: use os.path or whatever
61
62 # Remove double names ("topology.topology" -> "topology")
63 if name.find('.') != -1:
64 n = name.split('.')
65 if len(n) >= 2:
66 if n[-1] == n[-2]:
67 del n[-1]
68 name = '.'.join(n)
69
70 if name.endswith(".__init__"):
71 name = name.rsplit(".__init__",1)[0]
72
73 l = logging.getLogger(name)
74 g=globals()
75 if not hasattr(l, "print"):
76 def printmsg (*args, **kw):
77 #squelch = kw.get('squelch', True)
78 msg = ' '.join((str(s) for s in args))
79 s = inspect.stack()[1]
80 o = '['
81 if 'self' in s[0].f_locals:
82 o += s[0].f_locals['self'].__class__.__name__ + '.'
83 o += s[3] + ':' + str(s[2]) + '] '
84 o += msg
85 if o == _squelch:
86 if time.time() >= _squelchTime:
87 l.debug("[Previous message repeated %i more times]" % (g['_squelchCount']+1,))
88 g['_squelchCount'] = 0
89 g['_squelchTime'] = time.time() + SQUELCH_TIME
90 else:
91 g['_squelchCount'] += 1
92 else:
93 g['_squelch'] = o
94 if g['_squelchCount'] > 0:
95 l.debug("[Previous message repeated %i more times]" % (g['_squelchCount'],))
96 g['_squelchCount'] = 0
97 g['_squelchTime'] = time.time() + SQUELCH_TIME
98 l.debug(o)
99
100 setattr(l, "print", printmsg)
101 setattr(l, "msg", printmsg)
102
103 return l
104
105
106log = (lambda : getLogger())()
107
108from pox.lib.revent import *
109
110# Now use revent's exception hook to put exceptions in event handlers into
111# the log...
112def _revent_exception_hook (source, event, args, kw, exc_info):
113 try:
114 c = source
115 t = event
116 if hasattr(c, "__class__"): c = c.__class__.__name__
117 if isinstance(t, Event): t = t.__class__.__name__
118 elif issubclass(t, Event): t = t.__name__
119 except:
120 pass
121 log.exception("Exception while handling %s!%s...\n" % (c,t))
122import pox.lib.revent.revent
123pox.lib.revent.revent.handleEventException = _revent_exception_hook
124
125class GoingUpEvent (Event):
126 """ Fired when system is going up. """
127 pass
128
129class GoingDownEvent (Event):
130 """ Fired when system is going down. """
131 pass
132
133class UpEvent (Event):
134 """ Fired when system is up. """
135 pass
136
137class DownEvent (Event):
138 """ Fired when system is down. """
139 pass
140
141class ComponentRegistered (Event):
142 """
143 This is raised by core whenever a new component is registered.
144 By watching this, a component can monitor whether other components it
145 depends on are available.
146 """
147 def __init__ (self, name, component):
148 Event.__init__(self)
149 self.name = name
150 self.component = component
151
152import pox.lib.recoco as recoco
153
154class POXCore (EventMixin):
155 """
156 A nexus of of the POX API.
157
158 pox.core.core is a reference to an instance of this class. This class
159 serves a number of functions.
160
161 An important one is that it can serve as a rendezvous point for
162 components. A component can register objects on core, and they can
163 then be accessed on the core object (e.g., if you register foo, then
164 there will then be a pox.core.core.foo). In many cases, this means you
165 won't need to import a module.
166
167 Another purpose to the central registration is that it decouples
168 functionality from a specific module. If myL2Switch and yourL2Switch
169 both register as "switch" and both provide the same API, then it doesn't
170 matter. Doing this with imports is a pain.
171
172 Additionally, a number of commmon API functions are vailable here.
173 """
174 _eventMixin_events = set([
175 UpEvent,
176 DownEvent,
177 GoingUpEvent,
178 GoingDownEvent,
179 ComponentRegistered
180 ])
181
182 def __init__ (self):
183 self.debug = False
184 self.running = True
185 self.components = {}
186
187 self.version = (0,0,0)
188 print "{0} / Copyright 2011 James McCauley".format(self.version_string)
189
190 self.scheduler = recoco.Scheduler(daemon=True)
191
192 @property
193 def version_string (self):
194 return "POX " + '.'.join(map(str, self.version))
195
196 def callDelayed (_self, _seconds, _func, *args, **kw):
197 """
198 Calls the function at a later time.
199 This is just a wrapper around a recoco timer.
200 """
201 t = recoco.Timer(_seconds, _func, args=args, kw=kw,
202 scheduler = _self.scheduler)
203 return t
204
205 def callLater (_self, _func, *args, **kw):
206 # first arg is `_self` rather than `self` in case the user wants
207 # to specify self as a keyword argument
208 """
209 Call the given function with the given arguments within the context
210 of the co-operative threading environment.
211 It actually calls it sooner rather than later. ;)
212 Much of POX is written without locks because it's all thread-safe
213 with respect to itself, as it's written using the recoco co-operative
214 threading library. If you have a real thread outside of the
215 co-operative thread context, you need to be careful about calling
216 things within it. This function provides a rather simple way that
217 works for most situations: you give it a callable (like a method)
218 and some arguments, and it will call that callable with those
219 arguments from within the co-operative threader, taking care of
220 synchronization for you.
221 """
222 _self.scheduler.callLater(_func, *args, **kw)
223
224 def raiseLater (_self, _obj, *args, **kw):
225 # first arg is `_self` rather than `self` in case the user wants
226 # to specify self as a keyword argument
227 """
228 This is similar to callLater(), but provides an easy way to raise a
229 revent event from outide the co-operative context.
230 Rather than foo.raiseEvent(BarEvent, baz, spam), you just do
231 core.raiseLater(foo, BarEvent, baz, spam).
232 """
233 _self.scheduler.callLater(_obj.raiseEvent, *args, **kw)
234
235 def getLogger (self, *args, **kw):
236 """
237 Returns a logger. Pass it the name you want if you'd like to specify
238 one (e.g., core.getLogger("foo")). If you don't specify a name, it
239 will make one up based on the module name it is called from.
240 """
241 return getLogger(moreFrames=1,*args, **kw)
242
243 def quit (self):
244 """
245 Shut down POX.
246 """
247 if self.running:
248 self.running = False
249 log.info("Going down...")
250 import gc
251 gc.collect()
252 self.raiseEvent(GoingDownEvent())
253 self.callLater(self.scheduler.quit)
254 for i in range(50):
255 if self.scheduler._hasQuit: break
256 gc.collect()
257 time.sleep(.1)
258 if not self.scheduler._allDone:
259 log.warning("Scheduler didn't quit in time")
260 self.raiseEvent(DownEvent())
261 log.info("Down.")
262
263 def goUp (self):
264 log.debug(self.version_string + " going up...")
265
266 import platform
267 py = "{impl} ({vers}/{build})".format(
268 impl=platform.python_implementation(),
269 vers=platform.python_version(),
270 build=platform.python_build()[1].replace(" "," "))
271 log.debug("Running on " + py)
272
273 self.raiseEvent(GoingUpEvent())
274 log.info(self.version_string + " is up.")
275 self.raiseEvent(UpEvent())
276
277 def hasComponent (self, name):
278 """
279 Returns True if a component with the given name has been registered.
280 """
281 return name in self.components
282
283 def registerNew (self, __componentClass, *args, **kw):
284 """
285 Give it a class (and optional __init__ arguments), and it will
286 create an instance and register it using the class name. If the
287 instance has a _core_name property, it will use that instead.
288 It returns the new instance.
289 core.registerNew(FooClass, arg) is roughly equivalent to
290 core.register("FooClass", FooClass(arg)).
291 """
292 name = __componentClass.__name__
293 obj = __componentClass(*args, **kw)
294 if hasattr(obj, '_core_name'):
295 # Default overridden
296 name = obj._core_name
297 self.register(name, obj)
298 return obj
299
300 def register (self, name, component):
301 """
302 Makes the object "component" available as pox.core.core.name.
303 """
304 #TODO: weak references?
305 if name in self.components:
306 log.warn("Warning: Registered '%s' multipled times" % (name,))
307 self.components[name] = component
308 self.raiseEventNoErrors(ComponentRegistered, name, component)
309
310 def listenToDependencies(self, sink, components):
311 """
312 If a component depends on having other components
313 registered with core before it can boot, it can use this method to
314 check for registration, and listen to events on those dependencies.
315
316 Note that event handlers named with the _handle* pattern in the sink must
317 include the name of the desired source as a prefix. For example, if topology is a
318 dependency, a handler for topology's SwitchJoin event must be labeled:
319 def _handle_topology_SwitchJoin(...)
320
321 sink - the component waiting on dependencies
322 components - a list of dependent component names
323
324 Returns whether all of the desired components are registered.
325 """
326 if components == None or len(components) == 0:
327 return True
328
329 got = set()
330 for c in components:
331 if self.hasComponent(c):
332 setattr(sink, c, getattr(self, c))
333 sink.listenTo(getattr(self, c), prefix=c)
334 got.add(c)
335 else:
336 setattr(sink, c, None)
337 for c in got:
338 components.remove(c)
339 if len(components) == 0:
340 log.debug(sink.__class__.__name__ + " ready")
341 return True
342 return False
343
344 def __getattr__ (self, name):
345 if name not in self.components:
346 raise AttributeError("'%s' not registered" % (name,))
347 return self.components[name]
348
349core = POXCore()