We are adding a new approach/tool used to check out SIP messages received, and unlike the existing way of doing that (using what has come/gone in front of the Proxy), the usage of sipp logs will be introduced. The idea is that we are looking for received SIP messages at both the originator(s) and recipient(s) side, and then using what we've gotten, run checks based on _test.yml.tt2 templates prepared in advance for each scenario, example: * sip_messages00_test.yml.tt2 * sip_messages_responder00_test.yml.tt2 - add missing options to help command - bin/check.sh: use T option to select what to do with checks - get_results.sh: remove deprecated -P -T options Change-Id: I2d0084fa59694ffd009736205a092a35f6864669mr10.0
parent
b2b690a72b
commit
e110dd346b
@ -0,0 +1,260 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# Copyright: 2013-2021 Sipwise Development Team <support@sipwise.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This package is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# On Debian systems, the complete text of the GNU General
|
||||
# Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||
#
|
||||
import io
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
from enum import Flag
|
||||
from pathlib import Path
|
||||
|
||||
from yaml import load
|
||||
|
||||
try:
|
||||
from yaml import CLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import Loader
|
||||
|
||||
|
||||
class Section(Flag):
|
||||
SIP = 32
|
||||
|
||||
|
||||
class Test:
|
||||
|
||||
""" Class to create TAP output """
|
||||
|
||||
def __init__(self):
|
||||
self._step = []
|
||||
self._errflag = Section(0)
|
||||
|
||||
def comment(self, msg):
|
||||
""" Add a comment """
|
||||
self._step.append({"result": None, "msg_ok": msg})
|
||||
|
||||
def ok(self, msg=None):
|
||||
""" Add a ok result """
|
||||
self._step.append({"result": True, "msg_ok": msg})
|
||||
|
||||
def error(self, section, msg_err):
|
||||
""" Add an error result"""
|
||||
self._step.append({"result": False, "msg_err": msg_err})
|
||||
self._errflag |= section
|
||||
|
||||
@classmethod
|
||||
def compare(cls, val0, val1):
|
||||
logging.debug(
|
||||
"val0:[%s]:'%s' val1:[%s]:'%s'"
|
||||
% (type(val0), str(val0), type(val1), str(val1))
|
||||
)
|
||||
if isinstance(val0, str):
|
||||
if re.search(val0, str(val1)) is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
elif isinstance(val0, int):
|
||||
try:
|
||||
result = val0 == int(val1)
|
||||
except Exception:
|
||||
result = False
|
||||
elif isinstance(val0, list) and isinstance(val1, list):
|
||||
size = len(val0)
|
||||
if size != len(val1):
|
||||
return False
|
||||
result = True
|
||||
for k in range(size):
|
||||
try:
|
||||
result = result and cls.compare(val0[k], val1[k])
|
||||
except Exception as e:
|
||||
logging.debug(e)
|
||||
return False
|
||||
else:
|
||||
result = val0 == val1
|
||||
return result
|
||||
|
||||
def test(self, section, value_expected, value, msg_err, msg_ok=None):
|
||||
""" Test two values and add the result"""
|
||||
result = Test.compare(value_expected, value)
|
||||
val = {"result": result, "msg_err": msg_err, "msg_ok": msg_ok}
|
||||
self._step.append(val)
|
||||
if not result:
|
||||
self._errflag |= section
|
||||
|
||||
def isError(self):
|
||||
return self._errflag.value != 0
|
||||
|
||||
def _num_tests(self):
|
||||
"""get the num of tests"""
|
||||
test = 0
|
||||
for s in self._step:
|
||||
if s["result"] is not None:
|
||||
test = test + 1
|
||||
return test
|
||||
|
||||
def __str__(self):
|
||||
"""get the TAP output"""
|
||||
output = "1..%s\n" % self._num_tests()
|
||||
test = 1
|
||||
for s in self._step:
|
||||
if s["result"] is None:
|
||||
output += "# %s\n" % s["msg_ok"]
|
||||
continue
|
||||
elif s["result"]:
|
||||
if s["msg_ok"] is not None:
|
||||
output += "ok %d - %s\n" % (test, s["msg_ok"])
|
||||
else:
|
||||
output += "ok %d\n" % test
|
||||
else:
|
||||
output += "not ok %d - ERROR: %s\n" % (test, s["msg_err"])
|
||||
test = test + 1
|
||||
return output
|
||||
|
||||
|
||||
def check_sip(sec, scen, msg, test):
|
||||
if isinstance(msg, list):
|
||||
if len(msg) != 1:
|
||||
test.error(sec, "sip_in len != 1")
|
||||
return
|
||||
else:
|
||||
msg = msg[0]
|
||||
for rule in scen:
|
||||
if rule.startswith("_:NOT:_"):
|
||||
flag = False
|
||||
rule = rule[7:]
|
||||
msg_ok = "%s not match"
|
||||
msg_ko = "%s match"
|
||||
else:
|
||||
flag = True
|
||||
msg_ok = "%s match"
|
||||
msg_ko = "%s not match"
|
||||
result = re.search(rule, msg)
|
||||
if (result is not None) == flag:
|
||||
test.ok(msg_ok % rule)
|
||||
continue
|
||||
test.comment("result:%s" % result)
|
||||
test.error(sec, msg_ko % rule)
|
||||
|
||||
|
||||
def check_sip_received(sec, scen, msgs, test):
|
||||
num_msgs = len(msgs)
|
||||
num_scen = len(scen)
|
||||
for i in range(num_scen):
|
||||
test.comment("messages %d" % i)
|
||||
if i < num_msgs:
|
||||
check_sip(sec, scen[i], msgs[i], test)
|
||||
else:
|
||||
test.error(sec, "messages[%d] does not exist" % i)
|
||||
if num_scen != num_msgs:
|
||||
msg = "we expected {} out messages but we have {}"
|
||||
test.error(sec, msg.format(num_scen, num_msgs))
|
||||
|
||||
|
||||
def load_yaml(filepath):
|
||||
output = None
|
||||
with io.open(filepath, "r") as file:
|
||||
output = load(file, Loader=Loader)
|
||||
file.close()
|
||||
return output
|
||||
|
||||
|
||||
def load_json(filepath):
|
||||
output = None
|
||||
with io.open(filepath, "r") as file:
|
||||
output = json.load(file)
|
||||
file.close()
|
||||
return output
|
||||
|
||||
|
||||
def convert_sippmsg_to_json(filepath):
|
||||
file_content = open(filepath, "r")
|
||||
c = file_content.read()
|
||||
|
||||
# prepare a list of sipp generated messages (raw text now)
|
||||
content_list = c.split("-----------------------------------------------")
|
||||
content_list_received = []
|
||||
file_content.close()
|
||||
|
||||
# now pick out only received sipp messages
|
||||
for each in content_list:
|
||||
if ("UDP message received" in each) or (
|
||||
"TCP message received" in each
|
||||
):
|
||||
# delete all undesired lines from each memeber's raw text
|
||||
each = each.replace("\n\n\n", "\n").replace("\n\n", "\n")
|
||||
each = each.replace('"', '\\"')
|
||||
each = each.split("\n")[2:]
|
||||
|
||||
i, arrLen = 0, len(each)
|
||||
while i < arrLen:
|
||||
each[i] = each[i] + "\\r\\n"
|
||||
i += 1
|
||||
|
||||
# make a raw text now from list
|
||||
each = "".join(each)
|
||||
content_list_received.append(each)
|
||||
|
||||
# add some eventual formatting to implement a JSON syntax
|
||||
i, arrLen = 0, len(content_list_received)
|
||||
while i < arrLen:
|
||||
content_list_received[i] = '"' + content_list_received[i] + '",'
|
||||
i += 1
|
||||
output = "".join(content_list_received)[:-1]
|
||||
return '{\n "messages": [' + output + "]\n}"
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
# convert sipp msg into json format, returns only received messages
|
||||
output = convert_sippmsg_to_json(args.msg_file)
|
||||
|
||||
if args.debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
test = Test()
|
||||
try:
|
||||
scen = load_yaml(args.test_file)
|
||||
except Exception:
|
||||
scen = "message:\n - ['']"
|
||||
test.error("Error loading yaml file:%s" % args[1])
|
||||
|
||||
try:
|
||||
check = json.loads(output)
|
||||
except Exception:
|
||||
check = {"messages": ""}
|
||||
test.error("Error loading json file:%s" % args[1])
|
||||
|
||||
test.comment("check sip messages")
|
||||
check_sip_received(Section.SIP, scen["messages"], check["messages"], test)
|
||||
|
||||
print(test)
|
||||
if test.isError():
|
||||
sys.exit(test._errflag.value)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="generate TAP result")
|
||||
grp = parser.add_mutually_exclusive_group()
|
||||
parser.add_argument("test_file", help="YAML file with checks")
|
||||
parser.add_argument("msg_file", help="sipp file")
|
||||
parser.add_argument("-d", "--debug", action="store_true")
|
||||
args = parser.parse_args()
|
||||
main(args)
|
||||
@ -0,0 +1,165 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright: 2013-2021 Sipwise Development Team <support@sipwise.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This package is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# On Debian systems, the complete text of the GNU General
|
||||
# Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||
#
|
||||
ERR_FLAG=0
|
||||
|
||||
# $1 destination tap file
|
||||
# $2 file path
|
||||
generate_error_tap() {
|
||||
local tap_file="$1"
|
||||
cat <<EOF > "${tap_file}"
|
||||
1..1
|
||||
not ok 1 - ERROR: File $2 does not exists
|
||||
EOF
|
||||
echo "$(date) - $(basename "$2") NOT ok"
|
||||
}
|
||||
|
||||
test_sip_filepath() {
|
||||
local msg_name
|
||||
msg_name=${1/_test.yml/.msg}
|
||||
msg_name=${msg_name/sip_messages/sipp_scenario}
|
||||
|
||||
msg="${LOG_DIR}/$(basename "${msg_name}")"
|
||||
if [ -f "${msg}" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "$(date) - WARNING: no sipp log file found! filename=<${msg}>. Skipping."
|
||||
return 1
|
||||
}
|
||||
|
||||
# $1 sip message test yml
|
||||
# $2 sipp msg parsed to .msg log file
|
||||
# $3 destination tap filename
|
||||
check_sip_test() {
|
||||
local test_file="${1}"
|
||||
local msg_file="${2}"
|
||||
local dest_file="${3}"
|
||||
local sipp_msg
|
||||
|
||||
if ! [ -f "$1" ]; then
|
||||
generate_error_tap "$3" "$1"
|
||||
ERR_FLAG=1
|
||||
return 1
|
||||
fi
|
||||
if ! [ -f "$2" ]; then
|
||||
generate_error_tap "$3" "$2"
|
||||
ERR_FLAG=1
|
||||
return 1
|
||||
fi
|
||||
|
||||
sipp_msg=$(basename "${msg_file}")
|
||||
if "${BIN_DIR}/check_sip.py" "${test_file}" "${msg_file}" > "${dest_file}" ; then
|
||||
echo " ok"
|
||||
test_ok+=("${sipp_msg}")
|
||||
return 0
|
||||
else
|
||||
echo " NOT ok[SIP]"
|
||||
ERR_FLAG=1
|
||||
# we have to add here show_sip.pl funcitoning to produce differences in results
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: check_sipp.sh [-h] [-p PROFILE ] -s [GROUP] check_name"
|
||||
echo "Options:"
|
||||
echo -e "\\t-p: CE|PRO default is CE"
|
||||
echo -e "\\t-s: scenario group. Default: scenarios"
|
||||
echo "Arguments:"
|
||||
echo -e "\\tcheck_name. Scenario name to check. This is the name of the directory on GROUP dir."
|
||||
}
|
||||
|
||||
while getopts 'hp:s:' opt; do
|
||||
case $opt in
|
||||
h) usage; exit 0;;
|
||||
p) PROFILE=${OPTARG};;
|
||||
s) GROUP=${OPTARG};;
|
||||
*) usage; exit 1;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [[ $# != 1 ]]; then
|
||||
echo "Wrong number of arguments"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GROUP="${GROUP:-scenarios}"
|
||||
NAME_CHECK="$1"
|
||||
BASE_DIR="${BASE_DIR:-/usr/share/kamailio-config-tests}"
|
||||
BIN_DIR="${BASE_DIR}/bin"
|
||||
LOG_DIR="${BASE_DIR}/log/${GROUP}/${NAME_CHECK}"
|
||||
RESULT_DIR="${BASE_DIR}/result/${GROUP}/${NAME_CHECK}"
|
||||
SCEN_DIR="${BASE_DIR}/${GROUP}"
|
||||
SCEN_CHECK_DIR="${SCEN_DIR}/${NAME_CHECK}"
|
||||
PROFILE="${PROFILE:-CE}"
|
||||
|
||||
if [ "${PROFILE}" != "CE" ] && [ "${PROFILE}" != "PRO" ]; then
|
||||
echo "PROFILE ${PROFILE} unknown"
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! [ -d "${SCEN_CHECK_DIR}" ]; then
|
||||
echo "no ${SCEN_CHECK_DIR} found"
|
||||
usage
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if ! [ -f "${SCEN_CHECK_DIR}/scenario.yml" ]; then
|
||||
echo "${SCEN_CHECK_DIR}/scenario.yml not found"
|
||||
exit 14
|
||||
fi
|
||||
|
||||
if [ -f "${SCEN_CHECK_DIR}/pro.yml" ] && [ "${PROFILE}" == "CE" ]; then
|
||||
echo "${SCEN_CHECK_DIR}/pro.yml found but PROFILE ${PROFILE}"
|
||||
echo "Skipping the tests because not in scope"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# let's check the results
|
||||
echo "$(date) - ================================================================================="
|
||||
echo "$(date) - Check [${GROUP}/${PROFILE}]: ${NAME_CHECK}"
|
||||
|
||||
mkdir -p "${RESULT_DIR}"
|
||||
|
||||
echo "$(date) - Cleaning tests files"
|
||||
find "${SCEN_CHECK_DIR}" -type f -name '*test.yml' -exec rm {} \;
|
||||
echo "$(date) - Generating tests files"
|
||||
"${BIN_DIR}/generate_tests.sh" \
|
||||
-f 'sip_messages*test.yml.tt2' \
|
||||
-d "${SCEN_CHECK_DIR}" "${LOG_DIR}/scenario_ids.yml" "${PROFILE}"
|
||||
|
||||
test_ok=()
|
||||
|
||||
for t in "${SCEN_CHECK_DIR}"/sip_messages*[0-9][0-9]_test.yml; do
|
||||
if test_sip_filepath "${t}"; then
|
||||
echo -n "$(date) - SIP: Check test $(basename "${t}") on ${msg}"
|
||||
dest=$(basename "${msg}")
|
||||
dest=${RESULT_DIR}/${dest/.msg/.tap}
|
||||
check_sip_test "${t}" "$msg" "${dest}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$(date) - ================================================================================="
|
||||
|
||||
exit ${ERR_FLAG}
|
||||
#EOF
|
||||
@ -0,0 +1,39 @@
|
||||
messages:
|
||||
- [
|
||||
'^SIP/2.0 100 Trying',
|
||||
'CSeq: \d+ INVITE',
|
||||
'From: <sip:[% scenarios.0.username %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
- [
|
||||
'^SIP/2.0 407 Proxy Authentication Required',
|
||||
'CSeq: \d+ INVITE',
|
||||
'From: <sip:[% scenarios.0.username %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
- [
|
||||
'^SIP/2.0 100 Trying',
|
||||
'CSeq: \d+ INVITE',
|
||||
'From: <sip:[% scenarios.0.username %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
- [
|
||||
'^SIP/2.0 180 Ringing',
|
||||
'CSeq: \d+ INVITE',
|
||||
'From: <sip:[% scenarios.0.username %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
- [
|
||||
'^SIP/2.0 200 OK',
|
||||
'CSeq: \d+ INVITE',
|
||||
'From: <sip:[% scenarios.0.username %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
- [
|
||||
'^SIP/2.0 200 OK',
|
||||
'CSeq: \d+ BYE',
|
||||
'Content-Length: 0',
|
||||
'From: <sip:[% scenarios.0.username %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
messages:
|
||||
- [
|
||||
'^INVITE sip:[% scenarios.0.responders.0.username %]@',
|
||||
'CSeq: \d+ INVITE',
|
||||
'From: <sip:[% invite_scenarios_test.testuser1002.phone_number %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@',
|
||||
'Content-Type: application/sdp'
|
||||
]
|
||||
- [
|
||||
'^ACK sip:',
|
||||
'CSeq: \d+ ACK',
|
||||
'From: <sip:[% invite_scenarios_test.testuser1002.phone_number %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@'
|
||||
]
|
||||
- [
|
||||
'^BYE sip:',
|
||||
'From: <sip:[% invite_scenarios_test.testuser1002.phone_number %]@',
|
||||
'To: <sip:[% scenarios.0.responders.0.username %]@',
|
||||
'CSeq: \d+ BYE',
|
||||
'Content-Length: 0'
|
||||
]
|
||||
|
||||
Loading…
Reference in new issue