Skip to Content
Technical Articles

How to deal with AWS Image and SAP Business Objects

Hello, my name is Julien Bras, I am a product manager for the company GB&Smith, SAP partner  that deliver software around SAP BusinessObjects solutions. I want to share some of our experience about working with SAP BusinessObjects platform on cloud systems like Amazon Web Services.

We have changed a lot our way to deal with servers in the last years, as lot of SAP customers or partners I imagine. From dedicated servers hosted on site or in a datacenter, we have now 100% of our IT managed directly by cloud providers, like AWS. It simplify the server management and server provisioning. We are mainly using the EC2 service, that is providing virtual servers to customer with minimal management for us.

To develop and deliver our software to our customers, we are working a lot with multiple versions of SAP Business Objects, from XIr2 to Bi4.3. There is a lot of major versions / service packs ! Our goal is to deliver products that can work without issues on each SAP BusinessObjects version. So we are using a lot EC2 images (AMI – Amazon Machine Image) to store pre-configured versions of SAP BusinessObjects. This allow our development team to test easily our product on a specific version of BOBJ:

AMI%20list

AMI list, from AWS Console (credit Julien Bras)

In a matter of minutes, you can start a EC2 instance from a specific AMI, and be able to reproduce this strange customer issue.

But as we create an instance from an AMI, we are hitting issue when working with the BOBJ server remotely (from an IDE for example):

  • A developer is connecting via BOBJ Java SDK to a remote BOBJ server using the BOBJ server IP (1.2.3.4) on CMS port (6400 by default)
  • The CMS is responding by giving to the client the list of available CMS on the cluster, even if there is only one node. And it give a fully qualified DNS  that is by default not resolved on the developer device (on AWS it give something like the computer name

The same for RESTful API url: we are sometime programmatically retrieve this URL from a Java SDK, to then use the REST API. If the URL set in CMC / Applications / RESTful API is not correct then we have some problems…

There is an easy fix for this issue, it is possible to force BOBJ to use a specific adress when sending back the list of CMS available on the cluster:

BOBJ%20CMS%20configuration%20%28credit%20Julien%20Bras%29

BOBJ CMS configuration (credit Julien Bras)

But this fix need to be done each time our developers start an instance from an image, and it can be multiple time a day. To save again a couple of minutes we have decided to develop a small tool that automatically set this configuration when the instance start from the first time.

  • we have decided to use the Program object available in the CMC to run a piece of code at instance start
  • the piece of code is setting the AWS EC2 Private IP in CMS Service, FRS Service, RESTful URL and WebServices URL.

For reference here is the Java Classes of the tool:

The main class BobjIpManager:

package com.gbs.tools;

import java.net.Inet4Address;
import com.crystaldecisions.sdk.exception.SDKException;
import com.crystaldecisions.sdk.framework.IEnterpriseSession;
import com.crystaldecisions.sdk.occa.infostore.IInfoObject;
import com.crystaldecisions.sdk.occa.infostore.IInfoStore;
import com.crystaldecisions.sdk.plugin.desktop.program.IProgramBaseEx;
import com.crystaldecisions.sdk.properties.IProperty;

public class BobjIpManager implements IProgramBaseEx {

    @Override
    public void run(IEnterpriseSession enterpriseSession, IInfoStore infoStore, IInfoObject programInfoObject, String programInstanceID, String[] args)
            throws SDKException {
        try {
            // Indicate that if there is an error when running the java program object, then the status of
            // the program object can be set to failure.
            programInfoObject.getProcessingInfo().properties().add("SI_PROGRAM_CAN_FAIL_JOB", Boolean.TRUE, IProperty.DIRTY);
            programInfoObject.save();
            
            String host = Inet4Address.getLocalHost().getHostAddress();
            System.out.println("Current host ip: " + host);

            Tools.setLocalIpOnBobjServer(infoStore, host);
            Tools.setLocalIpOnRestfulWebService(infoStore, host);
            Tools.setLocalIpWebService(infoStore, host);
        } catch (Exception e) {
            // We catch an exception here.  When there is an exception, we are going to
			// treat that as a program error.  When there is a program error, we want
			// the program object instance to have a Status of "Failed".  In order to do
			// that, we write "PROCPROGRAM:PROGRAM_ERROR" and "62009" to the System.out
			// and then we can write up to 3 lines to the System.out.
			// At the end, we do a System.exit(1) and the Status of this program
			// object will now be "Failed".
            System.out.println("PROCPROGRAM:PROGRAM_ERROR");
            System.out.println("62009");
            // On the first line, print out the error to the System.out:
			// The first line appears as the error message when the "FAILED" status
			// of the instance is clicked on the History page.
            System.out.println(e);
            // We will print out up to two extra lines of information.  These two
			// extra lines are not seen when clicking on the "FAILED" status  on the
            // instance History page
            
            // All three lines of information can be seen in the SI_STATUSINFO property
			// for the program instance object.
            // System.out.println("Optional line 1.");
            //System.out.println("Optional line 2.");
            // Exit the system so that the Status is set to "Failed".
			System.exit(1);
        }
    }
}

 

An helper class named Tools:

package com.gbs.tools;

import java.net.UnknownHostException;

import com.businessobjects.sdk.plugin.desktop.common.IConfiguredContainer;
import com.businessobjects.sdk.plugin.desktop.common.IExecProps;
import com.businessobjects.sdk.plugin.desktop.restwebservice.IRestWebService;
import com.businessobjects.sdk.plugin.desktop.webservice.IWebService;
import com.crystaldecisions.sdk.exception.SDKException;
import com.crystaldecisions.sdk.occa.infostore.IInfoObjects;
import com.crystaldecisions.sdk.occa.infostore.IInfoStore;
import com.crystaldecisions.sdk.plugin.desktop.server.ExpectedRunState;
import com.crystaldecisions.sdk.plugin.desktop.server.IServer;

public class Tools {

    public static void setLocalIpOnBobjServer(IInfoStore infostore, String host) throws SDKException, UnknownHostException {
        String serverQuery = "SELECT * FROM CI_SYSTEMOBJECTS WHERE SI_KIND='Server' AND SI_SERVER_KIND in ('aps', 'fileserver')";
        IInfoObjects servers = infostore.query(serverQuery);

        for(int i = 0; i < servers.size(); i++) {
            IServer server = (IServer) servers.get(i);
            System.out.println("Working on: " + server.getName());
            // Get the server command parameter
            IConfiguredContainer cfgContainer = server.getContainer();
            IExecProps execProps = cfgContainer.getExecProps();

            // Check if the current arguments contains correct host
            if (containCorrectHost(server.getServerKind(), execProps.getArgs(), host)) {
                System.out.println("Host is already set: " + execProps.getArgs());
            } else {
                // we need to replace/add
                execProps.setArgs(replaceHost(server.getServerKind(), execProps.getArgs(), host));
                server.setExpectedRunState(ExpectedRunState.RESTART);
                server.save();
                System.out.println("Change is done! New command line after change: " + execProps.getArgs());
            }
        }
    }

    /**
     * Fix the Web Service API
     * 
     * @param infoStore
     * @throws UnknownHostException
     * @throws SDKException
     */
    public static void setLocalIpWebService(IInfoStore infoStore, String host) throws SDKException {

        String applicationsQuery = "SELECT * FROM CI_APPOBJECTS WHERE SI_KIND='WebService'";
        IInfoObjects applications = infoStore.query(applicationsQuery);

        for(int i = 0; i < applications.size(); i++) {
            IWebService webService = (IWebService) applications.get(i);
            String url = "http://" + host + ":8080/dswsbobje";
            if(host.equals("")) {
                url = "http://localhost:8080/dswsbobje";
            }
            System.out.println("Set WebService URL: " + url);
            webService.setURL(url);
            webService.save();
        }
    }

    /**
     * Fix the RESTful API
     * 
     * @param infoStore
     * @throws UnknownHostException
     * @throws SDKException
     */
    public static void setLocalIpOnRestfulWebService(IInfoStore infoStore, String host) throws SDKException {
        String applicationsQuery = "SELECT * FROM CI_APPOBJECTS WHERE SI_KIND='RestWebService'";
        IInfoObjects applications = infoStore.query(applicationsQuery);

        for(int i = 0; i < applications.size(); i++) {
            IRestWebService restWebService = (IRestWebService) applications.get(i);
            String url = "http://" + host + ":6405/biprws";
            if(host.equals("")) {
                url = "http://localhost:6405/biprws";
            }
            System.out.println("Set RESTful URL: " + url);
            restWebService.setURL(url);
            restWebService.save();
        }
    }

        /**
     * 
     * @param serverKind
     * @param args
     * @return true if args contains a valid hostname
     */
    public static boolean containCorrectHost(String serverkind, String args, String host) {
        if(serverkind.equals("aps") && host.equals("")) {
            return args.contains("-port 6400");
        }
        else if(serverkind.equals("aps")) {
            return args.contains("-port " + host + ":6400");
        }
        if(serverkind.equals("fileserver") && host.equals("")) {
            return !args.contains("-port");
        }
        else {
            return args.contains("-port " + host);
        }
    }

    /**
     * 
     * @param serverKind
     * @param args
     * @param host
     * @return the args with hostname
     */
    public static String replaceHost(String serverKind, String args, String host) {
        if(serverKind.equals("aps") && host.equals("")) {
            return args.replaceAll("-port (.*)6400", "-port 6400");
        }
        else if(serverKind.equals("aps")) {
            return args.replaceAll("-port (.*)6400", "-port " + host + ":6400");
        }
        else {
            if(host.equals("")) {
                return args.replaceAll("-port (.*)", "");
            }
            else {
                if(args.contains("-port")) {
                    // if existing value
                    return args.replaceAll("-port (.*)", "-port " + host);
                }
                else {
                    //if no existing value
                    return args + " -port " + host;
                }
            }
        }
    }
    
}

 

Using the 2 classes, it is possible to generate a jar file that can be used by BOBJ as a ‘Program’ (you can use any IDE, like Eclipse for example for this task).

Then you can add the tool to the BOBJ system:

  • Logon the CMC of the system you want to install it
  • Create a Tools folder : right click -> New Folder
  • Inside the folder create a Program : Right Click -> Add -> Program File
  • Provide the jar file and set Program Type to Java
  • Set the class to run: Go to Schedule -> Program Parameter : com.gbs.tools.BobjIpManager
  • Go to Recurrence : Schedule it Once for H+1
  • Create quickly an AMI from this instance (don’t wait an hour !)
  • When an instance will be created from this very same image, the program will be launched (because BOBJ is launching all pending jobs) and the correct IP will be setup, CMS will be restarted. This action occurs only once.

Finally I can say that this process is helping a lot our resources to focus on our products instead on how to configure a SAP BusinessObjects platform each time we need need to work on a different version. It is now part of our standard process when creating pre-configured images. I think that the Program object on SAP BusinessObjects is a very powerful way to enhance the platform, and it can help to automatize manuals steps. I am looking forward to share with you other useful programs like this one !

Hope this little script can help some folks that are using AWS or other cloud solution ! Let me know in the comment section below if you use any other trick (Program object or other) to help you with SAP BusinessObjects !

6 Comments
You must be Logged on to comment or reply to a post.
    • Hello Denis,

      If the IP is not set directly on the Central Management Server, we are hitting issue when working with the BOBJ server remotely (from an IDE for example):

      • A developer is connecting via BOBJ Java SDK to a remote BOBJ server using an IP (1.2.3.4) on CMS port (6400 by default)
      • The CMS is responding by giving to the client the list of available CMS on the cluster, even if there is only one node. And it give a fully qualified DNS  that is by default not resolved on the developer device (on AWS it give something like the computer name)
      • Setting private IP address in the CMS service change this, and the CMS is responding by sending back this IP that can be resolved by the developer computer

      The same for RESTful API url: we are sometime programmatically retrieve this URL from a Java SDK, to then use the REST API. If the URL set in CMC / Applications / RESTful API is not correct then we have some problems…

      The goal of the article is more to show how the little Program object is a very powerful tool to save some time (here it save the configuration time + some training on all our development team 😉 )

      Thanks for your comment it may clarify the article !