gawkinet: MOBAGWHO
1
1 3.8 MOBAGWHO: a Simple Mobile Agent
1 ===================================
1
1 There are two ways of constructing a software design: One way is to
1 make it so simple that there are obviously no deficiencies, and the
1 other way is to make it so complicated that there are no obvious
1 deficiencies.
1 C. A. R. Hoare
1
1 A "mobile agent" is a program that can be dispatched from a computer
1 and transported to a remote server for execution. This is called
1 "migration", which means that a process on another system is started
1 that is independent from its originator. Ideally, it wanders through a
1 network while working for its creator or owner. In places like the UMBC
1 Agent Web, people are quite confident that (mobile) agents are a
1 software engineering paradigm that enables us to significantly increase
1 the efficiency of our work. Mobile agents could become the mediators
1 between users and the networking world. For an unbiased view at this
1 technology, see the remarkable paper 'Mobile Agents: Are they a good
1 idea?'.(1)
1
1 When trying to migrate a process from one system to another, a server
1 process is needed on the receiving side. Depending on the kind of
1 server process, several ways of implementation come to mind. How the
1 process is implemented depends upon the kind of server process:
1
1 * HTTP can be used as the protocol for delivery of the migrating
1 process. In this case, we use a common web server as the receiving
1 server process. A universal CGI script mediates between migrating
1 process and web server. Each server willing to accept migrating
1 agents makes this universal service available. HTTP supplies the
1 'POST' method to transfer some data to a file on the web server.
1 When a CGI script is called remotely with the 'POST' method instead
1 of the usual 'GET' method, data is transmitted from the client
1 process to the standard input of the server's CGI script. So, to
1 implement a mobile agent, we must not only write the agent program
1 to start on the client side, but also the CGI script to receive the
1 agent on the server side.
1
1 * The 'PUT' method can also be used for migration. HTTP does not
1 require a CGI script for migration via 'PUT'. However, with common
1 web servers there is no advantage to this solution, because web
1 servers such as Apache require explicit activation of a special
1 'PUT' script.
1
1 * 'Agent Tcl' pursues a different course; it relies on a dedicated
1 server process with a dedicated protocol specialized for receiving
1 mobile agents.
1
1 Our agent example abuses a common web server as a migration tool.
1 So, it needs a universal CGI script on the receiving side (the web
1 server). The receiving script is activated with a 'POST' request when
1 placed into a location like '/httpd/cgi-bin/PostAgent.sh'. Make sure
1 that the server system uses a version of 'gawk' that supports network
1 access (Version 3.1 or later; verify with 'gawk --version').
1
1 #!/bin/sh
1 MobAg=/tmp/MobileAgent.$$
1 # direct script to mobile agent file
1 cat > $MobAg
1 # execute agent concurrently
1 gawk -f $MobAg $MobAg > /dev/null &
1 # HTTP header, terminator and body
1 gawk 'BEGIN { print "\r\nAgent started" }'
1 rm $MobAg # delete script file of agent
1
1 By making its process id ('$$') part of the unique file name, the
1 script avoids conflicts between concurrent instances of the script.
1 First, all lines from standard input (the mobile agent's source code)
1 are copied into this unique file. Then, the agent is started as a
1 concurrent process and a short message reporting this fact is sent to
1 the submitting client. Finally, the script file of the mobile agent is
1 removed because it is no longer needed. Although it is a short script,
1 there are several noteworthy points:
1
1 Security
1 _There is none_. In fact, the CGI script should never be made
1 available on a server that is part of the Internet because everyone
1 would be allowed to execute arbitrary commands with it. This
1 behavior is acceptable only when performing rapid prototyping.
1
1 Self-Reference
1 Each migrating instance of an agent is started in a way that
1 enables it to read its own source code from standard input and use
1 the code for subsequent migrations. This is necessary because it
1 needs to treat the agent's code as data to transmit. 'gawk' is not
1 the ideal language for such a job. Lisp and Tcl are more suitable
1 because they do not make a distinction between program code and
1 data.
1
1 Independence
1 After migration, the agent is not linked to its former home in any
1 way. By reporting 'Agent started', it waves "Goodbye" to its
1 origin. The originator may choose to terminate or not.
1
1 The originating agent itself is started just like any other
1 command-line script, and reports the results on standard output. By
1 letting the name of the original host migrate with the agent, the agent
1 that migrates to a host far away from its origin can report the result
1 back home. Having arrived at the end of the journey, the agent
1 establishes a connection and reports the results. This is the reason
1 for determining the name of the host with 'uname -n' and storing it in
1 'MyOrigin' for later use. We may also set variables with the '-v'
1 option from the command line. This interactivity is only of importance
1 in the context of starting a mobile agent; therefore this 'BEGIN'
1 pattern and its action do not take part in migration:
1
1 BEGIN {
1 if (ARGC != 2) {
1 print "MOBAG - a simple mobile agent"
1 print "CALL:\n gawk -f mobag.awk mobag.awk"
1 print "IN:\n the name of this script as a command-line parameter"
1 print "PARAM:\n -v MyOrigin=myhost.com"
1 print "OUT:\n the result on stdout"
1 print "JK 29.03.1998 01.04.1998"
1 exit
1 }
1 if (MyOrigin == "") {
1 "uname -n" | getline MyOrigin
1 close("uname -n")
1 }
1 }
1
1 Since 'gawk' cannot manipulate and transmit parts of the program
1 directly, the source code is read and stored in strings. Therefore, the
1 program scans itself for the beginning and the ending of functions.
1 Each line in between is appended to the code string until the end of the
1 function has been reached. A special case is this part of the program
1 itself. It is not a function. Placing a similar framework around it
1 causes it to be treated like a function. Notice that this mechanism
1 works for all the functions of the source code, but it cannot guarantee
1 that the order of the functions is preserved during migration:
1
1 #ReadMySelf
1 /^function / { FUNC = $2 }
1 /^END/ || /^#ReadMySelf/ { FUNC = $1 }
1 FUNC != "" { MOBFUN[FUNC] = MOBFUN[FUNC] RS $0 }
1 (FUNC != "") && (/^}/ || /^#EndOfMySelf/) \
1 { FUNC = "" }
1 #EndOfMySelf
1
11 The web server code in ⇒A Web Service with Interaction
Interacting Service, was first developed as a site-independent core.
1 Likewise, the 'gawk'-based mobile agent starts with an agent-independent
1 core, to which can be appended application-dependent functions. What
1 follows is the only application-independent function needed for the
1 mobile agent:
1
1 function migrate(Destination, MobCode, Label) {
1 MOBVAR["Label"] = Label
1 MOBVAR["Destination"] = Destination
1 RS = ORS = "\r\n"
1 HttpService = "/inet/tcp/0/" Destination
1 for (i in MOBFUN)
1 MobCode = (MobCode "\n" MOBFUN[i])
1 MobCode = MobCode "\n\nBEGIN {"
1 for (i in MOBVAR)
1 MobCode = (MobCode "\n MOBVAR[\"" i "\"] = \"" MOBVAR[i] "\"")
1 MobCode = MobCode "\n}\n"
1 print "POST /cgi-bin/PostAgent.sh HTTP/1.0" |& HttpService
1 print "Content-length:", length(MobCode) ORS |& HttpService
1 printf "%s", MobCode |& HttpService
1 while ((HttpService |& getline) > 0)
1 print $0
1 close(HttpService)
1 }
1
1 The 'migrate()' function prepares the aforementioned strings
1 containing the program code and transmits them to a server. A
1 consequence of this modular approach is that the 'migrate()' function
1 takes some parameters that aren't needed in this application, but that
1 will be in future ones. Its mandatory parameter 'Destination' holds the
1 name (or IP address) of the server that the agent wants as a host for
1 its code. The optional parameter 'MobCode' may contain some 'gawk' code
1 that is inserted during migration in front of all other code. The
1 optional parameter 'Label' may contain a string that tells the agent
1 what to do in program execution after arrival at its new home site. One
1 of the serious obstacles in implementing a framework for mobile agents
1 is that it does not suffice to migrate the code. It is also necessary
1 to migrate the state of execution of the agent. In contrast to 'Agent
1 Tcl', this program does not try to migrate the complete set of
1 variables. The following conventions are used:
1
1 * Each variable in an agent program is local to the current host and
1 does _not_ migrate.
1
1 * The array 'MOBFUN' shown above is an exception. It is handled by
1 the function 'migrate()' and does migrate with the application.
1
1 * The other exception is the array 'MOBVAR'. Each variable that
1 takes part in migration has to be an element of this array.
1 'migrate()' also takes care of this.
1
1 Now it's clear what happens to the 'Label' parameter of the function
1 'migrate()'. It is copied into 'MOBVAR["Label"]' and travels alongside
1 the other data. Since travelling takes place via HTTP, records must be
1 separated with '"\r\n"' in 'RS' and 'ORS' as usual. The code assembly
1 for migration takes place in three steps:
1
1 * Iterate over 'MOBFUN' to collect all functions verbatim.
1
1 * Prepare a 'BEGIN' pattern and put assignments to mobile variables
1 into the action part.
1
1 * Transmission itself resembles GETURL: the header with the request
1 and the 'Content-length' is followed by the body. In case there is
1 any reply over the network, it is read completely and echoed to
1 standard output to avoid irritating the server.
1
1 The application-independent framework is now almost complete. What
1 follows is the 'END' pattern that is executed when the mobile agent has
1 finished reading its own code. First, it checks whether it is already
1 running on a remote host or not. In case initialization has not yet
1 taken place, it starts 'MyInit()'. Otherwise (later, on a remote host),
1 it starts 'MyJob()':
1
1 END {
1 if (ARGC != 2) exit # stop when called with wrong parameters
1 if (MyOrigin != "") # is this the originating host?
1 MyInit() # if so, initialize the application
1 else # we are on a host with migrated data
1 MyJob() # so we do our job
1 }
1
1 All that's left to extend the framework into a complete application
1 is to write two application-specific functions: 'MyInit()' and
1 'MyJob()'. Keep in mind that the former is executed once on the
1 originating host, while the latter is executed after each migration:
1
1 function MyInit() {
1 MOBVAR["MyOrigin"] = MyOrigin
1 MOBVAR["Machines"] = "localhost/80 max/80 moritz/80 castor/80"
1 split(MOBVAR["Machines"], Machines) # which host is the first?
1 migrate(Machines[1], "", "") # go to the first host
1 while (("/inet/tcp/8080/0/0" |& getline) > 0) # wait for result
1 print $0 # print result
1 close("/inet/tcp/8080/0/0")
1 }
1
1 As mentioned earlier, this agent takes the name of its origin
1 ('MyOrigin') with it. Then, it takes the name of its first destination
1 and goes there for further work. Notice that this name has the port
1 number of the web server appended to the name of the server, because the
1 function 'migrate()' needs it this way to create the 'HttpService'
1 variable. Finally, it waits for the result to arrive. The 'MyJob()'
1 function runs on the remote host:
1
1 function MyJob() {
1 # forget this host
1 sub(MOBVAR["Destination"], "", MOBVAR["Machines"])
1 MOBVAR["Result"]=MOBVAR["Result"] SUBSEP SUBSEP MOBVAR["Destination"] ":"
1 while (("who" | getline) > 0) # who is logged in?
1 MOBVAR["Result"] = MOBVAR["Result"] SUBSEP $0
1 close("who")
1 if (index(MOBVAR["Machines"], "/") > 0) { # any more machines to visit?
1 split(MOBVAR["Machines"], Machines) # which host is next?
1 migrate(Machines[1], "", "") # go there
1 } else { # no more machines
1 gsub(SUBSEP, "\n", MOBVAR["Result"]) # send result to origin
1 print MOBVAR["Result"] |& "/inet/tcp/0/" MOBVAR["MyOrigin"] "/8080"
1 close("/inet/tcp/0/" MOBVAR["MyOrigin"] "/8080")
1 }
1 }
1
1 After migrating, the first thing to do in 'MyJob()' is to delete the
1 name of the current host from the list of hosts to visit. Now, it is
1 time to start the real work by appending the host's name to the result
1 string, and reading line by line who is logged in on this host. A very
1 annoying circumstance is the fact that the elements of 'MOBVAR' cannot
1 hold the newline character ('"\n"'). If they did, migration of this
1 string did not work because the string didn't obey the syntax rule for a
1 string in 'gawk'. 'SUBSEP' is used as a temporary replacement. If the
1 list of hosts to visit holds at least one more entry, the agent migrates
1 to that place to go on working there. Otherwise, we replace the
1 'SUBSEP's with a newline character in the resulting string, and report
1 it to the originating host, whose name is stored in
1 'MOBVAR["MyOrigin"]'.
1
1 ---------- Footnotes ----------
1
1 (1) <http://www.research.ibm.com/massive/mobag.ps>
1