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