#!/bin/bash

#Author: <mihai-radu.dumitru.ext@unify.com>
#
# User script for calling inventory operations
#
#HOC:
# 2015.03.12 - initial creation
# 2015.03.18 - mihai
#TODO
#   - extended API with persistant register_notification_callback over configuration files
# 2015.03.25 - v0.2 - mihai
#   - added RAM disk for query results
#   - implemented appliance_list basic query
# 2015.03.25 - v0.3 - mihai
#   - implemented client feature notification
# 2015.03.26 - v0.4 - lukas.vokracko@ixperta.com
#   - implemented get and put actions
# 2015.04.01 - V0.5 - mihai
#   - temporarily took out sql queries from the API
#   - implemented full interface for appliance_list
# 2015.04.22 - v0.5 - jano
#   - Updated usage help message
# 2015.04.23 - v0.5 - jano
#   - Added 'host' search option
#   - Refactored variable names qresfile->queryResultFile, res->response
#   - Added JSON/XML header generation, containing exit code and error message to this script.
# 2015.04.28 - v0.5 - jano
#   - Updated usage message with latest HTTP interface usage.
# 2015.05.14 - v0.6 - jano
#   - Updated ACTION/GET to get client id from server - it might have used alternate id
# 2015.06.12 - v0.7 - jano
#   - Updated documentation with further details of possible paramters.
# 2015.06.25 - v0.8 - jano
#   - Fixed race condition - when the Inventory QUERY was completed by server sooner than inotifywait started watching for changes.
#                            By using file deletion as indicator instead it will work also when the file was already deleted before inotifywait was started.
# 2015.06.28 - v0.9 - mihai
#   - switched AMO to port 1205 not to conflict with RISO RMX systembackup - I48728
# 2015.06.30 - v1.0 - jano
#   - Added unregister_notification_callback and list_notification_callback interface.
# 2015.07.02 - v1.1 - jano
#   - put command requires that temporary file have read permission for apache server process.
# 2015.07.29 - v1.2 - jano
#   - Added warning message when --json/--xml are used without --full/--fields
# 2015.08.05 - v1.3 - jano
#   - Updated documentation - deployementText changed to be consistent with official deployment names.
# 2015.09.15 - v1.4 - jano
#   - Added implementation of --timeout option to command and command1.
# 2015.10.07 - v1.5 - jano
#   - Removed "hack" for configuring inventory HTTPS interface in apache2 configuration -
#     proper configuration was added to webservice.
# 2016.01.12 - v1.6 - jano
#   - Added timeout to command/put/get serialization - flock operation.
#     So that that inventory processes which are executed without other timeout don't accumulate indefinitely.
# 2019.12.15 - v1.7 - mihai
#   - added sshx verb for calling commands between the two nodes of a duplex ; for now it works only if called on the primary node

##TODO: use htdocs dir for common files to make available for all appliances for e.g. SNMP configuration
##TODO: socket interface for client

VER="1.7"

. /etc/profile

CONST_Upload_Dir="/var/tmp/inventory_upload_dir"
CONST_Queries_RAMDisk="/var/tmp/inventory_query_ramdisk"

