Skip to content

LNST Task API

Ondrej Lichtner edited this page Apr 16, 2014 · 35 revisions

After some time of writing LNST tasks in XML you probably keep repeating:

"XML is such a bad language to write sequence of commands, it's so limited. This should be done differently!!"

And you're quite right. We got tired of this, too. Therefore we've added a new feature that allows you to use your own python program that drives the test execution with ability to access all of the LNST infrastructure - machine properties, command execution, template functions, background execution - from the program. Besides the LNST infrastructure you can benefit from the Python language facilities as loops, conditional executions and whatever Python provides in it's library.

Note that the machine requirements description still needs to be provided as an XML code in the recipe.

1. Usage in LNST recipe

Let's assume that your Python program you're using for the task execution is called task_check_ping.py. To include it in the recipe use following code:

<lnstrecipe>
    <network>
        <host "1">
            <interfaces>
                <eth label="ttnet" id="testiface">
                    <addresses>
                        <address value="192.168.100.240/24"/>
                    </addresses>
                </eth>
            </interfaces>
        </host>
        <host id="2">
            <interfaces>
                <eth label="ttnet" id="testiface">
                    <addresses>
                        <address value="192.168.100.215/24"/>
                    </addresses>
                </eth>
            </interfaces>
        </host>
    </machines> 

    <!-- This is it! -->
    <task python="task_check_ping.py"/>
</lnstrecipe>

1.1 Content of the task_check_ping.py

    # Mandatory import, the ctl handle contains the API
    from lnst.Controller.Task import ctl

    # Get handles for the machines
    m1 = ctl.get_host("1")
    m2 = ctl.get_host("2")

    # Set a config option (persistent=True)
    m1.config("/proc/sys/net/ipv4/conf/all/forwarding", "0", True)

    # run a shell command
    devname = m2.get_devname("testiface")
    m1.run("echo %s" % devname, timeout=30)

    # prepare a module for execution
    ping_mod = ctl.get_module("IcmpPing", addr=m2.get_ip("testiface", 0),
                              count=40, interval=0.2, limit_rate=95)

    # run the module twice on machine one
    ping_test = m1.run(ping_mod, timeout=30)
    ping_test = m1.run(ping_mod, bg=True, timeout=30)

    # make the controller wait
    ctl.wait(5)

    # interrupt the process
    ping_test.intr()

As you can see all that you need to do to access the LNST API is simply importing the Controller handle

   from lnst.Controller.Task import ctl

Let's have a closer look on the API itself.

2. Controller API

The controller handle provides following methods

ControllerAPI get_host(self, host_id) to get a host API handle for the machine from the recipe spec with a specific id
get_module(self, name, **kwargs) to get a test module API handle
wait(self, seconds) to make controller wait for a specific amount of seconds
get_alias(self, alias) gets the value of a previously defined alias

The get_host() method returns a handle that is needed when you want to access the information about the interfaces or run a command on the machine. See the Host API for details on how to do that or look at the examples below.

The get_module() simply returns the Test Module handle that can be run on a test machine. This method takes an optional keyword argument list to initialize the Test Module.

The wait() method is an equivalent for the <ctl_wait> tag in the recipe xml. It takes one parameter seconds and it's value tells the controller how long it should wait before it continues in the task execution.

Finally the get_alias() method can be used to access aliases defined in the highest namespace of the recipe -- <lnstrecipe>.

Examples

Controller API's get_host() example
   m1 = ctl.get_host("1")
   m2 = ctl.get_host("2")

   ifc_ip = m2.get_ip("testiface")
   ifc_hwaddr = m2.get_hwaddr("testiface")

   m1.run("ping -c 5 " + ifc_ip)
   m1.run("arp -n | grep -i " + ifc_hwaddr)
Controller API's get_module() example

Following example shows the IcmpPing module initialization and it's execution on the test machine.

   m1 = ctl.get_host("1")
   m2 = ctl.get_host("2")

   ping_module = ctl.get_module("IcmpPing", addr=m2.get_ip("testiface", 0),
                              count=40, interval=0.2, limit_rate=95)
   m1.run(ping_module)
