The purpose of this document is to discuss the Unified REST RA framework Demonstration.
Audience
This document is for developers and solution architects working with REST APIs on the Metaswitch Rhino TAS or Sentinel platforms.
Contents
-
Introduction — introduces the Rhino REST API Framework Demonstration.
-
Demo Architecture and Implementation — provides an overview of the Rhino REST API Framework Demonstration architecture and features.
-
Introduction to Metaswitch Call Notification — describes the Prototype Metaswitch Call Notification API used with the REST Demonstration.
-
Demo Installation and Setup — explains how to install and setup the REST Demonstration.
-
Demo Call Flow Scenarios — explains the call flow scenarios in the REST Demonstration, how to run them and evaluate the call outcomes.
Introduction
An increasing number of services provide an HTTP-based REST API as their main interface to other network elements. The Rhino REST API Framework provide tools to allow Rhino developers to easily add support for REST APIs in their applications.
Objectives of the REST Demonstration
The focus of the Rhino REST API Framework Demonstration is to meet the following objectives:
-
Demonstrate how a REST API (generated with the REST RA framework) is used in a Rhino TAS based application
-
Demonstrate integration of REST APIs with Sentinel
-
Demonstrate how a Call Notification REST API can be used to influence call processing
The REST Demonstration emulates a real-world architecture with an application based on Sentinel-SIP that uses the prototype MetaSwitch Call Notification REST API during SIP call processing.
REST Demonstration Scenario (Real-World Architecture)
Architecture | Description |
---|---|
|
The MMTel-AS receives SIP triggers from the S-CSCF. The MMTel-AS, acting as an MSW-CN client, sends notification requests to the Business Logic node. The Business Logic node is an MSW-CN server that receives notifications from the MMTel-AS. The Business Logic node will return instructions in response to Call Direction requests that the MMTel-AS will follow in subsequent processing of SIP signaling. |
REST Demonstration Architecture that uses the Metaswitch Call Notification API
The following architecture for the REST Demonstration has been chosen because:
-
Sentinel-SIP implements the required SIP B2BUA behavior
-
Existing SIP features can be used
-
Demonstrates the integration of REST APIs with sentinel platforms
REST Demonstration Architecture | Description |
---|---|
|
A set of features and a session plan that uses sentinel-sip.
|
See Sentinel Overview and Concepts for an overview of the sentinel platform used in the REST Demonstration. See Sentinel Express for sentinel platform documentation. |
Rhino REST API Framework
Historically, applications running on Rhino TAS, including Sentinel VoLTE, have had to support these APIs by using the HTTP Resource Adaptor, and manually constructing the appropriate HTTP messages, which is time-consuming and prone to errors.
Alternatively, it is possible to write a custom Rhino resource adaptor (RA) that presents Rhino applications with a clean Java interface to a REST API. Writing a custom resource adaptor is a lot of work that must be repeated for each REST API that is to be supported. The result of this approach is a proliferation of RAs that are very similar, if each RA only supports one API.
The Rhino REST API Framework provides a better method for supporting REST APIs in Rhino by:
-
Providing a toolchain that can process a number of OpenAPI documents, and produce a fully functional REST Resource Adaptor supporting all of those APIs, ready for use in Rhino services.
-
Supporting OpenAPI v2 and OpenAPI v3 documents.
-
Providing a mechanism for supporting existing REST APIs that may not have an OpenAPI definition, such as OMA Call Notification.
-
Providing a mechanism for supporting REST APIs that use externally-defined types such as XML Schema.
See: Rhino REST API Framework for an overview of the Unified REST RA Framework. |
OpenAPI
The OpenAPI Specification has emerged as the industry standard for describing REST APIs, and this has enabled many tools for generating code and other resources. The problem of "how can my application talk to or implement this REST API" has largely been solved — tools such as openapi-generator can generate client and server code for many languages and frameworks, so getting up and running with a new REST API is a simpler task.
See: Introduction to REST APIs and OpenAPI for an overview of OpenAPI. |
Demo Architecture and Implementation
The REST Demonstration includes the components described below.
Metaswitch Call Notification REST API
The Rhino REST API Framework was used to create the MSW-CN REST API implementation (Creating a REST API). The generated module structure is:
msw-call-notification-rest-api msw-call-notification-rest-api/msw-call-notification-api/src/api.yaml msw-call-notification-rest-api/oma-call-notification-api/src/api.properties msw-call-notification-rest-api/msw-call-notification-api msw-call-notification-rest-api/msw-call-notification-api-ratype msw-call-notification-rest-api/msw-call-notification-api-plugin msw-call-notification-rest-api/msw-call-notification-api-client-sbbpart
-
The MSW-CN group module
-
The MSW-call-notification-api sub-module holds the API specification and a properties file. The other sub-modules have an ivy dependency on this module.
-
An OpenAPI formatted description of the MSW-CN API
-
A properties file that defines java packages, and the names of deployable units
-
Generates a RA type that includes events, provider interfaces and so on for a MSW-CN RA Type that can be deployed in Rhino
-
Generates a library component the Unified REST RA uses to load and drive the MSW-CN API
-
Generates a Jar containing a super-class that developers extend to create an sbb-part, for the client MSW-CN role, in their application. The generated super-class includes all event handlers and slee-annotations required to deploy such an sbb-part in Rhino
Developers may create multiple REST API modules. A REST RA can support one or more such REST APIs. |
Call Notification Resource Adaptor
The Rhino REST API Framework was used to create the Call Notification Resource Adaptor (Creating a REST Resource Adaptor). The REST Demonstration REST resource adaptor module is: call-notification-ra
The call-notification-ra
is an Ant/Ivy build module that depends on the Unified REST RA Core and the Metaswitch Call Notification REST API. The call-notification-ra
produces a Unified REST RA, that supports MSW-CN, used in Sentinel-SIP.
Developers may create multiple REST RA modules, each with different sets of APIs to suit different applications. |
Call Notification Client Event Handler
The MSW-CN event handler sbbpart (rest-demo-feature-modules/msw-call-notification-client-event-handler
) contains an sbb-part that extends the class generated in msw-call-notification-rest-api/msw-call-notification-api-client-sbbpart
. The MswCallNotificationClientEventHandlerSbbpart
class extends the MswCallNotificationApiClientSbbpart
class and implements behaviour on receipt of call direction and call event notification response events.
The super class includes the required SBBPart
, RATypeBinding
and EventMethod
slee annotations.
package com.opencloud.openapi.demo;
// msw call notification API
import com.opencloud.openapi.demo.msw_cn.api.*;
// super-class for an sbb-part to process
// msw call notification related response events
import com.opencloud.openapi.demo.msw_cn.impl.*;
// to bind the event handler sbbpart to the sentinel-sip service
@BinderTargets(services="sip")
public class MswCallNotificationClientEventHandlerSbbpart extends MswCallNotificationClientSbbpart {
public MswCallNotificationClientEventHandlerSbbpart(SbbPartContext context) { super(context); }
@Override
protected void handleCallDirectionNotification_200_SuccessResponse(
CallDirectionNotification_200_SuccessResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallDirectionNotification", response, response.getStatusCode(),
"SuccessResponse", aci, eventContext);
}
@Override
protected void handleCallDirectionNotificationDefaultResponse(
CallDirectionNotificationDefaultResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallDirectionNotification", response, response.getStatusCode(),
"DefaultResponse", aci, eventContext);
}
@Override
protected void handleCallEventNotification_200_SuccessResponse(
CallEventNotification_200_SuccessResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallEventNotification", response, response.getStatusCode(),
"SuccessResponse", aci, eventContext);
}
@Override
protected void handleCallEventNotificationDefaultResponse(
CallEventNotificationDefaultResponse response,
ActivityContextInterface aci,
EventContext eventContext) throws IOException
{
submitResponseEventToSentinel(
"CallEventNotification", response, response.getStatusCode(),
"DefaultResponse", aci, eventContext);
}
// Submit response event to sentinel runtime
// for delivery to a feature for processing
private void submitResponseEventToSentinel(
String operation, Object response, int statusCode,
String description,
ActivityContextInterface aci, EventContext eventContext)
{
final SentinelActivityContextInterface sentinelAci =
getSbbPartContext().asSbbPartActivityContextInterface(aci);
final String target = sentinelAci.getTargetFeature();
if (target == null || target.isEmpty()) {
getTracer().fine("No target feature for response, ignore");
return;
}
getTracer().finer("Pass {} {} {} to target feature \"{}\"",
operation, statusCode, description, target);
try {
SentinelEndpoint sentinelEndpoint =
(SentinelEndpoint) getSbbPartContext().getSbbLocalObject();
sentinelEndpoint.processEvent(
target, response, false, true, aci, eventContext);
}
catch (SentinelFireEventException | IllegalArgumentException | NullPointerException e)
{
getTracer().severe("Exception processing {} {} {} event",
operation, statusCode, description, e);
}
}
}
-
override method in
MswCallNotificationClientSbbpart
to handle a CallDirectionNotification 200 Success response -
override method in
MswCallNotificationClientSbbpart
to handle a CallDirectionNotification default response (i.e all other CallDirectionNotification responses) -
override method in
MswCallNotificationClientSbbpart
to handle a CallEventNotification 200 Success response -
override method in
MswCallNotificationClientSbbpart
to handle a CallEventNotification default response (i.e all other CallEventNotification responses) -
find the feature the response event should be processed by
-
submit the event to the Sentinel-SIP runtime for processing by the target feature
Set Session Plan Feature
The RestSetSessionPlan
feature (rest-demo-feature-modules/msw-set-session-plan-feature
) updates the sentinel selection key PlanID
field with the REST Demonstration session plan ID.
Learn about the Sentinel Selection Key and read Features for an introduction to Sentinel features. |
package com.opencloud.openapi.demo;
@SentinelFeature(
featureName = RestSetSessionPlan.NAME,
componentName = "@component.name@",
featureVendor = "@component.vendor@",
featureVersion = "@component.version@",
featureGroup = SentinelFeature.SIP_FEATURE_GROUP,
executionPhases = ExecutionPhase.SipSessionPhase,
usageStatistics = RestSetSessionPlanStats.class
)
// to bind the feature to the sentinel-sip service
@BinderTargets(services = "sip")
public class RestSetSessionPlan
extends BaseFeature<SentinelSipSessionState, SentinelSipMultiLegFeatureEndpoint>
implements InjectFeatureStats<RestSetSessionPlanStats>
{
public static final String NAME = "RestSetSessionPlan";
// ...
@Override
public void startFeature(Object trigger, Object activity, ActivityContextInterface aci) {
final SentinelSelectionKey selectionKey = getSessionState().getSentinelSelectionKey();
// Set the plan ID in the selectionKey to 'rest_demo'
selectionKey.setPlanId(REST_DEMO_SESSION_PLAN);
getTracer().finest("SentinelSelectionKey set: {}", selectionKey);
getSessionState().setSentinelSelectionKey(selectionKey);
getCaller().featureHasFinished();
}
private static final String REST_DEMO_SESSION_PLAN = "rest_demo";
}
The value of the SentinelSelectionKey
is used by the Sentinel run-time to resolve feature configuration, and the feature execution scripts that run.
There are many alternate approaches to deriving a plan ID. For example the plan ID could be derived by analysing headers in the SIP INVITE. |
Session plans, the Sentinel-SIP session plan and feature execution scripts are explained here: Session Plans, SIP Sessions Initiated via SIP INVITE and Feature Execution Scripts. |
Send Call Notification Feature
The RestSendCallNotification
feature (rest-demo-feature-modules/msw-send-call-notification
) sends CallDirection and CallEvent notification requests and processes the corresponding responses. The RestSendCallNotification feature:
-
determines the type of notification event to send, if appropriate, based on the point-in-session and the triggering SIP event.
-
will influence the current SIP session based on CallDirection response events. For example the session may be ended, routed to an alternate destination or allowed to continue.
@Override
public void startFeature(Object trigger,
Object activity,
ActivityContextInterface aci)
{
// Find the active point-in-session
final SipFeatureScriptExecutionPoint executionPoint =
getSessionState().getCurrentSipFeatureExecutionPoint();
if (trigger instanceof IncomingSipRequest
|| trigger instanceof IncomingSipResponse)
{
// Determine the notification type and call event
final CallNotificationTypes notificationType;
final CallEvents callEvent;
if (trigger instanceof IncomingSipRequest) {
final IncomingSipRequest request = (IncomingSipRequest) trigger;
notificationType = determineNotificationType(
executionPoint, request);
callEvent = determineCallEvent(executionPoint, request);
}
else {
final IncomingSipResponse response = (IncomingSipResponse) trigger;
notificationType = determineNotificationType(
executionPoint, response);
callEvent = determineCallEvent(executionPoint, response);
}
if (null == notificationType || null == callEvent) {
getCaller().featureHasFinished();
return;
}
final LegManager lm = getCaller().getLegManager();
final Leg calledPartyLeg = lm.getCallingPartyLeg().getLinkedLeg();
// if there is no linked leg, then this response will have been received
// on a leg associated with an MRF (for an announcement).
// A notification is not required in this case.
if (null == calledPartyLeg) {
final Leg leg = getCaller().getLegManager().getLeg(aci);
getTracer().finest("A notification is not required at: {} for response on leg {}",
executionPoint, leg.getLegName());
getCaller().featureHasFinished();
return;
}
// send a notification
// if the leg is not active, a sip session has not been created yet
// if there is no INVITE request, then an INVITE has not
// been sent on the cld leg yet
if (calledPartyLeg.isActive()
&& null != calledPartyLeg.getInviteRequest())
{
sendNotification(calledPartyLeg, notificationType, callEvent);
}
else {
sendNotification(aci, notificationType, callEvent);
}
}
else {
// Process a notification response event
if (processNotificationResponse(
executionPoint, trigger, activity, aci))
{
getCaller().featureHasFinished();
}
getCaller().detach(aci);
}
}
-
Find the currently execution feature script execution point.
-
Determine the notification type and call event based on the triggering SIP message, and the point-in-session
-
Send a notification request message
-
Process a
CallDirection
orCallEvent
notification response event.
Determining the MSW-CN notification event to send
// Determine the notification type from the triggering SIP request
private CallNotificationTypes determineNotificationType(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipRequest request)
{
switch (executionPoint) {
// the initial trigger
case SipAccess_SessionStart:
return CallNotificationTypes.CALLDIRECTION;
// one of the participants hangs up
case SipMidSession_PartyRequest:
return "BYE".equals(request.getMethod())
? CallNotificationTypes.CALLDIRECTION : null;
// a notification is not required
default: return null;
}
}
// Determine the notification type from the triggering SIP response
private CallNotificationTypes determineNotificationType(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipResponse response)
{
switch (executionPoint) {
case SipAccess_PartyResponse:
if (! "INVITE".equals(response.getRequest().getMethod()))
return null;
switch (response.getStatus()) {
case SipResponse.SC_OK:
return CallNotificationTypes.CALLEVENT;
// 486 - called party busy
case SipResponse.SC_BUSY_HERE:
// 404 - no route to destination
case SipResponse.SC_NOT_FOUND:
// 408 - no answer
case SipResponse.SC_REQUEST_TIMEOUT:
// 480 - no answer
case SipResponse.SC_TEMPORARLY_UNAVAILABLE:
return CallNotificationTypes.CALLDIRECTION;
// a notification is not required
default: return null;
}
case SipMidSession_PartyResponse:
if (SipResponse.SC_OK == response.getStatus()
&& "BYE".equals(response.getRequest().getMethod()))
return CallNotificationTypes.CALLEVENT;
// otherwise a notification is not required
return null;
// a notification is not required
default: return null;
}
}
-
At
SipAccess_SessionStart
Sentinel-SIP is processing a triggeringINVITE
request, so send aCallNotificationTypes.CALLDIRECTION
request. AtSipMidSession_PartyRequest
we are interested inBYE
requests and send aCallNotificationTypes.CALLDIRECTION
request. -
At
SipAccess_PartyResponse
we are interested in responses to the triggeringINVITE
request. On answer, send aCallNotificationTypes.CALLEVENT
request, otherwise send aCallNotificationTypes.CALLDIRECTION
request. AtSipMidSession_PartyResponse
we are interested in responses toBYE
requests and send aCallNotificationTypes.CALLEVENT
request.
// Determine the CallEvent from the triggering SIP request
private CallEvents determineCallEvent(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipRequest request)
{
switch (executionPoint) {
// the initial trigger
case SipAccess_SessionStart:
return CallEvents.CALLEDNUMBER;
// one of the participants hangs up
case SipMidSession_PartyRequest:
return "BYE".equals(request.getMethod())
? CallEvents.DISCONNECTED : null;
// a notification is not required
default: return null;
}
}
// Determine the CallEvent from the triggering SIP response
private CallEvents determineCallEvent(
SipFeatureScriptExecutionPoint executionPoint,
IncomingSipResponse response)
{
switch (executionPoint) {
case SipAccess_PartyResponse:
if (! "INVITE".equals(response.getRequest().getMethod()))
return null;
switch (response.getStatus()) {
case SipResponse.SC_OK:
return CallEvents.ANSWER;
// 486 - called party busy
case SipResponse.SC_BUSY_HERE:
return CallEvents.BUSY;
// 404 - no route to destination
case SipResponse.SC_NOT_FOUND:
return CallEvents.NOTREACHABLE;
// 408 - no answer
case SipResponse.SC_REQUEST_TIMEOUT:
// 480 - no answer
case SipResponse.SC_TEMPORARLY_UNAVAILABLE:
return CallEvents.NOANSWER;
// a notification is not required
default: return null;
}
case SipMidSession_PartyResponse:
if (SipResponse.SC_OK == response.getStatus()
&& "BYE".equals(response.getRequest().getMethod()))
return CallEvents.DISCONNECTED;
// otherwise a notification is not required
return null;
// a notification is not required
default: return null;
}
}
-
At
SipAccess_SessionStart
Sentinel-SIP is processing a triggeringINVITE
request (aCallEvents.CALLEDNUMBER
event). AtSipMidSession_PartyRequest
we are interested inBYE
requests (CallEvents.DISCONNECTED
events) -
At
SipAccess_PartyResponse
we are interested in responses to the triggeringINVITE
request (CallEvents.ANSWER
,CallEvents.BUSY
and so on). AtSipMidSession_PartyResponse
we are interested in responses toBYE
requests (CallEvents.DISCONNECTED
events).
Sending CallDirection and CallEvent notification requests
The RestSendCallNotification
feature sends CallDirection
and CallEvent
notification requests in the following way.
// Send a call event notification
private void sendNotification(
String from, String fromDisplayName, String to,
CallNotificationTypes notificationType, CallEvents callEvent)
{
final SendCallNotificationConfig configuration =
configurationReader.getConfiguration(
getSessionState().getSentinelSelectionKey());
final ApiConfiguration apiConfiguration =
ApiConfiguration.standardConfiguration();
apiConfiguration.set(ApiOptions.destinationUrl,
configuration.getDestinationUrl());
final MswCallNotificationRaTypeApiClient client =
cnProvider.getApiClient(apiConfiguration);
// Create a new CallEventNotification object
final CallEventNotification notification = new CallEventNotification();
try {
notification.notificationType(notificationType)
.eventDescription(new EventDescription().callEvent(callEvent))
.calledParticipant(new URI(to))
.callingParticipant(new URI(from))
.callingParticipantName(fromDisplayName);
}
catch (URISyntaxException e) {
getTracer().severe(
"Failed to extract participants from: {}, to: {}", from, to, e);
}
// Create a REST request to send
final NotificationApi nApi = client.getNotificationApi();
final RestRequestBuilder notificationRequest =
notificationType == CallNotificationTypes.CALLEVENT
? nApi.createCallEventNotificationRequest(notification)
: nApi.createCallDirectionNotificationRequest(notification);
try {
// Send the REST request
final OutgoingRestActivity outgoingRestActivity =
notificationApi.sendRequest(notificationRequest);
final ActivityContextInterface newAci =
cnACIFactory.getActivityContextInterface(outgoingRestActivity);
final SentinelActivityContextInterface outgoingAci =
getCaller().asSentinelActivityContextInterface(newAci);
stats.incrementSendCallNotification(1);
// Set the target feature to 'RestSendCallNotification'
outgoingAci.setTargetFeature(NAME);
// Attach to the new activity, wait for the response
outgoingAci.attach(getFacilities().getSbbLocalObject());
getTracer().finest("Notification sent. Waiting for response");
getCaller().featureWaiting(outgoingAci);
}
catch (IOException e) {
getCaller().featureFailedToExecute(
new FeatureError(FeatureError.Cause.unclassified,
"Failed to send notification", e));
}
}
-
The
CallEventNotification
has:-
a notification type (
CallNotificationTypes.CALLEVENT
orCallNotificationTypes.CALLDIRECTION)
-
a call event (
CallEvents.CALLEDNUMBER
,CallEvents.ANSWER
and so on) -
called participant (the
To
address), calling participant (theFrom
address) and the calling participant display name
-
-
Create an appropriate REST request message by using the MSW-CN
NotificationApi
. -
Send the REST request. The Unified REST RA returns an
OutgoingRestActivity
related to the sent REST request. -
Set
TargetFeature
on theActivityContextInterface
toRestSendCallNotification
so Sentinel-SIP will route the corresponding response to the currently executing feature. -
Attach to the activity and tell Sentinel-SIP that we are waiting for a response. Sentinel-SIP pauses executing the active feature execution script until a response event arrives.
Processing CallDirection 200 Success response events
A CallDirectionNotification
success response includes instructions that must be followed to effect ongoing call processing.
private boolean processDirectionNotificationSuccessResponse(
SipFeatureScriptExecutionPoint executionPoint,
CallDirectionNotification_200_SuccessResponse trigger,
Object activity)
{
// Determine the action to perform
final Action actionToTake = trigger.getContent();
if (null == actionToTake || null == actionToTake.getActionToPerform()) {
stats.incrementCallDirectionResponseWithNoActionToTake(1);
return true;
}
// Apply the requested Action
switch(actionToTake.getActionToPerform()) {
case ROUTE:
// Get the new destination
final URI newDestination = actionToTake.getRoutingAddress();
if (null == newDestination) {
stats.incrementCallDirectionNoRoutingAddress(1);
return true;
}
final org.jainslee.resources.sip.URI sipDestination =
getSipUri(newDestination);
if (null == sipDestination) {
stats.incrementCallDirectionNonSipRoutingAddress(1);
return true;
}
stats.incrementCallDirectionRoute(1);
// route the call to the new destination
routeToNewDestination(executionPoint, sipDestination);
return true;
case CONTINUE:
// Allow the call to continue without interruption
stats.incrementCallDirectionContinue(1);
return true;
case ENDCALL:
// End the call
stats.incrementCallDirectionEndCall(1);
// Check to see if an annoucement should be played
final String announcementIdStr = actionToTake.getAnnouncementId();
if (null == announcementIdStr) {
getTracer().fine("Action is to ENDCALL");
// defer to the leg manager to end the session
final LegManager lm = getCaller().getLegManager();
lm.endSession(SipResponse.SC_FORBIDDEN);
}
else {
// Schedule an announcement
try {
// announcement ID should be a Long.
final long announcementId = Long.parseLong(announcementIdStr);
getTracer().fine("Action is to ENDCALL after playing announcement: {}",
announcementIdStr);
// tell sentinel-sip about the announcement to be played
final List<SipAnnouncementInformation> announcementQueue =
getSessionState().getEarlyMediaAnnouncementInfoQueue();
announcementQueue.add(SipAnnouncementInformation.getBuilder()
.setAnnouncementId(announcementId).build());
getSessionState().setEarlyMediaAnnouncementInfoQueue(announcementQueue);
// tell sentinel-sip to end the session after the announcement
getSessionState().setEndSessionAfterAnnouncement(SipResponse.SC_FORBIDDEN);
// used in rest-demo feature execution scripts
getSessionState().setPlayAnnouncementThenEndSession(true);
}
catch (NumberFormatException nfe) {
getTracer().fine("Announcement id: {} is not a valid. Ending the call.", announcementIdStr);
// defer to the leg manager to end the session
final LegManager lm = getCaller().getLegManager();
lm.endSession(SipResponse.SC_FORBIDDEN);
}
}
// generate a DISCONNECTED notification
sendNotification(
lm.getCallingPartyLeg(),
CallNotificationTypes.CALLEVENT,
CallEvents.DISCONNECTED);
return false; // sent a notification, feature has not finished
}
return true;
}
-
The
CallDirectionNotification
200 success response body contains anAction
object that dictates how the active SIP session should be processed. -
The
ActionToPerform
is one ofROUTE
,CONTINUE
orENDCALL
-
If the action to perform is
ROUTE
theactionToTake
will also include a new destination address. -
Route the call to the new adddress.
-
If the action to perform is
CONTINUE
allow the call to continue uninterrupted. -
If the action to perform is
ENDCALL
delegate to Sentinel-SIP. -
The
actionToTake
may, optionally, include an announcement ID -
If
actionToTake
includes an announcement ID, then update session state, so thePlayAnnouncement
feature can play the announcement -
Generate a
DISCONNECTED
notification
Learn more about the SIP Play Announcement Feature. |
Feature Execution Scripts
The RestSetSessionPlan
feature (rest-demo-feature-modules/msw-set-session-plan-feature
) updates the sentinel selection key to with the REST Demonstration session plan. Sentinel-SIP uses the sentinel-selection-key when resolving the feature execution scripts to run at each point in session.
For more details on the sentinel selection key, feature execution scripts and Sentinel-SIP feature script execution points see: Sentinel Selection Key Feature Execution Scripts and Sentinel SIP Feature Execution Points |
The following feature execution scripts have been added for the REST Demonstration.
Point-in-Session | feature Execution Script |
---|---|
|
featurescript RestSessionStart { if not LegManager.endSession and not LegManager.detachAll and not LegManager.currentLeg.releaseLeg { run AcceptSip run RestSetSessionPlan run DoNotChargeSipSession } } |
|
featurescript DemoPostSessionStart { if not LegManager.endSession { run RecordTimestamps mode "outbound" run RestSendCallNotification if session.PlayAnnouncementThenEndSession { run SipPlayAnnouncement } } run DetermineCauseCode run DiameterServiceInfo run DiameterPerLegInfo mode "outbound" } |
|
featurescript RestDemoSessionCheck { if not session.PlayAnnouncementThenEndSession { run SipSubscriberDetermination run CallPartiesAddressDetermination run SessionTracing } } |
|
featurescript RestDemoSubscriberCheck { if not session.PlayAnnouncementThenEndSession { run SubscriberDataLookup run SubscriberValidity } } |
|
featurescript RestSipAccessPartyRequest { run RestSendCallNotification if session.PlayAnnouncementThenEndSession { run SipPlayAnnouncement } } |
|
featurescript RestSipAccessPartyResponse { run RestSendCallNotification if session.PlayAnnouncementThenEndSession { run SipPlayAnnouncement } } |
|
featurescript DemoMidSessionPartyRequest { run RestSendCallNotification if session.PlayAnnouncementThenEndSession { run SipPlayAnnouncement } } |
|
featurescript DemoMidSessionPartyResponse { run RestSendCallNotification if session.PlayAnnouncementThenEndSession { run SipPlayAnnouncement } } |
Introduction to Metaswitch Call Notification
The Metaswitch Call Notification API is a prototype call notification API based on a subset of the OMA Call Notification API. In particular the Metaswitch Call Notification API implements call event and call direction notifications with a minor addition to support annoucements.
See: {introduction-to-rest-and-openapi} for an introduction to REST (REpresentational State Transfer) APIs. |
Capabilities of the Call Notification API
The SIP AS (MSW-CN client), in the following diagram, receives a SIP message from the S-CSCF and determines if a call event or call direction should be generated (in terms of call leg, point-in-call and so on), The MSW-CN server sends a notification request to an MSW-CN server if a call event or call direction is required.
MSW-CN client MSW-CN server Notification |
|
There are two types of notification made by an MSW-CN client to an MSW-CN server.
-
Call Event: For call event notifications
-
An MSW-CN client notifies an MSW-CN server of events on a call/session.
Call processing continues.
This is analogous to an SSF sending event-reports, armed with notify-&-continue, to an SCF
-
-
Call Direction: For call direction notifications, where the client waits for instructions in the notification response.
-
An MSW-CN client notifies an MSW-CN server of events on a call/session.
The MSW-CN client blocks ongoing call processing and waits for instructions from the MSW-CN server (in the response to the notification).
This is analogous to an SSF sending event-reports, that had been armed with suspend, to an SCF where the BCSM blocks waiting-for-instructions.
-
The {arch-rest-api-framework } generates an OpenAPI specification description of MSW-CN into Resource Adaptor Type that Rhino applications use.
The API definition is: msw-call-notification-rest-api/msw-call-notification-api/src/api.yaml . |
The following sections illustrate aspects of the MSW-CN API and how it is realized in Java va the REST API framework.
Call Event and Call Direction Notifications
The YAML description below shows that an HTTP POST request, with a request URL including /callevent/notification
corresponds to a call event notification. The body of the request is a CallEventNotification
JSON object. A 200 OK
response indicates the request was received. Any other response corresponds to an error.
/callevent/notification:
post:
summary: A new Call Event notification
operationId: callEventNotification
tags:
- notification
requestBody:
required: true
description: call event notification
content:
application/json:
schema:
$ref: "#/components/schemas/CallEventNotification"
responses:
'204':
description: | The 204 No Content HTTP status code is returned if the data was received successfully
default:
description: unexpected error
The YAML description below shows that an HTTP POST request, with a request URL including /calldirection/notification
corresponds to a call direction notification. The body of the request is a CallEventNotification
JSON object. A 200 OK
response indicates the request was received successfully. The body of the response is a Action
JSON object. Any other response corresponds to an error.
/calldirection/notification:
post:
summary: A new Call Direction notification
operationId: callDirectionNotification
tags:
- notification
requestBody:
required: true
description: call direction notification
content:
application/json:
schema:
$ref: "#/components/schemas/CallEventNotification"
responses:
'200':
description: | The 200 OK HTTP status code is returned if the data was received successfully. The response body contains the request 'Action to take'
content:
application/json:
schema:
$ref: '#/components/schemas/Action'
default:
description: unexpected error
The REST API framework generates a number of Java interfaces, enums and classes from the YAML specification. For example, an application uses the NotificationApi
below to create Call Direction and Call Event notification requests.
package com.opencloud.openapi.demo.msw_cn.api;
import com.opencloud.slee.rest.common.*;
import com.opencloud.openapi.demo.msw_cn.model.*;
public interface NotificationApi {
// Create a RestRequestBuilder for request: CallDirectionNotification
RestRequestBuilder createCallDirectionNotificationRequest(
CallEventNotification callEventNotification);
// Create a RestRequestBuilder for request: CallEventNotification
RestRequestBuilder createCallEventNotificationRequest(
CallEventNotification callEventNotification);
// Send a request
OutgoingRestActivity sendRequest(
RestRequestBuilder requestBuilder) throws IOException;
}
A concrete example showing how the MSW-CN API can be used can be seen in See: Sending CallDirection and CallEvent notification requests for a code excerpt showing sending a Call Direction request |
When Notifications can be Sent
Notifications are sent at certain points in the basic call model. These are:
|
YAML CallEvents Enum
YAML CallNotificationTypes Enum
YAML AddressDirection Enum
|
Actions Available in a Call Direction Response
A Call Direction response contains an Action object in the response body. The action describes the actionToPerform
.
|
YAML Action Component
YAML ActionValues Enum
|
Demo Installation and Setup
Prerequisites
The REST Demo requires a Linux system with OpenJDK 11. The JAVA_HOME
shell environment variable should be set to the base directory of the OpenJDK installation.
The REST Demo is deployed on Rhino TAS. The REST Demo installer can install Rhino automatically (recommended), or you can install it manually. If installing Rhino manually, use Rhino SDK 3.0.0.0 or later.
The REST Demo requires access to Metaswitch Artifactory servers. Please contact Metaswitch for a username and password. |
Licensing
A license file is required to run Rhino, the Sentinel SIP components and the Metaswitch Call Notification API used in the demo.
The REST demo package includes a license file (rest-demo-license.license) that is valid for two months from the release of the REST demo.
Contact Metaswitch if you require a new license file. |
If you allow the installer to install Rhino automatically, it will prompt you for the location of the license file.
If you prefer to set up Rhino manually, then you need to install the license file prior to installing the demo.
Installation
Prepare the REST Demo package
Download the Rhino REST API Demonstration
The REST Demo requires access to Metaswitch Artifactory servers. Please contact Metaswitch for a username and password. |
The REST demo package contains an installer program that guides you through the installation process, and downloads the other components needed to run the demo.
After downloading, extract the REST Demo zip file. This will create the rest-demo-sdk
directory. In this directory there is an installer that guides you through the following steps:
-
You may optionally take the demo offline. The installer will download all additional components so that installation can continue later without accessing the network.
-
You can choose to install the Rhino TAS SDK, if you don’t have it already.
-
Source code for the REST Demo modules is extracted and built.
-
Finally the REST Demo components are deployed and configured in the Rhino TAS instance specified in (2).
The installer captures full logging from the various tools that it uses, and writes these logs into the rest-demo-sdk/build/target/log directory. This can be helpful when debugging issues. |
Before installing, if the host requires a proxy to access Artifactory then it must be configured in sdk.properties . sdk.properties can be found in the top-level directory of the unzipped package. Find the section marked with # Proxy settings and change it to the following: |
# Proxy settings # sdk.http.proxyHost=<proxy hostname here> sdk.http.proxyPort=<proxy port here> sdk.https.proxyHost<proxy hostname here> sdk.https.proxyPort=<proxy port here> # # These properties are used for both http and https. sdk.http.nonProxyHosts=localhost|127.0.0.1
Install the REST Demo modules
1 |
Start the installer Run the $ cd rest-demo-sdk $ build/bin/installer The installer first initialises its environment, showing output similar to the following: Initialising the SDK ... Retrieving Installer dependencies ... done. The installer may prompt for Artifactory credentials, which should have been supplied to you by Metaswitch. |
||
---|---|---|---|
2 |
Prompting for information The installer will prompt the user for some configuration values. A value inside square brackets, if present, is the default answer for that question. Pressing the Explanations of all of the questions the installer will ask are laid out over the next few steps. Note that some of the questions will only appear under certain circumstances, so not all of them will be seen in a given installer run. |
||
3 |
Taking the SDK offline The installer asks if the SDK should be taken offline. You can optionally take the SDK offline by creating a local repository. This will take several minutes depending on connection speed, but will make subsequent retrievals much faster and remove the need for an internet connection. Do you want to take the SDK offline? y/[N] > If the user presses the The user is then presented with progress information related to the downloading of artifacts necessary to take the SDK offline. This process can take around 10 minutes. |
||
4 |
Basic SDK Questions The values provided in this step are used in SLEE component identifiers and Ivy artifacts created when the REST Demo modules are built below. Your organization's name, e.g. Rocket Communications Inc. sdk.component.vendor [UNSET] > This value will be used for the sdk.component.version [1.0] > This value will be used for the The name of the platform operator, e.g. Rocket. sdk.platform.operator.name [UNSET] > The name of the platform operator for the system. It is used extensively throughout configuration profiles. An Ivy organization field, recommended lower case with no whitespace e.g. "rocket". sdk.ivy.org [UNSET] > This value is used as the sdk.ivy.publish.revision [1.0.0] > This value is used as the base of the |
||
5 |
Installing Rhino You can either have the installer set up a Rhino SDK for you or point it at an existing Rhino installation, SDK or production. Note: If you want to use an existing Rhino installation it has to be running and a proper license has to be installed when finishing the installation after the configuration. Also make sure that you have adjusted the memory settings and created a tcapsim-gt-table.txt file as detailed in the documentation. Set up a Rhino SDK installation automatically? y/[N] > If the installer needs to set up a new Rhino SDK installation, it will prompt for a license file. Enter the path to your Rhino license file > /home/restdemo/rest-demo-sdk/opencloud.license The Rhino TAS SDK will be installed and started in the background. If the user instructs the installer to use an existing Rhino TAS, the path to the Rhino client directory must be entered. Enter the path to your Rhino client directory > /home/restdemo/RhinoSDK If the Rhino TAS installation is a production version, then additional information is required to complete configuration. You can either have the installer deploy against Rhino SDK or production. Does the specified client point to a production installation? y/[N] > If Yes is selected, the installer prompts for details of the cluster nodes and hosts. Otherwise, the user enters the host that Rhino TAS SDK is installed on. The host your Rhino SDK is installed on. Rhino host [localhost] > |
||
6 |
Review settings Once the basic SDK configuration questions have been answered, the user is provided the opportunity to review, and if happy, accept the settings.
Review settings *************** Basic SDK properties ==================== sdk.component.vendor: Rocket Communications Inc sdk.component.version: 1.0 sdk.platform.operator.name: Rocket sdk.ivy.org: rocket sdk.ivy.publish.revision: 1.0.0 Rhino settings ============== Deploy Rhino: Yes License location: /home/restdemo/rest-demo-sdk/opencloud.license Accept these values? [Y]/n > y Updating file sdk.properties Updating file sdk.properties Configuration changes written. If the user presses the |
||
7 |
Create deployment modules Next, the installer prompts the user to create and build the REST demo modules. These include Sentinel-SIP features and REST API RA Types used by those features to communicate with an external API server. This creates the Create REST Demo Modules ======================== You can optionally create & build the demo REST RA and Call Notification API feature modules. This will create the "rest-demo-modules" directory and build the modules it contains. These modules will be deployed automatically by the installer. If the "rest-demo-modules" directory already exists, it will be deleted before being recreated and rebuilt. Delete & rebuild REST demo modules? [Y]/n > The user is then asked to confirm: Review settings *************** Create REST Demo Modules ======================== Create & build REST demo modules: Yes Accept these values? [Y]/n > y Building the modules will take a few minutes. Creating module rest-demo-modules ...done. Building REST Demo modules; this is going to take a while ... done. Configuration changes written. Creating deployment module deploy-rest-demo ...done. |
||
8 |
Deployment The installer can now deploy the REST Demo features into Rhino. It will prompt the user to confirm whether to proceed with the installation: Install now? [Y]/n > Installation can be deferred to a later time by entering Otherwise, the installer proceeds with deploying the REST Demo modules into Rhino. This may take around 10-15 minutes. Installing Rhino ...done. Starting Rhino in the background ...done. Installing List CDRs tool ...done. Publishing deployment module deploy-rest-demo ...done. Deploying; this is going to take a while ...done. Binding; this is going to take a while ...done. Configuring; this is going to take a while ...done. Installation completed successfully in 11 minutes and 14 seconds. Rhino has been left running to finish applying configuration changes. The configuration has been saved to the file /home/restdemo/rest-demo-sdk/install.properties. This file can be used to re-run the installation non-interactively with the same settings. The installation is now complete. |
When the installer completes, the Rhino TAS will be running with Sentinel SIP and the call notification features described in Demo Architecture and Implementation.
Directory Structure
The contents of the rest-demo-sdk
directory are as follows:
Name | Description |
---|---|
build/ |
Directory containing the installer and Ant scripts for building and deploying SDK components. |
rest-demo-modules/ |
Directory containing:
|
test-suite/ |
Directory containing a test suite that simulates some example SIP call scenarios, showing the demo features making REST calls to a simulated HTTP server. See [running-the-test-suite] below. |
run-tests |
A script that runs the tests in the |
rhino-sdk/ |
Directory containing the Rhino TAS SDK that was created by the installer (if requested). |
build.xml, sdk.properties, deps.properties |
Ant build and properties files for building the project. Should not need to be modified. |
ivy.properties, release.properties |
Dynamically-generated properties files for resolving the various components deployed by the demo. Automatically recreated when building or deploying REST demo modules. |
Running the Test Suite
The test-suite
directory contains integration tests that demonstrate various scenarios using the demo Call Notification API. The test scenarios simulate a SIP node (e.g. an S-CSCF) sending calls to Rhino, and an HTTP server that responds to the REST requests sent by the Sentinel-SIP features running in Rhino.
To run the tests, first run the installer as above, then run the run-tests
script. This runs all scenarios in the test suite, and logs the call flows as they are performed.
See Demo Call Flow Scenarios for more information on the test scenarios.
Demo Call Flow Scenarios
This section describes the call flow scenarios provided with the REST API Framework demonstration, and provides instructions for running the scenarios and analysing the results.
Rest Demo Test Suite
The REST Demonstration includes a test-suite at: test-suite
.
Test suite structure
The most important files and directories in the test suite are:
test-suite/build.xml test-suite/config test-suite/log4j.properties test-suite/scenarios test-suite/target
-
apache ant
build file for running integration tests -
directory containing configuration files for interfaces and scenario simulators used in the test suite
-
log4j properties files that dictates the logging generated by the test suite and scenario simulators. This does not have any impact on the tracing levels of Rhino (i.e the system under test)
-
directory containing a sub-directory per test scenario
-
directory that contains sub-directories such as
results
, which the output from each scenario is collated
Anatomy of a call flow scenario
The scenarios in the test suite are all contained in sub-directories of scenarios
.
scenarios/call-notification/cn01001-successful-call-no-cn scenarios/call-notification/cn01003-successful-call-direction-continue scenarios/call-notification/cn01004-successful-call-direction-endcall ...
Each scenario directory contains up to three files:
-
scscf.scen — A scenario simulator script that defines the expected signalling between all the roles in the scenario. In the case of the REST demo, the roles include the SCSCF, Sentinel/Rhino TAS and the Call Notification server.
-
sas.yaml — the expected Service Assurance Server (SAS) event messages during the scenario
-
usage.yaml — the expected statistics that will be raised during the call flow
Learn more about the Scenario Simulator and Scenario Editor. |
How to execute scenarios
There three methods you may follow to run scenarios:
-
Run all tests by using the run-tests script from the root of the rest-demo installation:
rest-demo$ ./run-tests
-
Run all tests by using
apache ant
in the testsuite directory:rest-demo/test-suite $ ../build/bin/ant auto run-integration-tests
-
Run a single test by using
apache ant
in the testsuite directory:rest-demo/test-suite $ ../build/bin/ant auto choose
A file-chooser will open, which allows you to select one of the tests in the test suite to execute.
Reviewing the test logs
The outcome of each scenario appears in complementary directories in targets/results
. Each scenario results directory contains up to three files:
-
stu.log — records the output from the test runner, including the messages sent/to from protocol simulators, statistics collected and sas events analysed.
-
localhost-101-rhino.log — records the output from the Rhino TAS as the scenario executes. The log will include output from the SIP SIS, Rhino, the call notification resource adaptor and the Sentinel-SIP service.
-
sas-sim.log — records output from the SAS simulator, which includes details of all marker and event messages received from Rhino whilst the scenario executes.
Scenarios
Each scenario may include the following roles:
-
SCSCF — SIP signalling between the SCSCF and a SIP AS, which in this case is Sentinel SIP on the Rhino TAS
-
MRF — SIP signalling between the Rhino TAS and a MRF (for announcements)
-
Sentinel — Sentinel SIP running on the Rhino TAS, including the REST Demo features and the REST Demo call notification resource adaptor.
-
CN-Server — HTTP signalling between Sentinel and an external Call Notification server. The CN server responds to call notification direction and event REST requests and answers with call notification direction and event REST responses.
Some SIP signalling, such as reliable provisional responses, have been omitted from the following call flow diagrams for the sake of brevity. |
Action during call setup
The focus of these scenarios is the call notification direction request generated on the initial SIP INVITE. In each scenario, the Call Notification server responds with instructions to influence subsequent processing of the call.
cn01003-successful-call-direction-continue
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
Called party answers |
… sends a |
Calling party ends the call |
… sends a The CN server responds with an |
On the CN server response |
… sends a |
cn01004-successful-call-direction-endcall
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
On the CN server response |
… rejects the call by sending a |
cn01005-successful-call-direction-route
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
Called party answers |
… sends a |
Calling party ends the call |
… sends a The CN server responds with an |
On the CN server response |
… sends a |
cn01009-successful-call-direction-number-translation
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
Called party answers |
… sends a |
Calling party ends the call |
… sends a The CN server responds with an |
On the CN server response |
… sends a |
cn01010-successful-call-direction-endcall-with-announcement
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
On the CN server response |
… plays announcement ( |
The announcement ends |
… rejects the call by sending a |
Action based on response of the called party
The focus of these scenarios is the call notification direction request generated when the called party fails to respond. In each scenario, the Call Notification server responds with instructions to ROUTE
the call to an alternate destination.
cn01006-successful-call-forward-on-busy
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
Called party is busy |
… sends a The CN server responds with an |
On the CN server response |
… directs the call to the alternate destination ( |
Called party answers |
… sends a |
Calling party ends the call |
… sends a The CN server responds with an |
On the CN server response |
… sends a |
cn01007-successful-call-forward-on-no-answer
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
Called party does not answer |
… sends a The CN server responds with an |
On the CN server response |
… directs the call to the alternate destination ( |
Called party answers |
… sends a |
Calling party ends the call |
… sends a The CN server responds with an |
Called party is notified the call is ended |
… sends a |
cn01008-successful-call-forward-on-rsf
Point in session | Sentinel … |
---|---|
On the initial INVITE |
… sends a The CN server responds with an |
Called party cannot be reached |
… sends a The CN server responds with an |
On the CN server response |
… directs the call to the alternate destination ( |
Called party answers |
… sends a |
Calling party ends the call |
… sends a The CN server responds with an |
Called party is notified the call is ended |
… sends a |