function usage() {
    cat << _EOF_USAGE
SOCO2 Inventory ver $VER

Usage:

inventory <command> [<appliance_id> [<option1> [<option2 ...]]]

where <command> can be one of the following ACTIONS, QUERIES or OTHER:

ACTIONS:

  command <appliance_id> [--timeout=<secs>] <cmd> [<arg1> [<arg2> ... ]]
     - executes a command on the given appliance
       Default timeout of command is 18 seconds. Change with --timeout option if needed.

  command1 <appliance_id> [--timeout=<secs>] <cmd> [<arg1> [<arg2> ... ]]
     - executes a command on the given appliance with merged stdout and stderr.
       Default timeout of command is 18 seconds. Change with --timeout option if needed.

  get <appliance_id> <remotefile> <localfile>
     - copies a file from given appliance to the given destination on host

  put <appliance_id> <localfile> <remotefile>
     - copies a file from host to given appliance into the given destination

  NOTES: <appliance_id> is the inventory id of the appliance.
         You can find it in inventory appliance_list --full query output inside the <id> tag.

         the execution of the above commands will be serialized in case multiple
         calls are made in parallel to the same appliance independent on action type.

QUERIES:

  appliance_list [--basic|--full|--fields=<field1>,<field2>,...] [--json|--xml] [--initialized|--all] [--search=(ape|sg|sgonly|survsg|host|stmix|all|<ipaddr>|<id>)]

  Detail options:
     --basic: (default)    basic display of appliances, displays a table with appliance IDs and IPDA IP Addresses.
     --full:               display full information about given appliances
     --fields:             display only the selected fields for given appliances

  Filter options:
     --initialized : (default)  display only initialized appliances (means accessible from Host)
     --all :                    display also offline appliances.
     --search: filter display just for some given type of appliance or the given IP address or the given ID
               Possible search parameters:
                   ape - Display only APE appliances.
                   sg - Display only SoftGate appliances, possibly including SurvivableSoftgate, Simplex + Integrated SG, Quorum + SG.
                   sgonly - Display only standalone SoftGates.
                   survsg - Display only Survivable SoftGates.
                   host - Display only Host nodes, including quorum.
                   stmix - Display only STMIX card appliances.
                   <ipaddr> - Display only appliance which IPDAIpAddr matches given IP. Example: 10.1.2.3
                   <id> - Display only appliance which has the given id. Example: 101700
                   all - Display all appliances.

    NOTE: These options can be combined.
          Using --initialized and --search together will perform the search in the initialized appliances only.
          Using --all and --search together will perform the search in the all appliances.
          If both --all and --initialized are used together, then --all has priority.

  Format options:
  (NOTE: These are useful only in combination with --full or --fields options)
     --xml    (default) Display appliance information in XML format
     --json:            Display appliance information in JSON format.

  HTTP Interface:
  Queries can also be retrieved over HTTP interface:
    https://<OS4k-HOST>/inventory/cgi-bin/inventory_user_query.cgi

  Use parameters to specify the output format (same defaults as the associated CLI options):
    detail=basic/full                                   - see --basic / --full options
    format=xml/json                                     - see --format option
    fields=<field1>,<field2>,...                        - see --fields option
    search=all/ape/sg/sgonly/survsg/host/<ipaddr>/<id>  - see --search option
    initialized_only=true/false                         - see --all/--initialized option. Default: true

  Example:
    curl -k 'https://os4kplt/inventory/cgi-bin/inventory_user_query.cgi?detail=full&format=json'

  NOTE: OS4k deployments with multiple purpose (e.g. Survivable SoftGate, Simplex with Integrated SoftGate)
  are shown as one entry in

  The query output can be XML or JSON document based on selected format, which has two top-level tags/fields:
    "appliance_list"
    "queryStatus"

  The "appliance_list" contains a list of "appliance" tags/objects with these attributes:
    label - Short name containing basic identification of the appliance, e.g. "Host", "SG22"
    id - Inventory id - used by inventory to identify the appliance ("appliance_id" in put/get/command)
    deploymentText - Identification of deployment as text, .e.g "Simplex", "StandaloneSoftgate"
    deploymentNumber - Indentification of deployment by number. e.g. "40"
    hwTypeText - Identification of Hardware Type in text. e.g. "DSCXL", "OSA500"
    hwTypeNumber - Identification of Hardware Type by number. e.g. "1"
    osa500Type - "i"/"a" - Further OSA500 hardware type specification. Empty for any HW type other than OSA500.
    soco2Version - SOCO2 platform version. e.g. "V7_R2.5.0"
    sgVersion - SoftGate release version. e.g. "pzksgw50.A0.027-003"
    ccapNumber - APE/SurvSG access point number.  e.g. "26"
    ltuNumber - LTU (Line Trunk Unit) number of the appliance. e.g. "17"
    hostname - Hostname of the appliance machine. e.g. "soco2-47-A"
    IPDAIpAddress - IPDA address for systems running an active SoftGate. e.g. "10.82.47.20"
                    - For "Simplex with Integrated SG" deployment it is the SoftGates Signalling IPDA IP address, either:
                        a) APIADDR from AMO APRT in network link setup.
                        b) LSRTADDR from AMO UCSU in direct link setup.
                    - For "Survivable SG", "Quorum with SoftGate" deployments it is also the SG IPDA IP address same as above.
                    - For other appliances or nodes of the Host system without an active SG, this field is set to "0.0.0.0"
    assistantIpAddress - the IP address of assistant for the appliance. e.g. "10.82.47.5"
    customerIpAddress - Customer Lan IP address of the appliance (as configured in YaST - "yast" IP).
    managementIpAddress - Management IP address of the appliance. e.g. "10.82.47.25"
    CCIpAddress - IPDA IP address of CCA/CCB/CCAP call processor running on appliance. e.g. "10.82.47.15".
                  This fields is valid for Host nodes, APE or SurvSG deployment.
                  It will be set to "0.0.0.0" for other deployments.
    portalUri - the URI of Portal for this appliance. e.g. "https://10.82.37.30:8443"
    alternateKey - Appliances with multiple SW deployments have alternative inventory id in inventory. e.g. "22900"
                   (Survivable SG, Quorum with SoftGate, etc.)
    hasSoftgate - true if appliance has an active Softgate configured.
    isDuplex - true if the appliance is part of duplex system.
    isQuorum - true if the appliance is the quorum node in separated duplex.
    isApe - true if the appliance has Access Point Emergency feature. (APE/SurvSG)
    isActive - true if the node is active node of duplex host system.
    isAccessible - true if the appliance is currently accessible via inventory.
                   false if appliance did not send any message to Inventory server within the last 36 seconds.
    isHost - true if the appliance is a node of the Host system.
    isLoaded - true if the appliance has connected at least once and provided it's data.
    secondsSinceLastConnected - Number of seconds since appliance last connected to Inventory server. e.g. "10"
    timestampLastConnected - Timestamp of last appliance connection to server in seconds since Epoch. e.g. "1434098994"
                             You should use correct timezone / locale for formatting this timestamp.
                             (Inventory client on appliance polls this server every ~20 seconds -
                              timestamp older than 60 seconds means the appliance is not accessible.)
    timestampLastLoaded - Timestamp of last update of data from appliance in epoch time. e.g. "1434095882"
                          (Indicates age of data shown in Inventory)

  The "queryStatus" tag/field contains these sub-fields:
    returnCode - Exit code of query command which was performed, e.g. "127"
    message - In case the query fails, this will contain the error description.

  Possible value tables for fields:

  hwTypeNumber | hwTypeText
  ================================
  | 1          |  DSCXL
  | 2          |  HPA500   (if V6)
  | 2          |  OSA500
  | 3          |  ECOSERVER
  | 4          |  BRANCH
  | 11         |  VM
  | 20         |  SERVER
  | 55         |  UNKNOWN

  deploymentNumber | deploymentText:
  ================================
  | 100, 61, 11, 30, 99  |  Unknown
  | 0, 50                |  Simplex
  | 60                   |  Duplex
  | 10, 62               |  SeparatedDuplex
  | 21                   |  StandaloneSoftgate
  | 20                   |  SurvivableSoftgate
  | 40                   |  APE
  | 41                   |  RG8350/RG8300

    NOTE: These deployment and deploymentText fields are loosely coupled.
          You might get for example 100 / SimplexNode.

