Release

This is the example of the Application (Perl module) that provisions SIM card details to the HSS. The Application is subscribed to process event types of the Subcriber group.

#!/usr/bin/perl

# Example Web Service to process events from EventSender handler

#

# run:

#   PORTA_BILLING_API=10.0.3.6 \

#   PORTA_BILLING_API_USER=api-login \

#   PORTA_BILLING_API_PASSWORD=api-password \

#   RESULT_FILE=/tmp/hss.log \

#   SERVICE_LOGIN=events \

#   SERVICE_PASSWORD=topsecret \

#   plackup –host 127.0.0.1 –port 9090 perl_example.psgi

use strict;

use warnings;

use Const::Fast;

use Cpanel::JSON::XS qw(decode_json encode_json);

use English qw(-no_match_vars);

use HTTP::Status qw(:constants);

use HTTP::Tiny;

use IO::File;

use MIME::Base64 qw(encode_base64);

use Plack::Request;

use POSIX qw(strftime);

use Cache::LRU;

const my $RESULT_FILE => ( $ENV{RESULT_FILE} // ‘/tmp/hss.log’ );

# basic authorization

my $user = $ENV{SERVICE_LOGIN} // ‘events’;

my $password = $ENV{SERVICE_PASSWORD} // ‘topsecret’;

my $base_auth_string

= ‘Basic’ . encode_base64( $user . ‘:’ . $password, ” );

# PortaBilling API server

my $PB_API_HOST     = $ENV{PORTA_BILLING_API} // ‘10.0.0.1’;

my $PB_API_USER     = $ENV{PORTA_BILLING_API_USER} // ”;

my $PB_API_PASSWORD = $ENV{PORTA_BILLING_API_PASSWORD} // ”;

# reuse PB API session

my $SESSION_EXPIRATION = $ENV{SESSION_EXPIRATION} // 60;

my ( $session, $session_last_usage );

my $http = HTTP::Tiny->new( verify_SSL => 0, timeout => 5 );

# track active requests to detect retries

my $active_req = Cache::LRU->new( size => 100 );

# error logging

sub log_error {

my $message = shift;

print STDERR ‘[ERROR] ‘, $message, “\n”;

return;

}

sub log_debug {

my $message = shift;

print STDERR ‘[DEBUG] ‘, $message, “\n”;

return;

}

# Perform HTTP/REST request to PortaBilling API

sub get_api_result {

my ( $method, $session_id, $params ) = @_;

log_debug(

sprintf “API: POST https://%s/rest/%s %s”,

$PB_API_HOST, $method, encode_json($params)

);

my $response = $http->post_form(

‘https://’ . $PB_API_HOST . ‘/rest/’ . $method, {

auth_info => encode_json(

$session_id

? { session_id => $session_id }

: {

login    => $PB_API_USER,

password => $PB_API_PASSWORD,

}

),

params => encode_json($params),

}

);

if ( !$response->{success} ) {

log_error(

sprintf ‘PB API %s failed, error %s %s’,

$method, $response->{status}, $response->{reason}

);

return undef;

}

# debug, if required:

#print STDERR ‘PB API ‘, $method, ‘ response: ‘,

#               $response->{content}, “\n”;

my $data = eval { decode_json( $response->{content} ) };

if ( $EVAL_ERROR || !$data ) {

# no content or malformed JSON

log_error(

sprintf ‘Failed to parse reply content: %s, error %s’,

$response->{content} // ”, $EVAL_ERROR

);

return undef;

}

return $data;

} ## end sub get_api_result

# Login to PortaBilling API

sub api_login {

my ( $api_login, $api_password ) = @_;

if (   $session_last_usage

&& $session_last_usage + $SESSION_EXPIRATION > time() ) {

# session active

log_debug( sprintf ‘Reusing session %s’, $session );

return $session;

}

my $data = get_api_result(

‘Session/login’,

undef, {

login    => $api_login,

password => $api_password,

}

);

return undef if ( !$data );

$session            = $data->{session_id};

$session_last_usage = time();

log_debug( sprintf ‘Created session %s’, $session );

return $session;

} ## end sub api_login

# Get Account information

sub api_get_account_info {

my ( $session_id, $i_account ) = @_;

my $data = get_api_result(

‘Account/get_account_info’,

$session_id, { i_account => $i_account }

);

return undef if ( !$data );

$session_last_usage = time();

return $data->{account_info};

}

# Get list of SIM Cards assigned to Account

sub api_get_sim_cards {

my ( $session_id, $i_account ) = @_;

my $data = get_api_result(

‘SIMCard/get_card_list’,

$session_id, { i_account => $i_account }

);

return undef if ( !$data );

$session_last_usage = time();

return $data->{card_list};

}

# Here we perform actual provisioning of collected data

# to external HSS

# As an example, we just write information to local file

# row format: action,account-id,balance,status,IMSI,datetime

#  where

#   action – string, one of ‘Created’, ‘Updated’, ‘Deleted’

#   account-id – string, ID of account

#   balance – number, account’s balance

#   status – string, account’s status

#   IMSI – string, SIM card IMSI (optional)

#   datetime – string, datetime in YYYY-MM-DD hh:mm:ss format

