# -*- coding: utf-8 -*-
"""只操作本程序创建的临时对象；全部数据为合成样本。"""
import os, sys, json, sqlite3, tempfile, pathlib, subprocess, time, socket, stat
from pathlib import Path
def emit(**kw):
    print(json.dumps(kw, ensure_ascii=False, sort_keys=True))

import shutil, threading, urllib.request, urllib.error, http.server, socketserver, contextlib
NGINX=os.environ.get('NGINX_BIN') or shutil.which('nginx')

def freeport():
    s=socket.socket(); s.bind(('127.0.0.1',0)); p=s.getsockname()[1]; s.close(); return p
class Threaded(socketserver.ThreadingMixIn,http.server.HTTPServer):
    daemon_threads=True
def backend(handler,host='127.0.0.1',port=0):
    class H(http.server.BaseHTTPRequestHandler):
        protocol_version='HTTP/1.1'
        def log_message(self,*a): pass
        def do_GET(self):
            status,headers,body=handler(self.path)
            self.send_response(status)
            for k,v in headers.items(): self.send_header(k,v)
            self.send_header('Content-Length',str(len(body))); self.end_headers()
            try: self.wfile.write(body)
            except (BrokenPipeError,ConnectionResetError): pass
    s=Threaded((host,port),H); threading.Thread(target=s.serve_forever,daemon=True).start(); return s
def request(port,path='/',host='demo.test'):
    q=urllib.request.Request('http://127.0.0.1:'+str(port)+path,headers={'Host':host,'Connection':'close'})
    opener=urllib.request.build_opener(urllib.request.ProxyHandler({}))
    try: r=opener.open(q,timeout=5)
    except urllib.error.HTTPError as e: r=e
    with r: return {'status':r.code,'headers':dict(r.headers),'body':r.read().decode()}
@contextlib.contextmanager
def nginx(http_config):
    assert os.name=='posix', '本实验按Linux独立prefix设计'
    with tempfile.TemporaryDirectory(prefix='ywebs-next132-b2-nginx-') as d:
        p=Path(d); assert p.name.startswith('ywebs-next132-b2-nginx-')
        def render(body):
            text='worker_processes 1; daemon off; master_process on; pid '+d+'/nginx.pid; error_log '+d+'/error.log notice; events {worker_connections 128;} http {access_log off; client_body_temp_path '+d+'/body; proxy_temp_path '+d+'/proxy; '+body+'}'
            for forbidden in ['/var/www','/opt/ywebs','/usr/local/nginx/conf']: assert forbidden not in text
            (p/'nginx.conf').write_text(text,encoding='utf-8')
        render(http_config)
        def command(*a): return subprocess.run([NGINX,'-p',d+'/','-c',str(p/'nginx.conf')]+list(a),stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True)
        tested=command('-t'); assert tested.returncode==0,tested.stdout
        proc=subprocess.Popen([NGINX,'-p',d+'/','-c',str(p/'nginx.conf')],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
        time.sleep(.3); assert proc.poll() is None,(p/'error.log').read_text()
        try: yield p,proc,render,command
        finally:
            if proc.poll() is None: proc.terminate()
            try: proc.wait(timeout=5)
            except subprocess.TimeoutExpired: proc.kill(); proc.wait()

PROMETHEUS=os.environ.get('PROMETHEUS_BIN') or shutil.which('prometheus')
assert PROMETHEUS, '需官方Prometheus3.5.0真实服务；设置PROMETHEUS_BIN'
def getjson(port,path):
    with urllib.request.build_opener(urllib.request.ProxyHandler({})).open('http://127.0.0.1:'+str(port)+path,timeout=3) as r: return json.load(r)
def waitfor(fn,expected,seconds=8):
    deadline=time.monotonic()+seconds; last=None
    while time.monotonic()<deadline:
        try:
            last=fn()
            if last==expected: return last
        except Exception as e: last=str(e)
        time.sleep(.15)
    raise AssertionError('等待结果 '+repr(expected)+' 实际 '+repr(last))
@contextlib.contextmanager
def prometheus(config):
    with tempfile.TemporaryDirectory(prefix='ywebs-next132-b2-prometheus-') as d:
        p=Path(d); port=freeport(); (p/'prometheus.yml').write_text(json.dumps(config),encoding='utf-8')
        log=open(str(p/'process.log'),'w+')
        proc=subprocess.Popen([PROMETHEUS,'--config.file='+str(p/'prometheus.yml'),'--storage.tsdb.path='+str(p/'data'),'--web.listen-address=127.0.0.1:'+str(port)],stdout=log,stderr=log)
        try:
            waitfor(lambda:getjson(port,'/api/v1/status/runtimeinfo')['status'],'success')
            def query(expr):
                import urllib.parse
                data=getjson(port,'/api/v1/query?query='+urllib.parse.quote(expr))
                assert data['status']=='success',data
                return data['data']['result']
            yield port,query
        finally:
            proc.terminate()
            try: proc.wait(timeout=5)
            except subprocess.TimeoutExpired: proc.kill(); proc.wait()
            log.close()

BLACKBOX=os.environ.get('BLACKBOX_BIN') or shutil.which('blackbox_exporter'); assert BLACKBOX,'需官方Blackbox Exporter0.26.0，设置BLACKBOX_BIN'
b=backend(lambda p:(200 if p=='/ok' else 503,{},b'demo')); bbport=freeport()
with tempfile.TemporaryDirectory(prefix='ywebs-next132-b2-blackbox-') as d:
    p=Path(d); (p/'blackbox.yml').write_text(json.dumps({'modules':{'demo_http':{'prober':'http','timeout':'1s','http':{'valid_status_codes':[200],'preferred_ip_protocol':'ip4'}}}}),encoding='utf-8')
    log=open(str(p/'blackbox.log'),'w+'); bb=subprocess.Popen([BLACKBOX,'--config.file='+str(p/'blackbox.yml'),'--web.listen-address=127.0.0.1:'+str(bbport)],stdout=log,stderr=log)
    config={'global':{'scrape_interval':'1s'},'scrape_configs':[{'job_name':name,'metrics_path':'/probe','params':{'module':['demo_http'],'target':['http://127.0.0.1:'+str(b.server_port)+path]},'static_configs':[{'targets':['127.0.0.1:'+str(bbport)]}]} for name,path in [('good','/ok'),('bad','/bad')]]}
    try:
        with prometheus(config) as lab:
            def value(metric,job):
                rows=lab[1](metric+'{job="'+job+'"}'); return float(rows[0]['value'][1]) if rows else None
            good=waitfor(lambda:value('probe_success','good'),1.0); bad=waitfor(lambda:value('probe_success','bad'),0.0); up=waitfor(lambda:value('up','bad'),1.0)
            bb.terminate(); bb.wait(timeout=5); stopped=waitfor(lambda:value('up','bad'),0.0)
            emit(good_probe=good,bad_probe=bad,bad_exporter_up=up,stopped_exporter_up=stopped)
    finally:
        if bb.poll() is None: bb.terminate(); bb.wait(timeout=5)
        log.close(); b.shutdown(); b.server_close()