OTHER:

  register_notification_callback <feature> <cmd>
       register that a given command in <cmd> is called by the inventory server when
       a notification comes form an appliance for the feature given in <feature>
       <feature> should be a text which uniquely identifies a feature.
       The callback command will be called like this:
         <cmd> <clientID> <feature> <args>
       where <clientID> is the ID of the client as returned by appliance_list query
       and <args> is the same as the <args> passed to the client side notification call command
       If on the client side <args> was set to "-" then the stdin passed to the client notification
       call will be passed to the callback as well
       It is possible to register multiple notification callback commands for the same feature
       either by successive calls to the register_notification_callback API or by creating configurations files
       like in /opt/dscxl/share/inventory_cbk_conf/<feature>.cbks and /etc/inventory_cbk_conf/<feature>.cbks

       Note: In Duplex system, the callback needs to be registered on Active Node, for it to take effect.

       Note: it is strongly advised that the notification callback commands return as fast as
       possible and do not block the caller for too long (should be less than 10 seconds),
       otherwise this may prevent other registered notification callback scripts to be called.

       In case multiple scripts are registered to process cbk notifications for the same features
       then they will be called one after another in a random sequence and the cumulated output from all
       of them will be written back to the client

  unregister_notification_callback <feature> <cmd>
       remove a the given command in <cmd> from the list of registered notification callbacks for the given
       feature in <feature>.

  list_notification_callback <feature>
       list all registered notification callbacks for given <feature>

  read_from_rmx_now
       Trigger immediate update of appliance data from SWU RMX database.

