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 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:

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.