def __init__(self, path=None, id=None, master=None, settings=None, log_level=logging.WARNING, baron=None, address=None): self.baron = baron self.path = os.path.abspath(path or '.') if id is None: id = platform.node() self.id = id if not os.path.exists(self.path): os.makedirs(path) self.log_level = log_level self.address = address self._socket = None self._started = False self._failed = False self.services = [] self._service_map = {} self._deployments = [] self._keys = set() # A set of keys allowed to edit things. self._node_map = {id: self} self.master = master or self self.neighbors = [] # Any node we know about. self.vassals = [] self.rogue = [] # TODO: Put nodes that should be vassals # but don't recognize us here. self._pool = GreenPool() if (self.master != self): self._node_map[self.master.id] = self.master self.dispatcher = Dispatcher(self) self.load_settings(settings=settings) print "Sovereign node (%s) created at %s" % (self.id, self.path) print "", "- primary authentication key:", self.key
class LocalNode(Node): def __init__(self, path=None, id=None, master=None, settings=None, log_level=logging.WARNING, baron=None, address=None): self.baron = baron self.path = os.path.abspath(path or '.') if id is None: id = platform.node() self.id = id if not os.path.exists(self.path): os.makedirs(path) self.log_level = log_level self.address = address self._socket = None self._started = False self._failed = False self.services = [] self._service_map = {} self._deployments = [] self._keys = set() # A set of keys allowed to edit things. self._node_map = {id: self} self.master = master or self self.neighbors = [] # Any node we know about. self.vassals = [] self.rogue = [] # TODO: Put nodes that should be vassals # but don't recognize us here. self._pool = GreenPool() if (self.master != self): self._node_map[self.master.id] = self.master self.dispatcher = Dispatcher(self) self.load_settings(settings=settings) print "Sovereign node (%s) created at %s" % (self.id, self.path) print "", "- primary authentication key:", self.key def serve(self, address=None): """ Serves the rest client at *address*. """ if self._socket: self.close() try: self._socket = self.build_socket(address or self.address) self.address = self._socket.getsockname() self.start() self._started = True print "listening on http://%s:%s" % self.address wsgi.server(self._socket, self, log=FileLikeLogger(logging)) self._socket = None except Exception: self._failed = True raise logging.exception("Error binding address.") finally: self.close() def nanny(self): """ Waits for the node to start and returns True if it succeeds. Usefull for testing. """ while not self._started and not self._failed: eventlet.sleep(.01) return not self._failed def spawn_thread(self, func, *args, **kwargs): thread = self._pool.spawn(func, *args, **kwargs) eventlet.sleep(0) return thread def start(self): for service in self.services: if not service.started and not service.disabled: service.deploy() def stop(self): for service in self.services: if service.started: service.stop() def close(self): if self._socket: try: self._socket.close() except: logging.exception("Socket will not shutdown.") pass self._socket = None self.address = None def __call__(self, env, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) response = self.route(env) if (response is None): response = http.NotFound() return response(env, start_response) def authorize(self, env): auth = env.get('HTTP_AUTHORIZATION', None) if not auth: return False return auth in self._keys def add_key(self, key=None): if key is None: key = random_str() self._keys.add(key) return key def rem_key(self, key): self._keys.remove(key) def route(self, env): try: for service in self.services: response = service.route(env) if response is not None: return response return self.dispatcher.route(env) except: logging.exception("Error handling request.") raise def build_socket(self, address, reusable=True, listen=500): if (self.baron): return self.baron.create_socket(address, reusable, listen) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if reusable: sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(tuple(address)) sock.listen(listen) return sock def load_settings(self, settings=None): path = os.path.join(self.path, 'settings.json') if settings is False or (not os.path.exists(path) and not settings): settings = { 'services': [{ 'id': 'admin', 'type': 'sovereign.contrib.admin.Service', }] } elif not settings: settings = json.load(open(path, 'r')) self.key = settings.get('key', random_str()) self._keys = set([self.key]) if (self.address is None): self.address = settings.get('address', ('0.0.0.0', 1648)) for service in settings.get('services', ()): self.create_service(service.get('id', 'type'), service, deploy=False) def save_settings(self): path = os.path.join(self.path, 'settings.json') settings = { 'id': self.id, 'key': self.key, 'address': self.address, 'services': [service.settings.flat('user') for service in self.services] } if self.master is not self: settings['master'] = self.master.address json.dump(settings, open(path, 'w')) ### Implementations ### def sys_install(self, packages): from system import sys_install return sys_install(packages) def sys_upgrade(self, packages): from system import sys_upgrade return sys_upgrade(packages) def sys_uninsall(self, packages): from system import sys_uninsall return sys_uninsall(packages) def pip_install(self, packages, env=None): if (env): cmd = "pip -E %s install %%s" % env else: cmd = "pip install %s" return self.sys(cmd % p for p in packages) def pip_uninstall(self, packages, env=None): if (env): cmd = "pip -E %s uninstall %%s" % env else: cmd = "pip uninstall %s" return self.sys(cmd % p for p in packages) def sys(self, cmds): stdout = [] stderr = [] for cmd in cmds: o, e, returncode = shell(self.path, cmd) stdout.append(o) stderr.append(e) return stdout, stderr def info(self): info = { 'id': self.id, 'address': self.address, 'services': [service.info() for service in self.services], 'vassals': [node.info() for node in self.vassals], } if self.master is not self: info['master'] = self.master.address return info def create_service(self, id, settings, deploy=True): if self._get_service(id): raise ServiceIdCollision("Cannot create another service with the same id: %r" % id) ServiceCls = get_service_class(settings['type']) service = ServiceCls(node=self, id=id, settings=settings) self.services.append(service) self._service_map[id] = service if deploy: if not service.settings.get('disabled', False): service.deploy() self.save_settings() return service.info() def modify_service(self, id, settings): service = self._get_service(id) if not service: return self.create_service(id, settings) else: service.stop() service.settings.update(settings) service.deploy() return service.info() def delete_service(self, id): service = self._get_service(id) if (not service): return True service.delete() self.services.remove(service) self._service_map.pop(id) self.save_settings() return True def msg_service(self, id, message, **kwargs): service = self._get_service(id) return service.msg(message, **kwargs) def get_service(self, id): service = self._get_service(id) if (service): return service.info() return None def _get_service(self, id): return self._service_map.get(id, None) def get_service_log(self, id, since=None): service = self._get_service(id) if service: if since: return tuple( reversed(tuple(service._log.get_lines_since(since))) ) else: return service._log.get_lines() else: return None def create_local_node(self, id=None, path=None, settings=None): if self.get_node(id): raise NodeIdCollision("Cannot create a node with the id of another, known node: %r" % id) settings['type'] = 'local' node = LocalNode(path=path, id=id, master=self, settings=settings) self.spawn_thread(node.serve, settings['address']) self._node_map[id] = node self.vassals.append(node) return node def delete_node(self, id): node = self.get_node(id) if (not node in self.vassals): return False if (not node): return True node.terminate() self.vassals.remove(node) del self._node_map[node.id] return True def get_node(self, id): return self._node_map.get(id, None) def identify_nodes(self, nodes): raise NotImplementedError() def file_operations(self, operations): raise NotImplementedError() def recognize(self, node): self.master = node logging.info("We recognize: %r" % (node.address,)) def _terminate(self, recurse): logging.info("Sovereign server terminating...") eventlet.sleep(.01) if recurse: for vassal in self.vassals: vassal.terminate() del self._node_map[vassal.id] if (self._socket): self.close() sys.exit(0) def terminate(self, recurse=True): self.stop() if (self._socket): eventlet.spawn_n(self._terminate, recurse) return True return False