sub provision_external_system {

my $h = shift;

my $status   = 0;

my $account  = $h->{account};

my $sim_list = $h->{sim_cards} // [];

my $datetime = strftime( ‘%Y-%m-%d %H:%M:%S’, localtime() );

my $fh = IO::File->new( $RESULT_FILE, ‘a’ );

if ( !defined $fh ) {

log_error(

sprintf(

‘Failed to open file %s, error %s’,

$RESULT_FILE, $OS_ERROR

)

);

return $status;

}

if ( scalar( @{$sim_list} ) == 0 ) {

# Account without SIM cards

if (

!printf $fh “%s,%s,%.5f,%s,,%s\n”,

$h->{action}, $account->{id}, $account->{balance},

( $account->{status} || ‘open’ ), $datetime

) {

log_error(

sprintf(

‘Failed to write file %s, error %s’,

$RESULT_FILE, $OS_ERROR

)

);

$status = 0;

}

$status = 1;

}

else {

foreach my $sim ( @{$sim_list} ) {

if (

!printf $fh “%s,%s,%.5f,%s,%s,%s\n”,

$h->{action}, $account->{id},

$account->{balance},

( $account->{status} || ‘open’ ),

$sim->{imsi},

$datetime

) {

log_error(

sprintf(

‘Failed to write file %s, error %s’,

$RESULT_FILE, $OS_ERROR

)

);

$status = 0;

last;

}

$status = 1;

} ## end foreach my $sim ( @{$sim_list…})

} ## end else [ if ( scalar( @{$sim_list…}))]

if ( !$fh->close ) {

log_error(

sprintf(

‘Failed to close file %s, error %s’,

$RESULT_FILE, $OS_ERROR

)

);

$status = 0;

}

log_debug(

sprintf ‘Provisioning status: %s’,

( $status ? ‘OK’ : ‘FAILURE’ )

);

# TEST: instert some random delay,

# to emulate request timeout on remote side

my $test_sleep = int( rand(10) );

log_debug( ‘Emulate network delay, sleep ‘ . $test_sleep );

sleep($test_sleep);

return $status;

} ## end sub provision_external_system

# check requirements for incoming request

sub validate_request {

my $req = shift;

# HTTP method

if ( $req->method ne ‘POST’ ) {

log_error(‘Only POST method allowed’);

return HTTP_METHOD_NOT_ALLOWED;

}

# Basic Authorization

my $auth_value = $req->header(‘Authorization’) || ”;

if ( $auth_value ne $base_auth_string ) {

log_error(‘Auth failed’);

return HTTP_UNAUTHORIZED;

}

# require Content-Type: application/json

if ( $req->content_type ne ‘application/json’ ) {

log_error(

sprintf ‘Content-Type %s, expected application/json’,

$req->content_type

);

return HTTP_UNSUPPORTED_MEDIA_TYPE;

}

return 0;

} ## end sub validate_request

sub process_request {

my $req = shift;

my $code = validate_request($req);

return $code if ( $code > 0 );

# parse request

my $event_content = $req->content;

my $event = eval { decode_json($event_content) };

if ( $EVAL_ERROR || !$event ) {

# received malformed JSON data: 400 Bad Request

log_error(‘Malformed JSON request’);

return HTTP_BAD_REQUEST;

}

log_debug(

sprintf ‘Received event: %s Variables: %s’,

$event->{event_type},

join(

‘ ‘,

map { $_ . ‘=’ . $event->{variables}->{$_} }

( sort keys %{ $event->{variables} } )

)

);

# detect retries for long-running requests

my $unique_id = $event->{variables}->{i_event};

if ( defined $unique_id

&& ( my $cached_result = $active_req->get($unique_id) ) ) {

log_debug(

sprintf ‘Detected retry request #%d, result: %s’,

$unique_id, $cached_result

);

if ( $cached_result ne ‘-‘ ) {

# remove stored result

# and return it without ‘long’ processing

$active_req->remove($unique_id);

return $cached_result;

}

else {

# request ‘in-progress’. Depending on implementation

# it can wait for result or start new processing.

# For this example we restart processing

$active_req->remove($unique_id);

}

} ## end if ( defined $unique_id…)

# Subscriber/Created

# Subscriber/Updated

# Subscriber/Deleted

#   variables: i_account

my ( $object, $action ) = split( /\//, $event->{event_type}, 2 );

if ( $object ne ‘Subscriber’ ) {

# ignore

return HTTP_OK;

}

my $i_account = $event->{variables}->{i_account};

if ( !$i_account ) {

# mandatory variable missing: 400 Bad Request

return HTTP_BAD_REQUEST;

}

my $api_session = api_login( $PB_API_USER, $PB_API_PASSWORD );

if ( !$api_session ) {

log_error(‘PB API login failed’);

return HTTP_INTERNAL_SERVER_ERROR;

}

my $account = api_get_account_info( $api_session, $i_account );

if ( !$account ) {

log_error(‘Account not found’);

return HTTP_OK;

}

my $sim_card_list = api_get_sim_cards( $api_session, $i_account );

if ( !$sim_card_list ) {

log_error(‘Failed to get SIM Cards for Account’);

return HTTP_INTERNAL_SERVER_ERROR;

}

# store ‘start’ of processing

$active_req->set( $unique_id => ‘-‘ );

if (

!provision_external_system( {

action    => $action,

account   => $account,

sim_cards => $sim_card_list,

}

)

) {

# TODO add required error processing (alerts, retries, etc)

$active_req->remove($unique_id);

return HTTP_INTERNAL_SERVER_ERROR;

}

# store result

$active_req->set( $unique_id => HTTP_OK );

return HTTP_OK;

} ## end sub process_request

# PSGI application

my $app = sub {

my $env = shift;

my $req = Plack::Request->new($env);

my $code = process_request($req);

return $req->new_response($code)->finalize;

};

log_debug(‘Started’);

return $app;

On this page

Release
What's new
Admin manuals
Handbooks
API
UI help
Back to main menu