Friday May 23, 2008

Use VBScript to cheat at poker with JMX!

This year at JavaOne Eamonn and I presented during our technocal Session, where we stand with respect to JMX. Eamonn covered JMX in general and JMX 2.0, while I covered the Web Services Connector for JMX. At the end of the talk, we performed a demo named “CSPoker, JMX Technology, Java VisualVM and WinRM at the JavaOne Tournament's Final Table”. We linked together a set of JMX related technologies to offer, in an original Online Poker Web Site use case, an interesting setup highlighting the power of JMX (here's a PDF file of the demo architecture) .

At the end of the talk, some people asked me some questions about the WS-Management access to JMX we had just demonstrated. More details on the WS-Management to JMX interoperability can be found in this article.

Here, I am providing some details on the actual script used during the demo. This script automates the Poker Engine monitoring and management tasks. It first registers to listen for JMX Notifications exposed as WS-Eventing notifications. Each time a “Poker table joined” notification is received, it checks that the IP address from which the player joined is not already known to the system. If an IP address is already known, it means that another user already joined from the same host.... which is very bad if you want to avoid the same player using multiple identities....

When such a “bad user” is detected, the script ejects all the players that joined from that machine.

Intialisation of the WS-Management access

dim wsmanObj
set wsmanObj = CreateObject("WSMAN.Automation")
dim objConnectionOptions
set objConnectionOptions = wsmanObj.CreateConnectionOptions
dim iFlags
iFlags = wsmanObj.SessionFlagUseNoAuthentication
dim session
set session = wsmanObj.CreateSession("http://localhost:8080/admin", iFlags)

At this point, you have a WS-Management proxy (the session object), that allows you to interact with a JMX Agent attached to the Web Services Connector.


Subscription to the MBean notifications

The MBean we want to subscribe to is named : cspoker:type=CSPokerControl

Because the WS-Management session doesn't offer a nice API for subscriptions, we need to construct the subscription invocation request using the “all purpose” invoke method :


Dim reply
reply = session.invoke("http://schemas.xmlsoap.org/ws/2004/08/eventing/Subscribe", _
"http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=cspoker:type=CSPokerControl", _
"<wse:Subscribe xmlns:wse='http://schemas.xmlsoap.org/ws/2004/08/eventing'>"_
& "<wse:Delivery Mode='http://schemas.dmtf.org/wbem/wsman/1/wsman/Pull'/></wse:Subscribe>")

At this point we have sent a WS-Eventing subscription request (in PULL mode, meaning that the client will send a request to pull notifications from the server) and received a response. The response contains the WS-Eventing context to be used to retrieve notifications.

Retrieving the Eventing context

To get the eventing context from the response, we need to make use of the VBScript DOM library : Microsoft.XMLDOM

Warning : Because namespaces are not handled well in this DOM library, we are trying to discover the Namespace prefix based on our knowledge of the way they are declared (ns<i>). I am not a VBScript expert and would be very interested to know if there is an equivalent to getElementsByTagNameNS using VBScript???

Function findElement(elem, tagElem)
' wscript.echo "Finding Element " & tagElem
 for i = 0 to 15
  set findElement = elem.getElementsByTagName("ns"& i &":" & tagElem)
  If findElement.Length > 0 Then Exit For
 next
End Function

Dim objXMLDoc
set objXMLDoc = CreateObject("Microsoft.XMLDOM")
objXMLDoc.async = False
objXMLDoc.loadXML(reply)

' Get the Eventing context to use in the next WS-Eventing Pull request
Dim nodeList
set nodeList = findElement(objXMLDoc.documentElement, "EnumerationContext")
dim context 
context = nodeList(0).text
wscript.echo "Subscription Enumeration Context " & context

At this point, the context variable contains the WS-Eventing context that identifies our subscription. This context is to be used to retrieve notifications.

Retrieving the Notifications

This is done by sending a Pull request to the server and providing it the context. The pull request blocks until some Notifications are emitted or the timeout (1 minute by default) is reached. As for subscribe requests, no Pull API is offered. We need to use the general purpose invoke operation. We have written a pull function that handles pull request timeouts properly. When the timeout occurs, instead of exiting the script, the error is trapped and the script continues. Because we expect multiple notifications, the Notification pulling is done in a loop.

Function pull(session, context)
 On Error Resume Next