Controller API's wait() example
   logging.info("I'll wait for 10 seconds")
   ctl.wait(10)
   logging.info("Task execution continues ...")
Controller API's get_alias() example
   my_value = ctl.get_alias("my_alias")

Will return the value of my_alias defined like this:

<lnstrecipe>
    <define>
        <alias name="my_alias" value="my_value"/>
    </define>
    ...
</lnstrecipe>

3. Host API

Host API provides following methods

HostAPI config(self, option, value, persistent=False) to configure values under /proc or /sys directories
run(self, what, **kwargs) to run commands or test modules on the test machines
get_devname(self, interface_id) to get the name of the interface on the test machine, e.g. eth0
get_hwaddr(self, interface_id) to get the hardware address of the interface, e.g. 00:11:22:33:44:55:FF
get_ip(self, interface_id, addr_number=0) to get the IP address of the interface, e.g. 192.168.1.10
sync_resources(self, modules=[], tools=[]) to manually synchronize resources to the test machine

The config() is the equivalent of the <config> command used in the recipe xml. It takes three parameters, the option defining the path to the file under /proc or /sys directory, the value containing the value to set the option to and persistent flag to make the value persistent between individual tasks.

The run() is the equivalent of the <run> command used in the recipe xml. The parameter what is used to pass either Test Module handle obtained thorugh the Host API's get_module() method or string containing a command to be run on the command line. This method takes following keyword arguments that modify its behaviour. For the usage see the examples section below.

run() **kwargs
bg boolean if set to True the command will be run in background
expect ["pass"|"fail"] if set to "fail" the command is expected to fail - in other words if it succeeds this is considered as the testcase failed
timeout integer time limit in seconds
tool string run from a tool (the same as from in recipe xml)

The methods get_devname(), get_hwaddr(), get_ip() are used to get information about configured test interfaces. All of them take one common parameter interface_id to identify the interface id that is set in the recipe xml. Method get_ip() takes an optional parameter addr_number that is used as index to the address list if more than one address has been set for the interface.

The last method sync_resources() can be used when the controller is run with the option -r (--reduce-sync) is enabled. In this case the controller will skip resource synchronization for the specific python task and instead the user is expected to manually synchronize required resources by calling this method.

Examples

Host API's config() example
    m1 = ctl.get_host("1")

    m1.config("/proc/sys/net/ipv4/conf/all/forwarding", "0", True)
Host API's run() examples

First example shows how to run the netcat tool on a test machine's command line.

    m1 = ctl.get_host("1")

    ifc_ipaddr = m1.get_ip("testiface")
    m1.run("nc -l %s" % ifc_ipaddr)

As you could note the example above is not very useful since it would block the task execution so the following example modifies it a bit so the program is run in the background.

    m1 = ctl.get_host("1")

    ifc_ipaddr = m1.get_ip("testiface")
    nc_cmd = m1.run("nc -l %s" % ifc_ipaddr, bg=True)

    ctl.wait(30)

    nc_cmd.kill()

Still not very useful, right? So, next example further extends the previous example and shows how to run a Test Module from the LNST Test Module library, in this example it is NetCat module as the client counterpart wrapper to the netcat tool

    m1 = ctl.get_host("1")
    m2 = ctl.get_host("2")

    listen_port = 1234

    ifc_ipaddr = m1.get_ip("testiface")
    nc_cmd = m1.run("nc -l %s %s" % (ifc_ipaddr, listen_port), bg=True)

    nc_module = ctl.get_module("NetCat", addr=ifc_ipaddr, port=listen_port, duration=30)
    m2.run(nc_module)

    nc_cmd.wait()

The last example shows how to run 3rd party tools. We'll be using the tcp_conn tool packaged within LNST.

    m1 = ctl.get_host("1")
    m2 = ctl.get_host("2")

    port_range="10000-10050"
    ifc_addr = m2.get_ip("testiface")

    # running the 3rd party tool
    server = m2.run("./tcp_listen -p %s -a %s -c" % (port_range, ifc_addr), from="tcp_conn", bg=True)
    client = m1.run("./tcp_connect -p %s -a %s -c" % (port_range, ifc_addr), from="tcp_conn")

    # wait one minute and interrupt the tcp_conn tools
    ctl.wait(60)
    client.intr()    
    server.intr()
Host API's get_devname() example

Following example shows how to use get_devname() method and use the config() method and device name to set it's forwarding state.

    m1 = ctl.get_host("1")
    devname = m1.get_devname("testiface")

    logging.info("enabling forwarding on interface %s" % devname) 
    m1.config(option="/proc/sys/net/ipv4/conf/%s/forwarding" % devname, 1)
Host API's get_hwaddr() example

In the following example get_hwaddr() method is used to check that hardware address of the testiface gets into the arp cache.

   m1 = ctl.get_host("1")
   m2 = ctl.get_host("2")

   ifc_ip = m2.get_ip("testiface")
   ifc_hwaddr = m2.get_hwaddr("testiface")

   m1.run("ping -c 5 " + ifc_ip)
   m1.run("arp -n | grep -i " + ifc_hwaddr)
Host API's get_ip() example

This example demonstrates get_ip() method used to get the IP address of testiface interface and use it as the parameter to the NetCat program.

    m1 = ctl.get_host("1")

    ifc_ipaddr = m1.get_ip("testiface")
    m1.run("nc -l %s" % ifc_ipaddr)
Host API's sync_resources() example

This example demonstrates sync_resources() method used to synchronize the IcmpPing and multicast resources with the slave machine with id 1.

    m1 = ctl.get_host("1")
    m1.sync_resources(modules=["IcmpPing"], tools=["multicast"])

4. Module API

Module API does not provide any methods that can be directly used. It's only API class representing a module. Instance of this class is passed as a parameter to run() method from the HostAPI.

5. Process API

ProcessAPI provides following methods for handling executed commands on the test machines

ProcessAPI passed(self) returns a boolean result of the process
get_result returns the whole command result
wait(self) blocking wait until the command returns
intr(self) interrupt the command sending SIG_INT signal
kill(self) kill the command sending SIG_KILL signal

Methods wait(), intr() and `kill() are used when a user runs any command or test module in the background.

Keep in mind that after issuing the kill() method the command results are disposed since it's considered an intentional termination of the process. That also means that LNST sets command's result as passed.

Process API's wait() example
    m1 = ctl.get_host("1")
    m2 = ctl.get_host("2")

    listen_port = 1234

    ifc_ipaddr = m1.get_ip("testiface")
    nc_cmd = m1.run("nc -l %s %s" % (ifc_ipaddr, listen_port), bg=True)

    nc_module = ctl.get_module("NetCat", addr=ifc_ipaddr, port=listen_port, duration=30)
    m2.run(nc_module)

    nc_cmd.wait()
Process API's intr() example
    m1 = ctl.get_host("1")
    m2 = ctl.get_host("2")

    port_range="10000-10050"
    ifc_addr = m2.get_ip("testiface")

    # running the 3rd party tool
    server = m2.run("./tcp_listen -p %s -a %s -c" % (port_range, ifc_addr), from="tcp_conn", bg=True)
    client = m1.run("./tcp_connect -p %s -a %s -c" % (port_range, ifc_addr), from="tcp_conn")

    # wait one minute and interrupt the tcp_conn tools
    ctl.wait(60)
    client.intr()    
    server.intr()
Process API's kill() example

Following example shows the usage of the kill() method. This example will start stress program in the background, keeps it running for one minute and finally kills the stress program.

    m1 = ctl.get_host("1")

    stress_bg = m1.run("stress -m 20 -c 4", bg=True)
    ctl.wait(60)
    stress_bg.kill()