How to run an application via a Prototype

Prerequisite

This feature is only available in NodeHandler v4 and above. Before going through this tutorial, you should:

Objective

In this tutorial, you will learn how to execute an application using a prototype. This method is complementary to the two previous methods presented here and here. It is the recommended method if you have an application, which you would like to use in the long term with different (possibly dynamic) parameters.

For example, this would be the case of a traffic generator that can generate different types of traffic depending on the parameters given by the experiment script. Using a prototype to execute this generator within your experiment allows you to:

  • run the application with different initial parameters without having to re-write any modified "wrapper" code,
  • change any eventual dynamic parameters during the application execution (for example, change the rate of the generated traffic after 60 seconds)

This method involves three steps:

Step 1: Defining a wrapper around your original application. This step would correspond to the creation of a generic Experiment Controller-compatible version of your application.
Step 2: Defining a prototype. This step would correspond to the creation of a specific type or instance of your application.
Step 3: Defining an experiment script, which would use your application via its prototype.

In the context of the above traffic generator example:

  1. Create an Experiment Controller-compatible application definition, which is a "wrapper" around your original application. This application definition will provide an interface to all the available parameters of your original application.
  2. Customise the previous application definition in a prototype definition. For example you decide to define an instance of your application (i.e. a prototype) which will only generate a given traffic model (for example, pareto) but which still accepts different parameter (for example, packet size, rate, etc...). You may also decide to define a second prototype, which will generate another type of traffic.
  3. Define an experiment which will use this customised application.

The rest of this tutorial describes these three steps and the execution of the related experiment script.

Step 1: Application Definition

In this step, we create an application definition, that is a "wrapper" for our original application. In this example, we take "ping" as our original application.

The following code should be saved into a file "pingWrapper.rb" in the same directory as the experiment scripts that would use it:

 1 # Define a new application
 2 # The following declaration defines a new application which as a URI 'pingWrapper'
 3 # and the name 'pingApp'
 4 
 5 defApplication('pingWrapper', 'pingApp') {|app|
 6 
 7   app.shortDescription = "This is a simple wrapper application around ping" 
 8   app.path="/bin/ping" 
 9 
10   # Here we define the "properties" of this application, i.e. the parameters that
11   # it supports and which we would like to access 
12   # Ping has many parameters, however in this example, we are only interested in 
13   # the following four ones.
14 
15   # defProperty has the following signature:
16   # defProperty ( name, description, mnemonic, options )
17   # - 'name' is interpreted as the long parameter for the command (For example, "--help")
18   # - 'mnemonic' is the short parameter call (for example, ?h will result in "-h")
19   # - 'options' is a Hash of options for this parameter:
20   #        :type => <type> 
21   #                 is the parameter types, any of ':string' or ':integer' 
22   #                 or ':boolean' or 'nil'
23   #        :dynamic => true | false 
24   #                    true property can be changed at run-time
25   #        :use_name => true | false
26   #                     if false then print only the parameter value and do 
27   #                     not print either 'name' or 'mnemonic' when calling the command
28   #        :order => integer
29   #                  Order the property according to integer when calling the command
30 
31   # This is the ping destination. It is a string and its value is not prefixed by any
32   # "--options" or "-X", thus we set ':use_name' to false
33   #
34   app.defProperty("dst", "Destination Address or Name", nil,
35                   {:dynamic => false, :type => :string, :use_name => false})
36 
37   # This is the number of ping packet to send. It is an integer, and on the command line
38   # it should be prefixed by "-c", hence the use of '?c'
39   #
40   app.defProperty("count", "Number of probe packet to send", ?c,
41                   {:dynamic => false, :type => :integer})
42 
43   # similar as previous comment...
44   app.defProperty("flood", "Flood packets", ?f,
45                   {:dynamic => false, :type => :boolean})
46   app.defProperty("interval", "Time interval between packets in a flood", ?i,
47                   {:dynamic => false, :type => :integer})
48 } 

Step2: Prototype Definition

In this step, we define a prototype that would use and customise the above "pingWrapper" application. For the purpose of this example, we will define two different prototypes:

  1. The first one is "agressivePing". It customises the "pingWrapper" application to send a fixed high amount of probes by default with minimal interval between them. This effectively floods the destination. This application can be used to get a quick estimation of the packet drop rate on a link.
  2. The second one is "gentlePing". It customises the "pingWrapper" application to send five probes every second (default ping interval is 1 second). This application can be used to test reachability between two nodes with minimal link disruption.

