admin | bae64d8 | 2013-08-01 10:50:15 -0700 | [diff] [blame] | 1 | # 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 | """ |
| 19 | Some of POX's core API and functionality is here, largely in the POXCore |
| 20 | class (an instance of which is available as pox.core.core). |
| 21 | |
| 22 | This includes things like component rendezvous, logging, system status |
| 23 | (up and down events), etc. |
| 24 | """ |
| 25 | |
| 26 | # Set up initial log state |
| 27 | import logging |
| 28 | |
| 29 | import inspect |
| 30 | import time |
| 31 | import 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 | |
| 38 | SQUELCH_TIME = 5 |
| 39 | |
| 40 | _squelch = '' |
| 41 | _squelchTime = 0 |
| 42 | _squelchCount = 0 |
| 43 | |
| 44 | def 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 | |
| 106 | log = (lambda : getLogger())() |
| 107 | |
| 108 | from pox.lib.revent import * |
| 109 | |
| 110 | # Now use revent's exception hook to put exceptions in event handlers into |
| 111 | # the log... |
| 112 | def _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)) |
| 122 | import pox.lib.revent.revent |
| 123 | pox.lib.revent.revent.handleEventException = _revent_exception_hook |
| 124 | |
| 125 | class GoingUpEvent (Event): |
| 126 | """ Fired when system is going up. """ |
| 127 | pass |
| 128 | |
| 129 | class GoingDownEvent (Event): |
| 130 | """ Fired when system is going down. """ |
| 131 | pass |
| 132 | |
| 133 | class UpEvent (Event): |
| 134 | """ Fired when system is up. """ |
| 135 | pass |
| 136 | |
| 137 | class DownEvent (Event): |
| 138 | """ Fired when system is down. """ |
| 139 | pass |
| 140 | |
| 141 | class 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 | |
| 152 | import pox.lib.recoco as recoco |
| 153 | |
| 154 | class 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 | |
| 349 | core = POXCore() |