WebSphere Application Server Programmierung mit Jython

Basics

Vorwort

Seit einiger Zeit beschäftige ich mich intensiv mit dem Produkt WebSphere Application Server (WAS). Nun, da ich in großen Rechenzentren zu Hause bin und das ewige rumgeklicker in der WebSphere Administrations Konsole leid war fing ich an mich in die WebSphere Programmierschnittstelle einzuarbeiten um laufende, immer wieder kehrende Tasks einfach, schnell, effizient und in gleichbleibender Qualität durchführen zu können.

Da zunächst Jacl als Programmiersprache abgekündigt war, habe ich mich spontan für Jython entschieden. Jython verwendet den Syntax wie Python. Nun komme ich leider aus einer seh komfortablen Programmierecke "Perl" und frage mich immer wieder warum Sie kein Jerl eingebaut haben. :-)
Nun ja, es ist wie es ist. Jython ist zwar nicht so komfortabel, man muss sehr auf Leezeichen achten aber man gewöhnt sich daran. Auf meinem Weg zum Jythoncoder bin ich über viele Jython, Python und WebSphere Homepages gestolpert. Leider findet man dort mehr schlecht als recht vernünftige Informationen. Um nun dem ein wenig entgegenzuwirken möchte ich auf dieser Seite ein paar Beispiele von "Best Paktices" wiedergeben die ich nutze.

Es gibt wohl sicher Teilweise sauberere Implementationen als die hier gezeigten Beispiele, auch haben die hier gezeigten Funktionen nicht den Anspruch auf Vollständigkeit. Aber evtl. geben sie dem ein oder anderen Admin eine einfache Möglichkeit für den Jython Einstieg.

Hinweis: Die hier gezeigten Funktionen können zusammen mit einigen anderen Funktionen als Jython Bibliothek im Bereich Downloads heruntergeladen werden.

Generelle Funktionen - Exception prevention

Die wichtigste aller Funktionen (so finde ich) ist die "AdminConfig.showAttribute(objectID, 'attributename')". Leider wirft sie sofort eine Exception wenn man sie aufruft und es den Attributnamen gar nicht gibt. Daher habe ich 3 kleine Funktionen darum herum geschrieben die diese Fehler vermeiden sollen.

Die 3 Funktionen sind "Trivialfunktionen", sie erledigen nur jeweils eine Aufgabe.

find_valueInArray

Der Funktion "find_valueInArray" übergibt man einen Wert und ein Array. Die Funktion sucht im Array nach dem Wert und gibt "true" zurück wenn der Wert im Array vorhanden ist und "false" wenn nicht.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# find_valueInArray
#   description: searching an value in an array
#   input: value (string), array (array of strings)
#   return: "true" | "false"
#----------------------------------------------------------------------------
def find_valueInArray(value, array):
  # return "false" if value or array is not given to function
  if value == "": return "false";
  if array == "": return "false";

  # check if value is in array
  if value in array: return "true";
  else: return "false";

get_AttributesFromObject

Die Funktion "get_AttributesFromObject" ist dazu da, alle möglichen Attribute dem Namen nach zu suchen. Das tut Sie innerhalb einer übergebenen Objekt ID. D.h. die Funktion gibt am Ende ein Array zurück die alle möglichen Attribute innerhalb eines Objekts abgefragt werden können.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# get_AttributesFromObject
#   description: get an array of all attributes of an object
#   input: objectID
#   return: array of attributes
#----------------------------------------------------------------------------
def get_AttributesFromObject(objectID):
  # return "" if object ID is not given to function
  if objectID == "": return "";

  # define array to hold the attributes
  Attributes = [];

  # get all elements in the object
  for elements in AdminConfig.show(objectID).split(lineSeparator):
    # split attribute and values and get the left side (attribute)
    attribute = elements.split()[0].replace("[", "");
    # if attribute not empty, append the attribute to the array
    if attribute != "":
      Attributes.append(attribute);

  # get array back
  return Attributes;

showAttribute

Die Funktion "showAttribute" ist die eigentliche Funktion zum Auslesen von Attributwerten. Sie ersetzt in allen anderen Programmteilen den Aufruf von "AdminConfig.showAttribute" indem man nur noch "showAttribute" sagt. Die Parametrisierung entspricht der Originalfunktion. Man übergibt Ihr die Objekt ID und den Name des Attributs welches man ausgeben möchte und erhält den Attribut Wert. JEdoch erhält man keine Exception mehr wenn das Attribut nicht im Objekt enthalten ist.
Die Funktion geht so vor. Zunächst holt Sie sich die möglichen Attribute die das Objekt hat. Also diejenigen Attribute die auch abfragbar sind. Hierzu wird die oben beschriebene Funktion "get_AttributesFromObject" verwendet. Diese Funktion gibt uns ein Array zurück, welches man sofort darauf prüft ob das abzufragende Attribut in der Summe der Attribute erhalten ist. Dazu wird die oben beschriebene Funktion "find_valueInArray" verwendet.
Ist nun das abzufragende Attribut Teil der Summe aller vorhandenen Attribute innerhalb des Objekts (Rückgabe von "true"), dann kann man das Attribut gefahrlos Abfragen. Dies wird dann sofort getan und zurückgegeben.
Ist nun das abzufragende Attribut nicht im Objekt vorhanden (Rückgabe von "false"), dann wird ein "nichts" zurückgegeben.

