Writing Traffic Server tests with DEFT
Test scripts
Test scripts are writting in perl though it should possible with
some effort to use a different interpretor if necessary. The testing
framework is core written in C++ and exports functions to perl for
controling test programs. The log parser process are written in perl.
High level concepts
To effectively write tests, we need to understand the high level
building blocks of the test framework. There are 5 processes run by
the test framework.
Test framework processes
- test_exec - runs the test scripts. Starts up
proc_managers on each node used in the test. Pushes test tool
packages to proc_managers. Starts up
test_log_collate and the log_viewer.pl
- proc_manager - starts up and manages tests processes.
There is one proc_manager started on each machine being used in the test.
- test_log_collate - collates the test log from each of
the proc_managers. This process is generally invisible to users of
the system since it is automatically spawned by test_exec.
- log_viewer.pl - displays the test log in a window.
- parse_test_log.pl - processes the test log at the end of
the tests and tallys up errors and warnings.
Test Process Management
Each process run under the test framework has different
requirements for config files and start up. For example, Traffic
Server has a whole set of configuration files that need to be setup
up to. An instantiator script is used to manage test process
setup. The instantiator setups up any necessary files and
directories, allocates ports and tells the process manager what
command line to use to start up the process. The instantiator can be
sent test specific configuration through an argument
pm_create_instance function. The configuration is treated as
an opaque string that is passed to the instantiator.
Variable substiution
It's rather inconveient to hard code a test to specific machines and
ports. To avoid hard coding, the test framework will make variable
substitutions on various parameters. To have substitution made, use
%%(varname)
. Substitutions are defined by .defs file
read in at the beginning of the test, command line agrument to
test_exec, and port bindings issued by instantiators. Note:
currently is no escaping mechanism to say %%(
and not
have be a variable sustitution.
In order to make tests easily reusable, the following convention
exists for substitutions for machine names:
- For traffic_server machines: ts1, ts2, ts3, ts4
- For load machines: load1, load2 ... load10
Most tests will only use ts1 and load1 to load 4. But more
complex tests that either need to generate very high loads or test
cache hierachies through parenting or icp may use the entire set.
The default defs file maps all hostnames to localhost. You can
generate your own defs files and use '-D' option to test_exec (or
run_test.pl which passes through its agruments through to test_exec).
The following characters are reserved and can not be used within a
variable name: ':'
and '/'
.
RAF
RAF (Remote Access Facility) is a simple protocol TCP based
protocol that can be used to both query and control test processes.
Ideally, test tools are enhanced to include RAF support but RAF
is not a requirement. Details on RAF are avaiable
here .
An example test
This test test that starts up traffic_server and a jtest and
issues an error if the ops per second are less than 5.
"use TestExec"
loads the perl module that contains
the the bindings to the underyling C++ functions of the testing
framework.
The $ts_config
var is set through a useful perl quoting syntax that makes everything
quoted text until the marker is seen, in this case, EOC
.
use TestExec;
$ts_config = <<EOC;
[records.config]
proxy.config.cache.storage_filename storage.config
proxy.config.proxy_name example.com
proxy.config.hostdb.size 50000
[storage.config]
. 700000
EOC
@ts_create_args = ("package", "ts",
"localpath", "%%(ts_localpath)",
"config", $ts_config);
@jtest_create_args = ("package", "jtest",
"config", "-P %%(ts1) -p %%(ts1:tsHttpPort) -c 5");
@jtest_start_args = ("args", "-C");
@empty_args = ();
# Fire up Traffic Server
$ts_inst_name = "ts1";
TestExec::pm_create_instance("ts1", "%%(ts1)", \@ts_create_args);
TestExec::pm_start_instance("ts1", \@empty_args);
sleep(5);
# Fire up jtest
TestExec::pm_create_instance("jtest1", "%%(load1)", \@jtest_create_args);
TestExec::pm_start_instance("jtest1", \@jtest_start_args);
sleep(5);
# Query jtest via RAF to get ops per second value
@raf_args1 = "/stats/client_sec";
@raf_result = TestExec::raf_instance("jtest1", "query", \@raf_args1);
# Must do at five ops per second
if ($raf_result[2] < 5) {
TestExec::add_to_log("Error: insufficent jtest ops: $raf_result[2]");
} else {
TestExec::add_to_log("Status: jtest ops: $raf_result[2]");
}
TestExec::pm_stop_instance("jtest1", \@empty_args);
TestExec::pm_destroy_instance("jtest1", \@empty_args);
TestExec::pm_stop_instance("ts1", \@empty_args);
TestExec::pm_destroy_instance("ts1", \@empty_args);
Function calls for managing test processes
pm_create_instance(String instance_name, String, hostname,
ArrayRef create_args) |
| Return Value:
0 on success, 1 on failure |
| Description: Creates an process
instance on the machine named by hostname. Hostname has DEFT variable
substitutions applied to it before it is used. Will push a package if the package specified on
create_args does not exist on remote or if the local pacakge
is different from the remote package. Runs the instantiator for the
package on the remote host. |
|
Array argruments to create |
localpath |
Path to use find the process binaries. Inhibits sending
packages. DEFT variable substitutions are made on the content
of the argument before it is used. |
package |
package name for the new instance. |
config |
configuration bytes passed to the instantitor for the
process. DEFT variable substitutions are made on the content
of the argument before it is used. |
|
pm_start_instance(String instance_name, ArrayRef start_args) |
| Return Value:
0 on success, 1 on failure |
| Description: Starts up an
instance previous created through pm_create_instance. |
|
Array argruments to start |
args |
Additional command line arguments to pass when starting the
instance this time |
|
pm_stop_instance(String instance_name, ArrayRef stop_args) |
| Return Value:
0 on success, 1 on failure |
| Description: Stops an
instance previous created through pm_start_instance. Currently, there
are no valid arguments in stop_args. |
pm_destroy_instance(String instance_name, ArrayRef destroy_args) |
| Return Value:
0 on success, 1 on failure |
| Description: Destroys an
instance previous created through pm_create_instance. Once destroyed,
the instance name can no longer be used in pm_start_instance and
pm_stop_instance commands. Currently, there are no valid arguments in
destroy_args. |
pm_run(String hostname, String binary, String args, Int timeout) |
| Return Value:
If timeout is < 0, returns the instance name of new process.
If timeout >= 0, returns the exit status of new process. If the
process lives beyond the timeout, -1 is returned for it's status. If
there is an internal error or arguments to the function are invalid, -2
is returned. |
| Description:
Starts a process using 'binary' on 'hostname'. If 'binary' is not an
absolute path, the PATH enviroment variable (from the remote host) is
used to search for 'binary'. 'args' has DEFT variable substitution
applied to it and this then used as the command line arguments to the
new process. If 'timeout' is less than 0, once the new process
is started the function immediately returns and the callee is responsible for
managing the process using pm_stop_instance and
pm_destroy_instance. If 'timeout' is 0, function waits indefinately
for the process to exit. If 'timeout' is greater than zero, the
function will wait for 'timeout' milliseconds for the process to exit
on its own accord. If the process does exit within the timeout, it is
stopped and the function returns.
|
pm_run_slave(String master_instance, String binary, String args, Int timeout) |
| Return Value:
See pm_run return values.
|
| Description:
Similar functionality to pm_run. The process is started on the
host 'master_instance' is running on. 'binary' is relative to the run
directory for 'master_instance' and the PATH enviroment variable is
not searched for binary.
|
pm_alloc_port(String hostname) |
| Return Value:
-1 on failue, a port number greater than zero on success |
| Description:
allocs a port from the DEFT port range on host 'hostname'.
'Hostname' has DEFT variable substitution applied to it before it
is used. |
add_to_log(String log_line) |
| Return Value:
0 on success, 1 on failure |
| Description: adds a line to
the test log. |
set_log_parser(String instance, String parser) |
| Return Value:
0 on success, 1 on failure |
| Description: Sets
the log module used to parse to 'parser' for the output of 'instance' |
wait_for_server_port(String instance, String port, Int timeout_ms) |
| Return Value:
0 on success, -1 on failure |
| Description:
Tries to connect to the specified port on the machine 'instance' is
on. Port can be one of a port number in string form, deft substitution
of the form %%(varname), or a string that is port binding label for
'instance'. Will keep trying until successfully connecting to the
port or 'timeout_ms' milliseconds expire.
|
wait_for_instance_death(String instance, Int timeout_ms) |
| Return Value:
Returns the exit status of 'instance'. If 'instance'
lives beyond the timeout, -1 is returned for its status. If
there is an internal error or arguments to the function are invalid, -2
is returned.
|
| Description:
Waits 'timeout_ms' for 'instance' to exit. If 'timeout_ms' is zero,
waits in indefinately. If more than 'timeout_ms' elaspses, the
instance is stopped.
|
is_instance_alive(String instance) |
| Return Value:
1 if instance is running, 0 is instance not running |
| Description:
Checks to see if 'instance' is running. An instance is running if
is it's process is currently alive. If the instance has been stopped,
crashed or destoryed, it is not running.
|
get_instance_file(String instance, String filename) |
| Return Value:
name of the local copy of the file success, null on failure |
| Description:
Fetches a file from the machine 'instance' is on. If 'filename' is
a relative path, file is located relative to instance's 'run_dir'.
|
stat_instance_file(String instance, String filename) |
| Return Value:
array with stat information. The array has element which is a tag
followed by element with the value for the tag. Current tags
are "size" which is the file size in bytes and "mod_time" which is the
UNIX modification time for the file. |
| Description:
Stats a file from the machine 'instance' is on. If 'filename' is
a relative path, file is located relative to instance's 'run_dir'.
|
put_instance_file_raw(String instance, String dest, String src) |
| Return Value:
0 on success, 1 on failure |
| Description:
Copys file src to run directory of instance appened
with the path sepecified by dest dest . Unless
src is an absolute path, it's relative to the directory the test
script is in. The copied file is created with the name user, group
and other permission as the original execpt the umask is not overriden.
|
put_instance_file_subs(String instance, String dest, String src) |
| Return Value:
0 on success, 1 on failure |
| Description:
Generates a copy of src with DEFT variable substitutions made
for %%() style variables. Copies the substituted file to run directory of
instance appened with the path sepecified by dest dest
. Unless src is an absolute path, it's relative to the directory the test
script is in. The copied file is created with the name user, group
and other permission as the original execpt the umask is not overriden.
|
get_var_value(String var) |
| Return Value:
DEFT variable value on success. Empty string on failure |
| Description:
Gets the value of a DEFT substitution variable.
|
set_var_value(String var, String value) |
| Return Value:
0 on success, 1 on failure |
| Description:
Set the value of a DEFT substitution variable. If 'var' as ':' in it,
then the part of the name before the ':' is taken as the instance name
and the part after is a port binding for that instance.
|
raf_proc_manager(String instance_name, String raf_cmd, ArrayRef raf_args) |
| Return Value: Array with raf
result. The 0 element contains the raf success or fail code. |
| Description: Issues a raf
command to the proc_manager for instance_name. |
|
Array argruments to raf_proc_manager |
Each argument is in the array is
appened in order to the raf request. DEFT variable substitution
is done on all agruments in the array. |
|
raf_instance(String instance_name, String raf_cmd, ArrayRef raf_args) |
| Return Value: Array with raf
result. The 0 element contains the raf success or fail code. |
| Description: Issues a raf
command to instance_name. The instantiator for instance must have
issues a port binding name rafPort for the raf command to be issued.
|
|
Array argruments to
raf_instance |
Each argument is in the array is
appened in order to the raf request. DEFT variable substitution
is done on all agruments in the array. |
|
Tools and products already running under DEFT
More on variable substitution
There are two additional topics for understanding variable
substitution.
The first is how to substitute results from port
bindings. To substitute a port binding, the following form is used:
%%(instance_name:port_binding)
The second topic is modifiers to variable substitution. The form
for modifiers is:
%%(var_name/modifier)
A modifier is specified by a single character following the
slash. Currently, the only modifier is /r
which says
the result of the substitution should be passed to
gethostbyname()
and the resulting ip address should
be used.
What's next
The next step is learning how about test groups and result
reporting options. Read
Managing Tests to learn how.
Helpful Links
Back to getting started
All rights Reserved.