TT#71952 Parallelize templated output generation

Switch away from the old daemon/wrapper architecture, which complicates
things and requires more scaffolding code.

We turn the daemon/wrapper and build_config into a single perl process
that will load all YAML files, and then process each input/output pair
on a parallel child, to try to speed up the processing as much as
possible.

For whole rebuilds, it might speed up the generation by at least a
factor of x2, x4 or more, depending on the number of active processors.

Change-Id: I51aa2f90336e34a20983d8733f45b64d9b6fea0b
changes/02/35902/18
Guillem Jover 5 years ago
parent bfd9be075e
commit a9166843c2

@ -1,10 +1,14 @@
# for syntax checks
BASH_SCRIPTS = scripts/* functions/* etc/ngcp-config/ngcpcfg.cfg helper/build_config sbin/ngcpcfg helper/tt2-wrapper
BASH_SCRIPTS = \
scripts/* \
functions/* \
etc/ngcp-config/ngcpcfg.cfg \
sbin/ngcpcfg
PERL_SCRIPTS = \
lib/NGCP/Template.pm \
helper/sort-yml \
helper/sync-db \
helper/tt2-daemon \
helper/tt2-process \
helper/validate-yml helper/fileformat_version \
sbin/ngcp-network \
sbin/ngcp-network-validator \

2
debian/control vendored

@ -25,7 +25,6 @@ Build-Depends:
libtemplate-perl,
libterm-readpassword-perl,
libyaml-libyaml-perl,
netcat-openbsd,
pkwalify,
python3-pytest,
@ -66,7 +65,6 @@ Depends:
libsys-hostname-long-perl,
libterm-readpassword-perl,
libyaml-libyaml-perl,
netcat-openbsd,
ngcp-system-tools,
pkwalify,
psmisc,

@ -2,14 +2,12 @@ etc/ngcp-config/ngcpcfg.cfg etc/ngcp-config/
functions/init usr/share/ngcp-ngcpcfg/functions/
functions/logs usr/share/ngcp-ngcpcfg/functions/
functions/main usr/share/ngcp-ngcpcfg/functions/
helper/build_config usr/share/ngcp-ngcpcfg/helper/
helper/check-for-mysql usr/share/ngcp-ngcpcfg/helper/
helper/fileformat_version usr/share/ngcp-ngcpcfg/helper/
helper/restore-permissions usr/share/ngcp-ngcpcfg/helper/
helper/sort-yml usr/share/ngcp-ngcpcfg/helper/
helper/sync-db usr/share/ngcp-ngcpcfg/helper/
helper/tt2-daemon usr/share/ngcp-ngcpcfg/helper/
helper/tt2-wrapper usr/share/ngcp-ngcpcfg/helper/
helper/tt2-process usr/share/ngcp-ngcpcfg/helper/
helper/validate-yml usr/share/ngcp-ngcpcfg/helper/
hooks/ usr/share/ngcp-ngcpcfg/
lib/get_* usr/lib/ngcp-ngcpcfg/

@ -16,16 +16,15 @@ scripts and build system.
Templating
----------
Of very significant note here are the 'tt2-daemon' and 'tt2-wrapper' tandem,
which perform most of the work when processing the templates. The daemon is
a long-lived process that gets interacted with via a socket, and receives
"commands" from the 'tt2-wrapper'. The YAML configuration files are loaded
so that they only need to be validated and processed once. And then each
template file to be processed is fed to the daemon and the result returned
via the wrapper. All the configuration loaded from the YAML files is
flattened into a dot-separated namespaced tree, which can be accessed
directly by any template file even if no other code has references to those
configurations.
Of very significant note is the 'tt2-process' helper, which performs most
of the work when processing the templates. This process is started only
once during the whole ngcpcfg run, and loads and validates all the YAML
files, and then processes each input/output pairs on an independent process
to parallelize the execution.
All the configuration loaded from the YAML files is flattened into a
dot-separated namespaced tree, which can be accessed directly by any
template file even if no other code has references to those configurations.
/////////////////////////////
// vim: ft=asciidoc tw=80 ai

@ -248,6 +248,13 @@ NGCP systems) as well as stderr.
Display usage information and exit.
**--jobs[=<n>]** <action> [<further_options>]::
The _build_ and _apply_ actions process templates in parallel by default.
This option can be used to control the number of jobs used to process
templates, '0' for an unlimited amount, and '1' for serial execution to
for example get ordered output.
**--no-action-failure** <action> [<further_options>]::
The _check_ and _apply_ actions check for any possibly outstanding pull

@ -1,156 +0,0 @@
#!/bin/bash
# Filename: /usr/share/ngcp-ngcpcfg/helper/build_config
# Purpose: builds output configuration file based on tt2 template file
# using /usr/share/ngcp-ngcpcfg/helper/tt2-wrapper
################################################################################
set -e
set -u
if [ "${#:-}" -ne 3 ] ; then
echo "Usage: /usr/share/ngcp-ngcpcfg/helper/build_config <input_file> <output_file> <tmp_file>" >&2
exit 1
fi
# support for testsuite, assume defaults if unset
CONFIG_POOL="${CONFIG_POOL:-/etc}"
FUNCTIONS="${FUNCTIONS:-/usr/share/ngcp-ngcpcfg/functions/}"
HELPER="${HELPER:-/usr/share/ngcp-ngcpcfg/helper/}"
if ! [ -r "${FUNCTIONS}"/logs ] ; then
printf "Error: %s/logs could not be read. Exiting.\n" "${FUNCTIONS}" >&2
exit 1
fi
. "${FUNCTIONS}"/logs
# main script
input_file="$1" # like /etc/ngcp-config/templates/etc/mysql/my.cnf.tt2
output_file="$2" # like /etc/mysql/my.cnf
tmp_output_file="$3" # like /tmp/ngcpcfg.PID12345.KLJhiand/tmp_output_file
if [ -z "${input_file}" ] ; then
log_error "Missing <input_file> parameter. Exiting." >&2
exit 1
fi
if [ -z "${output_file}" ] ; then
log_error "Missing <output_file> parameter. Exiting." >&2
exit 1
fi
if [ -z "${tmp_output_file}" ] ; then
log_error "Missing <tmp_output_file> parameter. Exiting." >&2
exit 1
fi
# export variable for usage within {pre,post}build scripts,
# OUTPUT_DIRECTORY is for customization during testing
if [ -n "${OUTPUT_DIRECTORY:-}" ] ; then
log_debug "Using output directory ${OUTPUT_DIRECTORY}"
export output_file="${OUTPUT_DIRECTORY}/${output_file}"
else
export output_file
fi
output_file_dirname="$(dirname "${output_file}")" # like /etc/mysql
# ensure we don't try to generate a file where a directory with same name exists already
if [ -d "${output_file}" ] ; then
log_error "Generating file ${output_file} not possible, it's an existing directory." >&2
exit 1
fi
# pre-execution script in template store:
if [ -r "${NGCPCTL_MAIN}/templates/${output_file}.prebuild" ] ; then
log_info "Executing prebuild for ${output_file}"
bash "${NGCPCTL_MAIN}/templates/${output_file}.prebuild"
elif [ -r "${NGCPCTL_MAIN}/templates/${output_file_dirname}/ngcpcfg.prebuild" ] ; then
log_info "Executing prebuild for ${output_file}"
bash "${NGCPCTL_MAIN}/templates/${output_file_dirname}/ngcpcfg.prebuild"
fi
# if output directory does not exist yet, create it
if ! [ -d "${output_file_dirname}" ] ; then
umask 0022 # directory permissions should be '755'
mkdir -p "${output_file_dirname}"
fi
# assume safe defaults
umask 0077
# read host specific configuration file only if it exists
[ -r "${HOST_CONFIG:-}" ] && host_conf="$HOST_CONFIG"
# read local config only if it exists
[ -r "${LOCAL_CONFIG:-}" ] && local_conf="$LOCAL_CONFIG"
TT_WRAPPER="${HELPER}/tt2-wrapper"
# XXX: Docker breaks sane Unix expectations when moving a file into /etc/hosts,
# as it creates a bind mount on that pathname. We need to use an implementation
# that will fallback to use copy semantics in that case, but will default to
# use rename semantics to avoid races on ETXTBSY on executable files.
# <https://github.com/moby/moby/issues/22281>
move()
{
local src="$1"
local dst="$2"
if [ -e /.dockerinit ] || [ -e /.dockerenv ] ; then
perl -MFile::Copy=mv \
-E "mv('${src}', '${dst}') or die 'error: cannot move $src to $dst: \$!\n'"
else
mv "${src}" "${dst}"
fi
}
log_debug "Output file ${output_file} based on ${input_file}"
log_debug "Executing: $TT_WRAPPER ${input_file} > ${tmp_output_file}"
log_debug " and: move ${tmp_output_file} ${output_file}"
# 1) We need to use «readlink -f» so that we do not destroy any symlink pointing
# to the real file, which we were previously preserving while using «cat».
# 2) Care about ">" below, do not trust tmp file you receive.
if "$TT_WRAPPER" "${input_file}" > "${tmp_output_file}" 2>/dev/null &&
! grep -q -E '^file error' "${tmp_output_file}" 2>/dev/null &&
move "${tmp_output_file}" "$(readlink -f "${output_file}")" ; then
log_info "Generating ${output_file}: OK"
RC=0
else
log_error "Generating ${output_file} based on ${input_file}: FAILED"
RC=1
if [[ -r "${tmp_output_file}" ]] && grep -q -E '^file error' "${tmp_output_file}" ; then
log_error "from generated file:"
log_error " $(grep -E '^file error' "${tmp_output_file}")"
fi
log_info "NOTE: Check those files for valid syntax and encoding:"
for f in "${input_file}" ${host_conf:-} ${local_conf:-} "$NGCPCTL_CONFIG" "${NETWORK_CONFIG:-}" "${EXTRA_CONFIG_FILES[@]}" "$CONSTANTS_CONFIG" ; do
[ -r "$f" ] && log_info "$f"
done
log_info "Running /usr/share/ngcp-ngcpcfg/helper/tt2-wrapper <file>"
log_info "or inspecting temporary ${tmp_output_file}"
log_info "should provide more details."
fi
if [ -L "$output_file" ] ; then
log_warn "File $output_file is a symlink - NOT adjusting permissions"
else
# set permissions for generated config based on the ones of the template
chmod --reference="${input_file}" "${output_file}"
# finally drop all write permissions
chmod a-w "${output_file}"
fi
# post-execution script in template store:
if [ -r "${NGCPCTL_MAIN}/templates/${output_file}.postbuild" ] ; then
log_info "Executing postbuild for ${output_file}"
bash "${NGCPCTL_MAIN}/templates/${output_file}.postbuild"
elif [ -r "${NGCPCTL_MAIN}/templates/${output_file_dirname}/ngcpcfg.postbuild" ] ; then
log_info "Executing postbuild for ${output_file}"
bash "${NGCPCTL_MAIN}/templates/${output_file_dirname}/ngcpcfg.postbuild"
fi
exit $RC
## END OF FILE #################################################################

@ -1,91 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
use English;
use Carp;
use POSIX qw(setsid);
use IO::Socket::UNIX;
use Hash::Merge qw(merge);
use YAML::XS qw(LoadFile);
use Clone 'clone';
use Getopt::Long;
use NGCP::Template;
my $quiet = 0;
GetOptions("q|quiet" => \$quiet);
my $server_socket = get_server_socket();
daemonize();
handle_connections($server_socket, $quiet);
exit;
sub daemonize {
my $NGCP_BASE_TT2 = $ENV{'NGCP_BASE_TT2'} //= '/';
chdir $NGCP_BASE_TT2 or croak "Can't chdir to $NGCP_BASE_TT2: $ERRNO";
open(STDIN, '<', '/dev/null') or croak "Can't read /dev/null: $ERRNO";
open(STDOUT, '>', '/dev/null') or croak "Can't write to /dev/null: $ERRNO";
defined(my $pid = fork) or croak "Can't fork: $ERRNO";
exit if $pid;
setsid() or croak "Can't start a new session: $ERRNO";
open(STDERR, '>&', \*STDOUT) or croak "Can't dup stdout: $ERRNO";
return;
}
sub get_server_socket {
my $NGCP_SOCKETFILE = $ENV{'NGCP_SOCKETFILE'} //= '/run/ngcpcfg.socket';
my $server = IO::Socket::UNIX->new(
'Type' => SOCK_STREAM(),
'Local' => $NGCP_SOCKETFILE,
'Listen' => SOMAXCONN,
);
die "Error: can't setup tt2-daemon!\n$EVAL_ERROR\n$ERRNO\n" unless $server;
binmode $server => ":encoding(utf8)";
return $server;
}
sub handle_connections {
my $port = shift;
my $quiet = shift;
my $config = {};
my %loaded_ymls = ();
while (my $client = $port->accept()) {
my $input = <$client>;
chomp $input;
my @argv = split(' ', $input) or
print { $client } "Usage: echo '<template> [<config.yml> [<another.yml>]]' | netcat localhost 42042 \n";
my $template = shift @argv;
foreach my $file (@argv) {
next if exists $loaded_ymls{$file};
$loaded_ymls{$file} = undef;
print { $client } "Loading $file in memory:" unless $quiet;
my $hm = Hash::Merge->new('RIGHT_PRECEDENT');
$config = $hm->merge($config, LoadFile($file));
print { $client } " OK \n" unless $quiet;
}
my $tt = NGCP::Template->new();
open my $fh, '<', $template or
print { $client } "Unable to open file '$template' for reading: $ERRNO\n";
$tt->process($fh, clone($config), $client) or
print { $client } $tt->error;
close $fh;
close $client;
}
return;
}

@ -0,0 +1,251 @@
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long qw(:config posix_default bundling_values no_ignorecase);
use Cwd qw(realpath);
use File::Basename;
use File::Path qw(make_path);
use File::Copy qw(mv);
use Time::Piece;
use POSIX qw(:sys_wait_h);
use Hash::Merge qw(merge);
use YAML::XS qw(LoadFile);
use NGCP::Template;
my $HNAME = $ENV{HNAME} // '';
my $TIME_FORMAT = $ENV{TIME_FORMAT} // '%F %T';
$TIME_FORMAT =~ s/^\+//;
my %options = (
help => sub { usage(); exit 0; },
jobs => qx(nproc) // 1,
);
chomp $options{jobs};
GetOptions(\%options,
'help|?',
'quiet|q',
'jobs|j:i',
'config|c=s@',
);
setup();
exit process(%options);
sub usage {
print <<HELP
Usage: $0 [<option>...] (<input> <output>)...
Options:
-c, --config <files> List of comma-separated config YAML files.
Option can appear multiple times.
-j, --jobs [<n>] Use up to <n> processing jobs (defaults to nproc).
Missing argument means no limit of jobs.
-q, --quiet Do not print progress information.
-h, --help This help message.
HELP
}
sub output_prefix {
my $t = Time::Piece->new;
my $timestamp = $t->strftime($TIME_FORMAT);
return "$timestamp $HNAME";
}
sub error {
my $prefix = output_prefix();
die "$prefix: Error: @_\n";
}
sub warning {
my $prefix = output_prefix();
warn "$prefix: Warning: @_\n";
}
sub info {
return if $options{quiet};
my $prefix = output_prefix();
print "$prefix: @_\n";
}
sub setup {
my $NGCP_BASE_TT2 = $ENV{'NGCP_BASE_TT2'} //= '/';
chdir $NGCP_BASE_TT2
or error("Cannot chdir to $NGCP_BASE_TT2: $!");
}
sub process_template {
my ($tt, $config, $input, $output) = @_;
my $newfile = "$output.ngcpcfg-new";
open my $outfh, '>', $newfile
or error("Cannot open template new file $newfile: $!");
open my $infh, '<', $input
or error("Cannot open file '$input' for reading: $!");
$tt->process($infh, $config, $outfh)
or error("Cannot process template '$input':\n " . $tt->error());
close $infh;
close $outfh;
# XXX: Docker breaks sane Unix expectations when moving a file into
# /etc/hosts, as it creates a bind mount on that pathname. We need to
# use an implementation that will fallback to use copy semantics in
# that case, but will default to use rename semantics to avoid races
# on ETXTBSY on executable files.
# <https://github.com/moby/moby/issues/22281>
#
# In addition we need to dereference any target symlink, so that we do
# not destroy any symlink pointing to the real file.
my $target = realpath($output);
mv($newfile, $target)
or error("Cannot rename $newfile to $target: $!");
}
sub process_input {
my ($tt, $config, $input, $output) = @_;
# Export variable for usage within {pre,post}build scripts,
# OUTPUT_DIRECTORY is for customization during testing.
## no critic (Variables::RequireLocalizedPunctuationVars)
if (length $ENV{OUTPUT_DIRECTORY}) {
$output = "$ENV{OUTPUT_DIRECTORY}/$output";
}
$ENV{output_file} = $output;
# Ensure we do not try to generate a file where a directory with same
# name exists already.
if (-d $output) {
error("Generating file $output not possible, it's an existing directory.");
}
my $input_dirname = dirname($input);
my $output_basename = basename($output);
my $output_dirname = dirname($output);
# Execute prebuild script.
for my $prebuild ((
"$input_dirname/$output_basename.prebuild",
"$input_dirname/ngcpcfg.prebuild")) {
next unless -e $prebuild;
info("Executing prebuild for $output");
system("bash $prebuild") == 0
or error("Execution of prebuild script '$prebuild' failed: $?");
last;
}
# If output directory does not exist yet, create it
if (not -d $output_dirname) {
## no critic (ValuesAndExpressions::ProhibitLeadingZeros)
make_path($output_dirname, { mode => 0755 });
}
# Assume safe defaults.
umask 0077;
eval {
process_template($tt, $config, $input, $output);
};
if ($@) {
warn $@;
error("Generating $output based on $input: FAILED");
} else {
info("Generating $output: OK");
}
if (-l $output) {
warning("File $output is a symlink - NOT adjusting permissions");
} else {
# Set permissions for generated config based on the ones of the
# template, plus dropping all write permissions.
## no critic (ValuesAndExpressions::ProhibitLeadingZeros)
my $mode = (stat $input)[2] & ~0222;
chmod $mode, $output;
}
# Execute postbuild script.
for my $postbuild ((
"$input_dirname/$output_basename.postbuild",
"$input_dirname/ngcpcfg.postbuild")) {
next unless -e $postbuild;
info("Executing postbuid for $output");
system("bash $postbuild") == 0
or error("Execution of postbuild script '$postbuild' failed: $?");
last;
}
}
sub process {
my %options = @_;
my $config = {};
my %loaded_ymls = ();
my $visible_jobs = $options{jobs} || 'unlimited';
info("Building configurations with $visible_jobs concurrent jobs");
foreach my $file (@{$options{config}}) {
next if exists $loaded_ymls{$file};
$loaded_ymls{$file} = undef;
my $prefix = output_prefix();
print "$prefix: Loading $file in memory:" unless $options{quiet};
my $hm = Hash::Merge->new('RIGHT_PRECEDENT');
$config = $hm->merge($config, LoadFile($file));
print " OK \n" unless $options{quiet};
}
my $nprocs = 0;
my $rc = 0;
my $tt = NGCP::Template->new();
while (@ARGV) {
my $input = shift @ARGV;
my $output = shift @ARGV;
error('Missing input file') unless defined $input;
error("Missing output file for $input") unless defined $output;
my $pid = fork;
if (not defined $pid) {
error("Cannot fork child process to process $input: $!");
}
if ($pid != 0) {
# We are the parent.
$nprocs++;
# If we have queued enough work, wait for some to finish.
if ($options{jobs} > 0 && $nprocs >= $options{jobs}) {
my $kid = waitpid(-1, 0);
$nprocs-- if $kid > 0;
$rc = 1 if $kid > 0 && $? != 0;
}
# Queue more work if available.
next;
}
process_input($tt, $config, $input, $output);
exit 0;
}
# Reap any remaining zombies.
while (1) {
my $pid = waitpid(-1, 0);
last if $pid < 0;
$nprocs--;
$rc = 1 if $? != 0;
}
if ($nprocs != 0) {
warning("queued or reaped more jobs than expected, remaining $nprocs");
}
return $rc;
}

@ -1,12 +0,0 @@
#!/bin/bash
NGCP_SOCKETFILE="${NGCP_SOCKETFILE:-/run/ngcpcfg.socket}"
if [ ! -e "$NGCP_SOCKETFILE" ] ; then
echo "ERROR: Wrong ngcpcfg tt2-daemon socket file: $NGCP_SOCKETFILE" >&2
exit 1
fi
echo "$@" | netcat -U "$NGCP_SOCKETFILE"
exit $?

@ -168,6 +168,8 @@ case ${1:-} in
main_action "$@"
;;
--debug) export DEBUG=1 ; shift ; "$0" "$@" ;;
--jobs) export NGCP_JOBS=0; shift ; "$0" "$@" ;;
--jobs=*) export NGCP_JOBS="${1##--jobs=}"; shift ; "$0" "$@" ;;
--no-db-sync) export NO_DB_SYNC=1 ; shift ; "$0" "$@" ;;
--no-validate) export NO_VALIDATE=1; shift ; "$0" "$@" ;;
--no-action-failure) export NO_ACTION_FAILURE=1; shift ; "$0" "$@" ;;

@ -35,38 +35,21 @@ fi
"${SCRIPTS}"/patch "$@"
# Kill all previous started tt2-daemon Perl processes if they were not stopped properly
killall tt2-daemon 2>/dev/null || true
declare -a ARGS
if [ -n "${NGCP_SOCKETFILE:-}" ] ; then
log_debug "Using $NGCP_SOCKETFILE as tt2-daemon socket file as set via 'NGCP_SOCKETFILE'."
else
NGCP_SOCKETFILE='/run/ngcpcfg.socket'
if [ -n "${NGCP_JOBS:-}" ] ; then
ARGS+=("--jobs=${NGCP_JOBS}")
fi
rm -f "${NGCP_SOCKETFILE}"
# Start new tt2-daemon Perl process
"${HELPER}"/tt2-daemon
# Load all the configs in proper order and check their avialability and valid YAML syntax
for f in ${NGCPCTL_CONFIG:-} ${HOST_CONFIG:-} ${LOCAL_CONFIG:-} ${NETWORK_CONFIG:-} "${EXTRA_CONFIG_FILES[@]}" ${CONSTANTS_CONFIG:-} ; do
if [ -r "$f" ] ; then
if ! "${HELPER}/tt2-wrapper" "/dev/null" "${f}" ; then
log_error "tt2-daemon cannot load ${f}:"
"${HELPER}/tt2-wrapper" "/dev/null" "${f}"
exit 1
fi
ARGS+=("-c" "$f")
fi
done
build_config_files() {
# prepare tmp file to be reused hundred times inside "${HELPER}/build_config"
tmp_output_dir="$(mktemp -d --tmpdir ngcpcfg.PID"${NGCPCFG_PID}".XXXXXXXXXX)"
tmp_output_file="${tmp_output_dir}/tmp_output_file"
declare -a iofiles
for input_file in $(generate_template_list "$@") ; do
log_debug "calculate output file" # {{{
x=${input_file##${NGCPCTL_MAIN}/templates/} # drop leading /etc/ngcp-config
y=${x%%.tt2} # drop trailing suffix '.tt2'
@ -76,14 +59,13 @@ build_config_files() {
output_file=${y%%.customtt} # drop trailing suffix '.customtt'
# }}}
log_debug "${HELPER}/build_config ${input_file} ${output_file}"
"${HELPER}/build_config" "${input_file}" "${output_file}" "${tmp_output_file}"
iofiles+=("$input_file" "$output_file")
done
record_commit_id
log_debug "${HELPER}/tt2-process ${ARGS[*]} ${iofiles[*]}"
"${HELPER}/tt2-process" "${ARGS[@]}" "${iofiles[@]}"
rm -f "${tmp_output_file}"
rmdir "${tmp_output_dir}"
record_commit_id
}
# main script
@ -159,10 +141,6 @@ else
fi
fi
# Kill all previous started tt2-daemon Perl processes
killall tt2-daemon 2>/dev/null || true
rm -f "${NGCP_SOCKETFILE}"
# Apply configured file ownership and permissions
for f in ${NGCPCTL_CONFIG:-} ${HOST_CONFIG:-} ${LOCAL_CONFIG:-} "${EXTRA_CONFIG_FILES[@]}" ; do
if [ ! -f "${f}" ]; then

@ -13,7 +13,7 @@ get_value() {
echo "[% $1 %]" > "${input_file}"
output_file="$(mktemp)"
if "$TT_WRAPPER" "${input_file}" > "${output_file}" 2>/dev/null ; then
if "${HELPER}/tt2-process" -q "${ARGS[@]}" "${input_file}" "${output_file}"; then
cat "${output_file}"
else
RC=1
@ -38,29 +38,11 @@ fi
. "${FUNCTIONS}"/main
TT_WRAPPER="${HELPER}/tt2-wrapper"
# Kill all previous started tt2-daemon Perl processes if they were not stopped properly
killall tt2-daemon 2>/dev/null || true
if [ -n "${NGCP_SOCKETFILE:-}" ] ; then
log_debug "Using $NGCP_SOCKETFILE as tt2-daemon socket file as set via 'NGCP_SOCKETFILE'."
else
NGCP_SOCKETFILE='/run/ngcpcfg.socket'
fi
rm -f "${NGCP_SOCKETFILE}"
# Start new tt2-daemon Perl process
"${HELPER}"/tt2-daemon --quiet
# Load all the configs in proper order and check their avialability and valid YAML syntax
# Get the list of configs in proper order to load.
declare -a ARGS
for f in ${NGCPCTL_CONFIG:-} ${HOST_CONFIG:-} ${LOCAL_CONFIG:-} ${NETWORK_CONFIG:-} "${EXTRA_CONFIG_FILES[@]}" ${CONSTANTS_CONFIG:-} ; do
if [ -r "$f" ] ; then
if ! "${HELPER}/tt2-wrapper" "/dev/null" "${f}" ; then
log_error "tt2-daemon cannot load ${f}:"
"${HELPER}/tt2-wrapper" "/dev/null" "${f}"
exit 1
fi
ARGS+=("-c" "${f}")
fi
done
@ -75,10 +57,6 @@ else
echo "$res"
fi
# Kill all previous started tt2-daemon Perl processes
killall tt2-daemon 2>/dev/null || true
rm -f "${NGCP_SOCKETFILE}"
exit "$RC"
## END OF FILE #################################################################

@ -5,12 +5,12 @@ FROM docker.mgm.sipwise.com/sipwise-buster:latest
# is updated with the current date. It will force refresh of all
# of the base images and things like `apt-get update` won't be using
# old cached versions when the Dockerfile is built.
ENV REFRESHED_AT 2019-06-27
ENV REFRESHED_AT 2020-01-13
RUN apt-get update
# sourcecode test dependencies
RUN apt-get install --assume-yes netcat-openbsd libhash-merge-perl libtemplate-perl \
RUN apt-get install --assume-yes libhash-merge-perl libtemplate-perl \
libyaml-libyaml-perl libyaml-tiny-perl libyaml-perl \
libdata-validate-ip-perl libio-interface-perl libregexp-ipv6-perl
# misc stuff for execution/debugging/devel

@ -16,9 +16,16 @@ The following command line provides an example invocation:
[source,bash]
cd t # we're assuming in the following command line that you're inside this testing folder
HELPER=../helper HOOKS=../hooks CONFIG_POOL=/etc/ NGCPCFG=./fixtures/ngcpcfg.cfg SCRIPTS=../scripts/ FUNCTIONS=../functions/ \
NGCP_BASE_TT2=/tmp/pytest-of-root/pytest-XX/test_YYY/ NGCP_SOCKETFILE=/tmp/ngcpcfg.socket OUTPUT_DIRECTORY=/tmp/ \
TEMPLATE_POOL_BASE=/tmp/pytest-of-root/pytest-XX/test_YYY ../sbin/ngcpcfg [...]
HELPER=../helper \
HOOKS=../hooks \
CONFIG_POOL=/etc/ \
NGCPCFG=./fixtures/ngcpcfg.cfg \
SCRIPTS=../scripts/ \
FUNCTIONS=../functions/ \
NGCP_BASE_TT2=/tmp/pytest-of-root/pytest-XX/test_YYY/ \
OUTPUT_DIRECTORY=/tmp/ \
TEMPLATE_POOL_BASE=/tmp/pytest-of-root/pytest-XX/test_YYY \
../sbin/ngcpcfg [...]
Test runs
----------

@ -1,2 +0,0 @@
file error - parse error - input file handle line 1: unexpected end of directive
[% IF %]

@ -18,7 +18,6 @@ def ngcpcfgcli(tmpdir, *args):
'HELPER': '../helper/',
'HOOKS': '../hooks/',
'PERL5LIB': '../lib/',
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'NGCP_TESTSUITE': 'true',
'CONFIG_USER': 'nobody',
'CONFIG_GROUP': 'root',

@ -29,7 +29,6 @@ def test_simple_build_template_ok(ngcpcfgcli):
out = ngcpcfgcli("build", "--ignore-branch-check",
"/etc/apt/apt.conf.d/71_no_recommended",
env={
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
})
regex = re.compile(r"Generating " +
@ -45,7 +44,6 @@ def test_fail_on_existing_dir_matching_output_filename(ngcpcfgcli, tmpdir):
os.makedirs(tmpdir + output)
out = ngcpcfgcli("build", "--ignore-branch-check", output,
env={
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
})
regex = re.compile("Error: Generating file " +

@ -15,7 +15,6 @@ def test_network_interfaces(ngcpcfgcli, tmpdir):
"/etc/network/interfaces",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
'NGCPCFG': 'fixtures/ngcpcfg_network_interfaces.cfg',
'STATE_FILES_DIR': tmpdir+'/var/lib/ngcpcfg/state/',

@ -15,7 +15,6 @@ def test_bad_syntax(ngcpcfgcli, tmpdir):
"/etc/bad-syntax.txt",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
'NGCPCFG': 'fixtures/ngcpcfg_carrier.cfg',
})
@ -26,35 +25,17 @@ def test_bad_syntax(ngcpcfgcli, tmpdir):
print("stderr:")
print(out.stderr.replace("\\n", "\n"))
regex1 = re.compile(r"NOTE: Check those files for valid syntax and "
"encoding.*etc/bad-syntax.txt.tt2.*or inspecting "
"temporary /tmp/ngcpcfg.PID[0-9]+")
assert re.search(regex1, out.stdout)
regex1 = re.compile(r"Error: Cannot process template "
"'.*etc/bad-syntax.txt.tt2':.*"
"file error - parse error - input file handle line 1: "
"unexpected end of directive")
assert re.search(regex1, out.stderr)
regex2 = re.compile(r"Error: Generating /tmp/ngcp-.*-pytest-output.*"
"/etc/bad-syntax.txt based on .*"
"/etc/bad-syntax.txt.tt2: FAILED")
assert re.search(regex2, out.stderr)
regex3 = re.compile(r"Error: file error - parse error - input file "
"handle line 1: unexpected end of directive")
assert re.search(regex3, out.stderr)
output_file = os.path.join(tmpdir, "etc/bad-syntax.txt")
test_file = "fixtures/output/bad-syntax.txt"
output_temp_file = re.sub(r".* or inspecting temporary ", "", out.stdout)
output_temp_file = re.sub(r"\\.*", "", output_temp_file)
# debug
print("Output temp file: '%s'" % (output_temp_file))
assert not os.path.exists(output_file)
assert os.path.exists(output_temp_file)
assert os.path.exists(test_file)
# debug
if not filecmp.cmp(output_temp_file, test_file):
print("output_temp_file:")
print(open(output_temp_file).read())
print("test_file:")
print(open(test_file).read())
assert filecmp.cmp(output_temp_file, test_file)

@ -14,7 +14,6 @@ def test_all_ips(ngcpcfgcli, tmpdir):
"/etc/kamailio/lb/db/dispatcher",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
'NGCPCFG': 'fixtures/ngcpcfg_pro.cfg',
})
@ -36,7 +35,6 @@ def test_all_ips_carrier(ngcpcfgcli, tmpdir):
"/etc/kamailio/lb/db/dispatcher",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
'NGCPCFG': 'fixtures/ngcpcfg_carrier.cfg',
})

@ -14,7 +14,6 @@ def test_status_carrier(ngcpcfgcli, tmpdir):
"/etc/status.cfg",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': tmpdir,
'NGCPCFG': 'fixtures/ngcpcfg_carrier.cfg',
})

@ -11,10 +11,7 @@ import tempfile
@pytest.mark.tt_24920
def test_patch_action_no_args(ngcpcfgcli, tmpdir):
out = ngcpcfgcli("patch",
env={
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
})
out = ngcpcfgcli("patch")
string = r"No patchtt files found, nothing to patch."
assert string in out.stdout
assert out.stderr == "b''"
@ -45,7 +42,6 @@ APT::Install-Recommends "0";
"--help",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -82,7 +78,6 @@ APT::Install-Recommends "0";
"/etc/apt/apt.conf.d/",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -127,7 +122,6 @@ APT::Install-Recommends "0";
"/etc/apt/apt.conf.d/",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -164,7 +158,6 @@ def test_patch_action_template_missing_for_patchtt(ngcpcfgcli, tmpdir):
"/etc/apt/apt.conf.d/",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -216,7 +209,6 @@ APT::Install-Recommends "0";
"/etc/apt/apt.conf.d/71_no_recommended.patchtt.tt2",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -281,7 +273,6 @@ APT::Install-Recommends "0";
"71_no_recommended.patchtt.tt2",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -323,7 +314,6 @@ APT::Install-Recommends "0";
"/etc/missing_patchtt_file.patchtt.tt2",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -406,7 +396,6 @@ dome dummy customtt message
"/etc/apt/apt.conf.d/",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir) + "/output",
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -523,7 +512,6 @@ dome dummy customtt message
out = ngcpcfgcli("build", "--ignore-branch-check",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir) + "/output",
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -604,7 +592,6 @@ APT::Install-Recommends "1";
"/etc/apt/apt.conf.d/",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir) + "/output",
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -653,7 +640,6 @@ APT::Install-Recommends "0";
"/etc/apt/apt.conf.d/",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir) + "/output",
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -704,7 +690,6 @@ APT::Install-Recommends "2";
out = ngcpcfgcli("patch", "--from-customtt",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir) + "/output",
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -743,7 +728,6 @@ def test_patch_action_from_customtt_missing_file_argument(ngcpcfgcli, tmpdir):
"missing.customtt.tt2",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -786,7 +770,6 @@ APT::Install-Recommends "2";
"71_no_recommended.customtt.tt2",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',
@ -831,7 +814,6 @@ the content here doesn't matter as no tt2 file available
"--from-customtt",
env={
'NGCP_BASE_TT2': os.getcwd(),
'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket',
'OUTPUT_DIRECTORY': str(tmpdir),
'TEMPLATE_POOL_BASE': str(tmpdir),
'CONFIG_POOL': '/etc',

Loading…
Cancel
Save