Saturday, September 24, 2011

How to make an API call by an external script?

Today I would like to share with you how to make an API call by a C script. Maybe it is worth to list what we will mainly do in the script:
1) Read the configuration file
2) Initialize the session
3) Verify the user we are connecting
4) Change AR Server connection queue
5) Set a flag (Radio button field) on a Remedy form
6) If there is an error while setting the flag, write the error message to another field.
Below is the code we have generated. You can actually change the parameters in blue color with yours.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "area.h"
#include "ar.h"
#include "arerrno.h"
#include "arextern.h"
#include "arstruct.h"
#include "ars_utils.h"


char ErrMsgStatus[2048];


void LogArsStatus(ARStatusList *);


int main(int argc, char * argv[])
{
ARNameType FormName;
ARStatusList arStatus;
ARControlStruct control;
char ProcessName [10];


AREntryIdType entryIdArr[1];
AREntryIdList entryIdL;
ARFieldValueList fieldL;

ARFieldValueStruct process;
ARFieldValueStruct error[2];


char ErrMsg [500];
char ConfigFile [128];
char requestID[16];

strcpy(requestID, argv[1]);
strcpy (ProcessName, "<PROCESS_NAME>");
strcpy(FormName, "<FORM_NAME>");


/*--------------------------------------------------------------------*/
/* Setup configuration file and control structure.
/*--------------------------------------------------------------------*/
strcpy(ConfigFile, "<CONFIG_FILE>");
if ((ConfigInit (ConfigFile)) != OKAY)
{
sprintf(ErrMsg, "Fatal error initialising config file\n");
ERROR (ERR_FATAL, ProcessName, ErrMsg, 0);

exit (1);
}
SetupControl (&control);


/*--------------------------------------------------------------------*/
/* Initialise Remedy ARS session
/*--------------------------------------------------------------------*/
if (ARInitialization(&control, &arStatus) >= AR_RETURN_ERROR)
{   
sprintf(ErrMsg, "Fatal error - initialising ARS session");
ERROR (ERR_FATAL, ProcessName, ErrMsg, 0);
LogArsStatus (&arStatus);
FreeARStatusList(&arStatus, FALSE);
return;
}
FreeARStatusList(&arStatus, FALSE);
  
sprintf(ErrMsg, "Initialized ARS session");
ERROR (ERR_INFO, ProcessName, ErrMsg, 0);


/*--------------------------------------------------------------------*/
/* Verify the user we are running this process with
/*--------------------------------------------------------------------*/
if (ARVerifyUser(&control, NULL, NULL, NULL, &arStatus) >= AR_RETURN_ERROR)
{
sprintf (ErrMsg, "Fatal error - verifying user '%s' on server '%s'", control.user, control.server);
ERROR (ERR_FATAL, ProcessName, ErrMsg, 0);
LogArsStatus (&arStatus);
FreeARStatusList(&arStatus, FALSE);
return;
}
FreeARStatusList(&arStatus, FALSE);


sprintf(ErrMsg, "Verified user '%s'", control.user);
ERROR (ERR_INFO, ProcessName, ErrMsg, 0);


/*--------------------------------------------------------------------*/
/* Chnage the AR Server Connection Queue
/*--------------------------------------------------------------------*/
if (ARSetServerPort(&control, control.server, <SERVER_PORT>, <QUEUE_NUMBER>, &arStatus) >= AR_RETURN_ERROR)
{
sprintf (ErrMsg, "Fatal error - changing the queue for user '%s' on server '%s'", control.user, control.server);
ERROR (ERR_FATAL, ProcessName, ErrMsg, 0);
LogArsStatus (&arStatus);
FreeARStatusList(&arStatus, FALSE);
return;
}
FreeARStatusList(&arStatus, FALSE);


sprintf(ErrMsg, "Queue is changed");
ERROR (ERR_INFO, ProcessName, ErrMsg, 0);


/*--------------------------------------------------------------------*/
/* Set Flag
/*--------------------------------------------------------------------*/
entryIdL.numItems = 1;
entryIdL.entryIdList = entryIdArr;
strcpy(entryIdArr[0] , requestID);


fieldL.numItems = 1;
fieldL.fieldValueList = &process;


process.fieldId = atol("<FLAG_FIELD_ID>");
process.value.u.intVal = 0;
process.value.dataType = AR_DATA_TYPE_INTEGER; /*AR_DATA_TYPE_ENUM*/


if (ARSetEntry(&control, FormName, &entryIdL, &fieldL, 0, 1, &arStatus) >= AR_RETURN_ERROR)
{
sprintf(ErrMsg, "Fatal error at processing the transaction '%s'.", requestID);
ERROR (ERR_WARN, ProcessName, ErrMsg, 0);
LogArsStatus (&arStatus);
FreeARStatusList(&arStatus, FALSE);

fieldL.numItems = 2;
fieldL.fieldValueList = error;
error[0].fieldId = atol("<ERROR_MESSAGE_FIELD_ID>");
error[0].value.u.charVal = ErrMsgStatus;
error[0].value.dataType = AR_DATA_TYPE_CHAR;


if (ARSetEntry(&control, FormName, &entryIdL, &fieldL, 0, 1, &arStatus) >= AR_RETURN_ERROR)
{
sprintf(ErrMsg, "Fatal error at writing Error Msg to the request '%s'.", requestID);
ERROR (ERR_WARN, ProcessName, ErrMsg, 0);
LogArsStatus (&arStatus);
FreeARStatusList(&arStatus, FALSE);
}
exit(1);
}
return 1;
}


