Tuesday, October 03, 2006

Actuate: Program to Start Cluster Nodes on Reboot

One of the biggest complaints I heard during my time in Boston during the 2006 Actuate Users Conference was a lack of information in general on the web pertaining to Actuate Products. I totally agree. Do a search on Google for Apache and you can find hundreds of How-Tos, FAQs, Configuration Examples, Customizations, deployment examples, and just tons of information. Do a search for Actuate, and you will find press releases, and some obscure user forums with unanswered questions. I thought this was odd considering Actuates best of breed reputation and the large turnout at the users conference. I told people I would try my best to get more Actuate focused articles out there, so I was excited when I wrote this article.

For some time now, ever since we started our Actuate Cluster, we have been plagued by a particular issue. Since these are Windows servers, we schedule a weekly reboot. Now while I have heard that the need to reboot Windows servers is a myth, lets just that in practice I have yet to dispel that myth. Even it is a falsehood, it doesn’t seem to cause any harm. The problem here is that when the servers reboot, the child nodes on the cluster do not automatically restart or rejoin the cluster. While that is a pain, we have been fortunate since our cluster only has two nodes. There are others that have much larger nodes, and manually restarting all the nodes can be a big pain. Since this is one of the most requested fixes, I did a little mucking around, and came across some of the undocumented Actuate SOAP calls that will help to solve that little issue. One thing to take note of, because it is undocumented that also means it is unsupported, and they can pull the rug out at any time.

This article will show the source code I used to correct this issue. It demonstrates a couple of different things. First, it shows in Visual Basic .Net 2005 Express, how you can connect to an Actuate 8 SP1 Fix 3 servers SOAP ports and retrieve the exposed web services. It will also demonstrate how a SOAP message can be constructed using the StringBuilder class. Originally I had used the XmlDocument object to manually build, however I removed that big of code to save on sanity and create an easier to manage code base. It will also demonstrate how to use HttpRequest object to send the message, because after all, SOAP is nothing more than XML over HTTP.

First, create a new Console Application from the File/New menu. I called my project ActuateServerRestartConsole.

Once the project has been started, I need to add the Actuate Servers exposed SOAP API. In order to do this, I go up to the Project/Add Web Reference menu. This starts a simple dialog that prompts for a URL. By default, an Actuate 8 will use port 8000 as its exposed SOAP service, and the WSDL file describing the available services is under the /wsdl folder. Since I am connecting to a server called BISSW005, the url will look something like: http://bissw005:8000/wsdl. Once I put this URL in, I get a description page with some various platforms. I chose the Actuate 8, Visual Basic Link to get the exposed services for this version of Actuate that are formatted for VB .Net. One the next page I choose All to get a collection of all the Actuate API’s. Although I only need a few of the API’s, I have had limited success with selecting individual API’s. Once the API list is shown, I click on Add Reference.

Then, I wrote the following program:

Module Module1
    'Send a message to the server
    Private Function sendMessageToServer(ByVal message As String, ByVal server As String) As String
        Dim ht As System.Net.HttpWebRequest
        Dim enc As System.Text.Encoding
        Dim buffer(4000) As Byte

        'We need the enc object to convert Bytes to Strings and the other way around
        enc = New System.Text.ASCIIEncoding

        'Create our new HTTP Web Request and set the appropriate header information
        ht = System.Net.HttpWebRequest.Create(server)
        ht.Method = "POST"
        ht.ContentType = "text/xml;charset=""utf-8"""
        ht.ContentLength = message.Length
        ht.Headers.Add("SOAPAction", "")

        'Send the request and get the response, and store in the buffer
        ht.GetRequestStream.Write(enc.GetBytes(message), 0, message.Length)
        ht.GetResponse.GetResponseStream.Read(buffer, 0, 3000)

        'Return the result and free memory
        sendMessageToServer = enc.GetString(buffer)
        enc = Nothing
        ht = Nothing
    End Function

    'Message to bring the server online
    Private Function bringOnlineMessage(ByVal servername As String, ByVal authID As String) As String
        Dim sb As New System.Text.StringBuilder

        'Use a string buffer to build up the string, then return to caller
        sb.AppendLine("<?xml version=""1.0"" encoding=""UTF-8""?>")
        sb.AppendLine("<SOAP-ENV:Envelope xmlns:SOAP-ENV=""http://schemas.xmlsoap.org/soap/envelope/"" SOAP-ENV:encodingstyle=""http://schemas.xmlsoap.org/soap/encoding"" xmlns:xsd=""http://www.w3.org/2000/10/XMLSchema"" xmlns=""http://schemas.actuate.com/actuate8"" xmlns:SOAP-ENC=""http://schemas.xmlsoap.org/soap/encoding/"">")
        sb.AppendLine("<SOAP-ENV:Header>")
        sb.AppendLine("<AuthId>" & authID & "</AuthId>")
        sb.AppendLine("<Locale>en_US</Locale>")
        sb.AppendLine("</SOAP-ENV:Header>")

        sb.AppendLine("<SOAP-ENV:Body>")
        sb.AppendLine("<SOAP-ACTU:StartServer xmlns:SOAP-ACTU=""http://schemas.actuate.com/actuate8"">")
        sb.AppendLine("<ServerName>" & servername & "</ServerName>")
        sb.AppendLine("<SystemHeartbeatInformation><UseMulticast>false</UseMulticast></SystemHeartbeatInformation>")
        sb.AppendLine("</SOAP-ACTU:StartServer>")
        sb.AppendLine("</SOAP-ENV:Body></SOAP-ENV:Envelope>")

        bringOnlineMessage = sb.ToString
        sb = Nothing
    End Function

    'Message to bring the server offline
    Private Function bringOfflineMessage(ByVal servername As String, ByVal authID As String) As String
        Dim sb As New System.Text.StringBuilder

        sb.AppendLine("<?xml version=""1.0"" encoding=""UTF-8""?>")
        sb.AppendLine("<SOAP-ENV:Envelope xmlns:SOAP-ENV=""http://schemas.xmlsoap.org/soap/envelope/"" SOAP-ENV:encodingstyle=""http://schemas.xmlsoap.org/soap/encoding"" xmlns:xsd=""http://www.w3.org/2000/10/XMLSchema"" xmlns=""http://schemas.actuate.com/actuate8"" xmlns:SOAP-ENC=""http://schemas.xmlsoap.org/soap/encoding/"">")
        sb.AppendLine("<SOAP-ENV:Header>")
        sb.AppendLine("<AuthId>" & authID & "</AuthId>")
        sb.AppendLine("<Locale>en_US</Locale>")
        sb.AppendLine("</SOAP-ENV:Header>")

        sb.AppendLine("<SOAP-ENV:Body>")
        sb.AppendLine("<SOAP-ACTU:StopServer xmlns:SOAP-ACTU=""http://schemas.actuate.com/actuate8"">")
        sb.AppendLine("<ServerName>" & servername & "</ServerName>")
        sb.AppendLine("<SystemHeartbeatInformation><UseMulticast>false</UseMulticast></SystemHeartbeatInformation>")
        sb.AppendLine("</SOAP-ACTU:StopServer>")
        sb.AppendLine("</SOAP-ENV:Body></SOAP-ENV:Envelope>")

        bringOfflineMessage = sb.ToString
        sb = Nothing
    End Function

    'Login to the Actuate Server and return the AuthID for future calls
    Private Function ActuateLogin(ByVal password As String) As String
        Dim login As bissw005.SystemLogin
        Dim api As bissw005.ActuateAPI

        'First thing we need to do is login to the cluster master to get an Auth ID. So login to the cluster
        'master, get the authID and free the memory afterwards
        login = New bissw005.SystemLogin
        login.SystemPassword = "admin"
        api = New bissw005.ActuateAPI

        'Make the call to login and return the AuthID
        ActuateLogin = api.systemLoginOperation(login).AuthId

        'Free memory
        api = Nothing
        login = Nothing
    End Function

    'Get a list of servers and their current status
    Private Function getServerList(ByVal authID As String) As bissw005.ServerInformation()
        Dim api As bissw005.ActuateAPI
        Dim serverCall As bissw005.GetSystemServerList

        'Create an API object, and set the authID so we do not need to re-login
        api = New bissw005.ActuateAPI
        api.HeaderValue = New bissw005.Header
        api.HeaderValue.AuthId = authID

        'Get a list of servers and return the array to the caller
        serverCall = New bissw005.GetSystemServerList
        getServerList = api.getSystemServerListOperation(serverCall).ServerList

        'Free memory
        serverCall = Nothing
        api.HeaderValue = Nothing
        api = Nothing
    End Function

    Sub Main()
        'used to store the XMl message and the AuthID once logged in
        Dim xm As New System.Xml.XmlDocument
        Dim authID As String

        'Objects used to get Server List and determine Online/Offline status
        Dim serverResponse As bissw005.ServerInformation()
        Dim serverInformation As bissw005.ServerInformation

        'First thing we need to do is login to the cluster master to get an Auth ID. So login to the cluster
        'master, get the authID
        Try
            authID = ActuateLogin("admin")
        Catch ex As Exception
            Console.WriteLine("There was an error login into the system")
            Exit Sub
        End Try

        'Get a list of the servers, then go through each server and turn on the server
        serverResponse = getServerList(authID)
        For Each serverInformation In serverResponse
            'Now, with the second request, we need to do something a little different. We need to load the XML
            'message to send, then change the AuthID section to our new AuthID. Once completed, then we send off
            Console.WriteLine("Status of server " & serverInformation.ServerName & ": " & serverInformation.ServerStatusInformation.ServerState.ToString)

            'If there is a command line switch for offline, then turn off child nodes, otherwise, turn them on
            If My.Application.CommandLineArgs.Contains("-offline") Then
                Try
                    'Make sure this server is not our master server and that the server is offline
                    If ((serverInformation.ServerName.ToUpper <> "BISSW005") And (serverInformation.ServerStatusInformation.ServerState = bissw005.ServerState.ONLINE)) Then
                        xm.InnerXml = bringOfflineMessage(serverInformation.ServerName, authID)
                        Console.WriteLine("Attempting to bring server " & serverInformation.ServerName & " offline")
                        Console.WriteLine("Soap Response Message: " & sendMessageToServer(xm.InnerXml, "http://bissw005:8000/").Trim)
                    End If
                Catch ex As Exception
                    Console.WriteLine(ex.Message)
                End Try
            Else
                Try
                    'Make sure this server is not our master server and that the server is online
                    If ((serverInformation.ServerName.ToUpper <> "BISSW005") And (serverInformation.ServerStatusInformation.ServerState = bissw005.ServerState.OFFLINE)) Then
                        xm.InnerXml = bringOnlineMessage(serverInformation.ServerName, authID)
                        Console.WriteLine("Attempting to bring server " & serverInformation.ServerName & " online")
                        Console.WriteLine("Soap Response Message: " & sendMessageToServer(xm.InnerXml, "http://bissw005:8000/").Trim)
                    End If
                Catch ex As Exception
                    Console.WriteLine(ex.Message)
                End Try
            End If
        Next

        'Now, get a list of all the servers and print to the console for any external logging
        serverResponse = getServerList(authID)
        Console.WriteLine("---------------------------")
        Console.WriteLine("Status of servers after run")
        For Each serverInformation In serverResponse
            Console.WriteLine("Status of server " & serverInformation.ServerName & ": " & serverInformation.ServerStatusInformation.ServerState.ToString)
        Next

        'Free memory
        serverResponse = Nothing
        serverInformation = Nothing
        xm = Nothing
    End Sub
End Module

No comments: