def _update_transaction_id(object_model, id_list=None): """ Updates the in-memory transaction dict when object_models are updated. Arguments: id_list -- A list of <object_model>_ids Returns: None """ if id_list is not None: trans = flask.current_app.transactions[object_model] trans_time = time.time() lock_name = '%s-txid' % object_model utility.lock_acquire(lock_name) try: while trans_time in trans: trans_time += 0.000001 trans[trans_time] = set(id_list) except: utility.lock_release(lock_name) raise utility.lock_release(lock_name) # prune any updates > 5 min ago trans_time_past = trans_time - (60 * 5) for k in [x for x in trans.keys() if x < trans_time_past]: del trans[k] semaphore_name = '%s-changes' % object_model utility.notify(semaphore_name)
def task_by_id(object_id): result = generic.object_by_id(object_type, object_id) if flask.request.method == "PUT": api = api_from_models() task = api.task_get_by_id(object_id) if "node_id" in task: flask.current_app.logger.debug("Task: %s" % task) task_semaphore = "task-for-%s" % task["node_id"] flask.current_app.logger.debug("notifying event %s" % task_semaphore) utility.notify(task_semaphore) return result
def list(): _clean_tasks() result = generic.list(object_type) if flask.request.method == "POST": data = flask.request.json if "node_id" in data: task_semaphore = "task-for-%s" % data["node_id"] flask.current_app.logger.debug("notifying event %s" % task_semaphore) utility.notify(task_semaphore) return result
def task_by_id(object_id): result = generic.object_by_id(object_type, object_id) if flask.request.method == 'PUT': api = api_from_models() task = api.task_get_by_id(object_id) if 'node_id' in task: flask.current_app.logger.debug('Task: %s' % task) task_semaphore = 'task-for-%s' % task['node_id'] flask.current_app.logger.debug('notifying event %s' % task_semaphore) utility.notify(task_semaphore) return result
def list(): _clean_tasks() result = generic.list(object_type) if flask.request.method == 'POST': data = flask.request.json if 'node_id' in data: task_semaphore = 'task-for-%s' % data['node_id'] flask.current_app.logger.debug('notifying event %s' % task_semaphore) utility.notify(task_semaphore) return result
def _notify(updated_object, object_type, object_id): semaphore = '%s-id-%s' % (object_type, object_id) utility.notify(semaphore) # TODO: Generalize the block of code with a TODO below here. # if updated_object is not None and object_type in related_notifications: # for field, entity in related_notifications[object_type].iteritems(): # if field in updated_object and updated_object[field] is not None: # semaphore = '%s-id-%s' % (entity, updated_object[field]) # utility.notify(semaphore) # TODO (wilk or rpedde): Use specific notifications for inheritance if object_type not in ('attrs', 'facts', 'nodes'): return try: node_id = updated_object['node_id'] node = None except KeyError: node_id = updated_object['id'] node = updated_object if object_type != "attrs": api = api_from_models() # We're just going to notify every child when containers are updated if node is None: try: node = api._model_get_by_id('nodes', node_id) except (exceptions.IdNotFound): return if 'container' in node['facts'].get('backends', []): children = utility.get_direct_children(node, api) for child in children: semaphore = 'nodes-id-%s' % child['id'] utility.notify(semaphore) # Update transaction for node and children id_list = utility.fully_expand_nodelist([node], api) # TODO(shep): this needs to be better abstracted # Need a codepath to update transaction for attr modifications else: # TODO(shep): this needs to be better abstracted id_list = [node_id] _update_transaction_id('nodes', id_list)
def task_log(task_id): """ Tail a logfile on a client. Given a task id, this asks the client that ran it grab the last 1k of the logs from that task and push them at the server on an ephemeral port. This gets returned as 'log' in the return json blob. """ api = api_from_models() try: task = api._model_get_by_id('tasks', task_id) except exceptions.IdNotFound: return generic.http_notfound(msg='Task %s not found' % task_id) watching = flask.request.args.get('watch', False) is not False offset_raw = flask.request.args.get('offset', '1024') offset = {} try: offset['length'] = int(offset_raw) except ValueError: return generic.http_badrequest(msg='Offset must be an integer.') if offset_raw.startswith('+'): offset['position'] = 'start' else: offset['position'] = 'end' s = gevent.socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 0)) s.listen(1) addr, port = s.getsockname() if addr == '0.0.0.0': # we need something more specific. This is # pretty naive, but works. If we could get the # flask fd, we could getsockname on that side # and see what we requested on. that would # likely be a better guess. # generate a list of all interfaces with # non-loopback ipv4 addresses. addr = None addrs = {} for iface in netifaces.interfaces(): if netifaces.AF_INET in netifaces.ifaddresses(iface): for ablock in netifaces.ifaddresses(iface)[netifaces.AF_INET]: if not iface in addrs: addrs[iface] = [] if not ablock['addr'].startswith('127'): addrs[iface].append(ablock['addr']) if iface in addrs and len(addrs[iface]) == 0: addrs.pop(iface) if len(addrs) == 0: s.close() return generic.http_badrequest(msg='cannot determine interface') # try least-to-most interesting for iface in ['en1', 'en0', 'eth1', 'eth0']: if iface in addrs: addr = addrs[iface][0] # just grab the first if not addr: addr = addrs[addrs.keys().pop(0)][0] client_action = 'logfile.watch' if watching else 'logfile.tail' payload = {'node_id': task['node_id'], 'action': client_action, 'payload': {'task_id': task['id'], 'dest_ip': addr, 'dest_port': port, 'offset': offset}} if watching: payload['payload']['timeout'] = 30 new_task = api._model_create('tasks', payload) # this should really be done by the data model. <sigh> task_semaphore = 'task-for-%s' % task['node_id'] flask.current_app.logger.debug('notifying event %s' % task_semaphore) utility.notify(task_semaphore) # # force the wake # utility.sleep(0.1) # now wait for a few seconds to see if we get the # connection s.settimeout(10) try: conn, addr = s.accept() except socket.timeout: flask.current_app.logger.error('Error waiting for ' 'client connect on log tail') s.close() api._model_update_by_id('tasks', new_task['id'], {'state': 'cancelled'}) return generic.http_notfound(msg='cannot fetch logs') if watching: watch = str(uuid.uuid1()) watched_tasks[watch] = { 'socket': conn, 'time': time.time(), 'task_id': new_task['id'], 'notifier': gevent.event.Event(), 'accept_socket': s, 'event': gevent.spawn( lambda: _serve_connection( api, watch))} return generic.http_response(200, request=watch) # otherwise, just tail data = _generate_data(conn, s) return flask.Response(data, mimetype='text/plain')
def task_log(task_id): """ Tail a logfile on a client. Given a task id, this asks the client that ran it grab the last 1k of the logs from that task and push them at the server on an ephemeral port. This gets returned as 'log' in the return json blob. """ api = api_from_models() try: task = api._model_get_by_id("tasks", task_id) except exceptions.IdNotFound: return generic.http_notfound(msg="Task %s not found" % task_id) watching = flask.request.args.get("watch", False) is not False offset = flask.request.args.get("offset", 1024) try: offset = int(offset) except ValueError: pass if not isinstance(offset, int) or offset < 0: message = "Offset must be a non-negative integer" return generic.http_badrequest(msg=message) s = gevent.socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("", 0)) s.listen(1) addr, port = s.getsockname() if addr == "0.0.0.0": # we need something more specific. This is # pretty naive, but works. If we could get the # flask fd, we could getsockname on that side # and see what we requested on. that would # likely be a better guess. # generate a list of all interfaces with # non-loopback ipv4 addresses. addr = None addrs = {} for iface in netifaces.interfaces(): if netifaces.AF_INET in netifaces.ifaddresses(iface): for ablock in netifaces.ifaddresses(iface)[netifaces.AF_INET]: if not iface in addrs: addrs[iface] = [] if not ablock["addr"].startswith("127"): addrs[iface].append(ablock["addr"]) if iface in addrs and len(addrs[iface]) == 0: addrs.pop(iface) if len(addrs) == 0: s.close() return generic.http_badrequest(msg="cannot determine interface") # try least-to-most interesting for iface in ["en1", "en0", "eth1", "eth0"]: if iface in addrs: addr = addrs[iface][0] # just grab the first if not addr: addr = addrs[addrs.keys().pop(0)][0] client_action = "logfile.watch" if watching else "logfile.tail" payload = { "node_id": task["node_id"], "action": client_action, "payload": {"task_id": task["id"], "dest_ip": addr, "dest_port": port, "offset": offset}, } if watching: payload["payload"]["timeout"] = 30 new_task = api._model_create("tasks", payload) # this should really be done by the data model. <sigh> task_semaphore = "task-for-%s" % task["node_id"] flask.current_app.logger.debug("notifying event %s" % task_semaphore) utility.notify(task_semaphore) # force the wake utility.sleep(0.1) # now wait for a few seconds to see if we get the # connection s.settimeout(10) try: conn, addr = s.accept() except socket.timeout: flask.current_app.logger.error("Error waiting for " "client connect on log tail") s.close() api._model_update_by_id("tasks", new_task["id"], {"state": "cancelled"}) return generic.http_notfound(msg="cannot fetch logs") if watching: watch = str(uuid.uuid1()) watched_tasks[watch] = { "socket": conn, "time": time.time(), "task_id": new_task["id"], "notifier": gevent.event.Event(), "accept_socket": s, "event": gevent.spawn(lambda: _serve_connection(api, watch)), } return generic.http_response(200, request=watch) # otherwise, just tail data = _generate_data(conn, s) return flask.Response(data, mimetype="text/plain")