class VirtualSubNet: def __init__(self, owner): self._lock = Lock() self._thread = None self.owner = owner return def start(self): self._lock.acquire() try: if not self._thread: t = VirtualSubNetThread(self.owner) t.start() # Add interlock for successful start-up... (wait on Q for # 10 seconds). self._thread = t finally: self._lock.release() return def stop(self): self._lock.acquire() try: if self._thread: self._thread.stop() self._thread = None finally: self._lock.release() return
class XCommandIface(TcpConnection): def __init__(self, port, host, debug): self.__lock = Lock() # lock serializes XCommandIface messaging super(XCommandIface, self).__init__(port, host, debug) return def write(self, method_name, params): self.__lock.acquire() try: if not self.connection_ok(): self.open_connection() # marshal data from param tuple data = xmlrpclib.dumps(tuple([params]), method_name) #payload is 4 byte, little endian, field representing the length of #the xml data, followed by the data msg = struct.pack('<I', len(data)) + data try: self._s.send(msg) except: msglog.log('Adura', ERR, 'Error writing to XCommand socket.') raise EConnectionError rslt = self.read() finally: self.close_connection() self.__lock.release() def read(self): # leading 4 bytes indicates length of xml-rpc response payload read_len = struct.unpack('<I', self._s.recv(4, timeout=SOCK_OP_TIMEOUT))[0] # retreive and marshall the results. If the xml-rpc packet represents a # fault condition, loads() raises a Fault exception. @fixme - need a # better understanding of their normal result structure rslt = xmlrpclib.loads(self._s.recv(read_len, timeout=SOCK_OP_TIMEOUT))[0] return rslt
class SocketMap(dict): def __init__(self, *args, **kw): dict.__init__(self, *args, **kw) self.__busy = False self.__lock = Lock() # The StubNotifier is used during the creation of the real # SocketMapNotifier(). self.__notifier = StubNotifier() # Creating the SocketMapNotifier will add it to this SocketMap. self.__notifier = SocketMapNotifier(self) return def wakeup(self, force=False): self.__lock.acquire() force = force or self.__busy try: if force: self.__notifier.wakeup() finally: self.__lock.release() return def __delitem__(self,y): self.__lock.acquire() try: result = dict.__delitem__(self,y) if self.__busy: self.__notifier.wakeup() return result finally: self.__lock.release() raise EUnreachable() def __setitem__(self,i,y): self.__lock.acquire() try: result = dict.__setitem__(self,i,y) if self.__busy: self.__notifier.wakeup() return result finally: self.__lock.release() raise EUnreachable() def __invoke(self, func, *args): self.__lock.acquire() self.__busy = True self.__lock.release() try: result = apply(func, args) finally: self.__lock.acquire() self.__busy = False self.__lock.release() return result def poll(self, timeout=0.0): return self.__invoke(asyncore.poll, timeout, self) def poll2(self, timeout=0.0): return self.__invoke(asyncore.poll2, timeout, self) def poll3(self, timeout=0.0): return self.__invoke(asyncore.poll3, timeout, self) def loop(self, timeout=30.0, use_poll=0): return self.__invoke(asyncore.loop, timeout, use_poll, self)
class CANBus(ARMNode, AutoDiscoveredNode): def __init__(self): ARMNode.__init__(self) AutoDiscoveredNode.__init__(self) self._lock = Lock() self.conversion_list = {} self._queue = Queue() self.debug = 0 self.running = 0 self._start_called = 0 self.devices = '' self.device_addresses = [] self._been_discovered = 0 def lock(self): self._lock.acquire() def unlock(self): self._lock.release() ## # @see node.ARMNode#configure # def configure(self,config): ARMNode.configure(self,config) def configuration(self): config = ARMNode.configuration(self) #get_attribute(self, 'devices', config) return config ## # start temperature conversions # def start(self): ARMNode.start(self) self.running = 0 def stop(self): self.running = 0 ## # discover and create object instances # def _discover_children(self, force=0): if force: self._been_discovered = 0 if self.running == 1 and not self._been_discovered: pass return self._nascent_children
class ExplicitSocketMap(dict): def __init__(self, *args, **kw): dict.__init__(self, *args, **kw) self.__lock = Lock() self.__notifier = SocketMapNotifier(self) return def wakeup(self): self.__lock.acquire() try: self.__notifier.wakeup() finally: self.__lock.release() return
class UniqueID(PersistentDataObject): def __init__(self,node): self._lock = Lock() self.id = 0 PersistentDataObject.__init__(self,node) self.load() def allocate_id(self): self._lock.acquire() try: id = self.id self.id += 1 self.save('id') finally: self._lock.release() return id
class _Lock: def __init__(self): self._minutes = 0 self._lock = Lock() self._scheduled = None self._stack = None def acquire(self,blocking=0): value = self._lock.acquire(blocking) self._stack = traceback.extract_stack() self._schedule_print() return value def release(self): try: if self._scheduled: self._scheduled.cancel() finally: self._lock.release() def locked(self): return self._lock.locked() def _schedule_print(self): self._scheduled = scheduler.after(60,self._print,(self._stack,)) def _print(self,stack): self._minutes += 1 print 'Lock acquired: %s min' % self._minutes print string.join(traceback.format_list(stack)) if self.locked(): self._schedule_print()
class SSL: def __init__(self,socket,*args): if not isinstance(socket,_Socket): raise EInvalidValue('socket',socket, 'Must be mpx.lib.socket.socket') self._safety = 0 if isinstance(socket,_SafetySocket): self._safety = 1 self._socket = socket self._ssl = _original_ssl(socket._socket,*args) self._lock = Lock() def read(self,count=None,timeout=None): args = (count,) if count is None: args = () if ((self._safety or timeout is not None) and not self._socket._wait_readable(0)): blocking = self._socket.is_blocking() self._lock.acquire() try: self._socket.setblocking(0) try: return self._ssl.read(*args) except sslerror,why: if why[0] not in (2,11) and blocking: raise why finally: self._socket.setblocking(blocking) self._lock.release() else: return self._ssl.read(*args) if (self._socket._connected and not self._socket._wait_readable(timeout)): raise ETimeout('Socket did not become readable') return self._ssl.read(*args) def write(self,data,timeout=None): if ((timeout is not None or self._safety) and self._socket._connected): if not self._socket._wait_writable(timeout): raise ETimeout('Socket did not become writable') self._lock.acquire() try: return self._ssl.write(data) finally: self._lock.release() def __getattr__(self,name): return getattr(self._ssl,name)
class TunnelManager(CompositeNode): def __init__(self, *args): global PTY_DEVS self._lock = Lock() self._pty_devs = [] self._ptys_allocated = 0 module_lock.acquire() try: if PTY_DEVS is None: PTY_DEVS = [] for major in 'wxyz': for minor in '0123456789abcdef': PTY_DEVS.append('/dev/pty%s%s' % (major, minor)) finally: module_lock.release() def configure(self, config): # vcp_limit is a "hidden" attribute. set_attribute(self, 'vcp_limit', 64, config, int) CompositeNode.configure(self, config) def configuration(self): config = CompositeNode.configuration(self) get_attribute(self, 'vcp_limit', config, str) return config ## # Allocate a pseudo-terminal for use by the Port object. # # @return a string, ie. /dev/ptyr0. def get_pty(self): global PTY_DEVS self._lock.acquire() try: while len(PTY_DEVS): pty = PTY_DEVS.pop() try: # confirm that the pty is accessible. fd = open(pty) fd.close() self._ptys_allocated += 1 return pty except: pass raise EResourceError finally: self._lock.release()
class Consumer(EventConsumerAbstract): def __init__(self, *args, **kw): EventConsumerAbstract.__init__(self, *args, **kw) self.entries = [] self.errors = [] self.lock = Lock() def event_thread(self,event): # The values returned in the event: values = event.values # The column as read from the source Log instance: column_dict = event.source[event.seq] # A map of COLUMN_DICT keys to VALUES indexes. column_value_map = { 'timestamp':0, 'reverse':1, 'c2':2, 'c3':3 } # Validate that the list of values matches the actual column in # the log: for key,index in column_value_map.items(): if not column_dict.has_key(key): self.errors.append('column_dict has no %r key.' % key) return if index >= len(values): self.errors.append('Index(%r) >= len(values:%r).' % (index, len(values))) return if column_dict[key] != values[index]: self.errors.append( 'column_dict[%r]:%r != values[%r]:%r' % ( key, column_dict[key], index, values[index])) return self.lock.acquire() try: # If any entries are left, the test will fail. self.entries.remove(values) except: # Also, if errors is not empty the test will fail. self.errors.append("Failed to find %r in entries." % values) self.lock.release() def event_handler(self,event): t = Thread(target=self.event_thread, args=(event,)) t.start() return
class KwList(CircList): def __init__(self, length): super(KwList, self).__init__(length) self.__last_ts = time.time() self.__last_kwh = None self.__lock = Lock() return def add(self, kwh, ts): try: kwh = float(kwh) except: return if self.__last_kwh is None: self.__last_kwh = kwh self.__lock.acquire() try: if kwh < self.__last_kwh or ts < self.__last_ts: # either time shifted on us or our kwh rolled\reset. self.clear() self.__last_ts = ts self.__last_kwh = kwh self.append(KwEntry(kwh, ts)) finally: self.__lock.release() return def moving_average(self): avg = None self.__lock.acquire() try: if len(self) >= 2: s_kw_entry = self._data[0] e_kw_entry = self._data[-1] delta_s = e_kw_entry.get_ts() - s_kw_entry.get_ts() delta_kwh = e_kw_entry.get_kwh() - s_kw_entry.get_kwh() avg = delta_kwh / self._seconds_as_hours(delta_s) finally: self.__lock.release() return avg def _seconds_as_hours(self, seconds): return (seconds / 60.0) / 60.0
class CachedValue(CompositeNode): def __init__(self): self._lock = Lock() self._value = _Value() CompositeNode.__init__(self) def configure(self,config): CompositeNode.configure(self,config) set_attribute(self, 'expires_after', 0, config, float) set_attribute(self, 'node', self.parent, config, as_node) def configuration(self): config = CompositeNode.configuration(self) get_attribute(self, 'expires_after', config, str) get_attribute(self, 'node', config, as_node_url) return config def get(self, skipCache=0): self._lock.acquire() try: if self._value.age() > self.expires_after: self._value.set(self.node.get()) finally: self._lock.release() return self._value.get()
class mytime(object): def __init__(self, systime): self._skewlock = Lock() self._skewdetected = Event() self.base_time_time = systime self.base_uptime_time = uptime.secs() def notify_detected(self): self._skewlock.acquire() self._skewdetected.set() self._skewlock.release() def await_detection(self, timeout = None): self._skewlock.acquire() self._skewdetected.clear() self._skewlock.release() return self._skewdetected.wait(timeout) def time(self): dtime = uptime.secs() - self.base_uptime_time comp_time = dtime + self.base_time_time if debug: print 'mytime.time() returning %f.' % comp_time return comp_time def skew_base_time(self, skew): self.base_time_time = self.base_time_time + skew
class AlarmLogger(ServiceNode): def __init__(self): self.__lock = Lock() ServiceNode.__init__(self) def configure(self,config): ServiceNode.configure(self,config) set_attribute(self,'log',REQUIRED,config) def configuration(self): config = ServiceNode.configuration(self) get_attribute(self,'log',config,as_node_url) return config def start(self): self.log = as_node(self.log) ServiceNode.start(self) def stop(self): self.log = as_node_url(self.log) ServiceNode.stop(self) def export(self,alarm): self.__lock.acquire() try: self.log.add_entry([time.time(),alarm.source.name,alarm.timestamp, alarm.critical,alarm.values,alarm.message]) finally: self.__lock.release()
class _CriticalData: def __init__(self): self._lock = Lock() self._cond = Condition(self._lock) self.state = ConnectionMixin.IDLE self.connection_count = 0 def acquire(self): return self._lock.acquire() def release(self): return self._lock.release() def wait(self, timeout=None): if timeout != None: self._cond.wait(timeout) else: self._cond.wait() def notify(self): self._cond.notify() def increment_connection_count(self): self.connection_count += 1 self.state = ConnectionMixin.UP return self.connection_count def decrement_connection_count(self): self.connection_count -= 1 if self.connection_count <1 : self.state = ConnectionMixin.DOWN def get_state(self): return self.state def set_state(self,state): self.state = state
class _CriticalData: def __init__(self): self._lock = Lock() self._cond = Condition(self._lock) self.state = ConnectionMixin.IDLE self.connection_count = 0 def acquire(self): return self._lock.acquire() def release(self): return self._lock.release() def wait(self, timeout=None): if timeout != None: self._cond.wait(timeout) else: self._cond.wait() def notify(self): self._cond.notify() def increment_connection_count(self): self.connection_count += 1 self.state = ConnectionMixin.UP return self.connection_count def decrement_connection_count(self): self.connection_count -= 1 if self.connection_count < 1: self.state = ConnectionMixin.DOWN def get_state(self): return self.state def set_state(self, state): self.state = state
class _UserDictionary(PersistentDataObject): def __init__(self): PersistentDataObject.__init__(self,'mpx.lib.user._UserDictionary') self.users = {} self.__lock = Lock() def has_key(self,name): self.__lock.acquire() try: return self.users.has_key(name) finally: self.__lock.release() def __getitem__(self,name): self.__lock.acquire() try: return self.users[name] finally: self.__lock.release() def __setitem__(self,name,key): self.__lock.acquire() try: self.users[name] = key self.save() finally: self.__lock.release()
class PeriodicExporter(Exporter, EventConsumerMixin): def __init__(self): Exporter.__init__(self) EventConsumerMixin.__init__(self, self.handle_connected, self.connection_event_error) self.running = 0 self._scheduled = None self._lock = Lock() def handle_connected(self, event): self.msglog("%s Got connection event" % self.name) if event.__class__ == ConnectionEvent: self.msglog("Connection state is %s." % str(event.state)) if event.state == ConnectionEvent.STATE.UP: self.msglog("Going to start export.") self.go() else: msg = ( "Unknown event recieved by %s from %s." % (self.name, str(self.connection_node)) ) + " Event: %s" % str(event) msglog.log("broadway", msglog.types.WARN, msg) def connection_event_error(self, exc, event): msg = ( "Connection Event for " + str(self.connection_node) + " had the following Error\n" + "Event: " + str(event) + "Error: " + str(exc) ) msglog.log("broadway", msglog.types.WARN, msg) def msglog(self, msg, force=0): if self.debug or force: msglog.log("broadway.mpx.service.data.periodic_exporter", msglog.types.DB, msg) def configure(self, config): map_to_attribute(self, "period", 900, config, map_to_seconds) if self.period == 0: raise EInvalidValue("period", self.period, "Export period cannot be 0") set_attribute(self, "debug", 0, config, as_boolean) set_attribute(self, "synchronize_on", "00:00", config) set_attribute(self, "timeout", 60, config, int) set_attribute(self, "connection_node", "/services/network", config) set_attribute(self, "connection_attempts", 3, config, int) set_attribute(self, "always_export", 0, config, as_boolean) set_attribute(self, "breakup_on_period", 0, config, as_boolean) Exporter.configure(self, config) self._time = _TimeStore(self) def configuration(self): config = Exporter.configuration(self) map_from_attribute(self, "period", config, map_from_seconds) get_attribute(self, "connection_node", config) get_attribute(self, "debug", config, str) get_attribute(self, "connection_attempts", config) get_attribute(self, "timeout", config) get_attribute(self, "always_export", config, str) get_attribute(self, "synchronize_on", config) get_attribute(self, "breakup_on_period", config, str) return config def start(self): Exporter.start(self) if not self.running: node = as_node(self.connection_node) if hasattr(node, "event_subscribe"): node.event_subscribe(self, ConnectionEvent) else: if self.debug: msg = "Connection node: " + str(self.connection_node) + " is not an event producer." msglog.log("broadway", msglog.types.INFO, msg) self.connection = node self.running = 1 self._init_next_time() self._schedule() else: raise EAlreadyRunning def stop(self): self.running = 0 if self._scheduled is not None: try: self._scheduled.cancel() except: pass Exporter.stop(self) def go(self, end_time, start_time=None): if self._lock.locked(): msglog.log("broadway", msglog.types.WARN, "Last export still active, skipping current request.") return Exporter_ThreadPool.queue_noresult(self._complete, end_time, start_time) def scheduled_time(self): return self.next_time() - self.period def last_time(self): return self._time.get_last_time() def _schedule(self): next = self.next_time() self._scheduled = scheduler.at(next, self.go, (next,)) def _complete(self, end_time, start_time=None): self._lock.acquire() try: self._export(end_time, start_time) except: msglog.exception() self._lock.release() if self.running: self._schedule() ## # def _init_next_time(self): time_format = "%Y%m%d %H:%M:%S" sync_format = "%Y%m%d " + self.synchronize_on + ":00" current_time = int(time.time()) f_sync = time.strftime(sync_format, self.time_function(current_time)) f_now = time.strftime(time_format, self.time_function(current_time)) sync = time.mktime(time.strptime(f_sync, time_format)) now = time.mktime(time.strptime(f_now, time_format)) if now > sync: # sync time in past, add one day to sync time. sync += map_to_seconds({"days": 1}) gap = sync - now if self.period > gap: sync_time = current_time + gap else: sync_time = current_time + (gap % self.period) # # # self._next_time = sync_time return self._next_time def next_time(self): current_time = time.time() while self._next_time < current_time: self._next_time += self.period return self._next_time def data_since_export(self): start_time = self.last_time() end_time = self.next_time() return self.log.get_slice("timestamp", start_time, end_time) def formatted_data_since_export_as_string(self): length = 0 stream = self.formatted_data_since_export() text = stream.read(1024) while len(text) > length: length = len(text) text += stream.read(1024) return text def formatted_data_since_export(self): return self.formatter.format(self.data_since_export()) def export_data_since_export(self): return self.transporter.transport(self.formatted_data_since_export()) def _export(self, end_time, start_time=None): attempts = 0 connected = 0 while attempts < self.connection_attempts: self.msglog("Acquiring connection %s." % str(self.connection_node)) try: connected = self.connection.acquire() except: msglog.exception() if connected: self.msglog("Connection acquired") break attempts += 1 self.msglog("Connection acquire failed %s times." % attempts) else: raise EConnectionError("Failed to connect %s times" % attempts) try: if start_time is None: start_time = self.last_time() if start_time == 0 and self.breakup_on_period: self.msglog("Start Time is 0 and set to Break on Transfer.") start_time = self.log.get_first_record()["timestamp"] self.msglog("Start Time set to timestamp of first row: %s" % time.ctime(start_time)) retrieve = self.log.get_slice if self.log.name == "msglog": msglog.log("msglog.exporter", msglog.types.INFO, "repr(mpx.properties)\n%s\n" % (repr(properties))) retrieve = self.log.get_range end_time = time.time() end = end_time if self.breakup_on_period: self.msglog("Breaking on period") end = start_time + self.period self.msglog("Full export of slice from %s to %s" % (time.ctime(start_time), time.ctime(end_time))) while start_time != end_time: if end > end_time: self.msglog("End greater than End Time. Resetting to End Time") end = end_time self.msglog("Going to export slice from %s to %s" % (time.ctime(start_time), time.ctime(end))) data = retrieve("timestamp", start_time, end) if (not data) and (not self.always_export): raise ENoData("timestamp", start_time, end) self.msglog("Sending data to formatter.") try: output = self.formatter.format(data) if not output is None: self.msglog("Sending formatted data to transporter.") self.transporter.transport(output) start_time = end except EBreakupTransfer, e: entry = e.break_at if entry["timestamp"] == end: # prevents loop where transporter is just failing. raise EIOError("EBreakupTransfer not progressing.") end = entry["timestamp"] msglog.log("broadway", msglog.types.WARN, "Breaking up data transfer at %s." % time.ctime(end)) else: self._time.set_last_time(start_time) self.msglog("Data transported") end = start_time + self.period finally: if hasattr(self.formatter, "cancel"): self.formatter.cancel() # prevent mult copies of data at next successful transport if connected: self.msglog("Releasing connection.") self.connection.release() def nodebrowser_handler(self, nb, path, node, node_url): html = nb.get_default_view(node, node_url) html += "<h4>Commands</h4>\n" s = "%s?action=invoke&method=do_export" % self.name html += '<a href="%s">Force export via nodebrowser.</a>' % (s,) return html def do_export(self, end_time=None, start_time=None): if end_time is None: end_time = time.time() self.go(end_time, start_time) return "Export triggered."
class DRASManager(RemoteWebServiceProxy): def __init__(self): super(DRASManager, self).__init__() self.__scheduled = None self.__observers = {} self.__lock = Lock() self.running = 0 return def configure(self, cd): super(DRASManager, self).configure(cd) set_attribute(self, 'poll_freq', 60, cd, int) set_attribute(self, 'debug', 0, cd, int) return def configuration(self): cd = super(DRASManager, self).configuration() get_attribute(self, 'poll_freq', cd) get_attribute(self, 'debug', cd) return cd def start(self): super(DRASManager, self).start() if self.debug > 1: # dump soap messages to the console self._set_soap_debug_lvl(1) if not self.running: self.running = 1 self._trigger_queue() return def stop(self): self.running = 0 super(DRASManager, self).stop() return def _set_soap_debug_lvl(self, lvl): self._proxy.soapproxy.config.dumpSOAPIn = lvl self._proxy.soapproxy.config.dumpSOAPOut = lvl return def register(self, soap_func, obj): self.__lock.acquire() try: callback_list = self.__observers.get(soap_func) if callback_list is None: callback_list = [] self.__observers[soap_func] = callback_list if not obj in callback_list: callback_list.append(obj) finally: self.__lock.release() return def unregister(self, soap_func, obj): self.__lock.acquire() try: callback_list = self.__observers.get(soap_func) if callback_list is None: return try: callback_list.remove(obj) except ValueError: # it's already gone pass finally: self.__lock.release() return def _do_poll(self): if self.debug: msglog.log('DRAS', INFO, 'Polling the demand response server') for soap_func, callback_list in self.__observers.items(): for obj in callback_list: args = obj.get_args() try: if args: value = soap_func(*args) else: value = soap_func() except: # SOAP errors live here if self.debug: msglog.log('DRAS', INFO, 'Error polling the demand response server') msglog.exception() value = ETimeout() obj.update(value) self._schedule() return def _trigger_queue(self): NORMAL.queue_noresult(self._do_poll) return def _schedule(self): if self.running: self.poll_freq self._scheduled = scheduler.seconds_from_now_do( self.poll_freq, self._trigger_queue) return
class ActiveProxyAbstractClass(EventConsumerMixin): def __init__(self): self._link = None #get 'actual' node self.link = None self._proxy_get = None #set to actual's preferred get method self._proxy_set = None self._proxy_start_exception = None self._proxy_sid = None self.proxy_direction = GET_ONLY #direction subscription "pushes" the data self._proxy_active_source = None self._proxy_active_destination = None self._proxy_active_lock = Lock() self._proxy_active_thread_lock = Lock() self._proxy_active_event = None self._proxy_trigger_counter = 0 self._retry_win_high = 30 EventConsumerMixin.__init__(self, self.change_of_value) self.debug = debug def configure(self, cd): set_attribute(self, 'link', None, cd, str) set_attribute(self, 'error_response', '%ERROR%', cd, str) #keywords: %ERROR% %NONE% or desired value set_attribute(self, 'proxy_direction', self.proxy_direction, cd, int) def configuration(self, cd=None): if cd is None: cd = {} get_attribute(self, 'link', cd, str) get_attribute(self, 'error_response', cd, str) get_attribute(self, 'proxy_direction', cd, str) return cd ## def start(self): self._proxy_start_exception = None if self.is_proxy(): self._proxy_start_active_mode() def stop(self): if self._proxy_sid is not None: SM.destroy(self._proxy_sid) self._proxy_sid = None return ## # start up subscription service if we are in active mode # keep trying until we are successful # todo: thread safe? def _proxy_start_active_mode(self): if self.link: try: if self._proxy_sid is None: #have not started subscription service yet if self.proxy_direction == GET_ONLY: self._proxy_active_source = self._proxy_linked_node() if self._proxy_active_source is None: raise ENotStarted() self._proxy_active_destination = self else: #SET_ONLY self._proxy_active_source = self self._proxy_active_destination = self._proxy_linked_node() if self._proxy_active_destination is None: raise ENotStarted() self._proxy_active_queue = Queue() self._proxy_sid = SM.create_delivered(self, {1:self._proxy_active_source}) if self.debug: print 'Active proxy %s started successfully' % (self.name) except: #it didn't work. Setup schedule to try again in x seconds. if self._retry_win_high < 90: self._retry_win_high += 1 retry_in = randint(int(self._retry_win_high * .66), self._retry_win_high) scheduler.seconds_from_now_do(retry_in, self._proxy_start_active_mode) #raise #took this out since it mostly just served to force the scheduler tread to restart if self.debug: msglog.exception() def change_of_value(self, event): #print 'proxy change of value event' self._last_event = event #if not isinstance(event, ChangeOfValueEvent): #return # Insert event into queue, and (automatically) notify _event_handler_thread: trigger = 0 self._proxy_active_lock.acquire() try: self._proxy_active_event = event # save only latest event, throw away older values if self._proxy_trigger_counter < 3: #no point in trigger too many times for noisy inputs self._proxy_trigger_counter += 1 trigger = 1 finally: self._proxy_active_lock.release() #could this line go below the next to reduce triggers to thread? if trigger: #print 'proxy trigger' self._proxy_trigger_queue() def _proxy_trigger_queue(self): #print 'proxy triggerED' # run the set function on a thread pool thread: NORMAL.queue_noresult(self.proxy_active_set, self) return def proxy_active_set(self, dummy): #print 'active proxy event' self._proxy_active_thread_lock.acquire() #only one set at a time is active try: try: event = None self._proxy_active_lock.acquire() try: event = self._proxy_active_event self._proxy_active_event = None if self._proxy_trigger_counter: self._proxy_trigger_counter -= 1 finally: self._proxy_active_lock.release() #allow any new covs while we do the set if event: #pending event if self.debug: print str(event) value = event.results()[1]['value'] if isinstance(value, Exception): raise value try: #to set() value on destination node self._proxy_active_destination.set(value) #don't know how long this will take self._proxy_set_exception(None) #failure in attempt to set data, maybe node is not ready yet, try again later except (ETimeout, EConnectionError, ENotStarted, ENoSuchNode): #put the event back in the active event if no new one has come in while we were trying to set() self._proxy_active_lock.acquire() try: if self._proxy_active_event is None: self._proxy_active_event = event #put it back in for next attempt unless a new one came in finally: self._proxy_active_lock.release() #allow any new covs while we do the set scheduler.seconds_from_now_do(60, self._proxy_trigger_queue) #try again in one minute raise #re-raise the set() exception except: raise if self.debug: print 'proxy_active_set call set returned' except Exception, e: try: self._proxy_set_exception(e) # we have squashed the exception # we want to log exceptions that are potential bugs # but we don't want to fill the msglog with ETimeouts if not isinstance(e, ETimeout): msglog.exception() except: # if there is a bug in the set_exception method we want # to see this otherwise it makes debugging difficult msglog.exception() finally: self._proxy_active_thread_lock.release() if self.debug: print 'proxy_active_set done' pass def is_proxy(self): if self.link: return 1 return 0 def linked_node_has_set(self): return hasattr(self._proxy_linked_node(), 'set') ## # Lazily discovers reference to linked node # def _proxy_linked_node(self): if self._link is None: self._link = as_node(self.link) return self._link ## # If the subclass supports the 'set_exception' interface, keep it updated # def _proxy_set_exception(self, e): if hasattr(self, 'set_exception'): self.set_exception(e)
class Kwh2Kw(CompositeNode): def __init__(self): self._history = None self._history_lock = Lock() self._sid = None self._nid = 1 self._poll_failure = False self._scheduled = None self.running = False super(Kwh2Kw, self).__init__() return def configure(self, cd): super(Kwh2Kw, self).configure(cd) set_attribute(self, 'link', REQUIRED, cd) # sample_period and window used to set the number of # samples that constitutes the size of the moving avg. set_attribute(self, 'sample_period', 10.0, cd, float) set_attribute(self, 'window', 120, cd, int) self._window_size = self.window / self.sample_period if self.running: # reconfigure things self.stop() self.start() return def configuration(self): cd = super(Kwh2Kw, self).configuration() get_attribute(self, 'link', cd) get_attribute(self, 'sample_period', cd) get_attribute(self, 'window', cd) return cd def start(self): super(Kwh2Kw, self).start() self.running = True self._history = KwList(self._window_size) self._sid = SM.create_polled({self._nid:self.link}) # retrieve an initial value to start things off value = ts = None result = SM.poll_all(self._sid) if result is None: # still waiting try: value = as_node(self.link).get() ts = time.time() except: pass else: try: value = result[self._nid]['value'] ts = result[self._nid]['timestamp'] except: pass if isinstance(value, MpxException): value = None if value and ts: self._history.add(value, ts) self._scheduled = scheduler.seconds_from_now_do(self.sample_period, self.run_update) return def stop(self): self.running = False self._history = None try: SM.destroy(self._sid) except: pass self._sid = None self._history_lock.acquire() try: self._history = None s = self._scheduled self._scheduled = None if s is not None: try: s.cancel() except: pass finally: self._history_lock.release() return ## # update() can be relatively slow, run it on a threadpool def run_update(self): NORMAL.queue_noresult(self.update) return def update(self): try: value = ts = None result = SM.poll_all(self._sid) if result is not None: value = result[self._nid]['value'] ts = result[self._nid]['timestamp'] self._history_lock.acquire() try: if value is None or isinstance(value, MpxException): # there were problems collecting during this period, # our calculation should not proceed self._history.clear() if not self._poll_failure: # log the failure, but don't spam the msglog self._poll_failure = True msglog.log('Kwh2Kw', msglog.types.WARN, 'Failed to retrieve data from %s' % self.link) else: self._poll_failure = False self._history.add(value, ts) finally: self._history_lock.release() except: msglog.exception() self._scheduled = scheduler.seconds_from_now_do(self.sample_period, self.run_update) return def get(self, skipCache=0): return self._history.moving_average()
class _User(PersistentDataObject): USERS = _UserDictionary() def __init__(self,name,new=0, password_file=PASSWD_FILE,group_file=GROUP_FILE, shadow_file = SHADOW_FILE): self.__lock = Lock() self.__password_file = password_file self.__shadow_file = shadow_file self.__group_file = group_file self.__loaded = 0 self.__file_modified = 0 self.load(name) self.meta = {} self.USERS.load() if not self.USERS.has_key(self.name()): msglog.log('broadway',msglog.types.INFO, ('No profile for user %s found, creating' ' new profile' % name)) self.USERS[self.name()] = str(UUID()) PersistentDataObject.__init__(self,self.USERS[self.name()]) PersistentDataObject.load(self) def loaded(self): self.__lock.acquire() try: return self.__loaded finally: self.__lock.release() def load(self,name): self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) passwd_db.load() if name in passwd_db: self.__user = passwd_db[name] else: self.__user = None raise EInvalidValue('name',name,'No such user.') self.__file_modified = passwd_db.last_modified() # loading /etc/shadow database shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() if name in shadow_db: self.__shadow = shadow_db[name] else: self.__shadow = None raise EInvalidValue('User (' ,name, ') does not exist in shadow') self.__shadow_file_modified = shadow_db.last_modified() self.__loaded = 1 finally: self.__lock.release() def reload(self): self.load(self.name()) def save(self): self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) passwd_db.load() passwd_db[self.name()] = self.password_entry() passwd_db.save() # save /etc/shadow content shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() shadow_db[self.name()] = self.shadow_entry() shadow_db.save() finally: self.__lock.release() self.load(self.name()) def name(self): return self.__user.user() def password(self): raise ENotImplemented(self.password) def set_password(self,password, validate=True): self.__lock.acquire() try: shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() shadowentry = shadow_db[self.name()] shadowentry.passwd(password, validate) shadow_db[self.name()] = shadowentry shadow_db.save() finally: self.__lock.release() self.load(self.name()) def crypt(self): return self.__shadow.crypt() def set_crypt(self,crypt): self.__shadow.crypt(crypt) def uid(self): return self.__user.uid() def set_uid(self,uid): self.__user.uid(uid) def gid(self): return self.__user.gid() def set_gid(self,gid): self.__user.gid(gid) def group(self): return self.__user.groups()[0] def groups(self): group_db = GroupFile(self.__group_file) group_db.load() return self.__user.groups(group_db) def group_ids(self): ids = [] for group in self.groups(): ids.append(group.gid()) return ids def gecos(self): return self.__user.gecos() def set_gecos(self,gecos): self.__user.gecos(gecos) def directory(self): return self.__user.directory() def set_directory(self,directory): self.__user.directory(directory) def shell(self): return self.__user.shell() def set_shell(self,shell): self.__user.shell(shell) def is_dirty(self): if not self.__loaded: return 1 self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) if not passwd_db.exists(): return 1 else: return not not (passwd_db.last_modified() > self.__file_modified) shadow_db = ShadowFile(self.__shadow_file) if not shadow_db.exists(): return 1 else: return not not (shadow_db.last_modified() > self.__shadow_file_modified) finally: self.__lock.release() def set_meta_value(self,name,value): self.meta[name] = value PersistentDataObject.save(self) def get_meta_value(self,name,default=None): if self.meta.has_key(name): return self.meta[name] return default def get_meta(self): return self.meta.copy() def __getitem__(self,name): return self.get_meta_value(name) def __setitem__(self,name,value): return self.set_meta_value(name,value) def has_key(self,k): return self.meta.has_key(k) def items(self): return self.meta.items() def values(self): return self.meta.values() def keys(self): return self.meta.keys() def password_entry(self): return self.__user def shadow_entry(self): return self.__shadow def user_type(self): return self.__user.user_type() def password_expired(self): if self.crypt()[:3] == '$1$': return False else: raise EPasswordExpired(self.name()) return True def is_admin(self): return self.user_type() == "mpxadmin"
class __impl(ImmortalThread): tm_counter = Counter(0) def __init__(self, timeout=2.0): self.timeout = timeout self.stations = {} self._monitor = monitor.ChannelMonitor(self.timeout) self.tm_number = self.tm_counter.increment() self._response_tp = ThreadPool(1, 'Jace Response Pool') self._pending_responses = Queue() self._callbacks = {} self._running = False self._sync_get_lock = Lock() self._last_sync_get = uptime.secs() self._cv = Condition() ImmortalThread.__init__(self, None, None, 'Jace Transaction Manager') return def start(self): if not self._monitor.is_running(): self._monitor.start_monitor() self._running = True self._synchronous_transaction = Transaction(self, None, self._bump_cv) self._synchronous_transaction.set_timeout(self.timeout) ImmortalThread.start(self) return def stop(self): msglog.log('Jace', INFO, 'Stop Jace Prism Transaction Manger') if self._monitor.is_running(): self._monitor.stop_monitor() self._running = False return def run(self): msglog.log('Jace', INFO, 'Starting Jace Prism Transaction Manger.') while self._running: try: self.send_loop() self.response_loop() except: msglog.log('Jace', WARN, 'Jace Transaction Manager - error sending next.') msglog.exception() return def transaction_completion_handler(self, transaction): self.tm_number = self.tm_counter.increment() try: tid = transaction.tid s_id, callback = self._callbacks.get(tid) if callback: del self._callbacks[tid] self._pending_responses.put((callback, transaction.get_response())) except: msglog.exception() # recycle the transaction for reuse within the queue self.stations.get(s_id).put_transaction(transaction) return def add_station(self, station): s_id = station.get_id() self.stations[s_id] = station return def get_synchronous(self, station, rqst): self._sync_get_lock.acquire() try: t = self._synchronous_transaction hdr = self._get_auth_header(station) hdr['Connection'] = 'close' t.build_request(rqst.url, None, hdr) self._cv.acquire() try: response = ETimeout() try: t.send_request() self._cv.wait(self.timeout) self._last_sync_get = uptime.secs() if t.is_expired(): t.cancel() else: response = t.get_response() except: t.cancel() finally: self._cv.release() return response finally: self._sync_get_lock.release() return def _bump_cv(self, transaction): # transaction isn't used self._cv.acquire() self._cv.notify() self._cv.release() return def send_loop(self): for s_id, station in self.stations.items(): for i in range(station.transaction_limit): try: t, rqst = station.get_next() except Empty: break hdr = self._get_auth_header(station) hdr['Connection'] = 'close' t.build_request(rqst.url, None, hdr) self._callbacks[t.tid] = (s_id, rqst.callback) t.send_request() return def response_loop(self): while 1: try: callback, rsp = self._pending_responses.get(False) callback(rsp) except Empty: return except: msglog.log('Jace', WARN, 'Unexpected error in response_loop') msglog.exception() return def _get_auth_header(self, station): return {"Authorization": "Basic %s" % station.base64string}
class DeviceProperty(CompositeNode, EventProducerMixin, UpdateMixin): _release_cmd_base = '' _ovrd_cmd_base = '' def __init__(self): # overridden by subclasses self._pv_index = None self._last_rcvd = 0.0 self._last_rcvd_dlta = 0.0 self._cached_value = None self._cached_result = None self._prop_values = None self._subscription_lock = Lock() CompositeNode.__init__(self) EventProducerMixin.__init__(self) return def configure(self, cd): super(DeviceProperty, self).configure(cd) set_attribute(self, 'swid', REQUIRED, cd) set_attribute(self, 'prop_type', REQUIRED, cd) set_attribute(self, 'prop_name', self.name, cd) set_attribute(self, 'node_root', 'nodeDump', cd) set_attribute(self, 'value_key', 'presentValue', cd) set_attribute(self, 'bundle', 1, cd, int) set_attribute(self, 'ttl', 60, cd, int) set_attribute(self, 'read_only', 1, cd, int) return def configuration(self): cd = super(DeviceProperty, self).configuration() get_attribute(self, 'swid', cd) get_attribute(self, 'prop_type', cd) get_attribute(self, 'prop_name', cd) get_attribute(self, 'node_root', cd) get_attribute(self, 'value_key', cd) get_attribute(self, 'bundle', cd) get_attribute(self, 'ttl', cd) get_attribute(self, 'read_only', cd) return cd def start(self): for prop_name in PROPERTIES.get(self.prop_type, ()): if prop_name == self.prop_name: # this property will be returned via a get() # to this node continue p = Prop() cd = {'name':prop_name, 'parent':self} p.configure(cd) p.start() if self.node_root == 'nodeDump' and self.value_key == 'presentValue': setattr(self, 'decode', self._decode_fast) else: setattr(self, 'decode', self._decode_slow) self.url = BASE_URL % (self.station.host, self.swid) self._rqst = JaceRequest(self.url, ttl=self.ttl) self._configure_set() super(DeviceProperty, self).start() return def _configure_set(self): if self.read_only: return self._ovrd_cmd = self._ovrd_cmd_base % (self.station.host, self.swid) self._release_cmd = self._release_cmd_base % (self.station.host, self.swid) setattr(self, 'set', self._set) return def get_property_value(self, prop_name): if self._prop_values is None: self._load_property_value() return self._prop_values.get(prop_name, '') def _load_property_value(self): rsp = self.station.add_request(JaceRequest(self.url)) if isinstance(rsp, Exception): return self._prop_values = {} if not rsp.is_complete(): rsp.await_completion() data = rsp.read() if data.startswith('<!--'): data = data[(data[1:].find('<')+1):] data_o = self._decode_slow(data) for prop in self.children_nodes(): if not isinstance(prop, Prop): continue try: value = fix_up_value(getattr(data_o, prop.name).get_tag_value()) except: value = '' self._prop_values[prop.name] = value return def _decode_fast(self, data): value = None data = data.split('\n') if self._pv_index is None: cnt = 0 for l in data: if l.count('presentValue'): self._pv_index = cnt break cnt += 1 else: try: l = data[self._pv_index] except: value = self._decode_slow(data) if value: value = fix_up_value(value) return value if l.count('presentValue') == 0: value = self._decode_slow(data) else: try: value = l.split('>')[1].split('<')[0] except: value = self._decode_slow(data) if value: value = fix_up_value(value) return value def _decode_slow(self, data): try: data_o = xml2code(data) except: data_o = None return data_o def event_subscribe(self, *args): self._subscription_lock.acquire() try: already_subscribed = self.event_has_subscribers() EventProducerMixin.event_subscribe(self, *args) if self.parent.can_bundle() and self.bundle: self.parent.subscribe(self.prop_name, self.update_cache) elif not already_subscribed: self.update_continuous(None) if self._cached_result and \ (uptime.secs() - self._cached_result.timestamp) < self.ttl: self._trigger_cov( self._cached_result.value, self._cached_result.value, time.time() ) finally: self._subscription_lock.release() return def event_unsubscribe(self, *args): self._subscription_lock.acquire() try: EventProducerMixin.event_unsubscribe(self, *args) if self.parent.can_bundle() and self.bundle: self.parent.unsubscribe(self.prop_name) finally: self._subscription_lock.release() return def _trigger_cov(self, old_value, new_value, t): cov = ChangeOfValueEvent(self, old_value, new_value, t) self.event_generate(cov) return def get(self, skipCache=0): v = self.get_result(skipCache).value if isinstance(v, Exception): raise ETimeout return v def get_result(self, skipCache=0): dt = uptime.secs() - self._last_rcvd if dt > self.ttl or self._cached_value is None: # data is stale self.update() # blocks return self._cached_result def update_cache(self, value): now = uptime.secs() if isinstance(value, ValueObj): value = value.get(self.prop_name) if value is None or isinstance(value, Exception): value = ETimeout() if value != self._cached_value: if self.event_has_subscribers(): self._trigger_cov(self._cached_value, value, time.time()) self._cached_value = value if self._cached_result is None: changes = 0 else: changes = self._cached_result.changes + 1 self._cached_result = Result( self._cached_value, self._last_rcvd, changes ) self._last_rcvd = now return def has_cov(self): return 1 def _set(self, value): if value in (None,'None'): url = self._release_cmd else: url = self._ovrd_cmd + str(value) self.station.add_request(JaceRequest(url)) return def _get_station(self): return self.parent.parent station = property(_get_station)
class Schedule(CompositeNode, EventProducerMixin): def __init__(self): CompositeNode.__init__(self) EventProducerMixin.__init__(self) self._schedule_lock = Lock() self._schedule_condition = Condition() self._value_lock = Lock() self._value_condition = Condition() self.__schedule = None self.__value = None def configure(self, config): CompositeNode.configure(self, config) def configuration(self): config = CompositeNode.configuration(self) return config def start(self): CompositeNode.start(self) def stop(self): CompositeNode.stop(self) def set_schedule(self, client, schedule): self._schedule_lock.acquire() self._schedule_condition.acquire() try: self.__schedule = schedule self._schedule_condition.notifyAll() finally: self._schedule_lock.release() self._schedule_condition.release() self.event_generate(ScheduleChangedEvent(client, schedule)) def get_schedule(self): self._schedule_lock.acquire() try: schedule = self.__schedule finally: self._schedule_lock.release() if isinstance(schedule, Exception): raise schedule return schedule ## # @param schedule Schedule client believes to be current. def get_next_schedule(self, schedule, timeout=None): self._schedule_lock.acquire() try: if schedule is not self.__schedule: return self.__schedule self._schedule_condition.acquire() finally: self._schedule_lock.release() try: self._schedule_condition.wait(timeout) schedule = self.__schedule finally: self._schedule_condition.release() if isinstance(schedule, Exception): raise schedule return schedule def is_schedule_current(self, schedule): self._schedule_lock.acquire() try: changed = not schedule is self.__schedule finally: self._schedule_lock.release() return changed def _set(self, value): self._value_lock.acquire() self._value_condition.acquire() try: old = self.__value self.__value = value self._value_condition.notifyAll() finally: self._value_lock.release() self._value_condition.release() if old != value: self.event_generate(ChangeOfValueEvent(self, old, value)) def get(self, skipCache=0): self._value_lock.acquire() try: value = self.__value finally: self._value_lock.release() return value def get_next_value(self, value, timeout=None): self._value_lock.acquire() try: if value != self.__value: return self.__value self._value_condition.acquire() finally: self._value_lock.release() try: self._value_condition.wait(timeout) value = self.__value finally: self._value_condition.release() return value def is_value_current(self, value): self._value_lock.acquire() try: changed = not value == self.__value finally: self._value_lock.release() return changed
class TestCase(DefaultTestFixture): def setUp(self): DefaultTestFixture.setUp(self) self.lock = Lock() self.pool = ThreadPool(3) self.queue = Queue() self.simple_action_counter = 0 return def tearDown(self): self.pool._unload() DefaultTestFixture.tearDown(self) return def simple_action(self, object): # @note It appears that even the '+= 1' operation is not # guaranteed to be atomic. self.lock.acquire() self.simple_action_counter += 1 self.lock.release() return 'simple_action_result' def slow_action(self, object): time.sleep(1.0) return 'slow_action_result' def simple_queue_action(self, object): self.queue.put(object) return def test_simple_queue(self): self.pool.queue(self.simple_queue_action, self) result = self.queue.get(1.0) if result is not self: raise "Queue returned %r instead of self, %r." % (result, self) return def test_result(self): t1 = time.time() pending_result = self.pool.queue(self.simple_action, self) result = pending_result.result(10.0) t2 = time.time() if result != 'simple_action_result': raise ("pending_result.result() returned the wrong value (%s)." % result) if (t2 - t1) >= 10.0: raise "pending_result.result() blocked for no reason." return def test_pending_reasult(self): t1 = time.time() pending_result = PendingResult(None, None, self.simple_action, self) pending_result_two = self.pool.queue_pending_result(pending_result) if pending_result_two is not pending_result: raise "pending_result_two is NOT pending_result" result = pending_result.result(10.0) t2 = time.time() if result != 'simple_action_result': raise ("pending_result.result() returned the wrong value (%s)." % result) if (t2 - t1) >= 10.0: raise "pending_result.result() blocked for no reason." return def test_pending_action(self): pending_action = PendingAction(self.simple_queue_action, self) self.pool.queue_pending_action(pending_action) result = self.queue.get(1.0) if result is not self: raise "Queue returned %r instead of self, %r." % (result, self) return return def test_result_timeout(self): t1 = time.time() pending_result = self.pool.queue(self.slow_action, self) result = pending_result.result(0.25) t2 = time.time() if (t2 - t2) >= 1.0: raise "Blocked 1 second when a 1/4 second timeout." if result != NORESULT: raise "Got a result (%s) when none was expected." return def test_1000_actions(self): for i in xrange(0, 1000): self.pool.queue(self.simple_action, self) time.sleep(0.1) t1 = time.time() while self.simple_action_counter < 1000: tn = time.time() if (tn - t1) > 3.0: raise ( "Taking ridiculously long to process 1000 queued actions.") time.sleep(0.1) return def test_HIGH_pool_1(self): t1 = time.time() pending_result = HIGH.queue(self.simple_action, self) result = pending_result.result(10.0) t2 = time.time() if result != 'simple_action_result': raise ("pending_result.result() returned the wrong value (%s)." % result) if (t2 - t1) >= 10.0: raise "pending_result.result() blocked for no reason." return def test_HIGH_pool_2(self): self.test_HIGH_pool_1() return def test_HIGH_pool_resize_1(self): HIGH.resize(1) if HIGH.size() != 1: raise "Resize to 1 thread failed." for i in xrange(0, 100): HIGH.queue(self.simple_action, self) t1 = time.time() while self.simple_action_counter < 100: tn = time.time() if (tn - t1) > 3.0: raise ( "Taking ridiculously long to process 100 queued actions.") time.sleep(0.1) return def test_HIGH_pool_resize_20(self): HIGH.resize(20) if HIGH.size() != 20: raise "Resize to 20 threads failed." for i in xrange(0, 100): HIGH.queue(self.simple_action, self) t1 = time.time() while self.simple_action_counter < 100: tn = time.time() if (tn - t1) > 3.0: raise ( "Taking ridiculously long to process 100 queued actions.") time.sleep(0.1) return
class _Trigger(CompositeNode): implements(ITrigger) security = SecurityInformation.from_default() secured_by(security) def __init__(self, *args): self.targetmap = {} self.targets = set() self.unresolvable = set() self.synclock = Lock() super(_Trigger, self).__init__(*args) security.protect('get_targets', 'View') def get_targets(self, unresolved=False): #get targets targets = [] for targeturl in self.targets: target = self.targetmap.get(targeturl) if target and not target.parent: message = "Trigger %s resetting pruned target: %r." msglog.warn(message % (self.name, targeturl)) self.targetmap.pop(targeturl) target = None if not target: try: target = self.nodespace.as_node(targeturl) except KeyError: if targeturl not in self.unresolvable: message = "Trigger %s Unable to resolve target: %r." msglog.warn(message % (self.name, targeturl)) self.unresolvable.add(targeturl) else: self.targetmap[targeturl] = target self.unresolvable.discard(targeturl) if target: targets.append(target) elif unresolved: targets.append(targeturl) return targets security.protect('get_target_names', 'View') def get_target_names(self): return [target.name for target in self.get_targets()] security.protect('add_target', 'Configure') def add_target(self, target): if not isinstance(target, str): targeturl = as_node_url(target) else: targeturl = target try: target = self.nodespace.as_node(targeturl) except KeyError: target = None if targeturl == "/": raise ValueError("Invalid trigger target: %r" % target) self.synclock.acquire() try: if targeturl not in self.targets: self.targets.add(targeturl) if target: self.targetmap[targeturl] = target else: message = "Trigger %r added unresolvable target: %r" msglog.warn(message % (self.name, targeturl)) added = True else: added = False message = "Trigger %r not adding target %r: already exists." msglog.warn(message % (self.name, targeturl)) finally: self.synclock.release() return added security.protect('remove_target', 'Configure') def remove_target(self, target): if not isinstance(target, str): targeturl = as_node_url(target) else: targeturl = target self.synclock.acquire() try: self.targets.remove(targeturl) except KeyError: removed = False message = "Target %s not removed from %s: does not exist." msglog.warn(message % (target, self)) else: try: self.targetmap.pop(targeturl) except KeyError: pass removed = True msglog.inform("Target %s removed from %s." % (target, self)) finally: self.synclock.release() return removed
class OmniProto(CompositeNode): """ This class is modelled to hold the omnimeter protocol . The class gives protocol level service for enabling and disabling protocol and enabling and disabling the debug prints . """ __node_id = 'fcab0563-fabf-452a-b527-f592d04dbe8e' def __init__(self): super(OmniProto, self).__init__() self.lock = Lock() self.lh = None self.conn = None def configure(self, config): # #polling period was used in case of thread # set_attribute(self, 'polling_period', 120, config, int) set_attribute(self, 'cache_life', 10, config, int) set_attribute(self, 'retry_count', 3, config, int) set_attribute(self, 'reply_timeout', 1, config, float) super(OmniProto, self).configure(config) if self.debug: msglog.log('omnimeter', msglog.types.INFO, "OMNIMETER in configure") msglog.log('omnimeter', msglog.types.INFO, "Enabled:%d" % (self.enabled)) msglog.log('omnimeter', msglog.types.INFO, "Debug:%d" % (self.debug)) def configuration(self): config = super(OmniProto, self).configuration() get_attribute(self, 'cache_life', config) get_attribute(self, 'retry_count', config) get_attribute(self, 'reply_timeout', config) # #polling period was used in case of thread # get_attribute(self, 'polling_period', config) return config def start(self): if self.is_running(): raise EAlreadyRunning() self.conn = gdconn.FrameworkSerialPortWrapper(self.parent) self.lh = gdlh.SimpleLineHandler(self.conn) try: self.lh.connect() except EAlreadyOpen: msglog.log('omnimeter', msglog.types.ERR, 'COM Port already in use') raise #stx seems to be the core of omnimeter. #though stx shouldn't be here, it ensures we recieve data properly #inspite of variable no. of FEs self.stx_obj = om.start_byte() # starting the polling thread. # Polling thread not being used currently # self.start_thread() msglog.log('omnimeter', msglog.types.INFO, "OMNIMETER Protocol started") super(OmniProto, self).start() def stop(self): if not self.is_running(): raise ENotRunning() self.lh.disconnect() #have to see some more here ?? super(OmniProto, self).stop() msglog.log('omnimeter', msglog.types.INFO, "OMNIMETER Protocol stopping") def send_request(self, request_obj, response_obj, wait_time=None, numretries=None): """API to devices to send a request object and wait for response devices may provide wait_time, retry_count else may use defaults provided by protocol """ if not self.is_running(): raise ENotRunning() if wait_time is None: wait_time = self.reply_timeout if numretries is None: numretries = self.retry_count #have to lock here to ensure, no one drains the port out if self.debug: msglog.log('omnimeter', msglog.types.INFO, 'wait time and numretries are %s' % str((wait_time, numretries))) save_wait_time = wait_time self.lock.acquire() try: while numretries: try: self.conn.drain() wait_time = save_wait_time t0 = time.time() #loopback object res = self.lh.send_request_with_response(request_obj, request_obj, wait_time) if not res: #should not happen raise EWriteError() wait_time = wait_time - (time.time() - t0) if self.debug: msglog.log('omnimeter', msglog.types.INFO, 'got loopback-resp:time left:%f' % wait_time) if wait_time < 0 : #can never be raise ETimeout() #wait until we get first byte of packet res = 0 while not res: t0 = time.time() res = self.lh.receive_response(self.stx_obj, wait_time) wait_time = wait_time - (time.time() - t0) if wait_time < 0 : raise ETimeout() if self.debug: msglog.log('omnimeter', msglog.types.INFO, 'got first byte. wait time:%f' % wait_time) res = self.lh.receive_response(response_obj, wait_time) if not res: raise EInvalidMessage() return except: numretries -= 1 finally: self.lock.release() if self.debug: msglog.log('omnimeter', msglog.types.INFO, 'Exhausted no. of retries. Raising last exception') raise
class LogAndExport(ServiceNode): def __init__(self): self._lock = Lock() self._started = 0 self._alarm = [] # can have a whole MESS o' alarms at startup... self._scheduled = None self.trigger_node_url_posn = None # allow source stamping self.trigger_node_msg_posn = None # allow source stamping self._waiting_alarm_sid = None ServiceNode.__init__(self) def configure(self, config): ServiceNode.configure(self, config) set_attribute(self, 'log', REQUIRED, config) def configuration(self): config = ServiceNode.configuration(self) get_attribute(self, 'log', config, as_node_url) return config def start(self): self._lock.acquire() try: self.log = as_node(self.log) columns_node = self.log.get_child('columns') self.ts_position = columns_node.get_child('timestamp').position # Allow source stamping: if columns_node.has_child('trigger_node_url'): self.trigger_node_url_posn = columns_node.get_child( 'trigger_node_url').position if columns_node.has_child('trigger_node_msg'): self.trigger_node_msg_posn = columns_node.get_child( 'trigger_node_msg').position self._started = 1 finally: self._lock.release() self.export_waiting_alarm() return ServiceNode.start(self) def stop(self): if not self._waiting_alarm_sid is None: try: scheduler.remove(self._waiting_alarm_sid) except: # SID may already have expired and been removed... msglog.exception() self._waiting_alarm_sid = None self._started = 0 ServiceNode.stop(self) return def export(self, alarm, attempt=0): self._lock.acquire() try: if (not self._started): self._alarm.append(alarm) # No need to set scheduler here; start() will call # export_waiting_alarm()... return # Even if this node is already started, do not attempt to # export alarm unless the linked log node and its collector # object are extant and started: if (self.log.collector is None): self._alarm.append(alarm) if (self._waiting_alarm_sid is None): # if we're not already scheduled, do it: # Need to wait long enough for log.start() to finish creating # and starting collector. ***GUESS*** 10.0 sec. Symptom of not # waiting long enough: ENotStarted error raised below: self._waiting_alarm_sid = scheduler.after( 10.0, self.export_waiting_alarm, ()) return finally: self._lock.release() self.log.collector.pause() try: try: if not self.log.collector.running: raise ENotStarted('Collector not started yet.') entry = self.log.collector.get_entry() entry[self.ts_position] = time.time() # Stamp source, if target log columns support it: if isinstance(self.trigger_node_url_posn, int): entry[self.trigger_node_url_posn] = as_node_url( alarm.source) if isinstance(self.trigger_node_msg_posn, int): entry[self.trigger_node_msg_posn] = str(alarm) self.log.add_entry(entry) t = time.time() for child in self.log.get_child('exporters').children_nodes(): child.go(t) # starts threads for long ops except: msglog.exception() if attempt > alarm.source.send_retries: msglog.log('broadway', msglog.types.WARN, 'Export of alarm failed, aborting send.') raise MpxException('Log and export failed.') else: msglog.log('broadway', msglog.types.WARN, 'Log on alarm failed, delaying 1.0 sec.') self._lock.acquire() try: if self._scheduled != None: scheduler.cancel(self._scheduled) self._scheduled = scheduler.after( 1, self.export, (alarm, attempt + 1)) finally: self._lock.release() finally: self.log.collector.play() return ## # export_waiting_alarm: forces logging/export of self._alarm elements # if self.log.collector was None during export(). # @todo May need to spin off thread(s), to avoid delaying Scheduler. # def export_waiting_alarm(self): if (self._started == 1) \ and (not self.log.collector is None): if not self._waiting_alarm_sid is None: try: scheduler.remove(self._waiting_alarm_sid) except: # SID may already have expired and been removed... msglog.exception() self._waiting_alarm_sid = None while len(self._alarm) > 0: init_len = len(self._alarm) alarm = self._alarm.pop(0) self.export(alarm) # should leave alarm off of list... if init_len <= len(self._alarm): break # failed to keep alarm off the list elif (len(self._alarm) > 0): self._waiting_alarm_sid = scheduler.after( 10.0, self.export_waiting_alarm, ()) return
class DynDNSManager( ConfigurableNode ): def __init__( self ): ConfigurableNode.__init__( self ) self.isRunning = 0 self.ipcheck_pid = 0 self._lock = Lock() self.thread = None self.first_time = 0 self.log_name = 'broadway' self.update_count = 0 def _is_debug( self ): if self.__dict__.has_key( 'debug' ): if self.debug: return 1 return 0 def msglog( self, msg ): if self._is_debug(): msglog.log( self.log_name, msglog.types.DB, msg ) def configure( self, config_dict ): ConfigurableNode.configure( self, config_dict ) set_attribute( self, 'enable', 0, config_dict, int ) set_attribute( self, 'debug', 0, config_dict, int ) set_attribute( self, 'ddns_service', 0, config_dict ) set_attribute( self, 'ddns_acct', 0, config_dict ) set_attribute( self, 'ddns_pswd', 0, config_dict ) set_attribute( self, 'host_name', 0, config_dict ) map_to_attribute( self, 'period', 0, config_dict, map_to_seconds ) def configuration( self ): config_dict = ConfigurableNode.configuration( self ) get_attribute( self, 'enable', config_dict ) get_attribute( self, 'debug', config_dict ) get_attribute( self, 'ddns_service', config_dict ) get_attribute( self, 'ddns_acct', config_dict ) get_attribute( self, 'ddns_pswd', config_dict ) get_attribute( self, 'host_name', config_dict ) map_from_attribute( self, 'period', config_dict, map_from_seconds ) return config_dict def start( self ): if self.enable: msglog.log( self.log_name, msglog.types.INFO, "STARTING %s, period = %d" % (self.name, self.period) ) if not self.isRunning: self.isRunning = 1 # Wait for a bit to give time for a possible PPP connection # to be brought up. scheduler.seconds_from_now_do( 90, self.go ) else: raise EAlreadyRunning def stop( self ): if self.isRunning: if self.ipcheck_pid: os.kill( self.ipcheck_pid, signal.SIGKILL ) self.isRunning = 0 def go( self ): # Lock here self._lock.acquire() try: if self.thread and self.thread.isAlive(): # Don't do it! return self.thread = Thread( name = self.name, target = self._complete, args = () ) self.thread.start() finally: self._lock.release() def _complete( self ): try: self._ipcheck() except: msglog.exception() if self.isRunning and self.period: # Schedule another run in self.period seconds scheduler.seconds_from_now_do( self.period, self.go ) def _ipcheck( self ): cmd = 'ipcheck -p ' + properties.ETC_DIR # Ignore errors the first time after startup. if self.update_count == 0: cmd += ' -e' if self._is_debug(): cmd += ' -dv' cmd += ' %s %s %s' % (self.ddns_acct, self.ddns_pswd, self.host_name) # Start the ip checker self.msglog( "running %s" % cmd ) child = Popen4( cmd ) self.ipcheck_pid = child.pid outfile = child.fromchild # Wait for ip checker to finish. Log any output in the message log. while 1: result = select( [outfile], [], [], 3.0 ) if result[0]: lines = outfile.readlines() for line in lines: self.msglog( line ) status = child.poll() if not status == -1: break self.ipcheck_pid = 0 if os.WIFEXITED( status ): exit_code = os.WEXITSTATUS( status ) self.msglog( 'ipcheck exit status = %d' % exit_code ) else: self.msglog( 'ipcheck forcibly stopped, status = %d' % status ) self.update_count += 1
class RO_CommandCache: class CommandION(CompositeNode): ## # @param rgvm A string representation of the Response Get Value Method. # Example 'self.cached_response().engdt1_response().engine_rpm'. def __init__(self, cache, name, rgvm, args): CompositeNode.__init__(self) self.parent = cache.ion() self.name = name self._cache = cache file = 'mpx.ion.capstone.micro_turbine.personality.RO_CommandCache.CommandION.__init__' self._compiled_reference = compile(rgvm, file, 'eval') self._rgvm_args = args self.parent._add_child(self) def cached_response(self): return self._cache.cached_response() def get(self, skipCache=0): cache = self._cache cache.lock() try: response, cached = cache._response() value = apply(eval(self._compiled_reference), self._rgvm_args) finally: cache.unlock() return value def get_result(self, skipCache=0, **keywords): cache = self._cache cache.lock() try: response, cached = cache._response() result = Result() result.timestamp = cache.timestamp() result.cached = cached result.value = apply(eval(self._compiled_reference), self._rgvm_args) finally: cache.unlock() return result def __init__(self, parent, line_handler, command, timeout=1.0): self._ion = parent self._lh = line_handler self._command = command self._timeout = timeout self._cache = None self._timestamp = time.time() self._expire_after = self._timestamp - 1.0 self._lock = Lock() # Light weight, non-reentrant lock. self._map = {} ## # @return The time that the cache was last refreshed. def timestamp(self): return self._timestamp ## # @return The ion that is considerred the parent of all cached values. def ion(self): return self._ion ## # @return True if the cache is valid. def is_valid(self): return self._cache and self._expire_after >= time.time() ## # @return True if the cache is dirty (not valid). def is_dirty(self): return not self.is_valid() ## # Mark the cache as dirty, forcing a refresh on the next read. def mark_dirty(self): self._cache = None ## # Lock the cache for exclusive access. def lock(self): self._lock.acquire() ## # Unlock the cache from exclusive access mode. def unlock(self): self._lock.release() ## # Return the cached response, if any. def cached_response(self,skipCache=0): return self._cache ## # Refresh the cache from the command. # @note Should only be invoked locked. def _refresh(self): self._cache = self._lh.command(self._command) self._timestamp = time.time() self._expire_after = self._timestamp + self._timeout ## # Return the cached response, ensuring it is minty fresh. # @note Should only be invoked locked. def _response(self,skipCache=0): cached = 1 if skipCache or not self.is_valid(): self._refresh() cached = 0 return self._cache, cached ## # @param rgvm A string representation of the Response Get Value Method. def map_child(self, name, rgvm, args=()): cache = self child = self.CommandION(cache, name, rgvm, args) self._map[name] = child
class UserManager(ServiceNode): class _NoneUser: def __init__(self): self.__name = 'NoneUser' def name(self): return self.__name def __init__(self): ## # Used to control access to the user control dictionaries. self.__lock = Lock() ## # There is only every one instance of a User cached in memory. self.__users = {'NoneUser': self._NoneUser()} ## # For every derived CacheableAuthenticator that has been used # to authenticate a User instance, there is one cached # authenticator instance. self.__pending = {DigestRFC2617Authenticator: []} ServiceNode.__init__(self) return def has_user(self, name): users = self.__users if not users.has_key(name): try: user = User(name, 0, PASSWD_FILE) user._authenticators = {} users[name] = user except Exception: msglog.log("broadway", msglog.types.WARN, "User '%s' not found" % name) return False return users.has_key(name) def get_user(self, name): users = self.__users if not users.has_key(name): user = User(name, 0, PASSWD_FILE) user._authenticators = {} users[name] = user return users[name] def remove_user(self, name): users = self.__users if users.has_key(name): del users[name] def new_user(self, name): raise ENotImplemented(self.new_user) def initialize_authenticator(self, authenticator, **keywords): name = authenticator.name(**keywords) file = PASSWD_FILE if keywords.has_key('_file_'): file = keywords['_file_'] users = self.__users self.__lock.acquire() try: if not users.has_key(name): try: user = User(name, 0, file) except EInvalidValue: raise EAuthenticationFailed() user._authenticators = {} users[name] = user validator = authenticator(users[name], **keywords) else: user = users[name] current_key = authenticator.current_key(**keywords) validator = authenticator(user, **keywords) validator.validate(**keywords) next_key = validator.next_key() users[name]._authenticators[next_key] = validator finally: self.__lock.release() return validator def user_from_authenticator(self, authenticator, **keywords): validator = self.initialize_authenticator(authenticator, **keywords) return validator.user def user_from_pam(self, name, password, **keywords): return self.user_from_authenticator(PAMAuthenticator, name=name, passwd=password, **keywords) ## # Authenticate a user via the system user name and password. # # @param username The system username to authenticate. # @param password The clear text password of the user. # @return A User object representing the authenticated user. # @exception EAuthenticationFailed def user_from_cleartext(self, name, password, **keywords): return self.user_from_authenticator(ClearTextAuthenticator, name=name, passwd=password, **keywords) def validator_from_cleartext(self, name, password, **keywords): return self.initialize_authenticator(ClearTextAuthenticator, name=name, passwd=password, **keywords) ## # Authenticate a user via the system user name and already crypted # password. # # @param username The system username to authenticate. # @param password The password of the user, crypted. # @return A User object representing the authenticated user. # @exception EAuthenticationFailed def user_from_crypt(self, name, crypted_password, **keywords): return self.user_from_authenticator(CryptAuthenticator, name=name, crypt=crypted_password, **keywords) def validator_from_crypt(self, name, crypted_password, **keywords): return self.initialize_authenticator(CryptAuthenticator, name=name, crypt=crypted_password, **keywords) ## # # @return A User object representing the authenticated user. # @exception EAuthenticationFailed def user_from_rfc2617_basic(self, credentials, **keywords): return self.user_from_authenticator(BasicRFC2617Authenticator, credentials=credentials, **keywords) def validator_from_rfc2617_basic(self, credential, **keywords): return self.initialize_authenticator(BasicRFC2617Authenticator, credentials=credential, **keywords) def new_rfc2617_basic_user(self, **keywords): return self.__users['NoneUser'] ## # # @return A User object representing the authenticated user. # @exception EAuthenticationFailed def user_from_rfc2617_digest(self, **keywords): return self.validator_from_rfc2617_digest(**keywords).user def validator_from_rfc2617_digest(self, **keywords): authenticator = DigestRFC2617Authenticator file = PASSWD_FILE if keywords.has_key('_file_'): file = keywords['_file_'] name = authenticator.name(**keywords) nonce = authenticator.current_id(**keywords) current_key = authenticator.current_key(**keywords) self.__lock.acquire() try: if not self.__users.has_key(name): user = User(name, 0, file) user._authenticators = {} else: user = self.__users[name] authenticators = user._authenticators if not authenticators.has_key(current_key): pending = self.__pending[authenticator] if nonce in pending: pending.remove(nonce) authenticators[current_key] = authenticator( user, **keywords) authenticators[current_key].initiate_nonce(nonce) else: raise EAuthenticationFailed('Unknown nonce value.') self.__users[name] = user finally: self.__lock.release() return self.initialize_authenticator(authenticator, **keywords) def new_rfc2617_digest_user(self, next_nonce): self.__pending[DigestRFC2617Authenticator].append(next_nonce) return self.__users['NoneUser']
class ActiveProxyAbstractClass(EventConsumerMixin): def __init__(self): self._link = None #get 'actual' node self.link = None self._proxy_get = None #set to actual's preferred get method self._proxy_set = None self._proxy_start_exception = None self._proxy_sid = None self.proxy_direction = GET_ONLY #direction subscription "pushes" the data self._proxy_active_source = None self._proxy_active_destination = None self._proxy_active_lock = Lock() self._proxy_active_thread_lock = Lock() self._proxy_active_event = None self._proxy_trigger_counter = 0 self._retry_win_high = 30 EventConsumerMixin.__init__(self, self.change_of_value) self.debug = debug def configure(self, cd): set_attribute(self, 'link', None, cd, str) set_attribute(self, 'error_response', '%ERROR%', cd, str) #keywords: %ERROR% %NONE% or desired value set_attribute(self, 'proxy_direction', self.proxy_direction, cd, int) def configuration(self, cd=None): if cd is None: cd = {} get_attribute(self, 'link', cd, str) get_attribute(self, 'error_response', cd, str) get_attribute(self, 'proxy_direction', cd, str) return cd ## def start(self): self._proxy_start_exception = None if self.is_proxy(): self._proxy_start_active_mode() def stop(self): if self._proxy_sid is not None: SM.destroy(self._proxy_sid) self._proxy_sid = None return ## # start up subscription service if we are in active mode # keep trying until we are successful # todo: thread safe? def _proxy_start_active_mode(self): if self.link: try: if self._proxy_sid is None: #have not started subscription service yet if self.proxy_direction == GET_ONLY: self._proxy_active_source = self._proxy_linked_node() if self._proxy_active_source is None: raise ENotStarted() self._proxy_active_destination = self else: #SET_ONLY self._proxy_active_source = self self._proxy_active_destination = self._proxy_linked_node( ) if self._proxy_active_destination is None: raise ENotStarted() self._proxy_active_queue = Queue() self._proxy_sid = SM.create_delivered( self, {1: self._proxy_active_source}) if self.debug: print 'Active proxy %s started successfully' % ( self.name) except: #it didn't work. Setup schedule to try again in x seconds. if self._retry_win_high < 90: self._retry_win_high += 1 retry_in = randint(int(self._retry_win_high * .66), self._retry_win_high) scheduler.seconds_from_now_do(retry_in, self._proxy_start_active_mode) #raise #took this out since it mostly just served to force the scheduler tread to restart if self.debug: msglog.exception() def change_of_value(self, event): #print 'proxy change of value event' self._last_event = event #if not isinstance(event, ChangeOfValueEvent): #return # Insert event into queue, and (automatically) notify _event_handler_thread: trigger = 0 self._proxy_active_lock.acquire() try: self._proxy_active_event = event # save only latest event, throw away older values if self._proxy_trigger_counter < 3: #no point in trigger too many times for noisy inputs self._proxy_trigger_counter += 1 trigger = 1 finally: self._proxy_active_lock.release( ) #could this line go below the next to reduce triggers to thread? if trigger: #print 'proxy trigger' self._proxy_trigger_queue() def _proxy_trigger_queue(self): #print 'proxy triggerED' # run the set function on a thread pool thread: NORMAL.queue_noresult(self.proxy_active_set, self) return def proxy_active_set(self, dummy): #print 'active proxy event' self._proxy_active_thread_lock.acquire( ) #only one set at a time is active try: try: event = None self._proxy_active_lock.acquire() try: event = self._proxy_active_event self._proxy_active_event = None if self._proxy_trigger_counter: self._proxy_trigger_counter -= 1 finally: self._proxy_active_lock.release( ) #allow any new covs while we do the set if event: #pending event if self.debug: print str(event) value = event.results()[1]['value'] if isinstance(value, Exception): raise value try: #to set() value on destination node self._proxy_active_destination.set( value) #don't know how long this will take self._proxy_set_exception(None) #failure in attempt to set data, maybe node is not ready yet, try again later except (ETimeout, EConnectionError, ENotStarted, ENoSuchNode): #put the event back in the active event if no new one has come in while we were trying to set() self._proxy_active_lock.acquire() try: if self._proxy_active_event is None: self._proxy_active_event = event #put it back in for next attempt unless a new one came in finally: self._proxy_active_lock.release( ) #allow any new covs while we do the set scheduler.seconds_from_now_do( 60, self._proxy_trigger_queue ) #try again in one minute raise #re-raise the set() exception except: raise if self.debug: print 'proxy_active_set call set returned' except Exception, e: try: self._proxy_set_exception(e) # we have squashed the exception # we want to log exceptions that are potential bugs # but we don't want to fill the msglog with ETimeouts if not isinstance(e, ETimeout): msglog.exception() except: # if there is a bug in the set_exception method we want # to see this otherwise it makes debugging difficult msglog.exception() finally: self._proxy_active_thread_lock.release() if self.debug: print 'proxy_active_set done' pass def is_proxy(self): if self.link: return 1 return 0 def linked_node_has_set(self): return hasattr(self._proxy_linked_node(), 'set') ## # Lazily discovers reference to linked node # def _proxy_linked_node(self): if self._link is None: self._link = as_node(self.link) return self._link ## # If the subclass supports the 'set_exception' interface, keep it updated # def _proxy_set_exception(self, e): if hasattr(self, 'set_exception'): self.set_exception(e)
class DeviceProperty(CompositeNode, EventProducerMixin, UpdateMixin): _release_cmd_base = '' _ovrd_cmd_base = '' def __init__(self): # overridden by subclasses self._pv_index = None self._last_rcvd = 0.0 self._last_rcvd_dlta = 0.0 self._cached_value = None self._cached_result = None self._prop_values = None self._subscription_lock = Lock() CompositeNode.__init__(self) EventProducerMixin.__init__(self) return def configure(self, cd): super(DeviceProperty, self).configure(cd) set_attribute(self, 'swid', REQUIRED, cd) set_attribute(self, 'prop_type', REQUIRED, cd) set_attribute(self, 'prop_name', self.name, cd) set_attribute(self, 'node_root', 'nodeDump', cd) set_attribute(self, 'value_key', 'presentValue', cd) set_attribute(self, 'bundle', 1, cd, int) set_attribute(self, 'ttl', 60, cd, int) set_attribute(self, 'read_only', 1, cd, int) return def configuration(self): cd = super(DeviceProperty, self).configuration() get_attribute(self, 'swid', cd) get_attribute(self, 'prop_type', cd) get_attribute(self, 'prop_name', cd) get_attribute(self, 'node_root', cd) get_attribute(self, 'value_key', cd) get_attribute(self, 'bundle', cd) get_attribute(self, 'ttl', cd) get_attribute(self, 'read_only', cd) return cd def start(self): for prop_name in PROPERTIES.get(self.prop_type, ()): if prop_name == self.prop_name: # this property will be returned via a get() # to this node continue p = Prop() cd = {'name': prop_name, 'parent': self} p.configure(cd) p.start() if self.node_root == 'nodeDump' and self.value_key == 'presentValue': setattr(self, 'decode', self._decode_fast) else: setattr(self, 'decode', self._decode_slow) self.url = BASE_URL % (self.station.host, self.swid) self._rqst = JaceRequest(self.url, ttl=self.ttl) self._configure_set() super(DeviceProperty, self).start() return def _configure_set(self): if self.read_only: return self._ovrd_cmd = self._ovrd_cmd_base % (self.station.host, self.swid) self._release_cmd = self._release_cmd_base % (self.station.host, self.swid) setattr(self, 'set', self._set) return def get_property_value(self, prop_name): if self._prop_values is None: self._load_property_value() return self._prop_values.get(prop_name, '') def _load_property_value(self): rsp = self.station.add_request(JaceRequest(self.url)) if isinstance(rsp, Exception): return self._prop_values = {} if not rsp.is_complete(): rsp.await_completion() data = rsp.read() if data.startswith('<!--'): data = data[(data[1:].find('<') + 1):] data_o = self._decode_slow(data) for prop in self.children_nodes(): if not isinstance(prop, Prop): continue try: value = fix_up_value( getattr(data_o, prop.name).get_tag_value()) except: value = '' self._prop_values[prop.name] = value return def _decode_fast(self, data): value = None data = data.split('\n') if self._pv_index is None: cnt = 0 for l in data: if l.count('presentValue'): self._pv_index = cnt break cnt += 1 else: try: l = data[self._pv_index] except: value = self._decode_slow(data) if value: value = fix_up_value(value) return value if l.count('presentValue') == 0: value = self._decode_slow(data) else: try: value = l.split('>')[1].split('<')[0] except: value = self._decode_slow(data) if value: value = fix_up_value(value) return value def _decode_slow(self, data): try: data_o = xml2code(data) except: data_o = None return data_o def event_subscribe(self, *args): self._subscription_lock.acquire() try: already_subscribed = self.event_has_subscribers() EventProducerMixin.event_subscribe(self, *args) if self.parent.can_bundle() and self.bundle: self.parent.subscribe(self.prop_name, self.update_cache) elif not already_subscribed: self.update_continuous(None) if self._cached_result and \ (uptime.secs() - self._cached_result.timestamp) < self.ttl: self._trigger_cov(self._cached_result.value, self._cached_result.value, time.time()) finally: self._subscription_lock.release() return def event_unsubscribe(self, *args): self._subscription_lock.acquire() try: EventProducerMixin.event_unsubscribe(self, *args) if self.parent.can_bundle() and self.bundle: self.parent.unsubscribe(self.prop_name) finally: self._subscription_lock.release() return def _trigger_cov(self, old_value, new_value, t): cov = ChangeOfValueEvent(self, old_value, new_value, t) self.event_generate(cov) return def get(self, skipCache=0): v = self.get_result(skipCache).value if isinstance(v, Exception): raise ETimeout return v def get_result(self, skipCache=0): dt = uptime.secs() - self._last_rcvd if dt > self.ttl or self._cached_value is None: # data is stale self.update() # blocks return self._cached_result def update_cache(self, value): now = uptime.secs() if isinstance(value, ValueObj): value = value.get(self.prop_name) if value is None or isinstance(value, Exception): value = ETimeout() if value != self._cached_value: if self.event_has_subscribers(): self._trigger_cov(self._cached_value, value, time.time()) self._cached_value = value if self._cached_result is None: changes = 0 else: changes = self._cached_result.changes + 1 self._cached_result = Result(self._cached_value, self._last_rcvd, changes) self._last_rcvd = now return def has_cov(self): return 1 def _set(self, value): if value in (None, 'None'): url = self._release_cmd else: url = self._ovrd_cmd + str(value) self.station.add_request(JaceRequest(url)) return def _get_station(self): return self.parent.parent station = property(_get_station)
class __impl(ImmortalThread): tm_counter = Counter(0) def __init__(self, timeout=2.0): self.timeout = timeout self.stations = {} self._monitor = monitor.ChannelMonitor(self.timeout) self.tm_number = self.tm_counter.increment() self._response_tp = ThreadPool(1, 'Jace Response Pool') self._pending_responses = Queue() self._callbacks = {} self._running = False self._sync_get_lock = Lock() self._last_sync_get = uptime.secs() self._cv = Condition() ImmortalThread.__init__(self, None, None, 'Jace Transaction Manager') return def start(self): if not self._monitor.is_running(): self._monitor.start_monitor() self._running = True self._synchronous_transaction = Transaction( self, None, self._bump_cv) self._synchronous_transaction.set_timeout(self.timeout) ImmortalThread.start(self) return def stop(self): msglog.log('Jace', INFO, 'Stop Jace Prism Transaction Manger') if self._monitor.is_running(): self._monitor.stop_monitor() self._running = False return def run(self): msglog.log('Jace', INFO, 'Starting Jace Prism Transaction Manger.') while self._running: try: self.send_loop() self.response_loop() except: msglog.log( 'Jace', WARN, 'Jace Transaction Manager - error sending next.') msglog.exception() return def transaction_completion_handler(self, transaction): self.tm_number = self.tm_counter.increment() try: tid = transaction.tid s_id, callback = self._callbacks.get(tid) if callback: del self._callbacks[tid] self._pending_responses.put( (callback, transaction.get_response())) except: msglog.exception() # recycle the transaction for reuse within the queue self.stations.get(s_id).put_transaction(transaction) return def add_station(self, station): s_id = station.get_id() self.stations[s_id] = station return def get_synchronous(self, station, rqst): self._sync_get_lock.acquire() try: t = self._synchronous_transaction hdr = self._get_auth_header(station) hdr['Connection'] = 'close' t.build_request(rqst.url, None, hdr) self._cv.acquire() try: response = ETimeout() try: t.send_request() self._cv.wait(self.timeout) self._last_sync_get = uptime.secs() if t.is_expired(): t.cancel() else: response = t.get_response() except: t.cancel() finally: self._cv.release() return response finally: self._sync_get_lock.release() return def _bump_cv(self, transaction): # transaction isn't used self._cv.acquire() self._cv.notify() self._cv.release() return def send_loop(self): for s_id, station in self.stations.items(): for i in range(station.transaction_limit): try: t, rqst = station.get_next() except Empty: break hdr = self._get_auth_header(station) hdr['Connection'] = 'close' t.build_request(rqst.url, None, hdr) self._callbacks[t.tid] = (s_id, rqst.callback) t.send_request() return def response_loop(self): while 1: try: callback, rsp = self._pending_responses.get(False) callback(rsp) except Empty: return except: msglog.log('Jace', WARN, 'Unexpected error in response_loop') msglog.exception() return def _get_auth_header(self, station): return {"Authorization": "Basic %s" % station.base64string}
class TestCase(DefaultTestFixture, EventConsumerMixin): ID1 = "/services/time/UTC/second" ID2 = "/services/time/local" nrt1to2 = { ID1: ID1, ID2: ID2, } ID3 = "/services/time/UTC/milliseconds" ID4 = "/services/time/local/minute" nrt3to4 = { ID3: ID3, ID4: ID4, } nrt1to4 = {} nrt1to4.update(nrt1to2) nrt1to4.update(nrt3to4) ID5 = "/services/time/local/day" nrtB10 = {} for i in range(0, 10): url = "/BatchNode-%03d" % i nrtB10[url] = url del url def __init__(self, *args, **kw): DefaultTestFixture.__init__(self, *args, **kw) EventConsumerMixin.__init__(self, self.change_of_value) self.__event_lock = Lock() self.__event_updated_values = {} self._cov_counter = 0 return def change_of_value(self, event): self._cov_counter += 1 results = event.results() self.__event_lock.acquire() try: self.__event_updated_values.update(results) finally: self.__event_lock.release() return def setUp(self): DefaultTestFixture.setUp(self) self.__event_updated_values = {} self.new_node_tree() root = as_internal_node('/') self._cov_counter = 0 GetException().configure({'name': 'exception', 'parent': '/services'}) SUBSCRIPTION_MANAGER.configure({ 'debug': 0, '_normal_pool_size': 2, '_slow_pool_size': 2, '_prime_pool_size': 2, '_minimum_poll_interval': 0.001, '_slow_poll_threshold': 0.500, }) for i in range(0, 10): f = FastNode() f.configure({'parent': root, 'name': "FastNode-%03d" % i}) s = SlowNode() s.configure({'parent': root, 'name': "SlowNode-%03d" % i}) e = ErrorNode() e.configure({'parent': root, 'name': "ErrorNode-%03d" % i}) b = BatchNode(i & 1) b.configure({'parent': root, 'name': "BatchNode-%03d" % i}) root.start() return def tearDown(self): self.del_node_tree() DefaultTestFixture.tearDown(self) # self.dump_msglog() return def __values_changing(self, sid): r1 = SUBSCRIPTION_MANAGER.poll_all(sid) t1 = time.time() while 1: changed_values = SUBSCRIPTION_MANAGER.poll_changed(sid) if len(changed_values): return if (time.time() - t1) > 1.0: raise "Never got changes for any of the values." time.sleep(0.1) assert 1, "Can't reach here." def __all_plus_exceptions_check(self, all_values): no_such_node = all_values['/services/time/is/an/illusion']['value'] assert isinstance(no_such_node, mpx.lib.exceptions.ENoSuchName), ( "%r is not mpx.lib.exceptions.ENoSuchName" % no_such_node) get_exception = all_values['/services/exception']['value'] assert get_exception.__class__ is Exception, ( "%r is not an Exception" % get_exception) assert get_exception.args == ("GetException", ), ("%r is not %r" % (get_exception.args, ("GetException", ))) return def test_create_polled(self): sid = SUBSCRIPTION_MANAGER.create_polled() return def test_create_delivered(self): sid = SUBSCRIPTION_MANAGER.create_delivered(self, self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." time.sleep(0.1) t1 = time.time() while (time.time() - t1) < 1.0: self.__event_lock.acquire() try: if len(self.__event_updated_values) == 4: # We got all 4 values! return finally: self.__event_lock.release() time.sleep(0.1) if len(self.__event_updated_values) != 4: raise (("Never got changes for all four values, only %d.\n" "Values: %r") % (len(self.__event_updated_values), self.__event_updated_values)) def test_destroy(self): sids = [] for i in range(2): # ID3 is /services/time/UTC/milliseconds which should # change "really fast." sid = SUBSCRIPTION_MANAGER.create_polled({self.ID3: self.ID3}) # Make sure it comes up. t1 = time.time() self.__values_changing(sid) sids.append(sid) # Double check the values are changing. for sid in sids: self.__values_changing(sid) # Now nuke one of the suscriptions and see that the other stays valid. sid = sids.pop(0) SUBSCRIPTION_MANAGER.destroy(sid) try: SUBSCRIPTION_MANAGER.destroy(sid) except ENoSuchSubscription: pass else: raise "No such subscription not detected." # Make sure that the other subscription is valid. sid = sids.pop(0) self.__values_changing(sid) # Finally, make sure that the mnr is removed when the last snr is # deleted. if len(SUBSCRIPTION_MANAGER.diag_get_mnrs()) != 1: raise ("Bogus test, there should be 1 mnr at this point, not %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnrs())) SUBSCRIPTION_MANAGER.destroy(sid) if len(SUBSCRIPTION_MANAGER.diag_get_mnrs()) != 0: raise ("There should not be any mnrs at this point," " but there are %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnrs())) return def test_destroy_batch(self): sids = [] for i in range(2): # BatchNodes change "really fast." sid = SUBSCRIPTION_MANAGER.create_polled(self.nrtB10) # Make sure it comes up. t1 = time.time() self.__values_changing(sid) sids.append(sid) # Double check the values are changing. for sid in sids: self.__values_changing(sid) # Now nuke one of the suscriptions and see that the other stays valid. sid = sids.pop(0) SUBSCRIPTION_MANAGER.destroy(sid) try: SUBSCRIPTION_MANAGER.destroy(sid) except ENoSuchSubscription: pass else: raise "No such subscription not detected." # Make sure that the other subscription is valid. sid = sids.pop(0) self.__values_changing(sid) if len(SUBSCRIPTION_MANAGER.diag_get_mnrs()) != 10: raise ( "Bogus test, there should be 10 mnr at this point, not %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnrs())) if len(SUBSCRIPTION_MANAGER.diag_get_mnbs()) != 1: raise ("Bogus test, there should be 1 mnb at this point, not %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnbs())) SUBSCRIPTION_MANAGER.destroy(sid) # Make sure that the mnr is removed when the last snr is deleted. if len(SUBSCRIPTION_MANAGER.diag_get_mnrs()) != 0: raise ("There should not be any mnrs at this point," " but there are %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnrs())) # Finally, make sure that the mnb is removed when the last mnr is # deleted. if len(SUBSCRIPTION_MANAGER.diag_get_mnbs()) != 0: raise ("There should not be any mnbs at this point," " but there are %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnbs())) return def test_node_reference_table(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to2) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to2: raise "Node reference table mismatch." return def test_merge(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to2) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to2: raise "Initial node reference table mismatch." SUBSCRIPTION_MANAGER.merge(sid, self.nrt3to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Node reference table mismatch." return def test_replace(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to2) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to2: raise "Initial node reference table mismatch." SUBSCRIPTION_MANAGER.replace(sid, self.nrt3to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt3to4: raise "Replaced node reference table mismatch." return def test_empty(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." SUBSCRIPTION_MANAGER.empty(sid) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != {}: raise "Node reference table not empty." return def test_add(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to2) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to2: raise "Initial node reference table mismatch." SUBSCRIPTION_MANAGER.add(sid, self.ID5, self.ID5) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) nrt125 = {} nrt125.update(self.nrt1to2) nrt125[self.ID5] = self.ID5 if nrt != nrt125: raise "Node reference table mismatch." try: SUBSCRIPTION_MANAGER.add(sid, self.ID5, self.ID5) except ENodeIDExists: pass else: raise "Node ID in use not detected." return def test_add_and_get(self): st_time = time.time() rdict = SUBSCRIPTION_MANAGER.create_polled_and_get(self.nrt1to2) # Ensure we got back values for everything assert rdict['sid'] != None, "sid is not set in results dictionary." for x in rdict['values'].values(): assert x != None, "Got None in values: %s." % str(rdict['values']) def test_modify(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to2) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to2: raise "Initial node reference table mismatch." SUBSCRIPTION_MANAGER.modify(sid, self.ID2, self.ID3) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt == self.nrt1to2: raise "Modified node reference table not modified." if nrt != {self.ID1: self.ID1, self.ID2: self.ID3}: raise "Modified node reference table mismatch." try: SUBSCRIPTION_MANAGER.modify(sid, self.ID3, self.ID2) except ENoSuchNodeID: pass else: raise "No such NodeID not detected." return def test_remove(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." SUBSCRIPTION_MANAGER.remove(sid, self.ID2) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) nrt134 = {} nrt134.update(self.nrt1to4) del nrt134[self.ID2] if nrt != nrt134: raise "Node reference table mismatch." try: SUBSCRIPTION_MANAGER.remove(sid, self.ID2) except ENoSuchNodeID: pass else: raise "No such NodeID not detected." return def test_poll_all(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." # Check that each invokation gets all values. for i in range(0, 10): all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if len(all_values) != len(self.nrt1to4): # We did not get all 4 values! raise ("poll_all(self.nrt1to4) did not return all values." " (%d out of %d)" % (len(all_values), len(self.nrt1to4))) # Check that (eventually) all the values are result dictionaries. all_values = SUBSCRIPTION_MANAGER.poll_all(sid) t1 = time.time() while (time.time() - t1) < 1.0: if None not in all_values.values(): return time.sleep(0.1) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if None in all_values.values(): raise ("Never got changes for all four result dictionaries, %d." % len(all_values)) return def test_poll_all_plus_exceptions(self): SUBSCRIPTION_MANAGER._set_tunable_parameters({ 'minimum_poll_interval': 0.0, }) nrt1to4bad5to6 = {} nrt1to4bad5to6.update(self.nrt1to4) nrt1to4bad5to6['/services/time/is/an/illusion'] = ( '/services/time/is/an/illusion') nrt1to4bad5to6['/services/exception'] = '/services/exception' sid = SUBSCRIPTION_MANAGER.create_polled(nrt1to4bad5to6) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != nrt1to4bad5to6: raise "Initial node reference table mismatch." # Check that each invokation gets all values. for i in range(0, 10): all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if len(all_values) != len(nrt1to4bad5to6): # We did not get all 4 values! raise ("poll_all(self.nrt1to4) did not return all values." " (%d out of %d)" % (len(all_values), len(nrt1to4bad5to6))) # Check that (eventually) all the values are result dictionaries. all_values = SUBSCRIPTION_MANAGER.poll_all(sid) t1 = time.time() while (time.time() - t1) < 1.0: if None not in all_values.values(): self.__all_plus_exceptions_check(all_values) # Finally, test that a new subscription gets the correct # results. time.sleep(0.1) sid = SUBSCRIPTION_MANAGER.create_polled(nrt1to4bad5to6) time.sleep(0.1) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) self.__all_plus_exceptions_check(all_values) time.sleep(0.1) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) self.__all_plus_exceptions_check(all_values) return time.sleep(0.1) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if None in all_values.values(): raise ("Never got values for all nodes: %r." % all_values) return def test_poll_changed(self): sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." all_values = {} time.sleep(0.1) t1 = time.time() while (time.time() - t1) < 1.0: time.sleep(0.1) changed_values = SUBSCRIPTION_MANAGER.poll_changed(sid) all_values.update(changed_values) if len(all_values) == 4: # We got all 4 values! return raise "Never got changes for all four values, %d." % len(all_values) return def test_fast_minimum_poll_interval(self): SUBSCRIPTION_MANAGER._set_tunable_parameters({ 'minimum_poll_interval': 0.0, }) sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." # Check that each invokation gets all values. for i in range(0, 10): all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if len(all_values) != len(self.nrt1to4): # We did not get all 4 values! raise ("poll_all(self.nrt1to4) did not return all values." " (%d out of %d)" % (len(all_values), len(self.nrt1to4))) # Check that (eventually) all the values are result dictionaries. all_values = SUBSCRIPTION_MANAGER.poll_all(sid) t1 = time.time() while (time.time() - t1) < 1.0: time.sleep(0.1) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if None in all_values.values(): raise (("Never got changes for all four result dictionaries, %d.\n" "Values: %r") % (len(all_values), all_values)) # ID3 is /services/time/UTC/milliseconds which should # change "really fast." c1 = all_values[self.ID3]['changes'] time.sleep(1.0) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) c2 = all_values[self.ID3]['changes'] if (c2 - c1) < 25: # It's usually 500 on fearfactory... raise "%r only changed %d times in one second." % ( self.ID3, (c2 - c1), ) return def test_adjusted_minimum_poll_interval(self): SUBSCRIPTION_MANAGER._set_tunable_parameters({ 'minimum_poll_interval': 0.2, }) sid = SUBSCRIPTION_MANAGER.create_polled(self.nrt1to4) nrt = SUBSCRIPTION_MANAGER.node_reference_table(sid) if nrt != self.nrt1to4: raise "Initial node reference table mismatch." # Check that each invokation gets all values. for i in range(0, 10): all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if len(all_values) != len(self.nrt1to4): # We did not get all 4 values! raise ("poll_all(self.nrt1to4) did not return all values." " (%d out of %d)" % (len(all_values), len(self.nrt1to4))) # Check that (eventually) all the values are result dictionaries. t1 = time.time() while (time.time() - t1) < 1.0: all_values = SUBSCRIPTION_MANAGER.poll_all(sid) if None not in all_values.values(): # ID3 is /services/time/UTC/milliseconds which should # change "really fast." c1 = all_values[self.ID3]['changes'] time.sleep(1.0) all_values = SUBSCRIPTION_MANAGER.poll_all(sid) c2 = all_values[self.ID3]['changes'] if (c2 - c1) > 6: # 0.2 == Max 5/second. raise ("1/5th second throttle failed," " %r changed %d times in one second.") % ( self.ID3, (c2 - c1), ) return time.sleep(0.1) raise ("Never got changes for all four result dictionaries, %d." % len(all_values)) return def test_polled_event_handling(self): event_maker = EventProducerTestClass() event_maker.configure({'name': 'EventProducerTester', 'parent': '/'}) event_maker.start() sid = SUBSCRIPTION_MANAGER.create_polled({1: event_maker}) # Wait for polling to start and verify value made it without any events t1 = time.time() all_values = {1: None} while all_values[1] is None: if (time.time() - t1) > 1.0: raise "Got tired of waiting..." all_values = SUBSCRIPTION_MANAGER.poll_all(sid) time.sleep(0.1) # Check that subscription value is the initial value of 100 if all_values[1]['value'] != 100: raise ("polled_event_handling did not return inital value: " + str(all_values[1]['value'])) # make a rapid series of changes to the node value for i in range(10): event_maker._cov_check(i) time.sleep(0.1) # check change count, should be approx 10 all_values = SUBSCRIPTION_MANAGER.poll_all(sid) change_count = all_values[1]['changes'] if change_count < 8 or change_count > 12: raise ("polled_event_handling change count wrong." " Should be approx 10, not %d" % (change_count, )) # Check that the last value is corrent. final_value = all_values[1]['value'] if final_value != 9: raise ("polled_event_handling final value incorrect." " Should be 9, not %d" % (final_value, )) return def test_targeted_event_handling(self): event_maker = EventProducerTestClass() event_maker.configure({'name': 'EventProducerTester', 'parent': '/'}) event_maker.start() nr = {1: event_maker} sid = SUBSCRIPTION_MANAGER.create_delivered(self, nr) # Wait for polling to start and verify value made it without any events t1 = time.time() while (time.time() - t1) < 1.0: all_values = SUBSCRIPTION_MANAGER.poll_all(sid) time.sleep(0.1) # Check that subscription value is the initial value of 100 if all_values[1]['value'] != 100: raise ("polled_event_handling did not return inital value: " + str(all_values[1]['value'])) # make a rapid series of changes to the node value for i in range(10): event_maker._cov_check(i) time.sleep(0.1) # check change count, should be approx 10 value_updates = self.__event_updated_values[1]['changes'] cov_counts = self._cov_counter if value_updates < cov_counts: raise ( "Targeted event handling event count did not match %d vs %d" % (value_updates, cov_counts)) def test_timeout(self): sids = [] for i in range(2): if not i: timeout = 1.0 else: timeout = None # ID3 is /services/time/UTC/milliseconds which should # change "really fast." sid = SUBSCRIPTION_MANAGER.create_polled({self.ID3: self.ID3}, timeout) # Make sure it comes up. t1 = time.time() self.__values_changing(sid) sids.append(sid) # Double check the values are changing and that the subscriptions # stay valid while we poll for values. t1 = time.time() while (time.time() - t1) < 2.0: for sid in sids: self.__values_changing(sid) time.sleep(0.1) # Now ensure that sid[0] times out... sid = sids.pop(0) t1 = time.time() while sid in SUBSCRIPTION_MANAGER.diag_get_sids(): if (time.time() - t1) > 2.0: raise "%r did not timeout." % sid time.sleep(0.1) # Finally, make sure that the other subscription is valid. sid = sids.pop(0) self.__values_changing(sid) return def test_timeout_batch(self): # nrtB10 changes "really fast." sid = SUBSCRIPTION_MANAGER.create_polled(self.nrtB10, 1.0) # Make sure it comes up. t1 = time.time() self.__values_changing(sid) # Double check the values are changing and that the subscriptions # stay valid while we poll for values. t1 = time.time() while (time.time() - t1) < 2.0: self.__values_changing(sid) time.sleep(0.1) if len(SUBSCRIPTION_MANAGER.diag_get_mnrs()) != 10: raise ( "Bogus test, there should be 10 mnr at this point, not %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnrs())) if len(SUBSCRIPTION_MANAGER.diag_get_mnbs()) != 1: raise ("Bogus test, there should be 1 mnb at this point, not %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnbs())) t1 = time.time() while sid in SUBSCRIPTION_MANAGER.diag_get_sids(): if (time.time() - t1) > 2.0: raise "%r did not timeout." % sid time.sleep(0.1) # Make sure that the mnr is removed when the last snr is deleted. if len(SUBSCRIPTION_MANAGER.diag_get_mnrs()) != 0: raise ("There should not be any mnrs at this point," " but there are %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnrs())) # Finally, make sure that the mnb is removed when the last mnr is # deleted. if len(SUBSCRIPTION_MANAGER.diag_get_mnbs()) != 0: raise ("There should not be any mnbs at this point," " but there are %r." % len(SUBSCRIPTION_MANAGER.diag_get_mnbs())) return # # # def _print_subscriptions(self): print "" print "*" * 60 for s in SUBSCRIPTION_MANAGER.diag_get_subscriptions(): print s print "*" * 60 return def _print_sids(self): print "" print "*" * 60 for s in SUBSCRIPTION_MANAGER.diag_get_sids(): print s print "*" * 60 return def _print_mnrs(self): print "" print "*" * 60 for s in SUBSCRIPTION_MANAGER.diag_get_mnrs(): print s print "*" * 60 return def _print_mnbs(self): print "" print "*" * 60 for s in SUBSCRIPTION_MANAGER.diag_get_mnbs(): print s print "*" * 60 return
class Status(CompositeNode, EventProducerMixin): """ Status is the base class used by different host management services. It's primary role is to serve as an event producer and interact with the NBMManager class in a consistent manner. Classes derived from Status will implement the _get_status method to determine (i.e ping) the course of action that should be taken to interact with the remote NBM. """ def __init__(self): self._last_rcvd = 0 self._subscribers = 0 self._scheduled = None self._skip_cache = False self._cached_result = None self._exec_delay = _Buffer(5) self._subscription_lock = Lock() CompositeNode.__init__(self) EventProducerMixin.__init__(self) def configure(self, cd): CompositeNode.configure(self, cd) set_attribute(self, 'ttl', 300, cd, int) def configuration(self): cd = CompositeNode.configuration(self) get_attribute(self, 'ttl', cd) return cd # _get_status must be implemented by derived classes def _get_status(self): raise ENotImplemented() def has_cov(self): return 1 def event_has_subscribers(self): return bool(self._subscribers) def event_subscribe(self, *args): self._subscription_lock.acquire() try: already_subscribed = self.event_has_subscribers() result = EventProducerMixin.event_subscribe(self, *args) self._subscribers += 1 finally: self._subscription_lock.release() if not already_subscribed and self._cached_result: value = self._cached_result.value self._trigger_cov(value, value, time.time()) return result def event_unsubscribe(self, *args): self._subscription_lock.acquire() try: EventProducerMixin.event_unsubscribe(self, *args) self._subscribers = max(0, self._subscribers - 1) finally: self._subscription_lock.release() def skip_cache(self): # forces an update if there are consumers. self._cached_result = None def refresh_value(self): status = self._get_status() self.update_value(status) return bool(status) def update_value(self, value): if self._cached_result: previous = self._cached_result.value if value == previous: return False changes = self._cached_result.changes + 1 else: changes = 1 previous = None self._cached_result = Result(value, uptime.secs(), 1, changes) self._trigger_cov(previous, value, time.time()) return True def get(self, asyncok=True): return self.get_result().value def get_result(self, skipCache=0): if not self._cached_result or skipCache: self.refresh_value() return self._cached_result def _trigger_cov(self, old_value, new_value, timestamp=None): if not timestamp: timestamp = time.time() cov = ChangeOfValueEvent(self, old_value, new_value, timestamp) self.event_generate(cov) # compensate for the average delay in future scheduling considerations. def _get_exec_delay(self): avg_delay = 0 for delay in self._exec_delay: avg_delay += delay if avg_delay: avg_delay = avg_delay / len(self._exec_delay) return avg_delay exec_delay = property(_get_exec_delay)
class Device(CompositeNode, UpdateMixin): def __init__(self): self._subscription_lock = Lock() self._subscribed = 0 self._subscribers = {} self._last_value = None self._last_rcvd = None self._decode_indexes = {} return def configure(self, cd): super(Device, self).configure(cd) set_attribute(self, 'ttl', 60, cd, int) set_attribute(self, 'swid', '', cd) return def configuration(self): cd = super(Device, self).configuration() get_attribute(self, 'ttl', cd) get_attribute(self, 'swid', cd) return cd def start(self): if self.swid: self.url = BASE_URL % (self.station.host, self.swid) self._rqst = JaceRequest(self.url, ttl=self.ttl) super(Device, self).start() return def can_bundle(self): return bool(self.swid) def subscribe(self, name, func): self._subscription_lock.acquire() try: ## # if there are multiple external consumers, they are subscribed # via event producing child node. self._subscribers[name] = func self._subscribed += 1 if self._last_value and (uptime.secs() - self._last_rcvd) < self.ttl: try: value = self._last_value.get(name) func(value) except: pass if self._subscribed == 1: self.update_continuous(None) finally: self._subscription_lock.release() return def unsubscribe(self, name): self._subscription_lock.acquire() try: assert self._subscribed, 'Cannot decrement subscribers below 0' del self._subscribed[name] self._subscribed -= 1 finally: self._subscription_lock.release() return def event_has_subscribers(self): return bool(self._subscribed) def _load_indexes(self, data): d_len = len(data) for name in self._subscribers.keys(): index = offset = 0 for l in data: if l.count('<'+name+'>'): break index += 1 for l in data[index:]: if l.count('<value>'): break offset += 1 if (index+offset) > d_len: index = offset = None self._decode_indexes[name] = (index, offset) return def _get_indexes(self, name): return self._decode_indexes.get(name, (None, None)) def _have_indexes(self): indexes = self._decode_indexes.keys() for interest in self._subscribers.keys(): if interest not in indexes: return False return True def decode(self, data_s): if data_s.startswith('<!--'): data_s = data_s[(data_s[1:].find('<')+1):] data = data_s.split('\n') if not self._have_indexes(): self._load_indexes(data) values = {} for name in self._subscribers.keys(): index,offset = self._get_indexes(name) try: if not data[index].count(name) or not data[index+offset].count('value'): return self._decode_slow(data_s) l = data[index+offset] values[name] = l.split('>')[1].split('<')[0] except: return self._decode_slow(data_s) return values def _decode_slow(self, data): try: data_o = xml2code(data) except: data_o = None return data_o def update_cache(self, value_obj): for name, func in self._subscribers.items(): value = value_obj.get(name) func(value) self._last_value = value self._last_rcvd = uptime.secs() return def _get_station(self): return self.parent station = property(_get_station)
class PeriodicColumn(Column, EventConsumerMixin): def __init__(self): self.function = None self._calculator = None self.__node = None self.__node_url = None self.__lock = Lock() self.__started = 0 self._sid = None self._present_value = None Column.__init__(self) EventConsumerMixin.__init__(self, self.change_of_value) ## # @author Craig Warren # @param config # @key context Sets the context for the function passed in # @value anything Possible context 'import time' because the function # uses the time modual # @default None # @key function Function to return the value to record # @value function # @required # @key args Arguments to the function # @value list a list of arguments required by the function # @default an empty list # @return None # def configure(self, config): Column.configure(self, config) set_attribute(self, 'context', 'None', config, str) set_attribute(self, 'function', REQUIRED, config) set_attribute(self, 'use_subscription_manager', 1, config, int) ## # @fixme HACK to work around too much voodoo to fix right now. self.__function_attribute = self.function set_attribute(self, 'conversion', as_magnitude, config, _function) self.original_function = self.function if type(self.function) == types.StringType: self.function = string.replace( self.function, 'self.', 'as_internal_node("%s").' % as_node_url(self)) set_attribute(self, 'args', '()', config) # fix for bad configuration if self.args == '': self.args = '()' self.__function_config = self.function self._last_time = None self._last_value = None self._period = self.parent.parent.period def start(self): Column.start(self) if (type(self.__function_config) == types.StringType and string.count(self.__function_config, 'as_node') == 1 and self.__function_config.endswith('get')): func = self.__function_config self.__node = as_node(func[func.find('(') + 2:func.rfind(')') - 1]) if self.use_subscription_manager: self._sid = SM.create_delivered(self, {1: as_node_url(self.__node)}) self.function = self.get_last else: self.function = getattr(self.__node, func[func.rfind('.') + 1:]) rexec = self.parent.parent.get_environment() self.original_function = RFunction(self.function, args=self.args, context=self.context, rexec=rexec) self.function = self._convert self.variables = {} nodes = self.children_nodes() for potential_calculator in nodes: if hasattr(potential_calculator, 'evaluate'): if self._calculator: #oops raise EAttributeError('Too many calculator nodes', self) self._calculator = potential_calculator self.function = self._evaluate # hook the calculator in self.__original_function = self.original_function self.original_function = self.__evaluate_original_function self.__started = 1 def stop(self): self.__started = 0 Column.stop(self) self.variables = None self._calculator = None if self._sid: SM.destroy(self._sid) self._sid = None def __evaluate_original_function(self): if not self.__started: msglog.log( 'broadway', msglog.types.WARN, 'Attempting to get value of ' 'unstarted Column. Will attempt start.') try: self.start() except: self.stop() raise msglog.log('broadway', msglog.types.INFO, 'Column Start succeeded') elif self.__node is not None and self.__node.parent is None: self.__lock.acquire() try: # Redoing parent test with Lock acquired. # Prevents unecessary locking. if self.__node.parent is None: msglog.log( 'broadway', msglog.types.WARN, 'Source node for Column %s has no parent' % self.name) msglog.log('broadway', msglog.types.WARN, 'Stopping Column %s' % self.name) self.stop() msglog.log('broadway', msglog.types.WARN, 'Restarting Column %s' % self.name) self.start() finally: self.__lock.release() return self.__original_function() def attach_variable(self, name): #defaults for self.get and calculator.get if name == 'now': return time.time elif name == 'value': return self._current_value_ elif name == 'last_value': return self._last_value_ elif name == 'last_time': return self._last_time_ elif name == 'period': return self._period_ else: if debug: print 'Bad Attach: Attempted to attach to "%s".' % name return self._bad_attach_ def _current_value_(self): return self._convert() def _last_value_(self): if debug: print 'Get last value of :', self._last_value return self._last_value def _last_time_(self): if debug: print 'Get last time of :', self._last_time return self._last_time def _period_(self): if debug: print 'Get period : ', self._period return self._period def _bad_attach_(self): raise EAttributeError('attempt to attach to non-existant variable', self) ## # our hook to replace the function attribute with a calculator def _evaluate(self): now = self.scheduled_time() value = self._current_value_() if debug: print 'Now :', now, value answer = self._calculator.evaluate({'now': now, 'value': value}) if debug: print 'Lasts :', now, value self._last_time = now self._last_value = value return answer ## # Hook for getting scheduled time from a periodic_column. # Mostly here for backwards-compatibility, function should # be self.parent.scheduled_time. # # @return Timestamp of current run. # def scheduled_time(self): if self.parent is None: return time.time() return self.parent.parent.scheduled_time() ## # @author Craig Warren # @return dictionary # the current configuration dictionary # def configuration(self): config = Column.configuration(self) get_attribute(self, 'context', config) get_attribute(self, 'conversion', config, _name) config['function'] = self.__function_attribute get_attribute(self, 'args', config) return config def _convert(self): return self.conversion(self.original_function()) def get(self, skipCache=0): #async get from nodebrowser if self._calculator: return self._calculator.get() if not callable(self.function): raise ENotStarted('Function not callable, usually ' + 'means get called before start') return self._convert() def change_of_value(self, event): self._present_value = event.results()[1]['value'] def get_last(self): if isinstance(self._present_value, Exception): raise self._present_value if not self._present_value: self._present_value = self.__node.get() return self._present_value def get_source_node_url(self): # HACK to get the source node URL if type(self.function) == types.StringType: return self.function.split('"')[1] return 'Unknown URL'
class PeriodicExporter(Exporter, EventConsumerMixin): def __init__(self): Exporter.__init__(self) EventConsumerMixin.__init__(self, self.handle_connected, self.connection_event_error) self.running = 0 self._scheduled = None self._lock = Lock() def handle_connected(self, event): self.msglog('%s Got connection event' % self.name) if event.__class__ == ConnectionEvent: self.msglog('Connection state is %s.' % str(event.state)) if event.state == ConnectionEvent.STATE.UP: self.msglog('Going to start export.') self.go() else: msg = (('Unknown event recieved by %s from %s.' % (self.name, str(self.connection_node))) + ' Event: %s' % str(event)) msglog.log('broadway', msglog.types.WARN, msg) def connection_event_error(self, exc, event): msg = ('Connection Event for ' + str(self.connection_node) + ' had the following Error\n' + 'Event: ' + str(event) + 'Error: ' + str(exc)) msglog.log('broadway', msglog.types.WARN, msg) def msglog(self, msg, force=0): if self.debug or force: msglog.log('broadway.mpx.service.data.periodic_exporter', msglog.types.DB, msg) def configure(self, config): map_to_attribute(self, 'period', 900, config, map_to_seconds) if self.period == 0: raise EInvalidValue('period', self.period, 'Export period cannot be 0') set_attribute(self, 'debug', 0, config, as_boolean) set_attribute(self, 'synchronize_on', '00:00', config) set_attribute(self, 'timeout', 60, config, int) set_attribute(self, 'connection_node', '/services/network', config) set_attribute(self, 'connection_attempts', 3, config, int) set_attribute(self, 'always_export', 0, config, as_boolean) set_attribute(self, 'breakup_on_period', 0, config, as_boolean) Exporter.configure(self, config) self._time = _TimeStore(self) def configuration(self): config = Exporter.configuration(self) map_from_attribute(self, 'period', config, map_from_seconds) get_attribute(self, 'connection_node', config) get_attribute(self, 'debug', config, str) get_attribute(self, 'connection_attempts', config) get_attribute(self, 'timeout', config) get_attribute(self, 'always_export', config, str) get_attribute(self, 'synchronize_on', config) get_attribute(self, 'breakup_on_period', config, str) return config def start(self): Exporter.start(self) if not self.running: node = as_node(self.connection_node) if hasattr(node, 'event_subscribe'): node.event_subscribe(self, ConnectionEvent) else: if self.debug: msg = ('Connection node: ' + str(self.connection_node) + ' is not an event producer.') msglog.log('broadway', msglog.types.INFO, msg) self.connection = node self.running = 1 self._init_next_time() self._schedule() else: raise EAlreadyRunning def stop(self): self.running = 0 if self._scheduled is not None: try: self._scheduled.cancel() except: pass Exporter.stop(self) def go(self, end_time, start_time=None): if self._lock.locked(): msglog.log('broadway',msglog.types.WARN, \ 'Last export still active, skipping current request.') return Exporter_ThreadPool.queue_noresult(self._complete, end_time, start_time) def scheduled_time(self): return self.next_time() - self.period def last_time(self): return self._time.get_last_time() def _schedule(self): next = self.next_time() self._scheduled = scheduler.at(next, self.go, (next, )) def _complete(self, end_time, start_time=None): self._lock.acquire() try: self._export(end_time, start_time) except: msglog.exception() self._lock.release() if self.running: self._schedule() ## # def _init_next_time(self): time_format = '%Y%m%d %H:%M:%S' sync_format = '%Y%m%d ' + self.synchronize_on + ':00' current_time = int(time.time()) f_sync = time.strftime(sync_format, self.time_function(current_time)) f_now = time.strftime(time_format, self.time_function(current_time)) sync = time.mktime(time.strptime(f_sync, time_format)) now = time.mktime(time.strptime(f_now, time_format)) if now > sync: # sync time in past, add one day to sync time. sync += map_to_seconds({'days': 1}) gap = sync - now if self.period > gap: sync_time = current_time + gap else: sync_time = current_time + (gap % self.period) # # # self._next_time = sync_time return self._next_time def next_time(self): current_time = time.time() while self._next_time < current_time: self._next_time += self.period return self._next_time def data_since_export(self): start_time = self.last_time() end_time = self.next_time() return self.log.get_slice('timestamp', start_time, end_time) def formatted_data_since_export_as_string(self): length = 0 stream = self.formatted_data_since_export() text = stream.read(1024) while len(text) > length: length = len(text) text += stream.read(1024) return text def formatted_data_since_export(self): return self.formatter.format(self.data_since_export()) def export_data_since_export(self): return self.transporter.transport(self.formatted_data_since_export()) def _export(self, end_time, start_time=None): attempts = 0 connected = 0 while attempts < self.connection_attempts: self.msglog('Acquiring connection %s.' % str(self.connection_node)) try: connected = self.connection.acquire() except: msglog.exception() if connected: self.msglog('Connection acquired') break attempts += 1 self.msglog('Connection acquire failed %s times.' % attempts) else: raise EConnectionError('Failed to connect %s times' % attempts) try: if start_time is None: start_time = self.last_time() if start_time == 0 and self.breakup_on_period: self.msglog('Start Time is 0 and set to Break on Transfer.') start_time = self.log.get_first_record()['timestamp'] self.msglog('Start Time set to timestamp of first row: %s' % time.ctime(start_time)) retrieve = self.log.get_slice if self.log.name == 'msglog': msglog.log('msglog.exporter', msglog.types.INFO, 'repr(mpx.properties)\n%s\n' % (repr(properties))) retrieve = self.log.get_range end_time = time.time() end = end_time if self.breakup_on_period: self.msglog('Breaking on period') end = start_time + self.period self.msglog('Full export of slice from %s to %s' % (time.ctime(start_time), time.ctime(end_time))) while start_time != end_time: if end > end_time: self.msglog( 'End greater than End Time. Resetting to End Time') end = end_time self.msglog('Going to export slice from %s to %s' % (time.ctime(start_time), time.ctime(end))) data = retrieve('timestamp', start_time, end) if (not data) and (not self.always_export): raise ENoData('timestamp', start_time, end) self.msglog('Sending data to formatter.') try: output = self.formatter.format(data) if not output is None: self.msglog('Sending formatted data to transporter.') self.transporter.transport(output) start_time = end except EBreakupTransfer, e: entry = e.break_at if entry['timestamp'] == end: # prevents loop where transporter is just failing. raise EIOError('EBreakupTransfer not progressing.') end = entry['timestamp'] msglog.log( 'broadway', msglog.types.WARN, 'Breaking up data transfer at %s.' % time.ctime(end)) else: self._time.set_last_time(start_time) self.msglog('Data transported') end = start_time + self.period finally: if hasattr(self.formatter, 'cancel'): self.formatter.cancel( ) # prevent mult copies of data at next successful transport if connected: self.msglog('Releasing connection.') self.connection.release() def nodebrowser_handler(self, nb, path, node, node_url): html = nb.get_default_view(node, node_url) html += '<h4>Commands</h4>\n' s = '%s?action=invoke&method=do_export' % self.name html += '<a href="%s">Force export via nodebrowser.</a>' % (s, ) return html def do_export(self, end_time=None, start_time=None): if end_time is None: end_time = time.time() self.go(end_time, start_time) return 'Export triggered.'
class NewUser(PersistentDataObject): #USERS = _UserDictionary() def __init__(self, name, password_file=PASSWD_FILE, group_file=GROUP_FILE, shadow_file=SHADOW_FILE): self.__lock = Lock() self.__password_file = password_file self.__group_file = group_file self.__shadow_file = shadow_file self.meta = {} self.USERS.load() if not self.USERS.has_key(self.name()): msglog.log('broadway', msglog.types.INFO, ('No profile for user %s found, creating' ' new profile' % name)) self.USERS[self.name()] = str(UUID()) PersistentDataObject.__init__(self, self.USERS[self.name()]) PersistentDataObject.load(self) def save(self): self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) passwd_db.load() passwd_db[self.name()] = self.password_entry() passwd_db.save() # save /etc/shadow content shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() shadow_db[self.name()] = self.shadow_entry() shadow_db.save() finally: self.__lock.release() self.load(self.name()) def name(self): return self.__user.user() def group(self): return self.__groups[0].group() def groups(self): group_db = GroupFile(self.__group_file) group_db.load() return self.__user.groups(group_db) def group_ids(self): ids = [] for group in self.groups(): ids.append(group.gid()) return ids def type(self): return self.__user.user_type() def set_type(self, type): raise ENotImplemented(self.set_type) def password(self): raise ENotImplemented(self.password) def set_password(self, password): self.__shadow.crypt(crypted_password(self.__name, password)) def crypt(self): return self.__shadow.crypt() def set_crypt(self, crypt): self.__shadow.crypt(crypt) def uid(self): return self.__user.uid() def set_uid(self, uid): self.__user.uid(uid) def gid(self): return self.__groups[0].gid() def set_gid(self, gid): self.__user.gid(gid) def gids(self): gids = [] for group in self.__groups: gids.append(group.gid()) return gids def set_gids(self, gids): raise ENotImplemented(self.set_gids) def gecos(self): return self.__user.gecos() def set_gecos(self, gecos): self.__user.gecos(gecos) def directory(self): return self.__user.directory() def set_directory(self, directory): self.__user.directory(directory) def shell(self): return self.__user.shell() def set_shell(self, shell): self.__user.shell(shell) def is_dirty(self): return not self.__loaded def set_meta_value(self, name, value): self.meta[name] = value PersistentDataObject.save(self) def get_meta_value(self, name, default=None): if self.meta.has_key(name): return self.meta[name] return default def get_meta(self): return self.meta.copy() def __getitem__(self, name): return self.get_meta_value(name) def __setitem__(self, name, value): return self.set_meta_value(name, value) def password_entry(self): return self.__user def shadow_entry(self): return self.__shadow def group_entry(self): return self.__groups[0] def group_entries(self): return self.__groups
class SequenceStati(PersistentDataObject): def __init__(self, node): self.__lock = Lock() self.__last_save = {} self.max_seq = -1 self.pending_seqs = [] self.inprocess_seqs = [] PersistentDataObject.__init__(self, node, auto_load=True) return def __snapshot(self, attrs): self.__last_save = {} for attr in attrs: self.__last_save[attr] = copy.copy(getattr(self,attr)) return def __changed(self): if not self.__last_save: return True for attr in self.__last_save.keys(): if not self.__last_save.has_key(attr): return True if self.__last_save[attr] != getattr(self,attr): return True return False def __load(self): result = PersistentDataObject.load(self) self.__snapshot(self.loaded()) return result ## # @note Referrers should not call this method as the state is best # maintained via the sequence processing methods: # QUEUE_PENDING(), SEQUENCE_TO_PROCESS(), SEQUENCE_PROCESSED(), # and SEQUENCES_PROCESSED(). def load(self): self.__lock.acquire() try: return self.__load() finally: self.__lock.release() raise EUnreachableCode() def __save(self): result = PersistentDataObject.save(self) self.__snapshot(self.saved()) return result ## # @note Referrers should not call this method as the state is best # maintained via the sequence processing methods: # QUEUE_PENDING(), SEQUENCE_TO_PROCESS(), SEQUENCE_PROCESSED(), # and SEQUENCES_PROCESSED(). def save(self): self.__lock.acquire() try: return self.__save() finally: self.__lock.release() raise EUnreachableCode() def __too_stale(self): return self.__changed() and True def __save_if_stale(self, force_save=False): if not force_save: force_save = self.__too_stale() if force_save: self.__save() return def save_if_stale(self): self.__lock.acquire() try: return self.__save_if_stale() finally: self.__lock.release() raise EUnreachableCode() ## # Add a sequence number to the pending queue. def __queue_pending(self, seq): if seq not in self.pending_seqs: self.pending_seqs.append(seq) self.pending_seqs.sort() if seq > self.max_seq: self.max_seq = seq return ## # Add a sequence number to the pending queue. def queue_pending(self, seq): self.__lock.acquire() try: self.__queue_pending(seq) finally: self.__lock.release() return ## # Return (pop) the first sequence number from the pending queue, # and add it to the in-process queue. def sequence_to_process(self): self.__lock.acquire() try: if self.pending_seqs: seq = self.pending_seqs.pop(0) if seq not in self.inprocess_seqs: self.inprocess_seqs.append(seq) return seq finally: self.__lock.release() return None ## # Sequence was successfully processed (exported). def __sequence_processed(self, seq): if seq in self.pending_seqs: self.pending_seqs.remove(seq) pass # @fixme log weirdness. if seq not in self.inprocess_seqs: pass # #fixme Log wierdness. else: self.inprocess_seqs.remove(seq) return ## # Sequence was successfully processed (exported). # @note This commits changed to the PDO. def sequence_processed(self, seq): self.__lock.acquire() try: self.__sequence_processed(seq) self.__save_if_stale() finally: self.__lock.release() return ## # Sequences were successfully processed (exported). # @note This commits changed to the PDO. def sequences_processed(self, seqs): self.__lock.acquire() try: for seq in seqs: self.__sequence_processed(seq) self.__save_if_stale() finally: self.__lock.release() return ## # Simulate pending sequence numbers for all sequence numbers that do # not appear to have been exported. # @note Consumer must deal with sequence numbers that they don't have # in there event queue by looking them up in the Log. In the # case of looking up a sequence, consumers should call # sequence_processed() if the sequence does not exist so this # object knows not to simulate that number again. def __recover_pending(self, upto=-1): inprocess = self.inprocess_seqs self.inprocess_seqs = [] self.pending_seqs.extend(inprocess) self.pending_seqs.sort() start_seq = self.max_seq + 1 if self.pending_seqs: start_seq = max(start_seq, self.pending_seqs[-1]+1) for seq in range(start_seq, upto+1): self.__queue_pending(seq) return ## # Simulate pending sequence numbers for all sequence numbers that do # not appear to have been exported. def recover_pending(self, upto=-1): self.__lock.acquire() try: self.__recover_pending(upto) finally: self.__lock.release() return pass
class TcpTunnel(ImmortalThread): def __init__(self, vcp): #super(TcpTunnel, self).__init__(self) ImmortalThread.__init__(self) self._needs_reconfig = 0 # link to the port object self._vcp = vcp self._lock = Lock() self._op_lock = Lock() # list of operations to apply to an {out|in}bound tcp # segment. In the future this might include operations # such as encryption or compression - for now, only the # header info. that is applied by devices such as # Lantronix's UDS-10 is supported. Up refers to what is # being applied to outbound tcp data, down to received. # Methods should be added to these lists in the order they # are to be applied. self._up_segment_ops = [] self._down_segment_ops = [] # transaction identifier - used only in vcp mode. self.__tid = 0 self._pending_tid = 0 # one and only poll object. socket, serial fd and # command pipe are all polled. self._poll_obj = None # command pipe allows other threads to insert control # messages. self._cmd_pipe = None # both sides (serial & socket) of the tunnel self._sock_listen_fd = None self._sock_fd = None self._serial_port = None # tcp state management self._is_connected = 0 self.is_active = 0 # tunnel statistics self.tcp_bytes_rcvd = 0 self.tcp_bytes_sent = 0 self.serial_bytes_rcvd = 0 self.serial_bytes_sent = 0 self.connection_attempts = 0 def configure(self, config): self.tty = '/dev/tty' + config['dev'][-2:] self.tcp_port = int(config['tcp_port']) self.mode = config['mode'] self.timeout_msec = config['p_timeout_msec'] if self.mode == 'vcp': if self._up_segment_ops.count(self._add_vcp_header) == 0: self._up_segment_ops.append(self._add_vcp_header) if self._down_segment_ops.count(self._remove_vcp_header) == 0: self._down_segment_ops.append(self._remove_vcp_header) self.is_server = int(config['is_server']) if self.is_server == 0: self.host = config['host'] # who we're connecting to. if self.is_active: # tunnel is being reconfigured "in flight". self._needs_reconfig = 1 self._send_cmd('reconfig') if self._is_in_accept(): self._clear_accept() def run(self): self._needs_reconfig = 0 if self.is_active: # we've restarted due to a configuration change self.start_tunnel() else: if not self._cmd_pipe: # set up the command pipe and begin to build the poll obj. self._cmd_pipe = os.pipe() self._poll_obj = select.poll() self._poll_obj.register(self._cmd_pipe[READ], select.POLLIN | select.POLLERR | select.POLLHUP) while 1: # no poll timeout, wait here until kicked to start. evt = self._poll_obj.poll(-1) if evt[0][0] == self._cmd_pipe[READ]: if evt[0][1] == select.POLLIN: cmd = os.read(self._cmd_pipe[READ], 32) if cmd.find('start') >= 0: self.start_tunnel() # cmd pipe poll err, critical, hard restart self._cmd_pipe = None self._poll_obj = None raise ERestart() def start_tunnel(self): if self is not currentThread(): self._send_cmd('start') return self._op_lock.acquire() try: self.is_active = 1 # set up Port object for the tunnel that reads\writes to the # slave device file of the pseudo-terminal pair. if not self._serial_port: self._serial_port = Port() cfg = self._vcp.configuration() cfg['dev'] = self.tty cfg['name'] = '_slave' cfg['parent'] = self._vcp self._serial_port.configure(cfg) self._op_lock.release() except: self._op_lock.release() while self.is_active: if not self._serial_port.is_open(): self._serial_port.open() self._serial_port.drain() try: if self.is_server: self._do_listen() else: self._do_connect() except: msglog.exception() if self._serial_port and not self._serial_port.is_open(): self._serial_port.close() def stop_tunnel(self): self.is_active = 0 if self is not currentThread(): self._send_cmd('stop') if self._is_in_accept(): self._clear_accept() else: self._op_lock.acquire() try: self._tear_down_fds() finally: self._op_lock.release() #raise ERestart() def is_connected(self): self._op_lock.acquire() result = self._is_connected self._op_lock.release() return result def _create_socket(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return s def _close_socket(self): self._is_connected = 0 self._sock_fd.close() def _send_cmd(self, cmd): self._lock.acquire() try: os.write(self._cmd_pipe[WRITE], cmd) finally: self._lock.release() def _do_connect(self): while self.is_active: self._sock_fd = self._create_socket() while not self._is_connected: self.connection_attempts += 1 try: self._sock_fd.connect((self.host, self.tcp_port)) self._is_connected = 1 except socket.gaierror, e: # host related error, ie. hostname not resolving - possibly transient self.connection_attempts += 1 msglog.log('VCP', WARN, 'Error resolving hostname %s.' % self.host) time.sleep(60) raise EConnectionError except socket.error, e: # connection error, possibly transient - sleep for a bit and retry self.connection_attempts += 1 time.sleep(30) if self._needs_reconfig: self._is_connected = 0 self._tear_down_fds() raise ERestart() # loop in _do_tunnel until the tcp connection or the framework # based consumer (ie. protocol) "goes away". self._do_tunnel() self._is_connected = 0
class _ServerTSM: IDLE = 'idle' # ASHRAE 135-1995 5.4.5.1 SEGMENTED_REQUEST = 'segmented request' # ASHRAE 135-1995 5.4.5.2 AWAIT_RESPONSE = 'await response' # ASHRAE 135-1995 5.4.5.3 SEGMENTED_RESPONSE = 'segmented response' # ASHRAE 135-1995 5.4.5.4 COMPLETE = 'complete' # IDLE with a result. def __init__(self, device, interface, network, source, apdu): global _module_lock global _tsm_q self._cv = Lock() self.timestamp = now() #self.network = network self.device = device self.address = device.address self.request = apdu self.network = network self.source = source self.exception = None self.state = self.IDLE # maximum npdu length is from three sources, the interface, the device # and the request. NPDU header is 21 bytes max # max_apdu_len is the number of bytes, max_apdu_length_accepted is an encoded value mtu = min(interface.mtu - _network._MAX_APDU_OVERHEAD, device.max_apdu_len) #read max apdu size from request and make sure we pick the leastest # length is encoded according to bacnet 20.1.2.5 # this issue also shows up in lib.c bacnet_send_message if apdu.max_apdu_length_accepted < len(DECODE_MAX_APDU_LENGTH): mtu = min(mtu, DECODE_MAX_APDU_LENGTH[apdu.max_apdu_length_accepted]) self.send_segment_size = mtu # use either interface or device timeout/retry values self.T_seg = interface.T_seg if device.T_seg is not None: self.T_seg = device.T_seg self.T_wait_for_seg = interface.T_wait_for_seg if device.T_wait_for_seg is not None: self.T_wait_for_seg = device.T_wait_for_seg self.T_out = interface.T_out if device.T_out is not None: self.T_out = device.T_out self.N_retry = interface.N_retry if device.N_retry is not None: self.N_retry = device.N_retry self.retry_count = 0 self.segment_retry_count = 0 self.sent_all_segments = 0 self.last_sequence_number = 0 self.initial_sequence_number = 0 self.actual_window_size = None self.proposed_window_size = None self.segment_timer = None self.response = None self.invoke_id = apdu.invoke_id return def process_state(self, msg): try: self._cv.acquire() if self.state == self.IDLE: self._idle_state(msg) elif self.state == self.SEGMENTED_REQUEST: self._segmented_request_state(msg) elif self.state == self.SEGMENTED_RESPONSE: self._segmented_response_state(msg) elif self.state == self.COMPLETE: self._completed_state(msg) else: raise EIOError('Illegal TSM state') finally: self._cv.release() def _idle_state(self, msg): if not msg: self._stop_segment_timer() return #no timers while idle # @fixme Validate APDU... if msg.pdu_type == BACNET_CONFIRMED_SERVICE_REQUEST_PDU: if msg.segmented_message == 1: if msg.sequence_number == 0: self.request = APDU(msg) self.actual_window_size = msg.window_size self._send_segment_ack(msg) self.segment_retry_count = 0 self._start_segment_timer() self.last_sequence_number = 0 self.initial_sequence_number = 0 self.state = self.SEGMENTED_REQUEST return else: #bad sequence number self._UnexpectedPDU_Received(msg) return else: #unsegmented, get response now self._send_response( confirmed_service_indication(self.network, self.device, msg)) return elif msg.pdu_type == BACNET_ABORT_PDU: self._complete() return elif msg.pdu_type == BACNET_SEGMENT_ACK_PDU and msg.server == 0: self._UnexpectedPDU_Received(msg) else: print 'unexpected packet ignored in bacnet server idle state' pass #silence def _segmented_request_state(self, msg): if msg: if msg.pdu_type == BACNET_CONFIRMED_SERVICE_REQUEST_PDU: if msg.segmented_message == 1: if msg.sequence_number == int((self.last_sequence_number + 1) & 0xff): if msg.more_follows == 1: #still more coming in self.request.data.fromstring( msg.data) #append data to buffer self.last_sequence_number = int( (self.last_sequence_number + 1) & 0xff) if msg.sequence_number == int( (self.initial_sequence_number + self.actual_window_size) & 0xff): self.initial_sequence_number = self.last_sequence_number self._send_segment_ack(msg) self.segment_retry_count = 0 self._start_segment_timer() return else: #final segment has been received self.request.data.fromstring( msg.data) #append data to buffer self.last_sequence_number = int( (self.last_sequence_number + 1) & 0xff) self._stop_segment_timer() self._send_segment_ack(msg) self.initial_sequence_number = self.last_sequence_number self._send_response( confirmed_service_indication( self.network, self.device, self.request)) return else: #segment received out of order self._send_segment_nack(msg.invoke_id, self.last_sequence_number) self.segment_retry_count = 0 self._start_segment_timer() self.initial_sequence_number = self.last_sequence_number return elif msg.pdu_type == BACNET_ABORT_PDU: self._complete() return _UnexpectedPDU_Received(msg) else: #since clock tick t = self.segment_timer if t: if t.executing() or t.expired(): if self.segment_retry_count < 3: #lock? self.segment_retry_count += 1 self._start_segment_timer() else: self._complete(None) def _segmented_response_state(self, msg): if debug: print 'Server TSM _segmented_response_state' if msg: if msg.pdu_type == BACNET_SEGMENT_ACK_PDU: if not self._InWindow(msg): #duplicate ack if debug: print 'ServerTSM: segmented_response_state: duplicate ack' self._start_segment_timer() return #do nothing self._DuplicateACK_Received(msg) else: if self.sent_all_segments: #final ack received if debug: print '_ServerTSM: final ack received' self._stop_segment_timer() self._complete(msg) return else: self._start_segment_timer() self.segment_retry_count = 0 return self._NewACK_Received(msg) elif msg.pdu_type == BACNET_ABORT_PDU: self._complete(msg) elif ((msg.pdu_type == BACNET_SIMPLE_ACK_PDU) and \ (self.sent_all_segments == 1)): self._complete(msg) elif msg.pdu_type == BACNET_CONFIRMED_SERVICE_REQUEST_PDU: #duplicate if msg.segmented_message == 1: self._send_segment_nack(self.request.invoke_id, self.last_sequence_number) else: #anything else is unexpected self._UnexpectedPDU_Received(msg) else: #since tick t = self.segment_timer if t: if t.executing() or t.expired(): if self.segment_retry_count <= self.N_retry: #lock? self.segment_retry_count += 1 self._start_segment_timer() self._FillWindow() else: self._complete(None) pass def _NewACK_Received(self, msg): if debug: print '_ServerTSM._NewACK_Received' self.initial_sequence_number = (msg.sequence_number + 1) % 0x100 self.actual_window_size = msg.window_size self._FillWindow() ## # @return True if the BACnet-SegmentACK's sequence-number is in the # current transmission window. Otherwise, false. def _InWindow(self, msg): window_index = (msg.sequence_number - self.initial_sequence_number) \ % 0x100 return window_index < self.actual_window_size def _FillWindow(self): if debug: print 'ServerTSM: enter _FillWindow', self.initial_sequence_number, self.actual_window_size for ix in range(self.initial_sequence_number, self.initial_sequence_number + self.actual_window_size): last_seg = len(self.response.data) <= (self.send_segment_size * (ix + 1)) # Send the next segment. segment = APDU() segment.version = 1 segment.data_expecting_reply = 1 segment.pdu_type = BACNET_COMPLEX_ACK_PDU segment.segmented_message = 1 segment.more_follows = not last_seg segment.invoke_id = self.request.invoke_id segment.sequence_number = (ix % 256) segment.window_size = self.proposed_window_size segment.choice = self.request.choice segment.data = self.response.data[self.send_segment_size * ix:self.send_segment_size * (ix + 1)] if debug: print 'ServerTSM: _FillWindow: ', len( segment.data), last_seg, ix segment = segment.as_npdu() self._send(segment) if last_seg: self.sent_all_segments = 1 return def _send_response(self, apdu): self.response = apdu if debug: print 'ServerTSM:_send_response: ', str(len(apdu.data)) if is_APDU(apdu): if len(apdu.data) <= self.send_segment_size: self.send_UnsegmentedComplexOrSimpleAck(apdu.as_npdu()) else: self.send_SegmentedComplexAck(apdu) else: if len(apdu.data) <= self.send_segment_size: self.send_UnsegmentedComplexOrSimpleAck(apdu) else: self.send_SegmentedComplexAck(apdu) return def _send(self, response): if not _is_master_server(self.device, self.network): response.sspec = 1 response.slen = 6 #what should we chose? response.sadr = utils.bytes_as_string_of_hex_values( self.device.instance_number, response.slen) response.snet = self.device.network #correct local copy? if (hasattr(self.request, 'sspec')) and ( self.request.sspec == 1): #message came through router, send it back response.dspec = 1 response.dlen = self.request.slen response.dadr = self.request.sadr response.dnet = self.request.snet _send(self.network, self.source, response) def _send_segment_ack(self, msg): if debug: print '_ServerTSM._send_segment_ack' # Acknowledge the final segment ack = NPDU() ack.version = 1 ack.data_expecting_reply = 0 ack.pdu_type = BACNET_SEGMENT_ACK_PDU ack.negative_ack = 0 ack.server = 1 ack.window_size = self.actual_window_size ack.invoke_id = msg.invoke_id ack.sequence_number = msg.sequence_number self._send(ack) def _send_segment_nack(self, invoke_id, sequence_number): if debug: print '_ServerTSM._send_segment_nack' # Acknowledge the final segment ack = NPDU() ack.version = 1 ack.data_expecting_reply = 0 ack.pdu_type = BACNET_SEGMENT_ACK_PDU ack.negative_ack = 1 ack.server = 1 ack.window_size = self.actual_window_size ack.invoke_id = invoke_id ack.sequence_number = sequence_number self._send(ack) ## # BACnet event handler for send_UnsegmentedComplexAck. def send_UnsegmentedComplexOrSimpleAck(self, msg): if debug: print '_ServerTSM.send_ConfirmedUnsegmented' #if self.state != self.IDLE: ## @fixme exceptions... #raise EIOError('Transaction State Machine in use') msg.data_expecting_reply = 0 msg.server = 1 self._send(msg) self._complete(msg) ## # BACnet event handler for send_SegmentedComplexAck. def send_SegmentedComplexAck(self, msg): if debug: print '_ServerTSM.send_SegmentedComplexAck', len(msg.data) #if self.state != self.IDLE: ## @fixme exceptions... #raise EIOError('Transaction State Machine in use') self.segment_retry_count = 0 self.initial_sequence_number = 0 self.proposed_window_size = 10 self.actual_window_size = 1 self._start_segment_timer() self.sent_all_segments = 0 # Send the first segment. segment = APDU() segment.version = 1 segment.data_expecting_reply = 1 segment.server = 1 segment.pdu_type = BACNET_COMPLEX_ACK_PDU segment.segmented_message = 1 segment.more_follows = 1 segment.segmented_response_accepted = 1 segment.invoke_id = msg.invoke_id segment.sequence_number = 0 segment.window_size = self.proposed_window_size segment.choice = msg.choice segment.data = msg.data[0:self.send_segment_size] segment = segment.as_npdu() self.state = self.SEGMENTED_RESPONSE self._send(segment) def _completed_state(self, msg): pass #todo have all TSM exit through this state to clean up timers def _complete(self, msg): if debug: print 'SM _complete' self._stop_segment_timer() self.response = msg if not msg: self.exception = ETimeout self.state = self.COMPLETE def complete(self): return self.state == self.COMPLETE def _UnexpectedPDU_Received(self, msg): if debug: print '_ServerTSM._UnexpectedPDU_Received' # Send an ABORT. self._stop_segment_timer() abort = npdu.NPDU() abort.version = 1 abort.data_expecting_reply = 0 abort.pdu_type = BACNET_ABORT_PDU abort.server = 1 abort.invoke_id = msg.invoke_id abort.reason = ABORT_REASON_INVALID_APDU_IN_THIS_STATE self._send(abort) # Give up. self.exception = EIOError('Unexpected PDU.') # @fixme self._complete(None) return def _start_segment_timer(self, timeout=None): self._stop_segment_timer() if timeout is None: timeout = self.T_seg self.segment_timer = scheduler.seconds_from_now_do( timeout, self._segment_timeout) def _stop_segment_timer(self): if self.segment_timer: self.segment_timer.cancel() self.segment_timer = None def _segment_timeout(self): if debug: print '_ServerTSM: timeout' self.process_state(None)
class EnergywiseDomain(Node): def __init__(self): self._cpex_lock=RLock() # cpex switch list lock, used for setting/getting the "primary" cpex switch. self._cache_lock=Lock() # domain value cache lock. self._cpex_switch_map_lock = Lock() # lock for cpex switches cache data structure self._cache_value=None self._cache_time=0 self._cpex_switch_map_cache=SwitchMap({}) self._cpex_switch_map_time=0 self.ttl=30 self._reinit() return def _not_running(self, *args, **kw): raise ENotRunning("%r is not running." % self.as_node_url()) def _empty_domain_usage(self, importance=100, skipCache=False): return 0.0 def _reinit(self): self._cpex_switches = [] self._snmp_switches = [] self._all_switches = [] self._all_domains = [] self.domain = '' self.trend_node = None self.energywise_domain_usage = self._not_running return def configure(self, config): Node.configure(self, config) set_attribute(self, 'ttl', 30, config, int) return def configuration(self): config = Node.configuration(self) get_attribute(self, 'ttl', config, str) return config def start(self): self._cpex_lock.acquire() try: for child in self.children_nodes(): if isinstance(child,EnergywiseSwitch): if child.PROTOCOL_SNMP == child.protocol: self._snmp_switches.append(child) else: self._cpex_switches.append(child) self._all_switches.append(child) elif isinstance(child,EnergywiseDomain): self._all_domains.append(child) # elif @fixme magic hook for reverse compatibility. if self._snmp_switches and self._cpex_switches: raise EConfiguration( "All switches in a domain must be configurtion to use the" " same protocol." ) self._cpex_switches.sort(_cpex_switch_cmp) finally: self._cpex_lock.release() if not self.domain: self.domain = _find_domain(self) if self._snmp_switches: self.energywise_domain_usage = self.snmp_domain_usage elif self._cpex_switches: self.energywise_domain_usage = self.cpex_domain_usage else: self.energywise_domain_usage = self._empty_domain_usage Node.start(self) return def stop(self): self._reinit() Node.stop(self) return def cpex_domain_usage(self, importance=100, skipCache=False, max_attempts=3): self._cpex_lock.acquire() aggr_result = 0 try: if not self._cpex_switches: raise ENoDomainSwitches( "No switches are configured for the %r domain." % self.domain ) for switch in self._cpex_switches: for i in range(0, max_attempts): try: result = switch.cpex_domain_usage(importance) if result: aggr_result += result break except: pass except ENoDomainSwitches: raise finally: self._cpex_lock.release() return aggr_result # caching for cpex switches under a domain # @return A dictionary of Energywise usage values keyed by switch address, possibly from cache. def cpex_switch_usage_map(self, importance=100, skipCache=False): self._cpex_switch_map_lock.acquire() try: if skipCache or self._is_cpex_switch_map_stale(time.time()): #fetch actual value usage_map = self._cpex_switch_usage_map(importance) self._update_cpex_switch_map(usage_map, time.time()) return self._cpex_switch_map_cache finally: self._cpex_switch_map_lock.release() raise EUnreachableCode("Executed unreachable code!") ## # Call cpex_switch_usage_map on the primary cpex switch. # @return A dictionary of Energywise usage values keyed by switch address. def _cpex_switch_usage_map(self, importance=100, max_attempts=3): self._cpex_lock.acquire() aggr_result = {} try: if not self._cpex_switches: raise ENoDomainSwitches( "No switches are configured for the %r domain." % self.domain ) for switch in self._cpex_switches: for i in range(0, max_attempts): try: result = switch.cpex_switch_usage_map(importance) if result: aggr_result.update(result) break except: pass except ENoDomainSwitches: raise finally: self._cpex_lock.release() return aggr_result def snmp_domain_usage(self, importance=100, skipCache=False): result = 0 for switch in self._snmp_switches: try: result += switch.snmp_switch_usage(importance, skipCache) except: msglog.exception() msglog.log("Energywise",msglog.types.ERR, "Failed to get data from %r switch" %switch.name ) return result def _is_cpex_switch_map_stale(self, timestamp): return (self._cpex_switch_map_time + self.ttl) < timestamp def _update_cpex_switch_map(self, switch_map, timestamp): self._cpex_switch_map_cache = SwitchMap(switch_map) self._cpex_switch_map_time = timestamp def _is_domain_cache_stale(self,timestamp): assert self._cache_lock.locked() return (self._cache_time + self.ttl) < timestamp def _update_domain_cache(self,value, timestamp): self._cache_value = value self._cache_time = timestamp #caching the domain-usage def aggregate_domain_usage(self, importance=100, skipCache=False): self._cache_lock.acquire() try: if skipCache or self._is_domain_cache_stale(time.time()): #fetch the actual value and update the cache try: result = self.energywise_domain_usage(importance, skipCache) except: msglog.exception() result = 0 for sub in self._all_domains: result += sub.aggregate_domain_usage(importance, skipCache) self._update_domain_cache(result, time.time()) return self._cache_value else: # use the cached value return self._cache_value finally: self._cache_lock.release() raise EUnreachableCode("Executed unreachable code!") def new_trend(self,period): return new_trend(self,period) def delete_trend(self): return delete_trend(self) def get(self, skipCache=False): return self.aggregate_domain_usage() def get_result(self, skipCache=False): return Result(self.get(skipCache), time.time(), cached=False)
class EWebConnectAlarmClient(Client): _batch_mode_default = 0 _host_default = REQUIRED _port_default = 4546 _timeout_default = 60 def _init_default_attribute_values(self): self.batch_mode = self._batch_mode_default self.host = self._host_default self.port = self._port_default self.timeout = self._timeout_default return def __init__(self): self.__alarm_queue = Queue() self.__current_thread = None self.__lock = Lock() self._init_default_attribute_values() Client.__init__(self) return def configure(self,config): Client.configure(self,config) set_attribute(self,'batch_mode', self._batch_mode_default, config, int) set_attribute(self,'host', self._host_default, config, str) set_attribute(self,'port', self._port_default, config, int) set_attribute(self,'timeout', self._timeout_default, config, int) return def configuration(self): config = Client.configuration(self) get_attribute(self, 'batch_mode', config, int) get_attribute(self, 'host', config, str) get_attribute(self, 'port', config, int) get_attribute(self, 'timeout', config, int) return config def start(self): self.__lock.acquire() try: self.__running = 1 self.register_event(NewAlarmsEvent,self._new_alarms) finally: self.__lock.release() Client.start(self) self.debug = 1 def stop(self): self.__lock.acquire() try: self.unregister_event(NewAlarmsEvent) self.__running = 0 finally: self.__lock.release() Client.stop(self) def is_running(self): return self.__running def message_log(self,message,message_type=msglog.types.DB): if message_type != msglog.types.DB or self.debug: msglog.log('EWebConnect Alarm Client',message_type,message) ## # Event handler for the NewAlarmsEvent # # Queues each new alarm for processing and then schedules # _prime_process_alarm_queue() on a thread pool to ensure that alarms # are processed. def _new_alarms(self, event): self.__lock.acquire() try: if not self.is_running(): raise ENotStarted('%s' % self.as_node_url()) finally: self.__lock.release() for alarm in event: self.__alarm_queue.put(alarm.as_dictionary()) self.message_log('New Alarms Event, queuing action') LOW.queue_noresult(self._prime_process_alarm_queue) return ## # If no thread is actively processing the alarm queue, then set this thread # as the current alarm queue processor and invoke _process_alarm_queue(). # # @note This method is in invoked as an action queued on a thread pool and # should never be called directly when processing an event. def _prime_process_alarm_queue(self): # @todo Save queue in a PDO? thread = currentThread() self.__current_thread = thread self._process_alarm_queue(thread) ## # Process all alarms on the alarm queue. # # @note This method is in invoked indirectly as an action queued on a # thread pool and should never be called directly when processing an # event. def _process_alarm_queue(self,my_thread): self.message_log('Processing Alarm Queue...') while my_thread == self.__current_thread: alarm_dict = self.__alarm_queue.get(0) if alarm_dict is NOTHING: break try: self._send_alarm_dict(alarm_dict) except: self.message_log('Failed to send alarm:\n %r' % alarm_dict, msglog.types.ERR) msglog.exception() else: self.message_log('New alarm process coincided with running process') self.message_log('Finished Processing Alarm Queue') ## # Format the Alarm described by alarm_dict as an eWebConnect Alarm # message and send it to the eWebConnect server. # @note This method always succeeds to format a message, any field that # is not valid for any reason is set to "N/A". Furthermore, # this method does not intercept any networking failures as it # is the caller's responsibility to handle retries, etc... def _send_alarm_dict(self, alarm_dict): if self.host is None: self.message_log('Failed to send alarm; host address is None:\n %r' % alarm_dict, msglog.types.INFO) return # nowhere to send it! ewebconnect_text = ( "%(timestamp)s, %(TZ)s, %(host)s, %(what)s, %(code)s:" " %(type)s %(text)s" ) % { "timestamp":self._alarm_timestamp(alarm_dict), "TZ":self._alarm_tz(alarm_dict), "host":self._alarm_host(alarm_dict), "what":self._alarm_what(alarm_dict), "code":self._alarm_code(alarm_dict), "type":self._alarm_type(alarm_dict), "text":self._alarm_text(alarm_dict), } self.message_log('Sending Alarm: %s' % ewebconnect_text) server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) try: # @fixme Try block only exists because the normal desctructor # invokation does not appear to work on mpx.lib.socket # sockets which is a big deal if there is an exception. # - mevans server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server_socket.connect((self.host, self.port), self.timeout) server_socket.sendall(ewebconnect_text, self.timeout) finally: # A finally block is used because the normal desctructor invokation # does not appear to work on mpx.lib.socket socket's. - mevans # @fixme Figure out why! server_socket.close() # Could this hang? Should I use shutdown? self.message_log('Alarm Sent') return ## # Convert an Alarm's time-stamp to a string in eWebConnect's format. # # @param alarm_dict The dictionary representation of an Alarm, presumably # returned by the Alarm's as_dictionary() method. # @return A string representing the the time-stamp in eWebConnect's format. # @note Any failure during the conversion results in the string "N/A" and # the exception is logged to the msglog. This is to help ensure that # the alarm is still delivered with as much useful information as # possible. def _alarm_timestamp(self, alarm_dict): result = "N/A" try: localtime = time.localtime(alarm_dict['timestamp']) result = time.strftime("%m/%d/%Y %H:%M:%S", localtime) except: msglog.exception() return result ## # @see _alarm_timestamp(). def _alarm_tz(self, alarm_dict): result = "N/A" try: is_dst = time.localtime(alarm_dict['timestamp']).tm_isdst result = time.tzname[is_dst] except: msglog.exception() return result ## # @see _alarm_timestamp(). def _alarm_host(self, alarm_dict): result = "N/A" try: # @fixme F/W should have this as a 'system attribute' (aka # property) and should support COV on properties. result = socket.gethostbyaddr(socket.gethostname())[0] except: msglog.exception() return result ## # @see _alarm_timestamp(). def _alarm_code(self, alarm_dict): result = "N/A" try: result = alarm_dict['state'] except: msglog.exception() return result ## # @see _alarm_timestamp(). def _alarm_what(self, alarm_dict): result = "ALARM" return result ## # @see _alarm_timestamp(). def _alarm_type(self, alarm_dict): result = "N/A" try: result = alarm_dict['type'] except: msglog.exception() return result ## # @see _alarm_timestamp(). def _alarm_text(self, alarm_dict): result = "N/A" try: result = alarm_dict['data'] except: msglog.exception() return result
class SNMP(CompositeNode): __node_id__ = '9e2a4bbf-d4cd-40c6-80ba-95edb94214ed' def __init__(self): super(SNMP, self).__init__() self.__lock = Lock() self.__mib_builder = None self.__mib_view = None self.__loaded_mibs = [] snmpEngine = engine.SnmpEngine() transportDispatcher = dispatch.AsynsockDispatcher() transportDispatcher.setSocketMap({}) snmpEngine.registerTransportDispatcher(transportDispatcher) self.__dispatcher = snmpEngine.transportDispatcher.runDispatcher self.__generator = cmdgen.AsynCommandGenerator(snmpEngine) self.__engine = snmpEngine return def start(self): self.__lock.acquire() try: self.__mib_builder = builder.MibBuilder() self.__mib_view = view.MibViewController(self.__mib_builder) finally: self.__lock.release() # Load some common MIBs: self.load_mib('SNMP-COMMUNITY-MIB') self.load_mib('SNMP-VIEW-BASED-ACM-MIB') self.load_mib('IF-MIB') super(SNMP, self).start() return def load_mib(self, mib_name, force=False): self.__lock.acquire() try: if force or (mib_name not in self.__loaded_mibs): self.__mib_builder.loadModules(mib_name) self.__loaded_mibs.append(mib_name) finally: self.__lock.release() return def describe_oid(self, oid): self.__lock.acquire() try: assert isinstance(oid, (tuple, univ.ObjectIdentifier)) matched_oid, matched_label, suffix = ( self.__mib_view.getNodeNameByOid(oid)) mib_name, oid_name, empty = ( self.__mib_view.getNodeLocation(matched_oid)) assert matched_label[-1] == oid_name assert empty == () matched_smi = self.__mib_builder.mibSymbols[mib_name][oid_name] return OidInfo(self.describe_oid, self.__mib_builder, self.__mib_view, oid, matched_oid, matched_label, suffix, mib_name, oid_name, matched_smi) finally: self.__lock.release() raise EUnreachableCode() def simple_varbinds_result(self, cbCtx): errorIndication = cbCtx['errorIndication'] errorStatus = cbCtx['errorStatus'] errorIndex = cbCtx['errorIndex'] varBinds = cbCtx['varBinds'] if errorIndication is not None or errorStatus: raise SNMPException(errorIndication=errorIndication, errorStatus=errorStatus, errorIndex=errorIndex, varBinds=varBinds) return varBinds classmethod(simple_varbinds_result) def multi_varbinds_result(klass, cbCtx): varBindHead, varBindTotalTable, appReturn = cbCtx errorIndication = appReturn['errorIndication'] errorStatus = appReturn['errorStatus'] errorIndex = appReturn['errorIndex'] varBindTable = appReturn['varBindTable'] if errorIndication is not None or errorStatus: raise SNMPException(errorIndication=errorIndication, errorStatus=errorStatus, errorIndex=errorIndex, varBindTable=varBindTable) varBinds = [] for sublist in varBindTable: varBinds.extend(sublist) return varBinds classmethod(multi_varbinds_result) def simple_callback(klass, sendRequestHandle, errorIndication, errorStatus, errorIndex, varBinds, cbCtx): cbCtx['errorIndication'] = errorIndication cbCtx['errorStatus'] = errorStatus cbCtx['errorIndex'] = errorIndex cbCtx['varBinds'] = varBinds # We are done, no more queries: return False classmethod(simple_callback) def multi_callback(klass, sendRequestHandle, errorIndication, errorStatus, errorIndex, varBindTable, cbCtx): varBindHead, varBindTotalTable, appReturn = cbCtx if errorIndication or errorStatus: appReturn['errorIndication'] = errorIndication appReturn['errorStatus'] = errorStatus appReturn['errorIndex'] = errorIndex appReturn['varBindTable'] = varBindTable # No more SNMP requests required: return False else: varBindTotalTable.extend(varBindTable) # XXX out of table # rows possible varBindTableRow = varBindTable[-1] for idx in range(len(varBindTableRow)): name, val = varBindTableRow[idx] if val is not None and varBindHead[idx].isPrefixOf(name): break else: appReturn['errorIndication'] = errorIndication appReturn['errorStatus'] = errorStatus appReturn['errorIndex'] = errorIndex appReturn['varBindTable'] = varBindTotalTable # No more SNMP requests required: return False # Continue table retrieval: return True classmethod(multi_callback) def snmp_get_multiple(self, auth_data, transport, *object_names): self.__lock.acquire() try: cbCtx = {} self.__generator.asyncGetCmd(auth_data, transport, object_names, (self.simple_callback, cbCtx)) self.__dispatcher() var_binds = self.simple_varbinds_result(cbCtx) return var_binds finally: self.__lock.release() raise EUnreachableCode() def snmp_set_multiple(self, auth_data, transport, var_binds): self.__lock.acquire() try: cbCtx = {} self.__generator.asyncSetCmd(auth_data, transport, var_binds, (self.simple_callback, cbCtx)) self.__dispatcher() self.simple_varbinds_result(cbCtx) return finally: self.__lock.release() raise EUnreachableCode() def snmp_getbulk_multiple(self, auth_data, transport, non_repeaters, max_repetitions, *object_names): self.__lock.acquire() try: appReturn = {} varBindHead = map( lambda (x, y): univ.ObjectIdentifier(x + y), map((lambda x, g=self.__generator: mibvar.mibNameToOid( g.mibViewController, x)), object_names)) varBindTotalTable = [] cbCtx = (varBindHead, varBindTotalTable, appReturn) self.__generator.asyncBulkCmd(auth_data, transport, non_repeaters, max_repetitions, object_names, (self.multi_callback, cbCtx)) self.__dispatcher() return self.multi_varbinds_result(cbCtx) finally: self.__lock.release() raise EUnreachableCode() def snmp_getnext_multiple(self, auth_data, transport, *object_names): self.__lock.acquire() try: appReturn = {} varBindHead = map( lambda (x, y): univ.ObjectIdentifier(x + y), map((lambda x, g=self.__generator: mibvar.mibNameToOid( g.mibViewController, x)), object_names)) varBindTotalTable = [] cbCtx = (varBindHead, varBindTotalTable, appReturn) self.__generator.asyncNextCmd(auth_data, transport, object_names, (self.multi_callback, cbCtx)) # Work around Eaton Powerare UPS's incorrect end-of-getnext packet. try: self.__dispatcher() except error.ProtocolError, e: # Some Eaton Powerware UPS SNMP stacks respond with an out # of range error-index (2) for the final getnext response # of NoSuchName. if not varBindTotalTable: msglog.log( "SNMP", msglog.types.WARN, "Caught ProtocolError with empty" " varBindTotalTable, re-raising.") msglog.log("SNMP", msglog.types.DB, "cbCtx: %r" % cbCtx) raise msglog.exception() msglog.log( "SNMP", msglog.types.WARN, "Ignoring protocol error to allow (partial)" " discovery.") appReturn['errorIndication'] = None appReturn['errorStatus'] = None appReturn['errorIndex'] = 0 appReturn['varBindTable'] = varBindTotalTable return self.multi_varbinds_result(cbCtx) finally:
class DeviceRT(DeviceRS5): def __init__(self): self.rt_request_obj = device.real_time_value_req() self.rt_response_obj = device.real_time_value_res() self.cr_request_obj = device.control_relay_req() self.cr_response_obj = device.control_relay_res() self.rt_lock = Lock() self.cr_lock = Lock() self.rt_last_updated = 0 super(DeviceRT, self).__init__() def configure(self, config): set_attribute(self, 'password', '11111100', config, str) super(DeviceRT, self).configure(config) def start(self): if self.is_running(): raise EAlreadyRunning() #bug: CSCts88534 if self.parent.parent.baud == 9600: self.response_obj = device.readmeterres() #bug ended self.configure_rt_packet() self.configure_cr_packet() super(DeviceRT, self).start() def configure_rt_packet(self): req_addr = self.rt_request_obj.findChildByName('addr') req_bcc = self.rt_request_obj.findChildByName('bcc') req_addr.setValue(self.bin_addr) checksum = (0xC3 + calc_sum(self.bin_addr)) & 0xFF req_bcc.setValue(checksum) def configure_cr_packet(self): req_addr = self.cr_request_obj.findChildByName('addr') req_pwd = self.cr_request_obj.findChildByName('pwd') password = format_password(self.password) req_addr.setValue(self.bin_addr) req_pwd.setValue(password) self.cr_checksum = (0x58 + calc_sum(self.bin_addr) + calc_sum(password)) & 0xFF def _read_rt_value(self, wait_time=None, numretries=None): """Don't use Me. """ if numretries is None: numretries = self.retry_count while numretries: try: self._send_request(self.rt_request_obj, self.rt_response_obj, wait_time, 1) resp_addr = self.rt_response_obj.findChildByName('addr') resp_data = self.rt_response_obj.findChildByName('data') resp_bcc = self.rt_response_obj.findChildByName('bcc') addr = resp_addr.getValue() data = resp_data.getValue() bcc = resp_bcc.getValue() checksum = (0xD9 + calc_sum(addr) + calc_sum(data)) & 0xFF if checksum != bcc: raise EBadChecksum() if self.bin_addr != addr: raise EInvalidMessage() values = {} values['EM_RecMeterValue'] = format_rt_reading(data[0:4]) values['EM_SenMeterValue'] = format_rt_reading(data[4:8]) values['EM_RecActivePower'] = format_rt_reading(data[8:12]) values['EM_SenActivePower'] = format_rt_reading(data[12:16]) values['EM_RecPassive1MeterValue'] = format_rt_reading( data[16:20]) values['EM_RecPassive2MeterValue'] = format_rt_reading( data[20:24]) values['EM_RecPassivePower1'] = format_rt_reading(data[24:28]) values['EM_RecPassivePower2'] = format_rt_reading(data[28:32]) values['EM_SenPassive1MeterValue'] = format_rt_reading( data[32:36]) values['EM_SenPassive2MeterValue'] = format_rt_reading( data[36:40]) values['EM_SenPassivePower1'] = format_rt_reading(data[40:44]) values['EM_SenPassivePower2'] = format_rt_reading(data[44:48]) #have to rename voltages and currents values['EM_Voltage_1'] = format_rt_reading(data[48:52]) values['EM_Voltage_2'] = format_rt_reading(data[52:56]) values['EM_Voltage_3'] = format_rt_reading(data[56:60]) values['EM_Current_1'] = format_rt_reading(data[60:64]) values['EM_Current_2'] = format_rt_reading(data[64:68]) values['EM_Current_3'] = format_rt_reading(data[68:72]) values['EM_Phase_1'] = format_rt_reading(data[72:76]) values['EM_Phase_2'] = format_rt_reading(data[76:80]) values['EM_Phase_3'] = format_rt_reading(data[80:84]) values['EM_Hz'] = format_rt_reading(data[84:88]) values['EM_PowerRelay'] = format_rt_reading(data[88:92]) values['WM_MeterValue'] = format_rt_reading(data[92:96]) values['WM_MeterPower'] = format_rt_reading(data[96:100]) values['HM_MeterValue'] = format_rt_reading(data[100:104]) values['HM_MeterPower'] = format_rt_reading(data[104:108]) values['GM_MeterValue'] = format_rt_reading(data[108:112]) values['GM_MeterPower'] = format_rt_reading(data[112:116]) values['CM_MeterValue'] = format_rt_reading(data[116:120]) values['CM_MeterPower'] = format_rt_reading(data[120:124]) self.update_rt_value(values) return except: numretries -= 1 raise def _write_cr_value(self, value, wait_time=None, numretries=None): """Only to be used by set_cr_value method Value should only be an integer """ if not self.is_running(): raise ENotRunning() if numretries is None: numretries = self.retry_count data = self.cr_request_obj.findChildByName('data') bcc = self.cr_request_obj.findChildByName('bcc') checksum = (self.cr_checksum + value) & 0xFF data.setValue(value) bcc.setValue(checksum) while numretries: try: self._send_request(self.cr_request_obj, self.cr_response_obj, wait_time, 1) resp_addr = self.cr_response_obj.findChildByName('addr') resp_bcc = self.cr_response_obj.findChildByName('bcc') addr = resp_addr.getValue() bcc = resp_bcc.getValue() checksum = (0xD3 + calc_sum(addr)) & 0xFF if checksum != bcc: raise EBadChecksum() if self.bin_addr != addr: raise EInvalidMessage() return except: numretries -= 1 raise def get_rt_value_as_dict(self): return self.rt_value def update_rt_value(self, value): self.rt_value = value self.rt_last_updated = time.time() def is_rt_value_stale(self): return ((time.time() - self.rt_last_updated) > self.cache_life) def get_rt_value_by_name(self, name, skipcache=0): """get method for rt values name is case sensitive """ self.rt_lock.acquire() try: if self.is_rt_value_stale() or skipcache: self._read_rt_value() finally: self.rt_lock.release() return self.rt_value[name] def set_cr_value(self, value): self.cr_lock.acquire() try: self._write_cr_value(value) finally: self.cr_lock.release()
class Kwh2Kw(CompositeNode): def __init__(self): self._history = None self._history_lock = Lock() self._sid = None self._nid = 1 self._poll_failure = False self._scheduled = None self.running = False super(Kwh2Kw, self).__init__() return def configure(self, cd): super(Kwh2Kw, self).configure(cd) set_attribute(self, 'link', REQUIRED, cd) # sample_period and window used to set the number of # samples that constitutes the size of the moving avg. set_attribute(self, 'sample_period', 10.0, cd, float) set_attribute(self, 'window', 120, cd, int) self._window_size = self.window / self.sample_period if self.running: # reconfigure things self.stop() self.start() return def configuration(self): cd = super(Kwh2Kw, self).configuration() get_attribute(self, 'link', cd) get_attribute(self, 'sample_period', cd) get_attribute(self, 'window', cd) return cd def start(self): super(Kwh2Kw, self).start() self.running = True self._history = KwList(self._window_size) self._sid = SM.create_polled({self._nid: self.link}) # retrieve an initial value to start things off value = ts = None result = SM.poll_all(self._sid) if result is None: # still waiting try: value = as_node(self.link).get() ts = time.time() except: pass else: try: value = result[self._nid]['value'] ts = result[self._nid]['timestamp'] except: pass if isinstance(value, MpxException): value = None if value and ts: self._history.add(value, ts) self._scheduled = scheduler.seconds_from_now_do( self.sample_period, self.run_update) return def stop(self): self.running = False self._history = None try: SM.destroy(self._sid) except: pass self._sid = None self._history_lock.acquire() try: self._history = None s = self._scheduled self._scheduled = None if s is not None: try: s.cancel() except: pass finally: self._history_lock.release() return ## # update() can be relatively slow, run it on a threadpool def run_update(self): NORMAL.queue_noresult(self.update) return def update(self): try: value = ts = None result = SM.poll_all(self._sid) if result is not None: value = result[self._nid]['value'] ts = result[self._nid]['timestamp'] self._history_lock.acquire() try: if value is None or isinstance(value, MpxException): # there were problems collecting during this period, # our calculation should not proceed self._history.clear() if not self._poll_failure: # log the failure, but don't spam the msglog self._poll_failure = True msglog.log( 'Kwh2Kw', msglog.types.WARN, 'Failed to retrieve data from %s' % self.link) else: self._poll_failure = False self._history.add(value, ts) finally: self._history_lock.release() except: msglog.exception() self._scheduled = scheduler.seconds_from_now_do( self.sample_period, self.run_update) return def get(self, skipCache=0): return self._history.moving_average()
class CBus(Node): __node_id__ = "50b9f586-fe01-4c1b-a92a-dc5b27b7567e" PROMPT = "ready>\n" def __init__(self): Node.__init__(self) self._dev_name = None # Can programmatically override BEFORE start. self._lock = Lock() self._popen = None self._apps = [] self._groups = [] self._discovery_ts = None return def _get_dev_name(self): if self._dev_name: return self._dev_name self._dev_name = self.parent.dev return self._dev_name def configure(self, config): Node.configure(self, config) set_attribute(self, "auto_discover", 1, config, as_boolean) set_attribute(self, "debug", 0, config, as_boolean) return def configuration(self): config = Node.configuration(self) get_attribute(self, "auto_discover", config) get_attribute(self, "debug", config) return config def _restart(self): assert self._lock.locked() self._reset_popen() self._popen = popen2.Popen3("superexec %s %s" % (CBUS_CLI, self._get_dev_name()), False) line = self._popen.fromchild.readline() if self.debug: print "< %r" % line while line and line != CBus.PROMPT: line = self._popen.fromchild.readline() if self.debug: print "< %r" % line if self.auto_discover: # This is really a work around to how CBM discovers initial values. self._discover() return def start(self): self._lock.acquire() try: self._restart() finally: self._lock.release() Node.start(self) return def stop(self): self._lock.acquire() try: self._reset_popen() self._dev_name = None self._discovery_ts = None finally: self._lock.release() Node.stop(self) return def _reset_popen(self): if self._popen is not None: try: os.kill(self._popen.pid, signal.SIGTERM) except OSError, e: if e.errno != errno.ESRCH: raise try: self._popen.wait() except OSError, e: if e.errno != errno.ECHILD: raise self._popen = None
class _User(PersistentDataObject): USERS = _UserDictionary() def __init__(self, name, new=0, password_file=PASSWD_FILE, group_file=GROUP_FILE, shadow_file=SHADOW_FILE): self.__lock = Lock() self.__password_file = password_file self.__shadow_file = shadow_file self.__group_file = group_file self.__loaded = 0 self.__file_modified = 0 self.load(name) self.meta = {} self.USERS.load() if not self.USERS.has_key(self.name()): msglog.log('broadway', msglog.types.INFO, ('No profile for user %s found, creating' ' new profile' % name)) self.USERS[self.name()] = str(UUID()) PersistentDataObject.__init__(self, self.USERS[self.name()]) PersistentDataObject.load(self) def loaded(self): self.__lock.acquire() try: return self.__loaded finally: self.__lock.release() def load(self, name): self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) passwd_db.load() if name in passwd_db: self.__user = passwd_db[name] else: self.__user = None raise EInvalidValue('name', name, 'No such user.') self.__file_modified = passwd_db.last_modified() # loading /etc/shadow database shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() if name in shadow_db: self.__shadow = shadow_db[name] else: self.__shadow = None raise EInvalidValue('User (', name, ') does not exist in shadow') self.__shadow_file_modified = shadow_db.last_modified() self.__loaded = 1 finally: self.__lock.release() def reload(self): self.load(self.name()) def save(self): self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) passwd_db.load() passwd_db[self.name()] = self.password_entry() passwd_db.save() # save /etc/shadow content shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() shadow_db[self.name()] = self.shadow_entry() shadow_db.save() finally: self.__lock.release() self.load(self.name()) def name(self): return self.__user.user() def password(self): raise ENotImplemented(self.password) def set_password(self, password, validate=True): self.__lock.acquire() try: shadow_db = ShadowFile(self.__shadow_file) shadow_db.load() shadowentry = shadow_db[self.name()] shadowentry.passwd(password, validate) shadow_db[self.name()] = shadowentry shadow_db.save() finally: self.__lock.release() self.load(self.name()) def crypt(self): return self.__shadow.crypt() def set_crypt(self, crypt): self.__shadow.crypt(crypt) def uid(self): return self.__user.uid() def set_uid(self, uid): self.__user.uid(uid) def gid(self): return self.__user.gid() def set_gid(self, gid): self.__user.gid(gid) def group(self): return self.__user.groups()[0] def groups(self): group_db = GroupFile(self.__group_file) group_db.load() return self.__user.groups(group_db) def group_ids(self): ids = [] for group in self.groups(): ids.append(group.gid()) return ids def gecos(self): return self.__user.gecos() def set_gecos(self, gecos): self.__user.gecos(gecos) def directory(self): return self.__user.directory() def set_directory(self, directory): self.__user.directory(directory) def shell(self): return self.__user.shell() def set_shell(self, shell): self.__user.shell(shell) def is_dirty(self): if not self.__loaded: return 1 self.__lock.acquire() try: passwd_db = PasswdFile(self.__password_file) if not passwd_db.exists(): return 1 else: return not not (passwd_db.last_modified() > self.__file_modified) shadow_db = ShadowFile(self.__shadow_file) if not shadow_db.exists(): return 1 else: return not not (shadow_db.last_modified() > self.__shadow_file_modified) finally: self.__lock.release() def set_meta_value(self, name, value): self.meta[name] = value PersistentDataObject.save(self) def get_meta_value(self, name, default=None): if self.meta.has_key(name): return self.meta[name] return default def get_meta(self): return self.meta.copy() def __getitem__(self, name): return self.get_meta_value(name) def __setitem__(self, name, value): return self.set_meta_value(name, value) def has_key(self, k): return self.meta.has_key(k) def items(self): return self.meta.items() def values(self): return self.meta.values() def keys(self): return self.meta.keys() def password_entry(self): return self.__user def shadow_entry(self): return self.__shadow def user_type(self): return self.__user.user_type() def password_expired(self): if self.crypt()[:3] == '$1$': return False else: raise EPasswordExpired(self.name()) return True def is_admin(self): return self.user_type() == "mpxadmin"
class CBus(Node): __node_id__ = "50b9f586-fe01-4c1b-a92a-dc5b27b7567e" PROMPT = "ready>\n" def __init__(self): Node.__init__(self) self._dev_name = None # Can programmatically override BEFORE start. self._lock = Lock() self._popen = None self._apps = [] self._groups = [] self._discovery_ts = None return def _get_dev_name(self): if self._dev_name: return self._dev_name self._dev_name = self.parent.dev return self._dev_name def configure(self, config): Node.configure(self, config) set_attribute(self, 'auto_discover', 1, config, as_boolean) set_attribute(self, 'debug', 0, config, as_boolean) return def configuration(self): config = Node.configuration(self) get_attribute(self, 'auto_discover', config) get_attribute(self, 'debug', config) return config def _restart(self): assert self._lock.locked() self._reset_popen() self._popen = popen2.Popen3( "superexec %s %s" % (CBUS_CLI, self._get_dev_name()), False) line = self._popen.fromchild.readline() if self.debug: print "< %r" % line while (line and line != CBus.PROMPT): line = self._popen.fromchild.readline() if self.debug: print "< %r" % line if self.auto_discover: # This is really a work around to how CBM discovers initial values. self._discover() return def start(self): self._lock.acquire() try: self._restart() finally: self._lock.release() Node.start(self) return def stop(self): self._lock.acquire() try: self._reset_popen() self._dev_name = None self._discovery_ts = None finally: self._lock.release() Node.stop(self) return def _reset_popen(self): if (self._popen is not None): try: os.kill(self._popen.pid, signal.SIGTERM) except OSError, e: if e.errno != errno.ESRCH: raise try: self._popen.wait() except OSError, e: if e.errno != errno.ECHILD: raise self._popen = None
class Device(CompositeNode, UpdateMixin): def __init__(self): self._subscription_lock = Lock() self._subscribed = 0 self._subscribers = {} self._last_value = None self._last_rcvd = None self._decode_indexes = {} return def configure(self, cd): super(Device, self).configure(cd) set_attribute(self, 'ttl', 60, cd, int) set_attribute(self, 'swid', '', cd) return def configuration(self): cd = super(Device, self).configuration() get_attribute(self, 'ttl', cd) get_attribute(self, 'swid', cd) return cd def start(self): if self.swid: self.url = BASE_URL % (self.station.host, self.swid) self._rqst = JaceRequest(self.url, ttl=self.ttl) super(Device, self).start() return def can_bundle(self): return bool(self.swid) def subscribe(self, name, func): self._subscription_lock.acquire() try: ## # if there are multiple external consumers, they are subscribed # via event producing child node. self._subscribers[name] = func self._subscribed += 1 if self._last_value and (uptime.secs() - self._last_rcvd) < self.ttl: try: value = self._last_value.get(name) func(value) except: pass if self._subscribed == 1: self.update_continuous(None) finally: self._subscription_lock.release() return def unsubscribe(self, name): self._subscription_lock.acquire() try: assert self._subscribed, 'Cannot decrement subscribers below 0' del self._subscribed[name] self._subscribed -= 1 finally: self._subscription_lock.release() return def event_has_subscribers(self): return bool(self._subscribed) def _load_indexes(self, data): d_len = len(data) for name in self._subscribers.keys(): index = offset = 0 for l in data: if l.count('<' + name + '>'): break index += 1 for l in data[index:]: if l.count('<value>'): break offset += 1 if (index + offset) > d_len: index = offset = None self._decode_indexes[name] = (index, offset) return def _get_indexes(self, name): return self._decode_indexes.get(name, (None, None)) def _have_indexes(self): indexes = self._decode_indexes.keys() for interest in self._subscribers.keys(): if interest not in indexes: return False return True def decode(self, data_s): if data_s.startswith('<!--'): data_s = data_s[(data_s[1:].find('<') + 1):] data = data_s.split('\n') if not self._have_indexes(): self._load_indexes(data) values = {} for name in self._subscribers.keys(): index, offset = self._get_indexes(name) try: if not data[index].count(name) or not data[ index + offset].count('value'): return self._decode_slow(data_s) l = data[index + offset] values[name] = l.split('>')[1].split('<')[0] except: return self._decode_slow(data_s) return values def _decode_slow(self, data): try: data_o = xml2code(data) except: data_o = None return data_o def update_cache(self, value_obj): for name, func in self._subscribers.items(): value = value_obj.get(name) func(value) self._last_value = value self._last_rcvd = uptime.secs() return def _get_station(self): return self.parent station = property(_get_station)
class EnergywiseSwitch(Node): SNMP_REMOTE_AGENTS_PATH = '/services/network/SNMP/Remote Agents' CEW_ENT_ENTRY_SUBPATH = ( 'Managed Objects/iso/org/dod/internet/private/enterprises/cisco/' 'ciscoMgmt/ciscoEnergywiseMIB/ciscoEnergywiseMIBObjects/cewEntTable/' 'cewEntEntry' ) PROTOCOL_SNMP = 'SNMP' PROTOCOL_NATIVE = 'Native' _PROTOCOL_MAP = {'snmp':PROTOCOL_SNMP,'native':PROTOCOL_NATIVE} def get_cewEntEnergyUsage_node(self): usage_url = os.path.join(self.SNMP_REMOTE_AGENTS_PATH, self.name, self.CEW_ENT_ENTRY_SUBPATH, 'cewEntEnergyUsage') return as_node(usage_url) def get_cewEntEnergyUnits_node(self): units_url = os.path.join(self.SNMP_REMOTE_AGENTS_PATH, self.name, self.CEW_ENT_ENTRY_SUBPATH, 'cewEntEnergyUnits') return as_node(units_url) def get_snmp_switch_agent_node(self): snmp_switch_url = os.path.join(self.SNMP_REMOTE_AGENTS_PATH, self.name) return as_node(snmp_switch_url) def _not_running(self, *args, **kw): raise ENotRunning("%r is not running." % self.as_node_url()) def as_protocol_name(self,protocol): try: return self._PROTOCOL_MAP[protocol.lower()] except KeyError: raise EInvalidValue('protocol',protocol, "%r is not a supported protocol for %r" %(protocol,self.as_node_url()) ) # # Initialize all parameters for energywise switch def __init__(self): Node.__init__(self) self._cpex_connect_orig = self._cpex_connect self._cpex_connect = self._not_running self.running = 0 self.domain = '' self.address = REQUIRED self.snmp_version = REQUIRED self.snmp_batch_size = 50 self.community_name = REQUIRED self.security_name = None self.username = None self.authentication_protocol = None self.authentication_key = None self.privacy_protocol = None self.privacy_key = None self.remote_agent = None self.trend_node = None self.period = 60 self.debug = 0 self.shared_secret = REQUIRED self.cpex_port = CPEX_DEFAULT_PORT self.primary = False self.protocol = 'SNMP' self.cpex_connect_retry = 60 self.cpex_timeout = 1 # 12 self.get_switch_usage = self._not_running self.cewEntEnergyUsage_node = None self.cewEntEnergyUnits_node = None self.snmp_usage_map = None self.snmp_switch_agent_node = None self._snmp_cache_lock=Lock() self._snmp_cache_value=None self._snmp_cache_time=0 self.ttl=30 return # on startup following parameters are passed. def configure(self, config): if self.debug: msg = 'Inside Configure api ' msglog.log('Energywise:', msglog.types.INFO, msg ) msg = 'sys path %s' msglog.log('Energywise:', msglog.types.INFO, msg %sys.path) Node.configure(self, config) set_attribute(self, 'ttl', 30, config, int) set_attribute(self, 'debug', 0, config, int) set_attribute(self, 'address', REQUIRED, config) set_attribute(self, 'shared_secret', REQUIRED, config) set_attribute(self, 'cpex_port', 43440, config, int) set_attribute(self, 'primary', False, config, as_boolean) #SNMP is taken as default for reverse compatibility set_attribute(self, 'protocol', 'SNMP', config, self.as_protocol_name) set_attribute(self, 'snmp_batch_size', 50, config, int) if self.debug: msg = 'Configured address %s ' msglog.log('Energywise:', msglog.types.INFO, msg %self.address) if not self.domain: self.domain = _find_domain(self) if self.debug: msg = 'Configured CPEX domain %s ' msglog.log('Energywise:', msglog.types.INFO, msg %self.domain) set_attribute(self,'snmp_version', REQUIRED, config) if self.debug: msg = 'Configured snmp_version %s' msglog.log('Energywise:', msglog.types.INFO, msg %self.snmp_version) set_attribute(self,'community_name',REQUIRED, config) if self.debug: msg = 'Configured community_name %s' msglog.log('Energywise:', msglog.types.INFO, msg %self.community_name) set_attribute(self,'security_name','default', config) if self.debug: msg = 'Configured security_name %s' msglog.log('Energywise:', msglog.types.INFO, msg %self.security_name) set_attribute(self,'user_name',' ', config) set_attribute(self,'authentication_protocol','usmNoAuthProtocol', config) set_attribute(self,'authentication_key',' ', config) set_attribute(self,'privacy_protocol','usmNoPrivProtocol', config) set_attribute(self,'privacy_key', ' ',config) return def configuration(self): if self.debug: msg = 'Inside Configuration()' msglog.log('Energywise:', msglog.types.INFO, msg) config = Node.configuration(self) get_attribute(self, 'ttl', config, str) get_attribute(self, 'debug', config, str) get_attribute(self, 'domain', config) get_attribute(self, 'address', config) get_attribute(self, 'snmp_version', config) get_attribute(self, 'snmp_batch_size', config, str) get_attribute(self, 'community_name', config) get_attribute(self, 'security_name', config) get_attribute(self, 'user_name', config) get_attribute(self, 'authentication_protocol', config) get_attribute(self, 'authentication_key', config) get_attribute(self, 'privacy_protocol', config) get_attribute(self, 'privacy_key', config) get_attribute(self, 'shared_secret', config) get_attribute(self, 'cpex_port', config) get_attribute(self, 'primary', config) get_attribute(self, 'protocol', config) return config def start(self): if self.debug: msg = 'Inside Start()' msglog.log('Energywise:', msglog.types.INFO, msg) if not self.running: if not (1024 < self.cpex_port < 65536): raise EConfiguration( "Invalid port specified (%d). " "Please enter values between 1025 and 65535 " % self.cpex_port ) self.running = 1 self._cpex_connect = self._cpex_connect_orig Node.start(self) if self.PROTOCOL_SNMP == self.protocol: # Create SNMP node for this remote_agent self.createEnergywiseSNMPNodes() self.snmp_switch_agent_node = self.get_snmp_switch_agent_node() self.cewEntEnergyUsage_node = self.get_cewEntEnergyUsage_node() self.cewEntEnergyUnits_node = self.get_cewEntEnergyUnits_node() self.snmp_usage_map = {} for child in self.cewEntEnergyUsage_node.children_nodes(): self.snmp_usage_map[('usage', child.name)] = child for child in self.cewEntEnergyUnits_node.children_nodes(): self.snmp_usage_map[('units', child.name)] = child self.get_switch_usage = self.snmp_switch_usage else: self.get_switch_usage = self.cpex_switch_usage return def stop(self): if self.debug: msg = 'Inside Stop()' msglog.log('Energywise:', msglog.types.INFO, msg) if self.running: Node.stop(self) self.running = 0 self.get_switch_usage = self._not_running return # Create all required mib and snmp nodes automatically for monitoring app. def createEnergywiseSNMPNodes(self): if self.debug: msglog.log('Energywise:',msglog.types.INFO, 'Inside createEnergywiseSNMPNodes') try: # check SNMP node is present snmpRef = as_node("/services/network/SNMP") except: msglog.log('Energywise:',msglog.types.ERR, 'Missing SNMP node under /services/network, please create it.') snmpNode = node_factory( "mpx.service.network.snmp.remote_agent.RemoteAgent") snmpNode.configure({ 'parent':"/services/network/SNMP/Remote Agents", 'name': self.name, 'address':self.address, 'mib_table':[{'name':'CISCO-ENERGYWISE-MIB', 'value':"CISCO-ENERGYWISE-MIB"}], 'port':161, 'discover_at_start':0, 'max_batch_size':self.snmp_batch_size, }) # create SNMP version node under snmpNode if self.snmp_version == 'v1': version = '1' snmpVersionNode = node_factory( "mpx.service.network.snmp.remote_agent.SNMPv1" ) parent_name = "/services/network/SNMP/Remote Agents/" parent_name += self.name snmpVersionNode.configure({ 'parent':parent_name, 'name':"SNMPv1", 'version':version, 'community_name':self.community_name, 'security_name':self.security_name }) if self.snmp_version == 'v2c': version = '2c' snmpVersionNode = node_factory( "mpx.service.network.snmp.remote_agent.SNMPv2c") parent_name = "/services/network/SNMP/Remote Agents/" parent_name += self.name snmpVersionNode.configure({ 'parent':parent_name, 'name':"SNMPv2c", 'version':version, 'community_name':self.community_name, 'security_name':self.security_name }) if self.snmp_version == 'v3': version = '3' snmpVersionNode = node_factory( "mpx.service.network.snmp.remote_agent.SNMPv3") parent_name = "/services/network/SNMP/Remote Agents/" parent_name += self.name snmpVersionNode.configure({ 'parent':parent_name, 'name':"SNMPv3", 'version':version, 'username':self.username, 'authentication_protocol':self.authentication_protocol, 'authentication_key':self.authentication_key, 'privacy_protocol':self.privacy_protocol, 'privacy_key':self.privacy_key }) snmpNode.start() # Start discovery of interested oids managedObjects = snmpNode.get_child(snmpNode.MANAGED_OBJECTS) seek_from = (1,3,6,1,4,1,9,9,683,1,3) # CISCO-ENERGYWISE::cewDomainName managedObjects.batch_all_from(seek_from,True) mib_node = parent_name mib_node += '/Managed Objects/iso/org/dod/internet/private/'\ 'enterprises/cisco/ciscoMgmt/ciscoEnergywiseMIB/'\ 'ciscoEnergywiseMIBObjects/cewDomainName/0' domainNode = as_node(mib_node) val = domainNode.get() if not str(val) == self.domain: raise EInvalidValue('self.domain',self.domain) if self.debug: msglog.log('Energywise:',msglog.types.INFO, 'Energywise domain managed by this switch is %s'%str(val)) seek_from = (1,3,6,1,4,1,9,9,683,1,6) # CISCO-ENERGYWISE::cewEntTable managedObjects.batch_all_from(seek_from,True) return def new_trend(self,period): return new_trend(self,period) def delete_trend(self): return delete_trend(self) def _cpex_connect(self): uuid = energywise_utl_createUuid() key = energywise_utl_composeKey(self.shared_secret, uuid) cpex_session = energywise_createSession( self.address, self.cpex_port, uuid, key, self.cpex_timeout ) if cpex_session == 0: raise EConnectFailed( "Failed to create a session with %(address)s:%(cpex_port)d" % self.configuration() ) return cpex_session def _cpex_disconnect(self, cpex_session): energywise_closeSession(cpex_session) return def cpex_domain_usage(self, importance=100): cpex_query = 0 result_set = 0 usage = 0 cpex_session = self._cpex_connect() try: cpex_query = energywise_createSumQuery(self.domain, importance) energywise_addGetAttribute(cpex_query, EW_ATTRIBUTE_TYPE_UNITS) energywise_addGetAttribute(cpex_query, EW_ATTRIBUTE_TYPE_USAGE) energywise_execQuery(cpex_session, cpex_query) result_set = energywise_queryResults(cpex_session, cpex_query) if result_set == 0: raise EnergywiseCommunicationFailure( "Domain sum query via %r failed." % self.address ) result_row = energywise_getNextRow(result_set) while result_row: units = energywise_getAttributeFromRowByType( result_row, EW_ATTRIBUTE_TYPE_UNITS ).value usage += energywise_getAttributeFromRowByType( result_row, EW_ATTRIBUTE_TYPE_USAGE ).value * 10**units result_row = energywise_getNextRow(result_set) finally: if result_set != 0: energywise_releaseResult(result_set) if cpex_query != 0: energywise_releaseQuery(cpex_query) self._cpex_disconnect(cpex_session) return usage def cpex_switch_usage_map(self, importance=100): cpex_query = 0 result_set = 0 usage_map = {} cpex_session = self._cpex_connect() try: cpex_query = energywise_createCollectQuery(self.domain, importance) energywise_addGetAttribute(cpex_query, EW_ATTRIBUTE_TYPE_USAGE) energywise_addGetAttribute(cpex_query, EW_ATTRIBUTE_TYPE_UNITS) energywise_execQuery(cpex_session, cpex_query) result_set = energywise_queryResults(cpex_session, cpex_query) if result_set == 0: raise EnergywiseCommunicationFailure( "Failed to query switch usage for %r via %r" % (self.domain, self.address) ) result_row = energywise_getNextRow(result_set) while result_row: units = energywise_getAttributeFromRowByType( result_row, EW_ATTRIBUTE_TYPE_UNITS ).value usage = energywise_getAttributeFromRowByType( result_row, EW_ATTRIBUTE_TYPE_USAGE ).value * 10**units usage_map[self.address] = usage_map.get(self.address,0) + usage result_row = energywise_getNextRow(result_set) finally: if result_set != 0: energywise_releaseResult(result_set) if cpex_query != 0: energywise_releaseQuery(cpex_query) self._cpex_disconnect(cpex_session) return usage_map def cpex_switch_usage(self, importance=100, skipCache=False): try: return self.parent.cpex_switch_usage_map(importance, skipCache)[self.address] except KeyError: raise EnergywiseCommunicationFailure("Failed to get usage for switch %r." % self.address) raise EUnreachableCode("Executed unreachable code!") def _is_snmp_cache_stale(self,timestamp): return (self._snmp_cache_time+self.ttl) < timestamp def _update_snmp_cache(self,value, timestamp): self._snmp_cache_value = value self._snmp_cache_time = timestamp #caching for snmp switch def snmp_switch_usage(self, importance=100, skipCache=False): self._snmp_cache_lock.acquire() try: if skipCache or self._is_snmp_cache_stale(time.time()): #fetch actual value if self.PROTOCOL_SNMP != self.protocol: raise ESNMPNotEnabled("SNMP is not enabled on %r" % self.as_node_url()) batches = self.snmp_switch_agent_node.create_batches( self.snmp_usage_map ) result = {} for batch in batches: result.update(self.snmp_switch_agent_node.get_batch(batch)) usage = 0 for usage_key in filter((lambda k:k[0]=='usage'), result.keys()): unit_key = ('units', usage_key[1]) usage_value = int(result[usage_key].value) unit_value = int(result[unit_key].value) usage += usage_value*10**unit_value self._update_snmp_cache(usage, time.time()) return usage else: #use cached value return self._snmp_cache_value finally: self._snmp_cache_lock.release() raise EUnreachableCode("Executed unreachable code!") def get(self, skipCache=False): return self.get_switch_usage() def get_result(self, skipCache=False): return Result(self.get(skipCache), time.time(), cached=False)
class XbowCache: def __init__(self, timeout, scan_time): #self._c == {group_id:{addr:last_msg}} self._c = {} self._cache_lock = Lock() #can add more granular locking, if need be self._subscr_list = {} # == {(group_id,addr):[call_back_meth]} ... self.timeout = timeout if not scan_time: self.scan_time = max(self.timeout / 4, 30) self._scan_scheduled = None def stop(self): s = self._scan_scheduled self._scan_scheduled = None if s is not None: try: s.cancel() except: pass def add_group(self, group_id): try: self._cache_lock.acquire() if not self._c.has_key(group_id): self._c[group_id] = {} finally: self._cache_lock.release() def get_group_ids(self): return self._c.keys() def add_group(self, group_id): self._c[group_id] = {} def add_mote(self, group_id, addr): try: self._cache_lock.acquire() if not self._c.has_key(group_id): raise EInvalidValue('cannot add addr to non-existant group', group_id, 'add_mote') if not self._c[group_id].has_key(addr): self._c[group_id][addr] = None finally: self._cache_lock.release() def get_mote_ids(self, group_id): return self._c[group_id].keys() def add_msg(self, msg): addr = msg.get_address() group_id = msg.get_group() id = (group_id, addr) try: self._cache_lock.acquire() if group_id not in self.get_group_ids(): self.add_group(group_id) self._c[group_id][addr] = msg if id in self._subscr_list.keys(): call_backs = self._subscr_list[(group_id, addr)] for cb in call_backs: cb() finally: self._cache_lock.release() def get_msg(self, group_id, addr): return self._c[group_id][addr] def add_callback(self, id, cb): #id == (group_id, addr) tuple try: self._cache_lock.acquire() if id in self._subscr_list.keys(): self._subscr_list[id].append(cb) else: self._subscr_list[id] = [cb] # if we're the only subscribed value, fire off the timeout scanner if len(self._subscr_list.keys()) == 1: self.setup_subscr_timer() finally: self._cache_lock.release() def remove_callback(self, id, cb): # id == (group_id, addr) tuple # cb needed just in case there are more than one callback try: self._cache_lock.acquire() if id in self._subscr_list.keys(): try: self._subscr_list[id].remove(cb) except: # hrm, cb went missing pass finally: self._cache_lock.release() def setup_subscr_timer(self): self._scan_scheduled = scheduler.at(self.next_scan_time(), self.scan, ()) def next_scan_time(self): now = int(time.time()) last_time = (now - (now % self.scan_time)) next_time = last_time + self.scan_time return float(next_time) def scan(self): try: # see prev comment about improving lock granularity self._cache_lock.acquire() for id in self._subscr_list.keys(): group_id = id[0] addr = id[1] t = time.time() if t - self._c[group_id][addr].time_stamp > self.timeout: # set the msg to None which will result in the ion side generating ETimeout COV msg self._c[group_id][addr] = None for cb in self._subscr_list[id]: try: cb() except: msglog.exception() if self._subscr_list.keys(): self.setup_subscr_timer() else: self._scan_scheduled = None finally: self._cache_lock.release()