Dim pullXml
pullXml = "<wsen:Pull xmlns:wsen='http://schemas.xmlsoap.org/ws/2004/09/enumeration'><wsen:EnumerationContext>" & context & "</wsen:EnumerationContext><wsen:MaxTime>PT1M0.000S</wsen:MaxTime><wsen:MaxElements>1000</wsen:MaxElements></wsen:Pull>"
 pull = session.invoke("http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull", "http://jsr262.dev.java.net/MBeanNotificationSubscriptionManager", pullXml)
  On Error Goto 0
End Function

' This piece of script is called in an infinite loop

Dim notifs

 notifs = pull(session, context)

At this point, the notifs variable contains (or does not contain, if the timeout occurred) the JMX notifications in an XML format.

Extracting the Notification content and tracking the cheat

We only do this extraction if the list of notifications is not null. The VBScript DOM library is again used to parse the notifications. Because a pull request can return multiple notifications, we must iterate on all TargetedNotification elements contained in the notification list. A TargetedNotification is an XML representation of a JMX Notification defined by the JSR 262 (JSR in which the Web Services Connector is defined).

In our case, the TargetedNotification/Message XML Element contains the Player name. The TargetedNotification/UserData XML Element contains the IP Address as a string (eg:<jmx:String>192.168.0.1</jmx:String>)

To keep the IP <==> Player relationship, we use a Map data structure and array. In VBScript, we use a dictionnary (scripting.dictionary Libray)

' Create a Map
dim dict
set dict = CreateObject("scripting.dictionary")
Dim ipArray(10)
Dim arrayIndex
arrayIndex = 0

if Not(isNull(notifs)) then

  set objXMLNotif = CreateObject("Microsoft.XMLDOM")
  objXMLNotif.async = False
  objXMLNotif.loadXML(notifs)
  set NodeListNotifs = findElement(objXMLNotif.documentElement, "TargetedNotification")
  WScript.echo "Pull returned " & NodeListNotifs.length & " notifications"

  ' Now that we get the list of notifications, extract the content.
  Dim i
  Dim EventType
  Dim msgObj
  ' Loop over the received Notifications
  For i = 0 To NodeListNotifs.length - 1
      Dim listMsg
      set listMsg = findElement(NodeListNotifs(i), "Message")
      Dim eventMessage 
      eventMessage = listMsg(0).text

      Dim listUserData
      set listUserData = findElement(NodeListNotifs(i2), "String")
      ' We have an IP address
      if(listUserData.length > 0) then
           Dim ipAddress
           ipAddress = listUserData(0).text
           wscript.echo eventMessage & " Joined From " & ipAddress
           if dict.exists(ipAddress) then
              Dim prev
              prev = dict.item(ipAddress)
              if Not StrComp(prev,eventMessage) Eqv 0 Then 
                 dim users
                  users = Array(eventMessage, prev)
                  For Each user In users
                    wscript.echo user & " already connected from same host [" & ipAddress &"], will eject him." '"Type return."
                    wscript.echo "Ejecting player " & user
                    ejectPlayer session, user
                    wscript.echo "Player " & user & " ejected"
                  Next
              end if
           else
             dict.add ipAddress, eventMessage
           end if
      else
         wscript.echo eventMessage
      end if
  Next

Ejecting the player

The function ejectPlayer calls the ejectPlayer MBean operation by making use of the general purpose invoke operation.

Function ejectPlayer(session, playerName)
 dim pokerURI
 pokerURI = "http://jsr262.dev.java.net/DynamicMBeanResource?ObjectName=cspoker:type=CSPokerControl"
 dim xmlInvoke
 xmlInvoke = "<jmx:ManagedResourceOperation name=" & Chr(34) & "ejectPlayer" & Chr(34) & " xmlns:jmx=" & Chr(34) & "http://jsr262.dev.java.net/jmxconnector" & Chr(34) & "><jmx:Input><jmx:Param><jmx:String>" & playerName & "</jmx:String></jmx:Param></jmx:Input></jmx:ManagedResourceOperation>"
 session.invoke "http://jsr262.dev.java.net/DynamicMBeanResource/Invoke", pokerURI, xmlInvoke
End Function


Hope that these VBScript extracts helped you understand better how you can use this path to interoperate with JMX.

Thanks.

Jean-François Denise

jfd@sun.com




        
    
About

jeanfrancoisdenise

Search

Categories
Archives
« May 2008 »
SunMonTueWedThuFriSat
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
24
25
26
27
28
29
30
31
       
Today