#!/usr/bin/envpython3 import json import logging import re from collections import defaultdict, OrderedDict import configparser import xmlrpc.client import concurrent.futures try: # django>=1.8 from django.template.context_processors import csrf except ImportError: # django==1.7 from django.core.context_processors import csrf from django.http import HttpResponse from django.shortcuts import redirect, render_to_response from django.views.decorators.csrf import ensure_csrf_cookie from django.conf import settings from pathlib import Path # from pprint import pformat LOG = logging.getLogger(__name__) def _get_supervisor(hostname): if isinstance(settings.SUPERVISORS, dict): url = settings.SUPERVISORS[hostname][0] else: url = "http://{}:9001".format(hostname) supervisor = xmlrpc.client.ServerProxy(url, verbose=False) return supervisor def _get_monhelper(hostname): if isinstance(settings.SUPERVISORS, dict): url = settings.SUPERVISORS[hostname][1] else: url = "http://{}:9002".format(hostname) monhelper = xmlrpc.client.ServerProxy(url, verbose=False) return monhelper def _get_server_data(hostname, resource_pids, metadata): supervisor = _get_supervisor(hostname) monhelper = _get_monhelper(hostname) try: processes = supervisor.supervisor.getAllProcessInfo() server = {} pids = [] for process in processes: if process["pid"]: pids.append(process["pid"]) group_name = process['group'] group = server.setdefault(group_name, {"processes": [], "total_processes": 0, "running_processes": 0}) process["can_be_stopped"] = process["statename"] in {"RUNNING"} process["can_be_restarted"] = process["statename"] in {"RUNNING"} process["can_be_started"] = \ process["statename"] in {"STOPPED", "EXITED"} if process["statename"] in {"RUNNING"}: group['running_processes'] += 1 else: group['has_not_running_processes'] = True group['total_processes'] += 1 group['processes'].append(process) if group['total_processes'] == 1: group_tags = set() for server_regex, group_regex, tags in metadata: group_match = group_regex.match(group_name) server_match = server_regex.match(hostname) if group_match and server_match: group_tags.update(tags) group['tags'] = list(sorted(group_tags)) if resource_pids: try: resources_dict = monhelper.getProcessResourceUsage( list(int(x) for x in resource_pids)) except ConnectionRefusedError: LOG.warning("Remote server %s doesn't have the monhelper" " extension", hostname) else: for pid, resources in resources_dict.items(): for process in processes: if str(process['pid']) == pid: process['resources'] = resources break finally: supervisor("close")() monhelper("close")() return server def _get_data(server_pids, metadata): # hostname -> group -> process services_by_host = OrderedDict() servers = sorted(settings.SUPERVISORS) with concurrent.futures.ThreadPoolExecutor(max_workers=15) as executor: for hostname in servers: server_data = executor.submit(_get_server_data, hostname, server_pids.get(hostname), metadata) services_by_host[hostname] = server_data for hostname in servers: services_by_host[hostname] = services_by_host[hostname].result() return services_by_host def get_data(request): # resource_pids = set(int(x) for x in request.GET.getlist('pid[]')) data = json.loads(request.body.decode("ascii")) # LOG.debug("data: %r", data) data = _get_data(data['server_pids'], []) data_json = json.dumps(data) return HttpResponse(data_json, content_type='application/json') def get_index_template_data(): metadata, tags_config, taggroups_dict = _get_metadata_conf() services_by_host = _get_data({}, metadata) all_tags = set() for server_data in services_by_host.values(): for group in server_data.values(): all_tags.update(group['tags']) tags_by_group = defaultdict(set) for tag_name in all_tags: tag = tags_config[tag_name] tags_by_group[tag.taggroup].add(tag_name) taggroups = [] for name, tags in sorted(tags_by_group.items()): taggroups.append((taggroups_dict[name].label, sorted(tags))) # sort everything data = [] for server, groups in sorted(services_by_host.items()): data.append((server, sorted(groups.items()))) context = { "data": data, "taggroups": taggroups, 'tags_config': tags_config, "SITE_ROOT": settings.SITE_ROOT, } return context @ensure_csrf_cookie def home(request, template_name="suponoff/index.html"): context = get_index_template_data() context.update(csrf(request)) resp = render_to_response(template_name, context) return resp def action(request): server = request.POST['server'] supervisor = _get_supervisor(server) try: if 'action_start_all' in request.POST: supervisor.supervisor.startAllProcesses() return HttpResponse(json.dumps("ok"), content_type='application/json') elif 'action_stop_all' in request.POST: supervisor.supervisor.stopAllProcesses() return HttpResponse(json.dumps("ok"), content_type='application/json') program = "{}:{}".format(request.POST['group'], request.POST['program']) if 'action_start' in request.POST: supervisor.supervisor.startProcess(program) elif 'action_stop' in request.POST: supervisor.supervisor.stopProcess(program) elif 'action_restart' in request.POST: supervisor.supervisor.stopProcess(program) supervisor.supervisor.startProcess(program) finally: supervisor("close")() return redirect(settings.SITE_ROOT) def get_program_logs(request): logs = "Logs for program {}:{} in server {}".format( request.GET['group'], request.GET['program'], request.GET['server']) stream = request.GET['stream'] assert stream in {'stdout', 'stderr', 'applog'} full_name = "{}:{}".format(request.GET['group'], request.GET['program']) if stream == 'stdout': supervisor = _get_supervisor(request.GET['server']) try: logs, _offeset, _overflow = \ supervisor.supervisor.tailProcessStdoutLog( full_name, -100000, 100000) finally: supervisor("close")() elif stream == 'stderr': supervisor = _get_supervisor(request.GET['server']) try: logs, _offeset, _overflow = \ supervisor.supervisor.tailProcessStderrLog( full_name, -100000, 100000) finally: supervisor("close")() elif stream == 'applog': supervisor = _get_monhelper(request.GET['server']) try: logs, _offeset, _overflow = supervisor.tailApplicationLog( int(request.GET['pid']), -100000, 100000) finally: supervisor("close")() else: raise AssertionError return HttpResponse(logs, content_type='text/plain') class TagConfig: # pylint: disable=R0903 enabled_by_default = True taggroup = 'other' class TagGroup: # pylint: disable=R0903 label = '' def _get_metadata_conf(): mappings = [] tags_config = defaultdict(TagConfig) taggroups = defaultdict(TagGroup) metadata_dir = getattr(settings, "METADATA_DIR", None) if not metadata_dir: return mappings, tags_config, taggroups for fname in Path(metadata_dir).iterdir(): config = configparser.ConfigParser() config.read(str(fname)) for section in config.sections(): if section.startswith("meta:"): group_regex = re.compile(config[section].get("group", '.*')) server_regex = re.compile(config[section].get("server", '.*')) tags = config[section].get("tags") if tags is None: tags = frozenset() else: tags = {tag.strip() for tag in tags.split(',')} mappings.append((server_regex, group_regex, tags)) elif section.startswith("tag:"): _, _, tag_name = section.partition("tag:") enabled = config[section].getboolean("enabled_by_default", True) tags_config[tag_name].enabled_by_default = enabled taggroup = config[section].get("taggroup", 'other') tags_config[tag_name].taggroup = taggroup elif section.startswith("taggroup:"): _, _, taggroup_name = section.partition("taggroup:") label = config[section].get("label", '') taggroups[taggroup_name].label = label return mappings, tags_config, taggroups