HELP:
    -h   | --short-help | short   -- Short help message
    help | --help                 -- Detailed (this) help message

NOTES:
    ====================================================
    Pre-requisites for adding appliance to Inventory:
    ====================================================
    For Access Point Emergency (APE):
        - AMO APESU needs to have IPADDR, CCAPNO parameter correctly set in CCAP data in the SWU (RMX).
        - AMO SIPCO must be configured correctly with CCAADDR / CCBADDR parameters in CCAP (RMX) of APE.
        - CCAP (RMX) of the APE needs to be in Active status.

    For SoftGate:
        - LAN interface configuration needs to be completed.                                           (in SG)
        - AMO UCSU needs to be configured with correct LTPARTNO, LTU.                                  (in SWU)
        - If you use network link (APNW) CONNTYPE, also APIPADDR needs to be configured in APRT AMO.   (in SWU)
        - If you use direct link (APDL) CONNTYPE, the LSRTADDR needs to be configured in the UCSU AMO. (in SWU)

    For Survivable SoftGate:
        - Both APE and SoftGate parts of SurvivableSoftgate appliance should be configured.

    For Host nodes:
        - Portal LAN configuration needs to be completed, for all nodes of chosen deployment.

    ====================================================
    To remove an appliance from Inventory:
    ====================================================
    If you delete an appliance from the SWU AMOs, it will be removed from the Inventory within 10 minutes.
    Or you can trigger an immediate update of the appliance_list by executing the inventory command:
    $0 read_from_rmx_now


_EOF_USAGE
    exit 1
}

function short_usage() {
    cat << _EOF_SHORT_USAGE
SOCO2 Inventory ver $VER

Usage:

inventory <command> [<appliance_id> [<option1> [<option2 ...]]]

ACTIONS:

  command  <appliance_id> [--timeout=<secs>] <cmd> [<arg1> [<arg2> ... ]]
  command1 <appliance_id> [--timeout=<secs>] <cmd> [<arg1> [<arg2> ... ]]
  get      <appliance_id> <remotefile> <localfile>
  put      <appliance_id> <localfile>  <remotefile>

QUERIES:

  appliance_list [--basic|--full|--fields=<field1>,<field2>,...] [--json|--xml] [--initialized|--all] [--search=(ape|sg|sgonly|survsg|host|stmix|all|<ipaddr>|<id>)]

  HTTP Interface:
    https://<OS4k-HOST>/inventory/cgi-bin/inventory_user_query.cgi

  Parameters:
    detail=basic/full
    format=xml/json
    fields=<field1>,<field2>,...
    search=all/ape/sg/sgonly/survsg/host/stmix/<ipaddr>/<id>
    initialized_only=true/false

  Example:
    curl -k 'https://os4kplt/inventory/cgi-bin/inventory_user_query.cgi?detail=full&format=json'

OTHER:
  register_notification_callback <feature> <cmd>
  unregister_notification_callback <feature> <cmd>
  list_notification_callback <feature>
  read_from_rmx_now

HELP:
    -h   | --short-help | short   -- Short (this) help message
    help | --help                 -- Detailed help message
_EOF_SHORT_USAGE
    exit 1
}

##
##  query <format> <sql_query>
##     - execute given sql query on appliance DB and output in desired format
##       <format> can be one of
##          --xml
##             - outputs result in xml form
##          --simple-text[,<column-sepataror>[,<line-separator>]]
##             - outputs result in text table form using given separators
##               default line separator is \n and default column separator is ";"
##          --binary
##             - outputs the binary value of a data field
##               can be used with sql queries which return just one single field
##
##
##
##  updatedb <sql_query>
##     - execute given sql query on appliance DB; not fully documented yet
##
##  updatedb_bin <sql_query> [<input_file>]
##     - execute given sql query to write data into binary value of a data field
##       data can come from given file or standard in
##
##
##

