mirror of https://github.com/sipwise/rtpengine.git
Extend rtpengine-recording HTTP/command notifications beyond the historical finished-only path so operators can track call and stream recording lifecycle. Events (notify-events CSV, default: finished): opened, started, finished, discarded, failed, call-started, call-finished, call-discarded New options: notify-events, notify-json, notify-no-metadata, notify-command-format, notify-queue-limit. Docs/conf only document the new notify options; upstream storage (S3/GCS/output-storage) docs are left unchanged. Backward compatible: default mask is finished only. Includes unit tests and Docker e2e smoke harness.pull/2142/head
parent
702ccec969
commit
0e59c3a0fe
@ -0,0 +1,120 @@
|
||||
#include "notify_events.h"
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* Pure event helpers (no curl / thread pool).
|
||||
* Linked into the recording daemon and into unit tests.
|
||||
*/
|
||||
|
||||
static const char *const event_names[NOTIFY_EVT_COUNT] = {
|
||||
[NOTIFY_EVT_FILE_OPENED] = "recording_file_opened",
|
||||
[NOTIFY_EVT_STARTED] = "recording_started",
|
||||
[NOTIFY_EVT_FINISHED] = "recording_finished",
|
||||
[NOTIFY_EVT_DISCARDED] = "recording_discarded",
|
||||
[NOTIFY_EVT_FAILED] = "recording_failed",
|
||||
[NOTIFY_EVT_CALL_STARTED] = "call_recording_started",
|
||||
[NOTIFY_EVT_CALL_FINISHED] = "call_recording_finished",
|
||||
[NOTIFY_EVT_CALL_DISCARDED] = "call_recording_discarded",
|
||||
};
|
||||
|
||||
static const char *const event_statuses[NOTIFY_EVT_COUNT] = {
|
||||
[NOTIFY_EVT_FILE_OPENED] = "opened",
|
||||
[NOTIFY_EVT_STARTED] = "started",
|
||||
[NOTIFY_EVT_FINISHED] = "finished",
|
||||
[NOTIFY_EVT_DISCARDED] = "discarded",
|
||||
[NOTIFY_EVT_FAILED] = "failed",
|
||||
[NOTIFY_EVT_CALL_STARTED] = "call-started",
|
||||
[NOTIFY_EVT_CALL_FINISHED] = "call-finished",
|
||||
[NOTIFY_EVT_CALL_DISCARDED] = "call-discarded",
|
||||
};
|
||||
|
||||
const char *notify_event_name(enum notify_event event) {
|
||||
if (event < 0 || event >= NOTIFY_EVT_COUNT)
|
||||
return "unknown";
|
||||
return event_names[event];
|
||||
}
|
||||
|
||||
const char *notify_event_status(enum notify_event event) {
|
||||
if (event < 0 || event >= NOTIFY_EVT_COUNT)
|
||||
return "unknown";
|
||||
return event_statuses[event];
|
||||
}
|
||||
|
||||
bool notify_event_enabled(enum notify_event event) {
|
||||
if (event < 0 || event >= NOTIFY_EVT_COUNT)
|
||||
return false;
|
||||
return (notify_events_mask & (1u << event)) != 0;
|
||||
}
|
||||
|
||||
bool notify_events_parse(const char *csv, unsigned int *mask_out, char **err_token) {
|
||||
unsigned int mask = 0;
|
||||
|
||||
if (err_token)
|
||||
*err_token = NULL;
|
||||
|
||||
if (!csv || !csv[0]) {
|
||||
*mask_out = NOTIFY_MASK_DEFAULT;
|
||||
return true;
|
||||
}
|
||||
|
||||
g_autofree char *dup = g_strdup(csv);
|
||||
char *save = NULL;
|
||||
for (char *tok = strtok_r(dup, ", \t", &save); tok; tok = strtok_r(NULL, ", \t", &save)) {
|
||||
if (!strcmp(tok, "opened"))
|
||||
mask |= NOTIFY_MASK_OPENED;
|
||||
else if (!strcmp(tok, "started"))
|
||||
mask |= NOTIFY_MASK_STARTED;
|
||||
else if (!strcmp(tok, "finished"))
|
||||
mask |= NOTIFY_MASK_FINISHED;
|
||||
else if (!strcmp(tok, "discarded"))
|
||||
mask |= NOTIFY_MASK_DISCARDED;
|
||||
else if (!strcmp(tok, "failed"))
|
||||
mask |= NOTIFY_MASK_FAILED;
|
||||
else if (!strcmp(tok, "call-started"))
|
||||
mask |= NOTIFY_MASK_CALL_STARTED;
|
||||
else if (!strcmp(tok, "call-finished"))
|
||||
mask |= NOTIFY_MASK_CALL_FINISHED;
|
||||
else if (!strcmp(tok, "call-discarded"))
|
||||
mask |= NOTIFY_MASK_CALL_DISCARDED;
|
||||
else if (!strcmp(tok, "all"))
|
||||
mask |= NOTIFY_MASK_ALL;
|
||||
else {
|
||||
if (err_token)
|
||||
*err_token = g_strdup(tok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mask == 0)
|
||||
mask = NOTIFY_MASK_DEFAULT;
|
||||
|
||||
*mask_out = mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool notify_event_is_terminal(enum notify_event event) {
|
||||
return event == NOTIFY_EVT_FINISHED
|
||||
|| event == NOTIFY_EVT_DISCARDED
|
||||
|| event == NOTIFY_EVT_FAILED
|
||||
|| event == NOTIFY_EVT_CALL_FINISHED
|
||||
|| event == NOTIFY_EVT_CALL_DISCARDED;
|
||||
}
|
||||
|
||||
bool notify_command_format_parse(const char *s, enum notify_command_format *out) {
|
||||
if (!out)
|
||||
return false;
|
||||
if (!s || !s[0] || !strcmp(s, "legacy")) {
|
||||
*out = NOTIFY_CMD_LEGACY;
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(s, "extended")) {
|
||||
*out = NOTIFY_CMD_EXTENDED;
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(s, "json-env")) {
|
||||
*out = NOTIFY_CMD_JSON_ENV;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
#ifndef _NOTIFY_EVENTS_H_
|
||||
#define _NOTIFY_EVENTS_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Recording lifecycle notification events (shared with unit tests).
|
||||
* Full notify transport API lives in notify.h.
|
||||
*/
|
||||
enum notify_event {
|
||||
NOTIFY_EVT_FILE_OPENED = 0,
|
||||
NOTIFY_EVT_STARTED,
|
||||
NOTIFY_EVT_FINISHED,
|
||||
NOTIFY_EVT_DISCARDED,
|
||||
NOTIFY_EVT_FAILED,
|
||||
NOTIFY_EVT_CALL_STARTED,
|
||||
NOTIFY_EVT_CALL_FINISHED,
|
||||
NOTIFY_EVT_CALL_DISCARDED,
|
||||
NOTIFY_EVT_COUNT,
|
||||
};
|
||||
|
||||
#define NOTIFY_MASK_OPENED (1u << NOTIFY_EVT_FILE_OPENED)
|
||||
#define NOTIFY_MASK_STARTED (1u << NOTIFY_EVT_STARTED)
|
||||
#define NOTIFY_MASK_FINISHED (1u << NOTIFY_EVT_FINISHED)
|
||||
#define NOTIFY_MASK_DISCARDED (1u << NOTIFY_EVT_DISCARDED)
|
||||
#define NOTIFY_MASK_FAILED (1u << NOTIFY_EVT_FAILED)
|
||||
#define NOTIFY_MASK_CALL_STARTED (1u << NOTIFY_EVT_CALL_STARTED)
|
||||
#define NOTIFY_MASK_CALL_FINISHED (1u << NOTIFY_EVT_CALL_FINISHED)
|
||||
#define NOTIFY_MASK_CALL_DISCARDED (1u << NOTIFY_EVT_CALL_DISCARDED)
|
||||
#define NOTIFY_MASK_STREAM_ALL (NOTIFY_MASK_OPENED | NOTIFY_MASK_STARTED | \
|
||||
NOTIFY_MASK_FINISHED | NOTIFY_MASK_DISCARDED | \
|
||||
NOTIFY_MASK_FAILED)
|
||||
#define NOTIFY_MASK_CALL_ALL (NOTIFY_MASK_CALL_STARTED | NOTIFY_MASK_CALL_FINISHED | \
|
||||
NOTIFY_MASK_CALL_DISCARDED)
|
||||
#define NOTIFY_MASK_ALL (NOTIFY_MASK_STREAM_ALL | NOTIFY_MASK_CALL_ALL)
|
||||
#define NOTIFY_MASK_DEFAULT NOTIFY_MASK_FINISHED
|
||||
|
||||
bool notify_events_parse(const char *csv, unsigned int *mask_out, char **err_token);
|
||||
const char *notify_event_name(enum notify_event event);
|
||||
const char *notify_event_status(enum notify_event event);
|
||||
bool notify_event_enabled(enum notify_event event);
|
||||
|
||||
/* Terminal = finished/discarded/failed and call terminal counterparts. */
|
||||
bool notify_event_is_terminal(enum notify_event event);
|
||||
|
||||
/* notify-command-format tokens (pure parse; no transport deps). */
|
||||
enum notify_command_format {
|
||||
NOTIFY_CMD_LEGACY = 0,
|
||||
NOTIFY_CMD_EXTENDED,
|
||||
NOTIFY_CMD_JSON_ENV,
|
||||
};
|
||||
bool notify_command_format_parse(const char *s, enum notify_command_format *out);
|
||||
|
||||
/* Defined by main.c in daemon; unit tests provide their own. */
|
||||
extern unsigned int notify_events_mask;
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal HTTP notify sink for rtpengine-recording lifecycle events."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
LOG = Path(sys.argv[1] if len(sys.argv) > 1 else "/tmp/notify_events.jsonl")
|
||||
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 8099
|
||||
LOCK = threading.Lock()
|
||||
SEEN: list[dict] = []
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args): # quieter
|
||||
sys.stderr.write("[recv] " + (fmt % args) + "\n")
|
||||
|
||||
def _handle(self):
|
||||
length = int(self.headers.get("Content-Length") or 0)
|
||||
body = self.rfile.read(length) if length else b""
|
||||
headers = {k: v for k, v in self.headers.items() if k.lower().startswith("x-recording")
|
||||
or k.lower() in ("content-type", "user-agent")}
|
||||
entry = {
|
||||
"ts": time.time(),
|
||||
"method": self.command,
|
||||
"path": urlparse(self.path).path,
|
||||
"headers": headers,
|
||||
"body_raw": body.decode("utf-8", errors="replace"),
|
||||
"body_json": None,
|
||||
}
|
||||
if body:
|
||||
try:
|
||||
entry["body_json"] = json.loads(body)
|
||||
except Exception:
|
||||
pass
|
||||
with LOCK:
|
||||
SEEN.append(entry)
|
||||
with LOG.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(entry, separators=(",", ":")) + "\n")
|
||||
ev = headers.get("X-Recording-Event") or headers.get("x-recording-event")
|
||||
print(f"EVENT {ev} method={self.command} path={self.path} body_bytes={len(body)}", flush=True)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(b'{"ok":true}')
|
||||
|
||||
def do_GET(self):
|
||||
self._handle()
|
||||
|
||||
def do_POST(self):
|
||||
self._handle()
|
||||
|
||||
def do_PUT(self):
|
||||
self._handle()
|
||||
|
||||
|
||||
def main():
|
||||
LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||
LOG.write_text("")
|
||||
httpd = ThreadingHTTPServer(("0.0.0.0", PORT), Handler)
|
||||
print(f"notify receiver on :{PORT} log={LOG}", flush=True)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end notify smoke test inside Linux (Docker).
|
||||
# Creates a metafile, deletes it -> expects call_recording_finished (and others if armed).
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="${ROOT:-/rtpengine}"
|
||||
WORK="${WORK:-/tmp/notify_e2e_work}"
|
||||
SPOOL="$WORK/spool"
|
||||
OUTDIR="$WORK/out"
|
||||
LOGDIR="$WORK/logs"
|
||||
EVTLOG="$LOGDIR/notify_events.jsonl"
|
||||
RECV_PORT=8099
|
||||
RECV_URL="http://127.0.0.1:${RECV_PORT}/rec/events"
|
||||
|
||||
rm -rf "$WORK"
|
||||
mkdir -p "$SPOOL" "$OUTDIR" "$LOGDIR"
|
||||
|
||||
write_section() {
|
||||
# write_section FILE SECTION CONTENT
|
||||
local f="$1" sec="$2" content="$3"
|
||||
local len
|
||||
len=$(printf '%s' "$content" | wc -c)
|
||||
printf '%s\n%u:\n%s\n\n' "$sec" "$len" "$content" >>"$f"
|
||||
}
|
||||
|
||||
echo "== starting notify receiver =="
|
||||
python3 "$ROOT/t/notify_e2e/notify_receiver.py" "$EVTLOG" "$RECV_PORT" \
|
||||
>"$LOGDIR/receiver.out" 2>"$LOGDIR/receiver.err" &
|
||||
RECV_PID=$!
|
||||
sleep 0.4
|
||||
if ! kill -0 "$RECV_PID" 2>/dev/null; then
|
||||
echo "receiver failed to start" >&2
|
||||
cat "$LOGDIR/receiver.err" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DAEMON_PID=""
|
||||
cleanup() {
|
||||
[[ -n "${DAEMON_PID:-}" ]] && kill "$DAEMON_PID" 2>/dev/null || true
|
||||
[[ -n "${RECV_PID:-}" ]] && kill "$RECV_PID" 2>/dev/null || true
|
||||
[[ -n "${DAEMON_PID:-}" ]] && wait "$DAEMON_PID" 2>/dev/null || true
|
||||
[[ -n "${RECV_PID:-}" ]] && wait "$RECV_PID" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "== starting rtpengine-recording =="
|
||||
# Prefer in-tree binary: image-built path, then $ROOT, then PATH
|
||||
BIN=""
|
||||
for cand in \
|
||||
/opt/rtpengine/recording-daemon/rtpengine-recording \
|
||||
/rtpengine/recording-daemon/rtpengine-recording \
|
||||
"$ROOT/recording-daemon/rtpengine-recording"
|
||||
do
|
||||
if [[ -x "$cand" ]]; then BIN="$cand"; break; fi
|
||||
done
|
||||
if [[ -z "$BIN" ]]; then
|
||||
BIN=$(command -v rtpengine-recording || true)
|
||||
fi
|
||||
if [[ -z "$BIN" || ! -x "$BIN" ]]; then
|
||||
echo "rtpengine-recording binary not found" >&2
|
||||
ls -la /rtpengine/recording-daemon 2>/dev/null | head || true
|
||||
ls -la /opt/rtpengine/recording-daemon 2>/dev/null | head || true
|
||||
exit 1
|
||||
fi
|
||||
echo "using binary: $BIN"
|
||||
|
||||
|
||||
"$BIN" \
|
||||
--foreground \
|
||||
--log-stderr \
|
||||
--log-level=7 \
|
||||
--table=0 \
|
||||
--spool-dir="$SPOOL" \
|
||||
--output-dir="$OUTDIR" \
|
||||
--output-storage=file \
|
||||
--output-format=wav \
|
||||
--output-single \
|
||||
--num-threads=2 \
|
||||
--notify-uri="$RECV_URL" \
|
||||
--notify-json \
|
||||
--notify-events=opened,started,finished,discarded,failed,call-started,call-finished,call-discarded \
|
||||
--notify-concurrency=2 \
|
||||
--notify-retries=1 \
|
||||
>"$LOGDIR/daemon.out" 2>"$LOGDIR/daemon.err" &
|
||||
DAEMON_PID=$!
|
||||
sleep 1.0
|
||||
if ! kill -0 "$DAEMON_PID" 2>/dev/null; then
|
||||
echo "daemon failed to start" >&2
|
||||
cat "$LOGDIR/daemon.err" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
echo "daemon pid=$DAEMON_PID receiver pid=$RECV_PID"
|
||||
|
||||
# ---- scenario 1: call finished (no streams) ----
|
||||
CALL="testcall-$(date +%s)"
|
||||
META="$SPOOL/$CALL"
|
||||
echo "== writing metafile $META =="
|
||||
: >"$META"
|
||||
write_section "$META" "CALL-ID" "$CALL"
|
||||
write_section "$META" "RANDOM_TAG" "aabbccddeeff0011"
|
||||
write_section "$META" "METADATA" "foo:bar|test:notify-e2e"
|
||||
write_section "$META" "RECORDING" "1"
|
||||
# Ensure CLOSE_WRITE by rewriting via temp + mv? inotify watches close_write of file.
|
||||
# Writing then closing by sync/open-close cycle:
|
||||
python3 - <<PY
|
||||
from pathlib import Path
|
||||
p = Path("$META")
|
||||
data = p.read_bytes()
|
||||
p.write_bytes(data) # rewrite + close
|
||||
print("meta bytes", len(data))
|
||||
PY
|
||||
sleep 0.8
|
||||
|
||||
echo "== deleting metafile (triggers call terminal notify) =="
|
||||
rm -f "$META"
|
||||
sleep 1.5
|
||||
|
||||
# ---- scenario 2: call discarded path via RECORDING off? still terminal ----
|
||||
CALL2="discard-$(date +%s)"
|
||||
META2="$SPOOL/$CALL2"
|
||||
: >"$META2"
|
||||
write_section "$META2" "CALL-ID" "$CALL2"
|
||||
write_section "$META2" "RANDOM_TAG" "1122334455667788"
|
||||
write_section "$META2" "METADATA" "case:discard"
|
||||
write_section "$META2" "RECORDING" "0"
|
||||
python3 - <<PY
|
||||
from pathlib import Path
|
||||
p=Path("$META2"); p.write_bytes(p.read_bytes())
|
||||
PY
|
||||
sleep 0.5
|
||||
rm -f "$META2"
|
||||
sleep 1.5
|
||||
|
||||
echo "== events received =="
|
||||
if [[ ! -s "$EVTLOG" ]]; then
|
||||
echo "FAIL: no notify events captured" >&2
|
||||
echo "--- daemon.err ---"; tail -80 "$LOGDIR/daemon.err" || true
|
||||
echo "--- receiver.err ---"; tail -40 "$LOGDIR/receiver.err" || true
|
||||
exit 2
|
||||
fi
|
||||
|
||||
python3 - "$EVTLOG" <<'PY'
|
||||
import json, sys
|
||||
from pathlib import Path
|
||||
log = Path(sys.argv[1])
|
||||
rows = [json.loads(l) for l in log.read_text().splitlines() if l.strip()]
|
||||
print(f"total_events={len(rows)}")
|
||||
events = []
|
||||
for r in rows:
|
||||
h = {k.lower(): v for k, v in r.get("headers", {}).items()}
|
||||
ev = h.get("x-recording-event") or (r.get("body_json") or {}).get("event")
|
||||
st = h.get("x-recording-status") or (r.get("body_json") or {}).get("status")
|
||||
cid = h.get("x-recording-call-id") or (r.get("body_json") or {}).get("call_id")
|
||||
events.append(ev)
|
||||
print(f" - method={r['method']} event={ev} status={st} call_id={cid} body_json={'yes' if r.get('body_json') else 'no'}")
|
||||
if r.get("body_json"):
|
||||
print(" json keys:", sorted(r["body_json"].keys()))
|
||||
|
||||
need = {"call_recording_finished"}
|
||||
got = set(e for e in events if e)
|
||||
missing = need - got
|
||||
if missing:
|
||||
print("FAIL missing required events:", sorted(missing))
|
||||
print("got:", events)
|
||||
sys.exit(3)
|
||||
if not any(r.get("body_json") for r in rows):
|
||||
print("FAIL: expected JSON bodies (notify-json)")
|
||||
sys.exit(4)
|
||||
print("E2E_NOTIFY_OK")
|
||||
PY
|
||||
|
||||
|
||||
echo "== sample event log =="
|
||||
cat "$EVTLOG"
|
||||
echo
|
||||
echo "DONE"
|
||||
@ -0,0 +1,156 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
/* Unit tests for recording-daemon lifecycle notify event helpers. */
|
||||
#include "../recording-daemon/notify_events.h"
|
||||
#include "../recording-daemon/notify_events.c"
|
||||
|
||||
/* Provide the global used by notify_event_enabled(). */
|
||||
unsigned int notify_events_mask = NOTIFY_MASK_DEFAULT;
|
||||
|
||||
#define err(fmt...) do { \
|
||||
fprintf(stderr, fmt); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
static void expect_parse_ok(const char *csv, unsigned int expect) {
|
||||
unsigned int mask = 0;
|
||||
char *bad = NULL;
|
||||
if (!notify_events_parse(csv, &mask, &bad))
|
||||
err("parse failed for '%s' (bad token '%s')\n", csv ? csv : "(null)",
|
||||
bad ? bad : "?");
|
||||
if (mask != expect)
|
||||
err("parse '%s': mask 0x%x != expected 0x%x\n", csv ? csv : "(null)", mask, expect);
|
||||
g_free(bad);
|
||||
}
|
||||
|
||||
static void expect_parse_fail(const char *csv, const char *expect_tok) {
|
||||
unsigned int mask = 0;
|
||||
char *bad = NULL;
|
||||
if (notify_events_parse(csv, &mask, &bad))
|
||||
err("parse should fail for '%s'\n", csv);
|
||||
if (expect_tok && (!bad || strcmp(bad, expect_tok) != 0))
|
||||
err("parse '%s': expected bad token '%s', got '%s'\n",
|
||||
csv, expect_tok, bad ? bad : "(null)");
|
||||
g_free(bad);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* Default / empty => finished only */
|
||||
expect_parse_ok(NULL, NOTIFY_MASK_DEFAULT);
|
||||
expect_parse_ok("", NOTIFY_MASK_DEFAULT);
|
||||
expect_parse_ok(" ", NOTIFY_MASK_DEFAULT);
|
||||
expect_parse_ok("finished", NOTIFY_MASK_FINISHED);
|
||||
|
||||
/* Single tokens */
|
||||
expect_parse_ok("opened", NOTIFY_MASK_OPENED);
|
||||
expect_parse_ok("started", NOTIFY_MASK_STARTED);
|
||||
expect_parse_ok("discarded", NOTIFY_MASK_DISCARDED);
|
||||
expect_parse_ok("failed", NOTIFY_MASK_FAILED);
|
||||
|
||||
/* CSV combinations */
|
||||
expect_parse_ok("opened,started,finished",
|
||||
NOTIFY_MASK_OPENED | NOTIFY_MASK_STARTED | NOTIFY_MASK_FINISHED);
|
||||
expect_parse_ok("opened, started, finished, discarded, failed",
|
||||
NOTIFY_MASK_STREAM_ALL);
|
||||
expect_parse_ok("all", NOTIFY_MASK_ALL);
|
||||
|
||||
/* Call-level tokens */
|
||||
expect_parse_ok("call-started,call-finished",
|
||||
NOTIFY_MASK_CALL_STARTED | NOTIFY_MASK_CALL_FINISHED);
|
||||
|
||||
/* Unknown token */
|
||||
expect_parse_fail("opened,bogus,finished", "bogus");
|
||||
expect_parse_fail("nope", "nope");
|
||||
|
||||
/* Name / status mapping */
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_FILE_OPENED), "recording_file_opened"))
|
||||
err("bad name for FILE_OPENED\n");
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_STARTED), "recording_started"))
|
||||
err("bad name for STARTED\n");
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_FINISHED), "recording_finished"))
|
||||
err("bad name for FINISHED\n");
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_DISCARDED), "recording_discarded"))
|
||||
err("bad name for DISCARDED\n");
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_FAILED), "recording_failed"))
|
||||
err("bad name for FAILED\n");
|
||||
if (strcmp(notify_event_status(NOTIFY_EVT_FILE_OPENED), "opened"))
|
||||
err("bad status for FILE_OPENED\n");
|
||||
if (strcmp(notify_event_status(NOTIFY_EVT_FINISHED), "finished"))
|
||||
err("bad status for FINISHED\n");
|
||||
if (strcmp(notify_event_name((enum notify_event) 99), "unknown"))
|
||||
err("bad name for invalid event\n");
|
||||
|
||||
/* Enabled mask checks */
|
||||
notify_events_mask = NOTIFY_MASK_FINISHED;
|
||||
if (!notify_event_enabled(NOTIFY_EVT_FINISHED))
|
||||
err("finished should be enabled\n");
|
||||
if (notify_event_enabled(NOTIFY_EVT_FILE_OPENED))
|
||||
err("opened should be disabled under default mask\n");
|
||||
if (notify_event_enabled(NOTIFY_EVT_STARTED))
|
||||
err("started should be disabled under default mask\n");
|
||||
|
||||
notify_events_mask = NOTIFY_MASK_OPENED | NOTIFY_MASK_STARTED | NOTIFY_MASK_FINISHED;
|
||||
if (!notify_event_enabled(NOTIFY_EVT_FILE_OPENED)
|
||||
|| !notify_event_enabled(NOTIFY_EVT_STARTED)
|
||||
|| !notify_event_enabled(NOTIFY_EVT_FINISHED))
|
||||
err("expected opened/started/finished enabled\n");
|
||||
if (notify_event_enabled(NOTIFY_EVT_DISCARDED))
|
||||
err("discarded should not be enabled\n");
|
||||
|
||||
/* Call-level name/status mapping (Phase 2) */
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_CALL_STARTED), "call_recording_started"))
|
||||
err("bad name for CALL_STARTED\n");
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_CALL_FINISHED), "call_recording_finished"))
|
||||
err("bad name for CALL_FINISHED\n");
|
||||
if (strcmp(notify_event_name(NOTIFY_EVT_CALL_DISCARDED), "call_recording_discarded"))
|
||||
err("bad name for CALL_DISCARDED\n");
|
||||
if (strcmp(notify_event_status(NOTIFY_EVT_CALL_STARTED), "call-started"))
|
||||
err("bad status for CALL_STARTED\n");
|
||||
if (strcmp(notify_event_status(NOTIFY_EVT_CALL_FINISHED), "call-finished"))
|
||||
err("bad status for CALL_FINISHED\n");
|
||||
if (strcmp(notify_event_status(NOTIFY_EVT_CALL_DISCARDED), "call-discarded"))
|
||||
err("bad status for CALL_DISCARDED\n");
|
||||
|
||||
/* Call-level enabled under all */
|
||||
notify_events_mask = NOTIFY_MASK_ALL;
|
||||
if (!notify_event_enabled(NOTIFY_EVT_CALL_STARTED)
|
||||
|| !notify_event_enabled(NOTIFY_EVT_CALL_FINISHED)
|
||||
|| !notify_event_enabled(NOTIFY_EVT_CALL_DISCARDED))
|
||||
err("call events should be enabled under all\n");
|
||||
|
||||
/* call-discarded alone */
|
||||
expect_parse_ok("call-discarded", NOTIFY_MASK_CALL_DISCARDED);
|
||||
|
||||
/* Terminal classification (Phase 3) */
|
||||
if (!notify_event_is_terminal(NOTIFY_EVT_FINISHED)
|
||||
|| !notify_event_is_terminal(NOTIFY_EVT_DISCARDED)
|
||||
|| !notify_event_is_terminal(NOTIFY_EVT_FAILED)
|
||||
|| !notify_event_is_terminal(NOTIFY_EVT_CALL_FINISHED)
|
||||
|| !notify_event_is_terminal(NOTIFY_EVT_CALL_DISCARDED))
|
||||
err("terminal events misclassified\n");
|
||||
if (notify_event_is_terminal(NOTIFY_EVT_FILE_OPENED)
|
||||
|| notify_event_is_terminal(NOTIFY_EVT_STARTED)
|
||||
|| notify_event_is_terminal(NOTIFY_EVT_CALL_STARTED))
|
||||
err("non-terminal events misclassified\n");
|
||||
|
||||
/* Command format parse (Phase 2) */
|
||||
{
|
||||
enum notify_command_format fmt;
|
||||
if (!notify_command_format_parse(NULL, &fmt) || fmt != NOTIFY_CMD_LEGACY)
|
||||
err("null format should be legacy\n");
|
||||
if (!notify_command_format_parse("legacy", &fmt) || fmt != NOTIFY_CMD_LEGACY)
|
||||
err("legacy parse failed\n");
|
||||
if (!notify_command_format_parse("extended", &fmt) || fmt != NOTIFY_CMD_EXTENDED)
|
||||
err("extended parse failed\n");
|
||||
if (!notify_command_format_parse("json-env", &fmt) || fmt != NOTIFY_CMD_JSON_ENV)
|
||||
err("json-env parse failed\n");
|
||||
if (notify_command_format_parse("bogus", &fmt))
|
||||
err("bogus format should fail\n");
|
||||
}
|
||||
|
||||
printf("ok\n");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in new issue