def forward_device_msg(identity, msg): """Forwards a message from plugin->client or client->plugin, editing it to take out all identifying information. This way, all identity management stays in the router, and plugins/clients talk to each other with no actual knowledge of who claims/says what. """ to_alias = msg[0] print _ctd print _dtc to_addr = None new_msg = None # TODO: Remove [0], make deal with multiclaims. Someday. # Plugins aren't allowed to know who owns them, they just see commands from # the system. Therefore they set their "to" address as "c", and we replace # it with the correct client identity. if to_alias == "c": new_msg = [identity] + list(msg[1:]) to_addr = _dtc[identity][0] # Client only know their device's provided address (bus address, bluetooth # id, etc...). We resolve that to the plugin's socket identity. elif to_alias in _dtc.keys(): # Plugins don't get to know where things come from. Spooooky. new_msg = ["s"] + list(msg[1:]) to_addr = _ctd[identity][0] else: logging.warning("No claims between %s and %s!", identity, to_addr) return logging.debug("Forwarding message %s to %s", new_msg, to_addr) queue.add(to_addr, new_msg)
def _heartbeat(identity, g): """Given an identity and its corresponding g, start a heartbeat loop. Maintain loop until either g dies or connection does not return a BPPing message in a timely manner. If message is not returned, kill corresponding g. """ while not g.ready(): e = event.add(identity, "BPPing") queue.add(identity, ["s", "BPPing"]) try: e.get(block=True, timeout=config.get_value("ping_max")) except gevent.Timeout: logging.info("Identity %s died via heartbeat", identity) g.kill() return except greenlet.BPGreenletExit: logging.debug("Heartbeat for %s exiting...", identity) return if g.ready(): logging.debug("Heartbeat for %s exiting...", identity) return try: gevent.sleep(config.get_value("ping_rate")) except greenlet.BPGreenletExit: logging.debug("Heartbeat for %s exiting...", identity) return
def _handle_internals(identity, msg): """Handle a request for internal information (greenlets, connections, etc.). To be used by dashboard. """ queue.add(identity, ["s", "BPInternals", greenlet._live_greenlets]) return True
def _run_count_plugin(plugin): """Runs the count process for a plugin. Constantly polls for list of devices, keeping an internal reference of the devices available from the plugin. """ count_identity = greenlet.random_ident() e = event.add(count_identity, "BPPluginRegisterCount") count_process_cmd = [plugin.executable_path, "--server_port=%s" % config.get_value("server_address"), "--count"] count_process = _start_process(count_process_cmd, count_identity) if not count_process: logging.warning("%s count process unable to start. removing.", plugin.name) return try: e.get(block=True, timeout=1) except gevent.Timeout: logging.info("%s count process never registered, removing.", plugin.name) return except greenlet.BPGreenletExit: logging.debug("Shutting down count process for %s", plugin.name) return logging.info("Count process for %s up on identity %s", plugin.name, count_identity) greenlet.add_identity_greenlet(count_identity, gevent.getcurrent()) hb = heartbeat.spawn_heartbeat(count_identity, gevent.getcurrent()) _plugins[plugin.name] = plugin while True: queue.add(count_identity, ["s", "BPPluginDeviceList"]) e = event.add(count_identity, "BPPluginDeviceList") try: (i, msg) = e.get(block=True, timeout=1) except gevent.Timeout: logging.info("%s count process timed out, removing.", plugin.name) break except greenlet.BPGreenletExit: logging.debug("Shutting down count process for %s", plugin.name) break _devices[plugin.name] = msg[2] # TODO: Make this a configuration value try: gevent.sleep(1) except greenlet.BPGreenletExit: logging.debug("Shutting down count process for %s", plugin.name) break # Heartbeat may already be dead if we're shutting down, so check first if not hb.ready(): hb.kill(exception=greenlet.BPGreenletExit, block=True, timeout=1) # Remove ourselves, but don't kill since we're already shutting down greenlet.remove_identity_greenlet(count_identity, kill_greenlet=False) # TODO: If a count process goes down, does every associated device go with # it? del _plugins[plugin.name] queue.add(count_identity, ["s", "BPClose"]) logging.debug("Count process %s for %s exiting...", count_identity, plugin.name)
def _handle_plugin_list(identity, msg): """Handle a request for the list of plugins available to the router. """ queue.add( identity, ["s", "BPPluginList", [{"name": p.name, "version": p.version} for p in plugin.plugins_available()]] ) return True
def _handle_device_list(identity, msg): """Handle a request for the list of devices available to router plugins. """ devices = [] for (k, v) in plugin._devices.items(): devices.append({"name": k, "devices": v}) queue.add(identity, ["s", "BPDeviceList", devices]) return True
def _handle_plugin_list(identity, msg): """Handle a request for the list of plugins available to the router. """ queue.add(identity, ["s", "BPPluginList", [{"name": p.name, "version": p.version} for p in plugin.plugins_available()]]) return True
def _handle_server_info(identity, msg): """Server Info - Server Name (Changable by user) - Server Software Version (static) - Server Build Date (static) """ queue.add( identity, ["s", "BPServerInfo", [{"name": "B******g", "version": bpinfo.SERVER_VERSION, "date": bpinfo.SERVER_DATE}]], ) return True
def _handle_server_info(identity, msg): """Server Info - Server Name (Changable by user) - Server Software Version (static) - Server Build Date (static) """ queue.add(identity, ["s", "BPServerInfo", [{"name": "B******g", "version": bpinfo.SERVER_VERSION, "date": bpinfo.SERVER_DATE}]]) return True
def handle_client(identity, msg): """Start a greenlet that will survive the duration of client connection. Handles replying to client registration, and cleaning up claims on disconnect.""" hb = heartbeat.spawn_heartbeat(identity, gevent.getcurrent()) greenlet.add_identity_greenlet(identity, gevent.getcurrent()) # Let the client know we're aware of it queue.add(identity, ["s", "BPRegisterClient", True]) while True: try: gevent.sleep(1) except greenlet.BPGreenletExit: break plugin.kill_claims(identity) if not hb.ready(): hb.kill(exception=greenlet.BPGreenletExit, block=True, timeout=1) # Remove ourselves, but don't kill since we're already shutting down greenlet.remove_identity_greenlet(identity, kill_greenlet=False) queue.add(identity, ["s", "BPClose"]) logging.debug("Client keeper %s exiting...", identity)
def run_device_plugin(identity, msg): """Execute the plugin claim protocol. This happens whenever a client requests to claim a resource advertised by a plugin. """ # Figure out the plugin that owns the device we want p = None dev_id = msg[2] for (plugin_name, device_list) in _devices.items(): if dev_id in device_list: p = _plugins[plugin_name] if p is None: logging.warning("Cannot find device %s, failing claim", dev_id) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return # See whether we already have a claim on the device if dev_id in _dtc.keys(): logging.warning("Device %s already claimed, failing claim", dev_id) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return # Client to system: bring up device process. # # Just name the new plugin process socket identity after the device id, # because why not. device_process = _start_process([p.executable_path, "--server_port=%s" % config.get_value("server_address")], dev_id) if not device_process: logging.warning("%s device process unable to start. removing.", p.name) return # Device process to system: Register with known identity e = event.add(dev_id, "BPPluginRegisterClaim") try: # TODO: Make device open timeout a config option (i, m) = e.get(timeout=5) except greenlet.BPGreenletExit: # If we shut down now, just drop return except gevent.Timeout: # If we timeout, fail the claim logging.info("Device %s failed to start...", dev_id) queue.add(dev_id, ["s", "BPClose"]) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return greenlet.add_identity_greenlet(dev_id, gevent.getcurrent()) # Add a heartbeat now that the process is up hb = heartbeat.spawn_heartbeat(dev_id, gevent.getcurrent()) # System to device process: Open device queue.add(dev_id, ["s", "BPPluginOpenDevice", dev_id]) e = event.add(dev_id, "BPPluginOpenDevice") try: (i, m) = e.get() except greenlet.BPGreenletExit: queue.add(dev_id, ["s", "BPClose"]) return # Device process to system: Open or fail if m[3] is False: logging.info("Device %s failed to open...", dev_id) queue.add(dev_id, ["s", "BPClose"]) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return # System to client: confirm device claim queue.add(identity, ["s", "BPClaimDevice", dev_id, True]) if identity not in _ctd.keys(): _ctd[identity] = [] _ctd[identity].append(dev_id) if dev_id not in _dtc.keys(): _dtc[dev_id] = [] _dtc[dev_id].append(identity) while True: try: gevent.sleep(1) except greenlet.BPGreenletExit: break if not hb.ready(): hb.kill(exception=greenlet.BPGreenletExit, block=True, timeout=1) _ctd[identity].remove(dev_id) del _dtc[dev_id] # Remove ourselves, but don't kill since we're already shutting down greenlet.remove_identity_greenlet(dev_id, kill_greenlet=False) queue.add(dev_id, ["s", "BPClose"]) logging.debug("Device keeper %s exiting...", dev_id)
def _run_count_plugin(plugin): """Runs the count process for a plugin. Constantly polls for list of devices, keeping an internal reference of the devices available from the plugin. """ count_identity = greenlet.random_ident() e = event.add(count_identity, "BPPluginRegisterCount") count_process_cmd = [ plugin.executable_path, "--server_port=%s" % config.get_value("server_address"), "--count" ] count_process = _start_process(count_process_cmd, count_identity) if not count_process: logging.warning("%s count process unable to start. removing.", plugin.name) return try: e.get(block=True, timeout=1) except gevent.Timeout: logging.info("%s count process never registered, removing.", plugin.name) return except greenlet.BPGreenletExit: logging.debug("Shutting down count process for %s", plugin.name) return logging.info("Count process for %s up on identity %s", plugin.name, count_identity) greenlet.add_identity_greenlet(count_identity, gevent.getcurrent()) hb = heartbeat.spawn_heartbeat(count_identity, gevent.getcurrent()) _plugins[plugin.name] = plugin while True: queue.add(count_identity, ["s", "BPPluginDeviceList"]) e = event.add(count_identity, "BPPluginDeviceList") try: (i, msg) = e.get(block=True, timeout=1) except gevent.Timeout: logging.info("%s count process timed out, removing.", plugin.name) break except greenlet.BPGreenletExit: logging.debug("Shutting down count process for %s", plugin.name) break _devices[plugin.name] = msg[2] # TODO: Make this a configuration value try: gevent.sleep(1) except greenlet.BPGreenletExit: logging.debug("Shutting down count process for %s", plugin.name) break # Heartbeat may already be dead if we're shutting down, so check first if not hb.ready(): hb.kill(exception=greenlet.BPGreenletExit, block=True, timeout=1) # Remove ourselves, but don't kill since we're already shutting down greenlet.remove_identity_greenlet(count_identity, kill_greenlet=False) # TODO: If a count process goes down, does every associated device go with # it? del _plugins[plugin.name] queue.add(count_identity, ["s", "BPClose"]) logging.debug("Count process %s for %s exiting...", count_identity, plugin.name)
def run_device_plugin(identity, msg): """Execute the plugin claim protocol. This happens whenever a client requests to claim a resource advertised by a plugin. """ # Figure out the plugin that owns the device we want p = None dev_id = msg[2] for (plugin_name, device_list) in _devices.items(): if dev_id in device_list: p = _plugins[plugin_name] if p is None: logging.warning("Cannot find device %s, failing claim", dev_id) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return # See whether we already have a claim on the device if dev_id in _dtc.keys(): logging.warning("Device %s already claimed, failing claim", dev_id) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return # Client to system: bring up device process. # # Just name the new plugin process socket identity after the device id, # because why not. device_process = _start_process([ p.executable_path, "--server_port=%s" % config.get_value("server_address") ], dev_id) if not device_process: logging.warning("%s device process unable to start. removing.", p.name) return # Device process to system: Register with known identity e = event.add(dev_id, "BPPluginRegisterClaim") try: # TODO: Make device open timeout a config option (i, m) = e.get(timeout=5) except greenlet.BPGreenletExit: # If we shut down now, just drop return except gevent.Timeout: # If we timeout, fail the claim logging.info("Device %s failed to start...", dev_id) queue.add(dev_id, ["s", "BPClose"]) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return greenlet.add_identity_greenlet(dev_id, gevent.getcurrent()) # Add a heartbeat now that the process is up hb = heartbeat.spawn_heartbeat(dev_id, gevent.getcurrent()) # System to device process: Open device queue.add(dev_id, ["s", "BPPluginOpenDevice", dev_id]) e = event.add(dev_id, "BPPluginOpenDevice") try: (i, m) = e.get() except greenlet.BPGreenletExit: queue.add(dev_id, ["s", "BPClose"]) return # Device process to system: Open or fail if m[3] is False: logging.info("Device %s failed to open...", dev_id) queue.add(dev_id, ["s", "BPClose"]) queue.add(identity, ["s", "BPClaimDevice", dev_id, False]) return # System to client: confirm device claim queue.add(identity, ["s", "BPClaimDevice", dev_id, True]) if identity not in _ctd.keys(): _ctd[identity] = [] _ctd[identity].append(dev_id) if dev_id not in _dtc.keys(): _dtc[dev_id] = [] _dtc[dev_id].append(identity) while True: try: gevent.sleep(1) except greenlet.BPGreenletExit: break if not hb.ready(): hb.kill(exception=greenlet.BPGreenletExit, block=True, timeout=1) _ctd[identity].remove(dev_id) del _dtc[dev_id] # Remove ourselves, but don't kill since we're already shutting down greenlet.remove_identity_greenlet(dev_id, kill_greenlet=False) queue.add(dev_id, ["s", "BPClose"]) logging.debug("Device keeper %s exiting...", dev_id)