Nun kann man gefahrlos Attribute im WebSphere Abfragen ohne das ein Programmabbruch zu befürchten ist wenn mal ein Attribut nicht existiert.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# showAttribute
#   description: returns the value of an attribute from an object
#   input: objectID, attribute
#   return: value as string | ""
#----------------------------------------------------------------------------
def showAttribute(objectID, attribute):
  # return nothing if objectID or attribute name is not given to function
  if objectID == "": return "";
  if attribute == "": return "";

  # get all attributes in the object and check if the given attribute is
  # part of the objects attributes
  if find_valueInArray(attribute, get_AttributesFromObject(objectID)) == "true":
    # OK, attribute exist, we can request it's value and give that value back
    return AdminConfig.showAttribute(objectID, attribute);
  else:
    # return an empty string if the attribute not exist in the object
    return "";



Generelle Funktionen - default loops

Dieser nächste Abschnitt beschäftigt sich mit Standard Schleifen die ich benutze um einfach und schnell über die WebSphere Infrastruktur zu "loopen".

get_clusterNames

Diese Funktion ermittelt alle im WebSphere Application Server konfigurierten Cluster. Speicher deren Clusternamen in einem Array und gibt am Ende das Array mit den Clusternamen zurück. Die Clusternamen können dann später im Programm weiterverwendet werden um diverse Clusterspezifische Informationen zu bekommen.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# get_clusterNames
#   description: get all cluster names
#   input: -
#   return: array of cluster names
#----------------------------------------------------------------------------
def get_clusterNames():
  # define array
  Clusters = [];

  # loop over Clusters
  for myCluster in AdminConfig.list("ServerCluster").split(lineSeparator):
    if myCluster == "":
      continue;
    myClusterName = showAttribute(myCluster, 'name');
    if myClusterName != "":
      Clusters.append(myClusterName);

  # return the array of node names
  return Clusters;

get_nodeNames

Die Funktion "get_nodeNames" ermittelt die vorhandenen Knoten (Servernodes) innerhalb der WebSpere Installation.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# get_nodeNames
#   description: get the node names from the cell
#   input: -
#   return: array of node names
#----------------------------------------------------------------------------
def get_nodeNames():
  # define array
  Nodes = [];

  # loop over nodes
  for myNode in AdminTask.listNodes().split(lineSeparator):
    # append node name if it is not empty
    if myNode != "": Nodes.append(myNode);

  # return the array of node names
  return Nodes;

get_serverMembersByNodeName

Die Funktion "get_serverMembersByNodeName" ermittelt alle Servermember die auf einem Knoten laufen. Dazu muss man den Knotennamen der Funktion übergeben. Die Funktion filtert nun alle Servermember aus die auf dem Knoten konfiguriert sind. Speichert deren Namen in einem Array und gibt dieses dann zurück.
Diese Funktion kann man sehr gut mit der oben beschriebenen Funktion "get_nodeNames" kombinieren. So erhät man eine hirarchische Abbildung von Nodes zu Servermembern.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# get_serverMembersByNodeName
#   description: get the names from all server members by node
#   input: name of node
#   return: array of names
#----------------------------------------------------------------------------
def get_serverMembersByNodeName(nodeName):
  # return if node name is not given to function
  if nodeName == "": return;

  # make node name to lower case
  nodeName = nodeName.lower();

  # define array
  servers = [];

  # loop over all servers
  for myServer in AdminConfig.list("Server").split(lineSeparator):
    # check if the server is on the given node
    if myServer.lower().find(nodeName) != -1:
      # get the name from the server object
      serverName = showAttribute(myServer, 'name');
      # append server name if it is not empty
      if serverName != "": servers.append(serverName);

  # return the array of server member names
  return servers;

Generelle Funktionen - Sonstiges

Nun, das waren eine wenige kleine Hilfsfunktionen die mir meine tägliche Arbeit vereinfachen. Es gibt nun noch ein paar kleine Funktionen die ich hier erwähnen möchte die ich auch verwende.

get_ObjectTypeByID

Es gibt immer wieder Tätigkeiten bei denen man an Stellen kommt bei denen man den "Scope" ermitteln muss um an weiterführende Objekte heranzukommen. Dieser "Scope" zusammen mit dem Namen des Objekts kann in der WebSphere Funktion "AdminConfig.getid" weiterverarbeitet werden.
Mit hilfe dieser kleinen Funktion kann man nun Funktionen schreiben die auf Cell, Cluster, Node und Server Ebene gleichermaßen arbeiten und gleich anzusprechen sind.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# get_ObjectTypeByID
#   description: extract the object type from the given object ID
#   input: objectID
#   return: objectType
#----------------------------------------------------------------------------
def get_ObjectTypeByID(objectID):
  # check if the objectID is given to function, return nothing if it is not
  if objectID == "": return "";

  # cut the objectID string to it's components
  # cut the string at the first '#' - take the right side of the string
  objectType = objectID.split("#")[-1];
  # cut the string at the first '_' - take the left side of the string
  objectType = objectID.split("_")[0];
  
  # give objectType back
  return objectType;