void LogArsStatus(ARStatusList *Status)
{
    static char *MsgType;
    ARStatusStruct *tempPtr;
    int i;
    sprintf(ErrMsgStatus, "ARS Status List: %u items", Status->numItems);
    if (Status->numItems != 0)
    {
        tempPtr = Status->statusList;
        for (i = 0; i < (int)Status -> numItems; i++)
        {
            switch(tempPtr->messageType)
            {
                case AR_RETURN_OK:      MsgType = "NOTE   "; break;
                case AR_RETURN_WARNING: MsgType = "WARNING"; break;
                case AR_RETURN_ERROR:   MsgType = "ERROR  "; break;
                default:                MsgType = "?????  "; break;
            }


            sprintf(ErrMsgStatus, "%s\n   Type: %s   Message number: %d\n",
                    ErrMsgStatus, MsgType, tempPtr->messageNum);


            if (tempPtr->messageText == NULL)
            {
                sprintf(ErrMsgStatus, "%s   Message:\n", ErrMsgStatus);
            }
            else
            {
                sprintf(ErrMsgStatus, "%s   Message: %s", ErrMsgStatus, tempPtr->messageText);
            }
            tempPtr++;
        }
    }
}
By Zekeriya Dinçer

Wednesday, September 21, 2011

Operation Ranking in Load Balancer Environment

In Load Balancer ARS environments, there is a form called "AR System Server Group Operation Ranking" where you can set which tasks need to be handled by which server. The most important ones of these tasks are listed below:
- Administration
- Archive
- DSO
- E-mail Engine
- Escalation

As you can see in the screenshot, all servers for each operation are listed by default. According to which server you give the priority for an operation, you need to set the Rank field. E.g. If you would like DSO to be handled by Node1 server first, then you need to set value "1" for this row. If Node1 server is not available, then Node2, Node3 and finally Node4 server will try to handle DSO operations respectively.

According to this Operation Ranking form, the options in form "AR System Administration: Server Information" are set automatically. For instance, "Disable Archive" option will not be marked for Node1 server since Node1 is defined as the default server for Archiving.

Tuesday, September 20, 2011

Converting ARS Timestamp to Date

There is one useful function on database level called "unixts_to_date" where you can convert your ARS or Unix timestamps to date. Here is the code for it:


CREATE OR REPLACE FUNCTION ARADMIN.unixts_to_date(unixts IN PLS_INTEGER) RETURN DATE IS

        /**
         * Converts a UNIX timestamp into an Oracle DATE
         */
        unix_epoch DATE := TO_DATE('19700101000000','YYYYMMDDHH24MISS');
        max_ts PLS_INTEGER := 2145916799; -- 2938-12-31 23:59:59
        min_ts PLS_INTEGER := -2114380800; -- 1903-01-01 00:00:00
        oracle_date DATE;
       
        BEGIN           
            IF unixts> max_ts THEN
                RAISE_APPLICATION_ERROR(
                    -20901,
                    'UNIX timestamp too large for 32 bit limit'
                );
            ELSIF unixts <min_ts THEN
                RAISE_APPLICATION_ERROR(
                    -20901,
                    'UNIX timestamp too small for 32 bit limit' );
            ELSE
                oracle_date := unix_epoch + NUMTODSINTERVAL(unixts, 'SECOND');
            END IF;           
            RETURN (oracle_date);
END;

The usage for this function is below:
select unixts_to_date(1316466000) from dual;

Materialized Views for Reporting

