Previous Next Contents

3   Services in PLAN

Writing services for PLAN is a two step process. First, the OCaml code that is to constitute the functionality of the services is written. This will make use of files from the PLAN implementation, so the implementation must be available in order to compile the new service code. Secondly, the services must be installed in the PLAN router. This may be done in two ways: either statically or dynamically. Installing services dynamically requires a slightly different implementation than the static way, so we shall first look at how to make static services.

3.1   An Example: A Random Number Generator

Before going into detail about how to write services in general, we shall demonstrate how to add a new service with an example. The new service, rand, returns a randomly-generated integer. The code presented in these examples may be found in the plan/switchlets directory of the distribution; other examples are also present in files in this directory.

The first thing to do is to write the OCaml code that implements the service. This should be in its own file; you want to make sure that only the functions that should be visible to other services and/or PLAN programs are visible in the file's interface. The second thing to do is to write wrappers for each of these functions which conform to what is expected by PLAN. These will also go in their own file. For our random service we first write the file rand.ml, which makes use of OCaml's Random module:

(* initialize the generator with the current time *)
let _ = Random.init (truncate (Unix.time ()))

(* the actual random function *)
let rand () =
  Random.int 0x3fffffff
The interface for this file shall only export the rand function, so it will look like this (in file rand.mli):

(* initialize the generator with the current time *)
val rand : unit -> int
The next thing to do is to write the proper wrapper for the rand function so that it may be called by the PLAN interpreter. All service functions should have the following type:
val service: Basis.active_packet * (Basis.value list) -> Basis.value
where service is the name of the function to be exported to PLAN. Let's look at the rest of the prototype.

value is an algebraic (aka structural) type that defines the form of all PLAN values. Since a PLAN value should be returned by all services invoked from a PLAN program, all service functions should return type value. In addition, it is possible that a service could raise a PLAN exception or cause an execution error. Both of these possibilities are handled by raising OCaml exceptions of type PLANException (defined in file interpreter/planExn.ml), and ExecException (defined in file interpreter/eval.ml), respectively.

The arguments to the service function are a list of value's, and an active_packet. When this function is invoked as a service call from PLAN, each element of the value list shall correspond to the PLAN value that was provided as an actual argument. The active_packet parameter shall be bound to the invoking PLAN packet, and may contain information of interest to the service, such as the currently available resource bound of the packet.

Applying this API we write the file rand_svc_impl.ml:

open Basis
open Eval
open Services

let rand (p,l) =
  match l with
    [] ->
      Int (Rand.rand ())
  | _ -> typecheck_args "rand" l []; Unit
For this service, if everything proceeds correctly, a PLAN value which corresponds to a PLAN integer is returned. The current version of PLAN requires that services do their own checking---this is for efficiency purposes. In general, a service routine will match against the expected form of its arguments in the first case of the pattern match, and then call typecheck_args for the default case. typecheck_args matches the actual arguments with a type specfication having type Basis.plan_type list. In this case, no arguments are expected, so the specification is empty ([]). typecheck_args will return true if typechecking succeeds, and raise an ExecException otherwise. In this case, we expect typechecking to always fail (since a type-correct program would have matched the initial case), and thus to raise an exception.1

Finally, we must add a register_svcs function to the module to install its new services. This method serves to place the name of each function corresponding to a PLAN service in the interpreter service symbol table. We can also provide the PLAN type for this function, which allows the static typechecker to infer the type of the service without requiring <tt>svc</tt> declarations in the PLAN packets. Here is the final version of the code:

open Basis
open Eval
open Services

let rand (p,l) =
  match l with
    [] ->
      Int (Rand.rand ())
  | _ -> typecheck_args "rand" l []; Unit

let register_svcs () =
  register_svc("rand",rand,Some "void -> int")
The register_svcs routine simply maps the string defining the name of the PLAN service, rand, to the function that implements that service, rand. This registration routine should be the only one that is available to other services (since the actual functionality in the rand module is already available), so its interface is (in file rand_svc_impl.mli):

val register_svcs : unit -> unit

Static installation

To statically install this service, we have to add it to the Makefile to be compiled. Let's assume that all of the files we've defined (rand.ml, rand.mli, rand_svc_impl.ml, rand_svc_impl.mli) are in the directory rand inside of the plan directory itself. First find the defintion of the variable BASE_DIRS:
BASE_DIRS        = basis interpreter planet port util RIP resident arp csum
  frag net reliable dns exp flow security crypto builder
This definition should be extended to include the name of your service directory, in this case rand:
BASE_DIRS        = basis interpreter planet port util RIP resident arp csum
  frag net reliable dns exp flow security crypto builder rand
