This tutorial should assist the reader in understanding and writing Jasmin scripts, which can be installed at an SNMP agent supporting the Script MIB and which can be executed by the Jasmin runtime engine.
A Jasmin script is a Java program consisting of a set of class files which are stored in a single file using the Java archive (JAR) format. When we talk about a Jasmin script as an entity, usually this kind of jar file is being addressed. Section 2 of this tutorial explains in general how to write the Java code. It focusses on the few differences between writing Jasmin scripts and writing plain Java programs. A library supporting communication between Jasmin scripts and the SNMP agent is introduced in Section 3. How to store a Java program consisting of several class files into a jar file is explained in Section 4. The jar file is also the format in which a script is handled by the SNMP agent. Section 5 lists the packages of the JDK which can be used in Jasmin scripts.
A more detailed discussion of programming constraints for Jasmin scripts concerning runtime security and multi-threaded scripts can be found in Section 6 (runtime security) and Section 7 (multi-threading). Section 8 lists the possible exit codes of terminating scripts and the conditions under which they are generated. Issues concerning multiple scripts running concurrently at the same network element are discussed in Section 9
Section 10 might be the most insteresting one for many readers. It lists and explains several test scripts which demonstrate how to use features of the Jasmin runtime environment for writing scripts. Each of these scripts concentrates on a single feature.
This section provides a basic introduction to writing Jasmin scripts. It excludes specific issues such as runtime security and multi-threading, which are addressed in Section 6 (runtime security) and Section 7 (multi-threading).
Jasmin scripts can use the full functionality of the standard JDK except for opening windows, which really does not make much sense on a most network elements. The following is a very simple Jasmin script:
public class Main { public static String main() { return "Hello Jasmin."; } }The static
method
main of class Main
returns
a greeting string. It wouldn't make much sense to print
"Hello Jasmin"
on the command line, since the command line is on a remote network
element and you probably would not see the output of your script.
Instead the string is returned by method main
as the
result of this script. You can read the script result after the
script has terminated by a get request on the Script MIB's
corresponding smRunResult
object.
For each system running Java programs the entry point of the
program has to satisfy some requirements. For Sun's JDK for example,
the main method must have the signature
public static void main(String[])
.
Jasmin scripts relax these requirements a bit.
The parameter list of the main method may be empty or contain
String[]
only. The return type may be void
,
String
, or byte[]
. If a string or a byte
array is returned, it is converted to an
OCTET STRING
, the type of script results in the MIB.
The name of the method has to be main
and it has to be
declared as public and static. The following list contains all
possible signatures of the main method:
public static void main(); public static void main(String[]); public static String main(); public static String main(String[]); public static byte[] main(); public static byte[] main(String[]);
Arguments are passed by setting the corresponding
smLaunchArgument
. If this argument contains no
ASCII control characters, It is treated as an argument list
and parsed for single arguments separated by whitespaces.
Then the list of arguments is passed as a string array with
one element per argument. If there are control characters contained
in the argument, the string array passed to the script contains
the whole argument as a single string.
While the name of the main method is fixed, the name of the class
containing this method is not. Although based on JDK1.1.5,
Jasmin already supports the Main-Class:
entry in the
jar file manifest (see Storing the Main Class in
the Manifest), which is introduced by JDK1.2.
This entry indicates the main class. If there is no such entry in
the manifest, the main class must be called Main
as in the example above.
For communication between a running script and the SNMP agent we provide a class handling intermediate results and a special exception class.
So far, we have discussed only one way of producing script output: returning a result from the main method. In general, a script can use more sophisticated ways, e.g. by establishing a network connetion to a management application (presuming a security profile allowing network connections). But there are two more basic ways to communicate via the SNMP agent.
A script can produce an intermediate result which is
observable for the manager when getting the corresponding
smRunResult
even while the script is still running. In addition
to the intermediate result, a smScriptResult
notification can be requested.
Sending intermediate results to the agent is supported by
class JasminResult
which offers four static methods:
public class JasminResult { public static void deliver(String str) { ... } public static void deliver(byte[] bytes) { ... } public static void notify(String str) { ... } public static void notify(byte[] bytes) { ... } }The methods called
deliver
take a string or a byte
array, convert it to an OCTET STRING, and deliver it to the SNMP agent.
A very simple script producing intermediate results is
/** * Send 8 intermediate results given by the first * argument. Wait 10 seconds between two results. **/ public class Intermediate { public static void main() { for ( int i = 8; i > 0; i--) { // Send the intermediate result as a string. JasminResult.deliver(i+"0 seconds left"); // Sleep 10 seconds. try { Thread.sleep(10000); } catch(InterruptedException e) { } } } }This script delivers an intermediate result every 10 seconds. The delivered string indicates the remaining lifetime of the script.
Thread.sleep()
may throw an InterruptedException
which has to be caught. If we replace in the example above
method deliver
by method notify
,
then the SNMP agent will not only keep the passed intermediate result,
but also generate an SNMP smScriptResult
notificaton.
The class file JasminResult.class
must be in your
class path when compiling a script. The easiest way is to get it
here or at the
Jasmin home page and just put it in the same directory
as your script sources.
If a script throws an uncaught exception, the Jasmin runtime
system terminates the script and sets the exit code to
the value defined for runtimeError
in the Script MIB.
You can change this behaviour by throwing
a JasminInvalidArgumentException
. When receiving
this exception, the runtime system will set the script's exit
code to invalidArgument
. So, this exception should
only be used when the script arguments are checked.
It is subtype of java.io.RuntimeException
and does
not have to be declared by the signature of the throwing method.
public class JasminInvalidArgumentException extends java.lang.RuntimeException { JasminInvalidArgumentException() { ... } JasminInvalidArgumentException(String message) { ... } }This exception should not be confounded with
java.lang.IllegalArgumentException
which is thrown
when the list of formal arguments of a method does not match
a passed argument list. You can get the class file for
JasminInvalidArgumentException
here
or at the
Jasmin home page.
The Script MIB handles scripts as single units which e.g. can be downloaded from an HTTP server. Since a general Java program consists of a set of class files, Jasmin supports the jar file format for scripts. All class files of a script have to be stored in a single jar file. The jar tool which is part of Sun's JDK has a syntax similar to the tar program on Unix systems.
You can create a sript from Java class files by typing
jar cf <script file name> <list of class files>If we take our first example defining
class Main
and store it in a file called Main.java, we create script file
called myFirstScript by typing
javac Main.java jar cf myFirstScript.jar Main.classNow, we can install and run myFirstScript.jar as a script at a Jasmin SNMP agent.
For our second exaple some more effort is necessary. Since the name of the main class is not Main, an entry in the jar file manifest is required.
Starting with version 1.2, the JDK supports executable jar files which indicate the main class name in the jar file manifest. The manifest keeps meta-information about the jar file which is mainly used for security. But it also may contain a line such as
Main-Class: Intermediateindicating that
Intermediate
is the name of the main
class in this jar file. Although Jasmin is based on JDK1.1.5, it
already supports the Main-Class:
entry.
In order to add this line to the manifest
you have to create a file containing this line only. If you call
this file mainclass, you create a Jasmin script from the second code
example created by
javac Intermediate.java jar cfm mySecondScript mainclass \ Intermediate.class JasminResult.classPlease note that file JasminResult.class has to be included in the jar file, because class
Intermediate
makes use
of class JasminResult
.
The runtime environment for Jasmin scripts offers the JDK1.1.5 packages. Hence, Jasmin scripts should be compiled with a JDK1.1.X, preferably JDK1.1.5 or higher. The following table shows which packages of JDK1.1.5 are available for scripts.
|
|
Although all supported packages are available, some of the classes in the packages might not be usable for a script depending on its runtime security profile (see the following section).
In Java, each access to crucial system resources, e.g. files or network connections, is mediated by the Java virtual machine and is checked in advance by the security manager or the class loader. A Jasmin security manager and a special Jasmin class loader restrict access of scripts to resources of the executing host.
Each running script is associated with a runtime security profile specifying the access rigths of that script. Security manager and class loader interpret the runtime security profile when deciding on whether access to a resource is granted to a script or not.
This implies that a script may run well with a specific runtime security profile while it may fail when running with another, more restricted profile. Currently, only two profiles have been implemented: "master" allows almost everything except manipulating other concurrently running scripts, and "untrusted" forbidding access to almost all resources.
When a script tries to violate its runtime security profile,
a JasminSecurityException
is thrown. It is a subtype
of java.lang.SecurityException
and therefore does
not have to be declared by methods potentially throwing it.
However, it can be caught as a
java.lang.SecurityException
by a script trying
to access a resource (see example script
CaughtSecurityViolation.jar
).
Like all other language features, also Java threads are available for Jasmin scripts. For each Jasmin script an own tread group is created in which the script is started as a thread, the script's main thread. The main thread executes the main method. It can create new threads and thread groups within its thread group. To all threads of a script the same runtime security policy is applied. However, there are a few differences in thread handling which are discussed in this section.
We defined the result of a script to be the return value of the main method. So, further threads created by the main thread can produce results either by communicating them to the main thread or by sending intermediate results.
Since it would be a quiet confusing situation if a thread produced intermediate results after the main method has returned the script result, we do not allow threads to continue after the main thread has been terminated. All other threads running at this point of time are terminated by the Jasmin runtime engine.
However, there is no problem for different threads to use
intermediate results concurrently. The library class
JasminResult
is thread save and all sent intermediate
results are delivered to the SNMP agent.
If one of the script's threads throws an uncaught exception, the Jasmin runtime engine terminates not only this thread, but the whole script and sets the scripts exit code according to the thrown exception (see Section 8). If the exception has not been thrown by the main thread of the script, the message in the script result will explicitely state this. (See Section 10.4 for examples.)
There is one exception, java.lang.ThreadDeath
,
for which this is not true. The JDK provides this exception for
"silently" terminating a thread without any notification of the user.
In Jasmin scripts this exception is used to "silently" terminate a
thread without terminating the whole script. This might be quiet
desirable in many situations, e.g. if a thread detects that it failed
to complete its task and should be terminated. You can compare such
a situation to a single-threaded Java program calling
System.exit()
). However, if the main thread
throws this exception, the whole script will be terminated.
(To be precise: java.lang.ThreadDeath
is in the
Java terminology not really an exception but an error.)
Terminating a script by calling System.exit()
is not desirable for Jasmin scripts. For plain Java programs, this
call not only terminates the program, but also terminates the Java
virtual machine. This is not acceptable for Java scripts, since the
JVM might be executing more than one script and all of these scripts
would be terminated, if one of them called System.exit()
.
In order to prevent this, a security exception is thrown, if a script
tries to calls this method. So, the call really terminates
the script, but does not produce the desired exit code.
Anyway, the possible exit codes of a script are predefined
by the Script MIB.
For a script terminating regularly, the exit code noError
is generated (corresponding to the definition of exit codes
in the Script MIB). If a script is halted by setting the corresponding
smRunControl
object to abort
, its exit
code will be halted
. If it is aborted, because the life
time has expired, the exit code will be lifeTimeExpired
.
Exit code languageError
is generated, if no main class
can be found in the jar file or if the main class does not contain
a method main
wit a valid signature.
All other exit codes are triggered by exceptions thrown by the script. The following table lists all possible exit codes and the condition which produced them.
Exit Code | Condition | Remark |
---|---|---|
noError (1) |
terminated regularly | |
halted (2) |
explicitely aborted by SNMP client | |
lifeTimeExpired (3) |
life time has expired | |
noResourcesLeft (4) |
script has thrown exception OutOfMemoryError
or StackOverflowError |
see examples OutOfMemory.jar
and StackOverflow.jar |
languageError (5) |
main class not found or no valid method main
| see Section 2.1 |
runtimeError (6) |
script has thrown any exception not explicitely mentioned in this table | |
invalidArgument (7) |
an argument has been passed to a script that does not take any
argument, or the script has thrown a
JasminInvalidArgumentException |
see Section 3.2 and example
ReturnFirstArgument.jar
|
securityViolation (8) |
script has thrown a security violation exception | see Section 6 and examples in section Section 10.1 |
Endless.jar
and
Busy.jar
.
This section lists and explains the permanent scripts installed at the Script MIB demo web page These are very simple scripts each demonstrating a basic action. They are intended to support studying the interactions between SNMP managers, SNMP agents, and scripts. They demonstrate returning results, passing arguments, producing intermediate results, and multi-threading in scripts.
This subsection introduces basic scripts demonstrating the return of results by a script. Results can be returned regularly or thrown as an exception. For results returned from multi-threaded scripts see subsection Multi-Threading, for intermediate results of scripts see subsection Intermediate Results.
public class EmptyScript { public static void main() { return; } }After termination, the
smRunResult
is empty and
the smExitCode
of the script is noError
.
public class StringResult { public static String main() { return "This is the script result."; } }Now, the value of
smRunResult
after termination
is "This is the script result.". As above, the
smExitCode
of the script is noError
.
public class ByteArrayResult { public static byte[] main() { return new byte[] { (byte)0x01, (byte)0x23, (byte)0xCD, (byte)0xEF }; } }After termination of this script, its
smRunResult
contains the four bytes of the byte array created by the script. Again,
the smExitCode
of the script is noError
.
public class SystemException { public static void main() { int[] a = new int[2];; a[7] = 11; } }It produces the
smRunResult
:
"Script exception: java.lang.ArrayIndexOutOfBoundsException: 7".
The smExitCode
of the script is runtimeError
.
UserException.jar
public class UserException { public static void main() { throw new RuntimeException( "my exception message"); } }produces the output "Script exception: java.lang.RuntimeException: my exception message" with exit code
runtimeError
.
There are only a few exceptions producing different exit codes.
These are listed in Section 8.
RuntimeException
does not have to be declared when
thrown by a method. This is not the case for many of the common
exceptions in Java. The following script throws a declared exception.
public class DeclaredException { public static void main() throws Exception { throw new Exception( "my exception message"); } }We see that the main method may additionally to the possible signatures listed in Section 2.1 can declare a list of exceptions that may be thrown.
A JasminSecurityException
is thrown when a script tries
to access a resource which it is forbidden to. There are several
resources for which access depends on the runtime security profile,
but in order to be sure to get a JasminSecurityException
,
we try to do something in this example, that no script is allowed to
do.
Calling System.exit()
would terminate the Java virtual
machine and thereby the entire Java runtime engine including all
concurrently running scripts. Therefore, this call is forbidden to
all scripts.
public class SecurityViolation { public static void main() { System.exit(0); } }The output of this script is: Script exception: security violation: Thread[5,4,5] tried to exit runtime engine. and the exit code is
securityViolation
.
A JasminSecurityException
is a subclass of
java.lang.SecurityException
which in turn is a
subclass of java.lang.RuntimeException
.
Therefore, it does not have to be declared by a method potentially
throwing it. However, it can be caugth by a script testing its access
rights. Since the class JasminSecurityException
is not
directly accessible to scripts, the exception must be caught as a
java.lang.SecurityException
.
public class CaughtSecurityViolation { public static void main() { try { System.exit(0); } catch(java.lang.SecurityException e) { // Handle the situation. } } }This script returns regularly without any message. In this way e.g. accessibility of files can be tested without terminating the script on a
JasminSecurityException
.
public class Endless { public static void main() { while (true) ; } }
public class ReturnFirstArgument0 { public static String main(String[] args) { if (args.length >= 1) { return args[0]; } return "Error: no argument passed"; } }An argument check required here is the lenght of the number of passed arguments. If it is 1 or greater, the first argument is returned as the script result. Otherwise, a String containing an error statement is returned. This does not seem to be the most proper way of reporting the error, because the exit code of the script is
noError
in each case.
public class ReturnFirstArgument { public static String main(String[] args) { if (args.length > 0) { return args[0]; } throw new JasminInvalidArgumentException( "no argument passed"); } }Now, the exit code of the script is
invalidArgument
if you invoked the script without any argument.
Because the script has been terminated by an exception,
the smRunResult
contains the following: "Script
exception: invalid arguments: no argument passed".
Timer.jar
does nothing but being inactive for a passed number of seconds.
public class Timer { public static String main(String[] args) { if (args.length > 0) { try { int seconds = Integer.parseInt(args[0]); try { Thread.sleep(1000 * seconds); } catch (InterruptedException e) { } return ("Script Timer slept " + seconds + " seconds."); } catch (NumberFormatException e) { throw new JasminInvalidArgumentException( "script requires integer argument"); } } throw new JasminInvalidArgumentException( "no argument passed"); } }The first argument is converted to an integer. If convertion fails, e.g. if there are no digits in the argument, a
NumberFormatException
is thrown by the convertion method
Integer.parseInt()
.
This exception is caught by the script and converted to a
JasminInvalidArgumentException
. So, if you run the program with
argument "hello"
, the result will be "Script exception:
invalid arguments: script requires integer argument".
Timer.jar
is Busy.jar
.
It also runs for a certain amount of time which is given by the
script argument, but instead of sleeping quietly as
Timer.jar
does, it creates high computing load by
continuously polling the system timer. By this, it might delay
the execution of concurrent scripts.
public class Busy { public static void main(String [] args) { if (args.length != 1) { throw new JasminInvalidArgumentException( "no argument passed"); } try { int seconds = Integer.parseInt(args[0]); long time = new java.util.Date().getTime(); time += 1000 * seconds; while (new java.util.Date().getTime() < time); } catch (NumberFormatException e) { throw new JasminInvalidArgumentException( "script requires integer argument"); } } }
Intermediate.jar
produces a passed number
of intermediate results waiting 10 seconds between two of them.
public class Intermediate { public static String main(String [] args) { if (args.length > 0) { try { int loops = Integer.parseInt(args[0]); for (int i = loops; i > 0; i --) { JasminResult.deliver(i+"0 seconds left"); try { Thread.sleep( 10000); } catch (InterruptedException e) { } } return ("Script Intermediate sent " + loops + " intermediate results"); } catch (NumberFormatException e) { throw new JasminInvalidArgumentException( "script requires integer argument"); } } throw new JasminInvalidArgumentException( "no argument passed"); } }
Trigger.jar
and
Intermediate.jar
is the method
JasminResult.notify()
replacing
JasminResult.deliver()
.
public class Trigger { public static String main(String [] args) { if (args.length > 0) { try { int loops = Integer.parseInt(args[0]); for (int i = loops; i > 0; i --) { JasminResult.notify(i+"0 seconds left"); try { Thread.sleep(10000); } catch (InterruptedException e) { } } return ("Script Intermediate sent " + loops + " intermediate results"); } catch (NumberFormatException e) { throw new JasminInvalidArgumentException( "script requires integer argument"); } } throw new JasminInvalidArgumentException( "no argument passed"); } }
public class ByteArrayTrigger { public static String main(String [] args) { if (args.length > 0) { try { int loops = Integer.parseInt(args[0]); for (int i = loops; i > 0; i--) { int byteValue = i; byteValue = byteValue > 255 ? 255 : byteValue; byteValue = byteValue > 127 ? byteValue - 256 : byteValue; JasminResult.notify( new byte[] { (byte)byteValue }); try { Thread.sleep(10000); } catch (InterruptedException e) { } } return ("Script Trigger sent " + loops + " notifications."); } catch (NumberFormatException e) { throw new JasminInvalidArgumentException( "script requires integer argument"); } } return ("Script Trigger returned immediately."); } }
This is just a simple script using two threads. The main thread creates and starts another one, which is realized by an anonymous inner class.
public class TwoThreads { private static String result = null; public static String main() throws InterruptedException { Thread otherThread = new Thread() { public void run() { result = "thread result"; } }; otherThread.start(); try { otherThread.join(); } catch (InterruptedException e) { throw e; } return result; } }The created thread produces a result which it writes to a static variable. After it has terminated, the result is returned by the main thread. While waiting for the created thread to terminate using method
join()
, the main thread may be interrupted
by an InterruptedException
. This exception is thrown
again in order to report the interruption.
Please note that when creating a jar file for a script with an
anonymous inner class as the one above, not only the file
TwoThreads.class
has to be included in the jar file,
but also the class file TwoThreads$1.class
which
contains the inner class.
public class ExceptionInThread { public static void main() { Thread throwIt = new Thread() { public void run() { throw new RuntimeException(); } }; throwIt.start(); try { throwIt.join(); } catch (InterruptedException e) { } } }This script produces the result: "Script exception in subthread: java.lang.RuntimeException". The message states that the exception was not thrown by the main thread.
ThreadDeath
:
public class ThreadDeathScript { public static void main() { Thread throwIt = new Thread() { public void run() { throw new ThreadDeath(); } }; throwIt.start(); try { throwIt.join(); } catch (InterruptedException e) { } } }So, this script terminates regularly with an empty result, because
ThreadDeath
terminated only the created thread.