Initial LisgloSIPS V2 implementation
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> int:
|
||||
redis_url = os.getenv("OPENSIPS_REDIS_URL", "redis:s20://127.0.0.1:6380/")
|
||||
config = sys.stdin.read()
|
||||
sys.stdout.write(config.replace("@@LISGLOSIPS_REDIS_URL@@", redis_url))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import selectors
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
|
||||
|
||||
LISTEN_HOST = os.getenv("LISTEN_HOST", "127.0.0.1")
|
||||
LISTEN_PORT = int(os.getenv("LISTEN_PORT", "6380"))
|
||||
REDIS_HOST = os.getenv("REDIS_HOST", "100.90.90.91")
|
||||
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))
|
||||
REDIS_USER = os.getenv("REDIS_USER", "lisglosips")
|
||||
CONNECT_TIMEOUT = float(os.getenv("CONNECT_TIMEOUT", "2.0"))
|
||||
|
||||
|
||||
def credential(name: str) -> str:
|
||||
cred_dir = os.getenv("CREDENTIALS_DIRECTORY")
|
||||
if not cred_dir:
|
||||
raise RuntimeError("CREDENTIALS_DIRECTORY is not set")
|
||||
path = os.path.join(cred_dir, name)
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
return handle.read().strip()
|
||||
|
||||
|
||||
def resp_array(parts: list[str]) -> bytes:
|
||||
output = f"*{len(parts)}\r\n".encode("utf-8")
|
||||
for part in parts:
|
||||
data = part.encode("utf-8")
|
||||
output += f"${len(data)}\r\n".encode("utf-8") + data + b"\r\n"
|
||||
return output
|
||||
|
||||
|
||||
def read_line(sock: socket.socket) -> bytes:
|
||||
data = b""
|
||||
while not data.endswith(b"\r\n"):
|
||||
chunk = sock.recv(1)
|
||||
if not chunk:
|
||||
break
|
||||
data += chunk
|
||||
if len(data) > 4096:
|
||||
break
|
||||
return data
|
||||
|
||||
|
||||
def connect_upstream() -> socket.socket:
|
||||
password = credential("redis_password")
|
||||
upstream = socket.create_connection((REDIS_HOST, REDIS_PORT), timeout=CONNECT_TIMEOUT)
|
||||
upstream.sendall(resp_array(["AUTH", REDIS_USER, password]))
|
||||
reply = read_line(upstream)
|
||||
if not reply.startswith(b"+OK"):
|
||||
upstream.close()
|
||||
raise RuntimeError("Redis AUTH failed")
|
||||
upstream.setblocking(False)
|
||||
return upstream
|
||||
|
||||
|
||||
def bridge(client: socket.socket) -> None:
|
||||
upstream = connect_upstream()
|
||||
client.setblocking(False)
|
||||
selector = selectors.DefaultSelector()
|
||||
selector.register(client, selectors.EVENT_READ, upstream)
|
||||
selector.register(upstream, selectors.EVENT_READ, client)
|
||||
try:
|
||||
while True:
|
||||
for key, _ in selector.select(timeout=30):
|
||||
src = key.fileobj
|
||||
dst = key.data
|
||||
data = src.recv(65536)
|
||||
if not data:
|
||||
return
|
||||
dst.sendall(data)
|
||||
finally:
|
||||
selector.close()
|
||||
client.close()
|
||||
upstream.close()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind((LISTEN_HOST, LISTEN_PORT))
|
||||
server.listen(128)
|
||||
print(f"lisglosips redis auth proxy listening on {LISTEN_HOST}:{LISTEN_PORT}", flush=True)
|
||||
while True:
|
||||
client, _ = server.accept()
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
server.close()
|
||||
try:
|
||||
bridge(client)
|
||||
except Exception as exc:
|
||||
print(f"redis proxy client failed: {exc}", file=sys.stderr, flush=True)
|
||||
return 0
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
SCRIPT_PATH = os.getenv("HOTPATH_LUA", "/etc/opensips/lisglosips_hotpath.lua")
|
||||
REDIS_HOST = os.getenv("REDIS_PROXY_HOST", "127.0.0.1")
|
||||
REDIS_PORT = int(os.getenv("REDIS_PROXY_PORT", "6380"))
|
||||
|
||||
|
||||
def enc(parts: list[str]) -> bytes:
|
||||
output = f"*{len(parts)}\r\n".encode("utf-8")
|
||||
for part in parts:
|
||||
data = part.encode("utf-8")
|
||||
output += f"${len(data)}\r\n".encode("utf-8") + data + b"\r\n"
|
||||
return output
|
||||
|
||||
|
||||
with open(SCRIPT_PATH, "r", encoding="utf-8") as handle:
|
||||
script = handle.read()
|
||||
|
||||
last_error: Exception | None = None
|
||||
for _ in range(20):
|
||||
try:
|
||||
with socket.create_connection((REDIS_HOST, REDIS_PORT), timeout=2) as sock:
|
||||
sock.sendall(enc(["SCRIPT", "LOAD", script]))
|
||||
response = sock.recv(4096)
|
||||
if not response.startswith(b"$"):
|
||||
raise SystemExit(f"unexpected Redis response while loading Lua: {response[:80]!r}")
|
||||
raise SystemExit(0)
|
||||
except OSError as exc:
|
||||
last_error = exc
|
||||
time.sleep(0.25)
|
||||
|
||||
raise SystemExit(f"could not connect to Redis proxy: {last_error}")
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUT_DIR=${1:-/var/lib/prometheus/node-exporter}
|
||||
OUT_FILE="$OUT_DIR/lisglosips_s20.prom"
|
||||
TMP_FILE="$OUT_FILE.$$"
|
||||
BASE_DIR=/dev/shm/voip_rec
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
tmp_total=0
|
||||
tmp_used=0
|
||||
tmp_avail=0
|
||||
if df_out=$(df -B1 "$BASE_DIR" 2>/dev/null | awk 'NR==2 {print $2" "$3" "$4}'); then
|
||||
read -r tmp_total tmp_used tmp_avail <<<"$df_out"
|
||||
fi
|
||||
|
||||
ready_count=$(find "$BASE_DIR/ready" -type f -name '*.ready' 2>/dev/null | wc -l)
|
||||
part_count=$(find "$BASE_DIR/ready" -type f -name '*.part' 2>/dev/null | wc -l)
|
||||
oldest_age=0
|
||||
oldest_epoch=$(find "$BASE_DIR/ready" -type f -name '*.ready' -printf '%T@\n' 2>/dev/null | sort -n | head -n 1 || true)
|
||||
if [ -n "$oldest_epoch" ]; then
|
||||
now_epoch=$(date +%s)
|
||||
oldest_sec=${oldest_epoch%.*}
|
||||
oldest_age=$(( now_epoch - oldest_sec ))
|
||||
if [ "$oldest_age" -lt 0 ]; then
|
||||
oldest_age=0
|
||||
fi
|
||||
fi
|
||||
|
||||
redis_reachable=0
|
||||
if timeout 2 bash -c '</dev/tcp/127.0.0.1/6380' 2>/dev/null; then
|
||||
redis_reachable=1
|
||||
fi
|
||||
|
||||
rtpengine_sessions=0
|
||||
if command -v rtpengine-ctl >/dev/null 2>&1; then
|
||||
rtpengine_sessions=$(rtpengine-ctl -ip 127.0.0.1 -port 2224 list numsessions 2>/dev/null | awk -F: '/Current sessions total/ {gsub(/ /, "", $2); print $2; found=1} END {if (!found) print 0}')
|
||||
fi
|
||||
|
||||
cat > "$TMP_FILE" <<EOF
|
||||
# HELP lisglosips_voip_rec_tmpfs_bytes Server A recording tmpfs bytes.
|
||||
# TYPE lisglosips_voip_rec_tmpfs_bytes gauge
|
||||
lisglosips_voip_rec_tmpfs_bytes{state="total"} $tmp_total
|
||||
lisglosips_voip_rec_tmpfs_bytes{state="used"} $tmp_used
|
||||
lisglosips_voip_rec_tmpfs_bytes{state="available"} $tmp_avail
|
||||
# HELP lisglosips_recording_files Server A recording staging files.
|
||||
# TYPE lisglosips_recording_files gauge
|
||||
lisglosips_recording_files{state="ready"} $ready_count
|
||||
lisglosips_recording_files{state="part"} $part_count
|
||||
# HELP lisglosips_recording_oldest_ready_age_seconds Oldest ready recording age in seconds.
|
||||
# TYPE lisglosips_recording_oldest_ready_age_seconds gauge
|
||||
lisglosips_recording_oldest_ready_age_seconds $oldest_age
|
||||
# HELP lisglosips_redis_proxy_up Local Redis auth proxy TCP reachability.
|
||||
# TYPE lisglosips_redis_proxy_up gauge
|
||||
lisglosips_redis_proxy_up $redis_reachable
|
||||
# HELP lisglosips_rtpengine_sessions Current RTPEngine sessions reported by CLI.
|
||||
# TYPE lisglosips_rtpengine_sessions gauge
|
||||
lisglosips_rtpengine_sessions $rtpengine_sessions
|
||||
EOF
|
||||
|
||||
chown prometheus:prometheus "$TMP_FILE" 2>/dev/null || true
|
||||
chmod 0644 "$TMP_FILE"
|
||||
mv "$TMP_FILE" "$OUT_FILE"
|
||||
Reference in New Issue
Block a user