A little further down, you will find the comment:
# The various packages to be installed
Just below it is a list of definitions (in alphabetical order) of the various logical file groupings. We shall add a group RAND to this list:
RAND = rand.ml rand_svc_impl.ml
A little further down, we add the definition:
SA_RAND=$(addprefix rand/,$(RAND))
Finally, we extend the definition for the variable STANDALONE_OBJS to include SA_RAND:
STANDALONE_OBJS=$(SA_COMMON) $(SA_UTIL) $(SA_CRYPTO) $(SA_NET) $(SA_BASIS) \
  $(SA_ARP) $(SA_PLANET) $(SA_PARSER) $(SA_RELIABLE) $(SA_INTERP) \
  $(SA_PLANET_SVC) $(SA_ARP_SVC) $(SA_PORT) $(SA_RIP) $(SA_FLOW) \
  $(SA_CSUM) $(SA_REL_SVC) $(SA_DNS_SVC) $(SA_QCM) $(SA_KEYNOTE) \
  $(SA_SECURITY) $(SA_RESIDENT) $(SA_FRAG) $(SA_EXP_SVC) \
  $(SA_RAND) custom.cmo router.cmo daemon.cmo
The files should now be properly compiled when you type make, and will be linked into the system. One detail remains: we must call the register_svcs function in rand_svc_impl.ml once the node has started in order to make the rand function available to PLAN programs. This is done by editing the file custom.ml. Currently the file consists of (in part) the following code:
let customize (portnum,sockfile,keyfile',
               neighbors,rout_file,host_file,policy_file,authlist) =
  let keyfile = 
...

You would change this to indicate that the rand service should be registered:
let customize (portnum,sockfile,keyfile',
               neighbors,rout_file,host_file,policy_file,authlist) =
  Rand_svc_impl.register_svcs ();
  let keyfile = 
...

Recompile and you should now have your new service.

Dynamic installation

The default PLAN daemon does not allow for dynamic service installation. However, there is provided with the distribution an executable called plan_loader which is augmented to allow dynamic installation of services. To build this executable, do make loader. This will build plan_loader and place it in the bin directory.

To build a loadable version of each of your files, you will need to do the following: Additionally, some functions which are available to services statically are not necessarily available to ones dynamically linked. This is the case with the Random.init function. The reason for this is safety, but the policy (which functions to hide or not hide) is a matter of current research. Thus, the modified files will look like the following.
rand.ml:
open Safeloader

(* initialize the generator with the current time *)
(* let _ = Random.init (truncate (Unix.time ())) *)

(* the actual random function *)
let rand () =
  Random.int 0x3fffffff
rand_svc_impl.ml:
open Safeloader
open Basis
open Eval
open Services

let rand (p,l) =
  match l with
    [] ->
      Int (Rand.rand ())
  | _ -> typecheck_args "rand" l []; Unit

let register_svcs () =
  register_svc("rand",rand)

let _ = register_svcs ();;
The next thing to do is to compile these files. The easiest thing to do is copy the two .ml files to the switchlets directory. Then:
[ switchlets ]> make rand.cmo
Makefile:11: .depend: No such file or directory
ocamldep -I ../loader *.mli *.ml > .depend
ocamlc -I ../loader -thread -nopervasives -c rand.ml -o rand.cmo
[ switchlets ]> make rand_svc_impl.cmo
ocamlc -I ../loader -thread -nopervasives -c rand_svc_impl.ml -o rand_svc_impl.cmo
[ switchlets ]> 
The two resulting .cmo files, rand.cmo and rand_svc_impl.cmo may now be dynamically loaded, using the installServices service function. In the rout_tests directory is a PLAN program that puts a wrapper around this service function to indicate success or failure. Assuming there is a router running locally, here is how you would install the service, using inject:
[ plan ]> inject rout_tests/install.plan 1
install("Rand",getBlobFromFile("switchlets/rand.cmo"))
Install succeeded

[ plan ]> inject rout_tests/install.plan 1
install("Rand",getBlobFromFile("switchlets/rand_svc_impl.cmo"))
Install succeeded

[ plan ]>
Note that the string "Rand" is ignored (an artifact of earlier implementations). If the install were to fail, the message ``Install failed!'' would be printed.

Now that the service is loaded, we can invoke it from PLAN, using the following plan function (in the file, say, rand.plan):
fun foo() : unit =
  print(rand())
Here is the invocation:
[ plan ]> inject rand.plan 1
foo()
164667826

[ plan ]>
For more details about how services are installed, please refer to the PLAN Programmer's Guide [4].

3.2   Writing Service Code

To properly write OCaml code that is to be made available as services in PLAN, a number of rules must be followed.


Previous Next Contents