Anwendungsbeispiel:
Wenn man alle Mailprovider in einem System ausgeben möchte und nur die Mailprovider die auf Serverebene (Zellebene, Clusterebene, Nodeebene) konfiguriert sind, dann, kann man diese Mailprovider mit dem folgenden Scriptbeispiel ausfindig machen:
#----------------------------------------------------------------------------
# get_MailProviderProperties
#   description: search the Mail provider properties by it's given objectID
#   input: objectID (can be cellID, clusterID, nodeID or serverID)
#   output: informations on stdout
#   return: -
#----------------------------------------------------------------------------
def get_MailProviderProperties(objectID)
  print "    Mail Providers:"
  # identify on wich level we are, by analysing the ID
  objectType = get_ObjectTypeByID(objectID);
  # get name of the objectID
  objectName = showAttribute(objectID, 'name');
  # get IDs, searching by scope, and type
  for MailproviderID in AdminConfig.getid('/' + objectType + ':' + objectName + '/MailProvider:/').split(lineSeparator):
    if MailproviderID != "":
      print "      Name: " + showAttribute(MailproviderID, 'name');
    else:
      print "      No Mail provider defined on this level.";

saveAndSyncNodes

Die unerlässliche Funktion die notwendig ist um scriptgesteuerte Veränderungen am System schnell zu speichern und diese auch direkt auf alle Nodes verteilt. Wenn die Funktion aufgerufen wird, wird zunächst die Konfiguration gesichert.
Danach holt sich die Funktion alle Nodes die Synchronisisert werden sollen. Das macht die Funktion mit der oben beschriebenen Funktion "getNodes". Über das zurückgegebene Array von Node Namen wird nun versucht den "Node Object Name" zu ermitteln. Dieser Name wird gebracuht um den "sync" anzustoßen. Denn dieser wird gleich im Anschluß durchgeführt.
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# saveAndSyncNodes
#   description: save configuration and sync nodes
#   input: -
#   return: -
#----------------------------------------------------------------------------
def saveAndSyncNodes():
  # first, save the changes
  AdminConfig.save();

  # loop over all nodes
  for nodeName in get_NodeNames():
    # get the MBean of the node sync object
    nodeMB = AdminControl.completeObjectName("type=NodeSync,node=" + nodeName + ",*");

    # invoke the sync
    AdminControl.invoke(nodeMB, 'sync');

get_configuration

Diese Funktion ist sehr hilfreich wenn man nicht zig Kommandozeilenparameter dem Script übergeben möchte. Gerade für größere Programme kann man so leicht die Funktionalität steuer. Die Funktion liest eine Konfigurationsdatei ein und erzeugt aus der Konfiguration ein 2-Dimensionales assoziatives Array. Einfach in Python zu realisieren (wenn man weis wie).
Die Konfiguration die ausgelesen und verabreitet wird sieht aus wie eine alte, herkömliche Windows INI Datei. Die zu Konfigurierenden Eigenschaften werden in Kapitel gegliedert. Diese Kapitel werden durch eckige Klammern definiert. Die Attribute und Ihre Werete stehen unterhalb des Kapitels und werden durch ein Gleichheitszeichen voneinander getrennt.
Beispiel:
[section1]
attr1=val1
attr2=val2

[section2]
attr1=val1
attr2=val2
Das Assoziative-Array (in Python Dictionary genanannt), welches aus den Daten erstellt wird, sieht dann wiefolgt aus:
myConfigArray[section][attribute] = value
Die Funktion sieht so aus:
#----------------------------------------------------------------------------
# get_configuration
#   description: opens a configuration file and parse it, return the
#     configuration object
#   input: path and filename as string
#   return: associative array:
#     array[section][attribute] = value
#----------------------------------------------------------------------------
def get_configuration(configfile):
# set the variable to hold configuration file
  configHash = {};
# set other variables
  section = "";

# open the configuration file
  FH = open(configfile, "r");

# parse copnfiguration file
  for line in FH.readlines():
#   remove leading and trailing whitespaces
    line = line.strip();
#   ignore empty lines and comment lines
    if line == "": continue;
    if line[0] == "#": continue;
#   find a new section
    if line[0] == "[":
      section = line.lower().replace("[", "").replace("]", "").strip();
#     define section as dictionare
      configHash[section] = {};
      continue;
#   all other lines are attribute/value pairs
#   split at equal char, and set value to lower case
    attribute, value = line.split("=",1);
    value = value.lower();
#   if we are in a section, and an attribute is set append attribute/value pair to section
    if section != "" and attribute != "":
      configHash[section][attribute] = value;

# after parsing the file, close the file
  FH.close();

# give configuration object back
  return configHash;

hacker emblem