function internal_cmd() {
    local mergeouterr=$1
    shift

    local client_id=$1
    shift

    if [[ "$1" =~ ^--timeout ]]; then
        local Timeout=$2
        shift 2
    else
        local Timeout=-1
    fi

    local cmd="$*"
    local thecmd="$*"
    local suff=""
    [ "$mergeouterr" = "1" ] && suff=1
    cd $CONST_Upload_Dir
    rm -f action-result-${client_id} action-result-${client_id}-* action-timeout-${client_id}
    echo $Timeout > action-timeout-${client_id}
    response=`perl /opt/inventory/write2server.pl "ACTION $client_id CMD$suff $cmd"`
    if [[ $response =~ ^IDLE ]]; then
        # format: IDLE <client_id>
        # Because server might executed command via alternate key of system with multiple appliances (SurvivableSoftgate).
        result_id=${response#IDLE };
        if [ -e action-result-$result_id ]; then
            1>/dev/null 2>&1 tar -xzf action-result-$result_id
            [ "$mergeouterr" = "1" ] || cat action-result-$result_id-err >&2
            cat action-result-$result_id-out >&1
            exit `cat action-result-$result_id-rc`
        else
            echo "Error: did not get detailed results - [$response]"
            exit 242
        fi
    else
        echo "Error: did not get results - [$response]"
        exit 243
    fi
}

function internal_query() {
    #create dummy file for query results on the RAM Disk
    local queryReturnCode=0
    local queryErrorMessage=""

    if [[ "$*" =~ '--full' ]] || [[ "$*" =~ '--fields' ]]; then
        local fieldOutput=true
    else
        local fieldOutput=false
    fi

    if [[ "$*" =~ "--json" ]]; then
        local jsonFormat=true
    else
        local jsonFormat=false
    fi

    if [[ "$*" =~ "--xml" ]]; then
        local xmlFormat=true
    else
        local xmlFormat=false
    fi

    local flag_dir=/dev/shm/inventory/
    if [[ "$*" =~ "--include-stmix" ]]; then
        [[ -d "$flag_dir" ]] || mkdir -p $flag_dir
        touch $flag_dir/include_stmix
    else
        rm -f $flag_dir/include_stmix
    fi

    if $jsonFormat && ! $fieldOutput; then
        >&2 echo "Warning: Ignoring --json format, using simple output!"
    fi

    if $xmlFormat && ! $fieldOutput; then
        >&2 echo "Warning: Ignoring --xml format, using simple output!"
    fi

    queryResultFile=`mktemp $CONST_Queries_RAMDisk/qr-XXXXXX`
    #use this indication file which will be touched by server when query result is completely written to $queryResultFile
    touch ${queryResultFile}.ind.wait
    #same file will be created again by the server - improves robustness and reveals other potential logical errors
    rm -f $queryResultFile
    #delete the files at the end in all cases
    trap "rm -f $queryResultFile ${queryResultFile}.ind.wait" EXIT
    #now pass the queryResultFile and complete query to the server, but before that start waiting that the indication file has been deleted
    #allow here 9 seconds since in the flock query.lock we allowed 10
    1>/dev/null 2>/dev/null inotifywait -t 9 -e delete_self ${queryResultFile}.ind.wait &

    response=`perl /opt/inventory/write2server.pl "QUERY $queryResultFile $*"`
    if [[ $response =~ ^_OK_ ]]; then
        #ok, query was understood by the server so wait for the results
        true
    else
        queryErrorMessage="Error: query failed with response: [$response]"
        queryReturnCode=1

        #unlock the inotifywait
        rm -f ${queryResultFile}.ind.wait
    fi
    wait
    #simply output the result and return success
    if [ -e $queryResultFile ]; then
        queryReturnCode=0
    else
        if [ "$queryReturnCode" -eq 0 ]; then
            queryErrorMessage="Error: got no result file $queryResultFile from server."
            queryReturnCode=2
        fi
    fi

    if $fieldOutput; then
        if $jsonFormat; then
            echo '{'
            echo '"queryStatus": {'
            echo '  "returnCode": "'$queryReturnCode'",'
            echo '  "message": "'$queryErrorMessage'"'
            echo ' },'
        else
            echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            echo "<appliance_list>"
            echo "<queryStatus>"
            echo "  <returnCode>$queryReturnCode</returnCode>"
            echo "  <message>$queryErrorMessage</message>"
            echo "</queryStatus>"
        fi
    fi

    if [ -e $queryResultFile ]; then
        cat $queryResultFile
    else
        # Empty list of appliances
        if $fieldOutput; then
            if $jsonFormat; then
                echo '"appliances": []'
            else
                echo '<appliances></appliances>'
            fi
        fi
    fi

    if $fieldOutput; then
        if $jsonFormat; then
            echo '}';
        else
            echo "</appliance_list>";
        fi
    fi

    >&2 echo $queryErrorMessage
    exit $queryReturnCode
}

function internal_put() {
    local client_id=$1
    local localfile=$2
    local remotefile=$3

    cd $CONST_Upload_Dir
    base="${localfile##*/}"
    #pass also basefilename of file in case destination location on remote is a directory
    temp="/opt/inventory/htdocs/filetoput-${client_id}-${base}"

    # Remove the temporary file if it already exists.
    rm -f "$temp"

    #just copy, since most probably a link would not work or be too dangerous from security POV if allowed
    cp -f -p "$localfile" "$temp"

    # File needs to be readable to apache process
    chmod o+r "$temp"
    localfile=`basename $localfile`
    response=`perl /opt/inventory/write2server.pl "ACTION $client_id PUT filetoput-$client_id-$base $remotefile"`

    rm -f "$temp"

    if [[ ! $response =~ ^IDLE ]]; then
        if [[ $response =~ FAILED ]]; then
            echo "Error: transfer failed on remote side - [$response]"
            exit 246
        else
            echo "Error: transfer failed for uknown reason - [$response]"
            exit 247
        fi
    fi
}

function internal_get() {
    local client_id=$1
    local remotefile=$2
    local localfile=`readlink -m $3`

    cd $CONST_Upload_Dir
    response=`perl /opt/inventory/write2server.pl "ACTION $client_id GET $remotefile $localfile"`

    result_id=${response#IDLE }
    local filename="filelink-${result_id}"
    if [[ ! $response =~ ^IDLE ]] || [[ ! -e $filename ]]; then
        if [[ $response =~ FAILED ]]; then
            echo "Error: transfer failed on remote side - [$response]"
            exit 246
        else
            echo "Error: transfer failed for uknown reason - [$response]"
            exit 247
        fi
    fi

    cd - >/dev/null
    #handle the case that given destination is a directory - in this case keep same base filename as on remote
    if [ -d $localfile ]; then
        mv $CONST_Upload_Dir/$filename $localfile/${remotefile##*/} || {
            echo "Error: remote file available but could not create file $localfile/${remotefile##*/}"
            exit 248
        }
    else
        mv $CONST_Upload_Dir/$filename $localfile || {
            echo "Error: remote file available but could not create file $localfile"
            exit 248
        }
    fi
}

function internal_mount_queries_ramdisk() {
    grep -q inventory_client_notif /etc/sudoers || {
        echo -en "\nwwwrun ALL=NOPASSWD:/opt/inventory/inventory_client_notif.sh\n" >> /etc/sudoers
        echo -en "\nwwwrun ALL=NOPASSWD:/opt/inventory/inventory\n" >> /etc/sudoers
    }
    chmod a+x /opt/inventory/inventory_client_notif.sh
    grep -q inventory_query_ramdisk /proc/self/mountinfo && {
        rm -rf $CONST_Queries_RAMDisk/*
        return 0
    }
    [ -e $CONST_Queries_RAMDisk ] || mkdir -p $CONST_Queries_RAMDisk
    mount -t tmpfs tmpfs -o size=20M $CONST_Queries_RAMDisk && echo "RAM Disk $CONST_Queries_RAMDisk setup ok." && return 0
    return 1
}

function is_node1() {
    #just check if I am the host node1 according to haipsetup.xml
    node1=""
    [ -e /var/opt/ha/conf/haipsetup.xml ] && grep -q system.name-node1 /var/opt/ha/conf/haipsetup.xml && {
        node1=$(grep system.name-node1 /var/opt/ha/conf/haipsetup.xml)
        node1=${node1#*>}
        node1=${node1%<*}
        [ "`hostname`" = "$node1" ] && return 0
    }
    return 1
}

function is_node2() {
    #just check if I am the host node2 according to haipsetup.xml
    node2=""
    [ -e /var/opt/ha/conf/haipsetup.xml ] && grep -q system.name-node2 /var/opt/ha/conf/haipsetup.xml && {
        node2=$(grep system.name-node2 /var/opt/ha/conf/haipsetup.xml)
        node2=${node2#*>}
        node2=${node2%<*}
        [ "`hostname`" = "$node2" ] && return 0
    }
    return 1
}

#MAIN begins here

[ -z "$1" ] && usage

action=$1
client_id=$2

case $action in
    command)
        #TODO: interpret --timeout
        [ -e $CONST_Upload_Dir/$client_id.lock ] || touch $CONST_Upload_Dir/$client_id.lock
        shift
        flock -w 15 -x $CONST_Upload_Dir/$client_id.lock /bin/bash $0 _internal_cmd_ 0 $*
        exit $?
        ;;
    command1)
        #TODO: interpret --timeout
        [ -e $CONST_Upload_Dir/$client_id.lock ] || touch $CONST_Upload_Dir/$client_id.lock
        shift
        flock -w 15 -x $CONST_Upload_Dir/$client_id.lock /bin/bash $0 _internal_cmd_ 1 $*
        exit $?
        ;;
    sshx)
        1>/dev/null 2>/dev/null /opt/webservice/scripts/is_duplex
        [ $? -eq 1 ] || exit 12
        shift
        if is_node1; then
            /bin/bash $0 command1 102700 $*
            exit $?
        fi
        if is_node2; then
            /bin/bash $0 command1 101700 $*
            exit $?
        fi
        exit 13
        ;;
    get)
        [ -e $CONST_Upload_Dir/$client_id.lock ] || touch $CONST_Upload_Dir/$client_id.lock
        shift
        flock -w 15 -x $CONST_Upload_Dir/$client_id.lock /bin/bash $0 _internal_get_ $*
        exit $?
        ;;
    put)
        [ -e $CONST_Upload_Dir/$client_id.lock ] || touch $CONST_Upload_Dir/$client_id.lock
	shift
        flock -w 15 -x $CONST_Upload_Dir/$client_id.lock /bin/bash $0 _internal_put_ $*
        exit $?
        ;;
    _internal_cmd_)
        shift
        internal_cmd $*
        exit $?
        ;;
    appliance_list)
        [ -e $CONST_Queries_RAMDisk/query.lock ] || touch $CONST_Queries_RAMDisk/query.lock
        #allow max 10 seconds for a query just to have some indication of heavy load
        flock -w 10 -x $CONST_Queries_RAMDisk/query.lock /bin/bash $0 _internal_query_ $* && exit 0
        >&2 echo "ERROR_OR_TIMEOUT"
        exit 1
        ;;
    _internal_get_)
	shift
	internal_get $*
	exit $?
	;;
    _internal_put_)
	shift
	internal_put $*
	exit $?
	;;
    _internal_query_)
        shift
        internal_query $*
        exit $?
        ;;
    _internal_post_install_server)
        export HOME=/root
        #1. update server-tuning.conf and restart apache
        # if SG apache config available then use it as master version
        # if current apache config is different then update also the template in the portal and also restart apache
        pltver=$(getydata -v)
        bUpdateServerTuning=false
        [[ $pltver =~ ^V8_R1 ]] && bUpdateServerTuning=true
        [[ $pltver =~ ^V8_R0 ]] && bUpdateServerTuning=true
        [[ $pltver =~ ^V7 ]] && bUpdateServerTuning=true
        [[ $pltver =~ ^V6 ]] && bUpdateServerTuning=true
        ( $bUpdateServerTuning && [ -e /opt/soco/config/templates/apache/2.4/server-tuning.conf ] ) && {
            echo "`date` Checking Apache2 server tuning config"
            if diff /opt/soco/config/templates/apache/2.4/server-tuning.conf /etc/apache2/server-tuning.conf; then
                echo "`date` Apache2 server tuning is up2date. Nothing to do."
            else
                echo "`date` Apache2 server tuning needs to be updated. Doing the update and restarting Apache2 ..."
                cp -v /opt/soco/config/templates/apache/2.4/server-tuning.conf /etc/apache2/server-tuning.conf
                cp -v /opt/soco/config/templates/apache/2.4/server-tuning.conf /opt/webservice/config/apache/2.4/server-tuning.conf
                service apache2 restart
            fi
        }
        #2. restart inventory server if it was running
        /etc/init.d/inventoryserverd status && {
            echo "`date` Inventory Server is running => will stop it now ..."
            crm resource stop rsc4k_inventory
            i=0
            while [ $i -lt 300 ]; do
                echo -en "."
                i=$(($i + 5))
                sleep 5
                1>/dev/null 2>&1 /etc/init.d/inventoryserverd status && continue
                break
            done
            echo "`date` Inventory Server is now stopped. Will start it back to activate new version ..."
            crm resource start rsc4k_inventory
            sleep 5
            /etc/init.d/inventoryserverd status
        }
        exit 0
        ;;
    _internal_mount_queries_ramdisk)
        shift
        internal_mount_queries_ramdisk $*
        exit $?
        ;;
    notification-trace-on)
        touch $CONST_Upload_Dir/trace_client_notification.on
        exit $?
        ;;
    notification-trace-off)
        rm -f $CONST_Upload_Dir/trace_client_notification.on
        exit $?
        ;;
    notification-trace)
        cat $CONST_Upload_Dir/inventory_client_notification.trace
        exit $?
        ;;
    notification-trace-delete)
        rm -f $CONST_Upload_Dir/inventory_client_notification.trace
        exit $?
        ;;
    read_from_rmx_now)
        touch /dev/inventory.immediate.rmx.update
        exit $?
        ;;
    register_notification_callback)
        shift
        feature=$1
        cmd=$2
        #do it relaxed, try to add the cbk in both conf files. Allow also to be called without the server running
        if [ -z "$feature" ] || [ -z "$cmd" ]; then
            >&2 echo "Callback registration failed, missing parameter. See --help"
            exit 3
        fi
        mkdir -p /opt/dscxl/share/inventory_cbk_conf /etc/inventory_cbk_conf
        for conffile in /opt/dscxl/share/inventory_cbk_conf/$feature.cbks /etc/inventory_cbk_conf/$feature.cbks; do
            if [ -e $conffile ]; then
                grep -q $cmd $conffile || echo $cmd>>$conffile
            else
                echo $cmd>>$conffile
            fi
        done
        exit 0
        ;;
    unregister_notification_callback)
        shift
        feature=$1
        cmd=$2
        if [ -z "$feature" ] || [ -z "$cmd" ]; then
            >&2 echo "Callback unregister failed, missing parameter. See --help"
            exit 3
        fi
        #do it relaxed, try to add the cbk in both conf files. Allow also to be called without the server running
        exit_code=0
        for conf_dir in "/opt/dscxl/share/inventory_cbk_conf" "/etc/inventory_cbk_conf"; do
            conffile="$conf_dir/${feature}.cbks"
            if [ -e "$conffile" ]; then
                temp=`mktemp`
                [ $? -eq 0 ] || exit_code=1
                grep -F -v -x "$cmd" "$conffile" > "$temp"
                mv -f "$temp" "$conffile" || exit_code=3
            else
                exit_code=4
            fi
        done
        exit $exit_code
        ;;
    list_notification_callback)
        shift
        feature=$1
        set -o pipefail
        2>/dev/null cat /opt/dscxl/share/inventory_cbk_conf/${feature}.cbks /etc/inventory_cbk_conf/${feature}.cbks | sort -f | uniq
        exit $?
        ;;
    --help|help)
        usage
        exit 0
        ;;
    -h|--short-help|short)
        short_usage
        exit 0
        ;;
    *)
        echo "Error: unimplemented action $1"
        exit 1
        ;;
esac

exit 88