Reporting is one of the important issues in AR System environment. If you are not able to take proper reports from your system, it is not meaningful to enter data, is not it? While you are writing select statements or even for updates, you can use ARS views e.g. select * from ar_system_email_messages. However you should remember that some workflow is running on this ARS form. When you are selecting from ARS view, you are accessing to the T table and you may lock the table. This actually would cause the workflow to run into errors.

For reporting, I recommend you to build a single interface although you may have more than one consumers. At least you can try to decrease the number of requested views as much as possible to avoid unnecessary DB load. To me the best structure for reporting is Materialized Views. Although its name is "view", it behaves like a table e.g. you can define index and triggers on it. It keeps all data on a separate place, therefore you do access to the ARS tables everytime you query.

Now let's come to the point how we populate data to Materialized Views. The name of the operation modifying a materialized view is called "Refresh". In Oracle, you can right click to the materialized view and you can see the "Refresh" option. There are different modes of refresh. If your view is basic and does not contain functional statements and complex joins, you can define Fast refresh. This option tracks the changes on the base tables of the view and refreshes the view considering these changes. It does not touch the unmodified data and that's why it is fast. There is also "Complete" mode. This builds the view from the beginning. You should know that there will be a huge data transfer with this mode. Here there is another concept called "Atomic" and "Non-atomic" refresh. If you select "Atomic" refresh, Oracle first detects the modified rows and changes these rows in the original view by inserts, updates and deletes. This is the reason why it is much slower compared to "Non-atomic" refresh. In our environment, we had a huge and complex view. For this view, "Complete Non-atomic" is the best refresh combination for materialized view. There is also another refresh mode called "Force". If this mode is selected, first Fast mode is taken to consideration. If it is not possible, a Complete refresh is done. Please note that refresh modes (Fast/Complete/Force) is defined while creating the view and refresh types (Atomic/Non-atomic) can be given in run time.

According to your need, you can build different materialized views e.g. you can keep last 7 days data in one view and last 6 months data in another view. In order to automate refreshes, you can define a stored procedure and call this by a scheduled job. Here is the piece of code which you can use for refresh in stored procedure:

DBMS_MVIEW.REFRESH('VIEW_NAME', ATOMIC_REFRESH=>FALSE);

Monday, September 12, 2011

What is Load Balancing and Load Balancer?

Load Balancing is a mechanism for distributing load among AR Servers. These AR Servers are generally node servers because they run the workflow and the load is usually high on them. Load Balancer server is a different physical or logical machine installed on the same environment. In our environment, there is a Perl script run by crontab on each server and it opens or closes port 7070 according to the availability of the server. Then the load balancer server checks port 7070 on a particular server and redirects requests if the port 7070 is open. Otherwise it means the server is not available and no requests are redirected to that server. Here is the code for opening and closing the port:

open (LOG,">>$LOGFILE");
print LOG "\n\n#####     Started: " . scalar localtime(). "   #####\n";


my $sock;
my $client;


my $flag=0, $debug=0;
while(1) {
    open (LOG,">>$LOGFILE");
    print LOG "#####     Check: " . scalar localtime(). "   #####\n";
    print "#####     Check: " . scalar localtime(). "   #####\n";
    close(LOG);
    $ARScrtl = ars_Login($ARSserver, $ARSuser, $ARSpassword);
    if ( ! $ARScrtl)
    {
        ######### Login fails ########
        open (LOG,">>$LOGFILE");
        print LOG "##### " . scalar localtime(). " Login failed: $ars_errstr #####\n";
        #print "##### " . scalar localtime(). " Login failed: $ars_errstr #####\n";
        close(LOG);
        close($client);
        close($sock);


        #end of cycle, wait for 10 sec. for next check cycle
        sleep(10);
    }
    else {
        ######## Login succeeds #######
        open (LOG,">>$LOGFILE");
        print LOG "##### " . scalar localtime(). " Login successful. #####\n";
        #print "##### " . scalar localtime(). " Login successful. #####\n";
        close(LOG);
        ars_Logoff($ARScrtl);


        $sock = new IO::Socket::INET (
                                  LocalHost => $LBChkServer,
                                  LocalPort => $LBChkPort,
                                  Proto => 'tcp',
                                  Listen => 1,
                                  Reuse => 1,
                                        #Blocking => 0
                                        Timeout => 10
                                 );


        close($client);
        if ( $client =$sock->accept() ) {
            #print LOG "New connection!\n";
            #sleep(3);
        }
        close($sock);
    }


    if($debug eq 1) {
        if($flag eq 0) {
                        $flag = 1;
        }
        else {
           $flag = 0;
        }
    }
    #sleep(1);
}


close($sock);
close(LOG);