The following two blocks of code should be saved into two separate files "agressivePing.rb" and "gentlePing.rb" in the same directory as the previous application definition.

 1 # Define a new prototype
 2 # This prototype is an instance/specialization of the above 'pingWrapper' application
 3 # 
 4 
 5 defPrototype("aggressivePing") { |proto|
 6 
 7   proto.name = "aggressivePing" 
 8   proto.description = "A node that flood packets to a destination to quickly determine packet drop rates" 
 9 
10   # Here we specify which property of the base application we would like to use 
11   # AND what are the default value we would like to give them.
12 
13   # 'destination' is a mandatory parameter of ping, thus we do not set any default value
14   proto.defProperty('destination', 'Name or IP address of the destination')
15 
16   # This is a agressive ping, so we set the default probe number to 1000, we also set the 
17   # flooding behavior as 'true' by default, along with an interval of 0
18   #
19   proto.defProperty('numPacket', 'Number of probe packets to flood', 1000)
20   proto.defProperty('enableFlood', 'Enable packet flooding', true)
21   proto.defProperty('pktInterval', 'Time interval between packets in a flood', 0)
22 
23   # Here we bind this prototype with the "pingWrapper" application definition
24   # And we also bind the properties that we decided to use and gave default values to 
25   #
26   proto.addApplication("pingApp", "pingWrapper") { |listener|
27     listener.bindProperty('dst', 'destination')
28     listener.bindProperty('count', 'numPacket')
29     listener.bindProperty('flood', 'enableFlood')
30     listener.bindProperty('interval', 'pktInterval')
31   }
32 }

and

 1 # Define another prototype
 2 # This prototype is another instance/specialization of the above 'pingWrapper' application
 3 # 
 4 
 5 defPrototype("gentlePing") { |proto|
 6 
 7   proto.name = "gentlePing" 
 8   proto.description = "A node that pings a destination to quiclky assess its reachability" 
 9 
10   # This is a gentle ping, so we set the default probe number to 5, we also set the 
11   # flooding behavior as 'false' by default, and we ignore the 'interval' parameter
12   #
13   proto.defProperty('destination', 'Name or IP address of the destination')
14 
15   proto.defProperty('numPacket', 'Number of probe packets to flood', 5)
16   proto.defProperty('enableFlood', 'Enable packet flooding', false)
17 
18   proto.addApplication("pingApp", "pingWrapper") { |listener|
19     listener.bindProperty('dst', 'destination')
20     listener.bindProperty('count', 'numPacket')
21     listener.bindProperty('flood', 'enableFlood')
22   }
23 }

The Experiment Script

In this step, we describe the experiment script that demonstrates the use of the above two prototypes.

This experiment involves an ad-hoc network of two group of nodes, each containing a single node. The first group "firstWorker" contains node [1,1], which will use the "gentlePing" prototype/application to assert that it can reach node [1,2]. The second group "secondWorker" contains node [1,2], which will use the "agressivePing" prototype/application to get a rough estimate of the packet drop rate on its link to node [1,1].

The following code should be saved into a file tut_app_3.rb in the same directory as the above application and prototype definitions:

 1 # Define the first group
 2 # The node(s) within this group will run the "gentlePing" prototype/application
 3 #
 4 defGroup('firstWorker', [1,1]) {|node|
 5 
 6    # Associate the prototype to use with this group
 7    # and configure any eventual parameters
 8    #
 9    node.prototype("gentlePing", {
10      'destination' => "192.168.0.2" 
11    })
12 
13    # Configure the wireless interface on the node(s) in this set
14    node.net.w0.mode = "ad-hoc" 
15    node.net.w0.type = "g" 
16    node.net.w0.essid = "tutorial" 
17    node.net.w0.ip = "192.168.0.1" 
18 }
19 
20 # Define the second group
21 # The node(s) within this group will run the "aggressivePing" prototype/application
22 #
23 defGroup('secondWorker', [1,2]) {|node|
24 
25    # Here we decide that the default 1000 probes are not enough in our case
26    # and we give a specific value of 4000 to the 'numPacket' parameter of 
27    # the "aggressivePing" prototype
28    #
29    node.prototype("aggressivePing", {
30      'destination' => "192.168.0.1",
31      'numPacket' => 4000
32    })
33 
34    # Configure the wireless interface on the node(s) in this set
35    node.net.w0.mode = "ad-hoc" 
36    node.net.w0.type = "g" 
37    node.net.w0.essid = "tutorial" 
38    node.net.w0.ip = "192.168.0.2" 
39 }
40 
41 # When all the applications on all the nodes in this experiment are ready
42 #
43 whenAllInstalled() {|node|
44 
45    # Wait 10 sec to make sure that the wireless interfaces are all 
46    # configured
47    wait 10
48 
49    # Start all the applications
50    allGroups.startApplications
51 
52    # Wait 10 sec, this will be the experiment duration time
53    wait 10
54 
55    # Stop the experiment
56    Experiment.done
57 }

Final Step

To run this example script, use the following command:

omf exec tut_app_3

The Results

The experiment screen output should then look like this. And the experiment log file should look like this.

Definitions of the Prototypes and Application used in the Hello World script

  • Orbit Traffic Generator(OTG): sender.rb is the file that contains the test:proto:sender2 prototype, which wraps around the otg.rb application.
  • Orbit Traffic Receiver (OTR): receiver.rb is the file that contains the test:proto:rceiver2 prototype, which wraps around the otr.rb application.