Reference14r1:Concept App Service TechAssist
Applies To
- innovaphone PBX from version 13r2
Overview
The idea of the TechAssist App is to analyze static and runtime information of a system and checks it for issues and improvements. This way, the app can help you find and solve problems and misconfigurations independently and quickly. To do this, the app supports the execution of tests. A Bunch of Vendor Tests are shipped with the App, but you can also create and save your own test.
Testresults can be downloaded to attach them to a support ticket.
Technical look inside
thumb|upright=1.0|right|Technical Overview The app service receives multiple incoming websocket connections of the app objects from the PBXes. By assigning rights to a to your desired "devices-apis" (in the Apps tab), the app service receives the corresponding rights to connect to the respectively activated DevicesApps. All managed domains and devices can then be read in via this connection. (One TechAssist can only connect with one device-api) With this procedure, you can work with one DevicesApp instance and across all domains and devices in it.
Submodules
The app contains various sub-modules in order to be able to carry out the tests provided by the manufacturer and also to be able to integrate custom tests created by yourself.
- scheduler
- The scheduler initially loads all tests and performs a plausibility check of the individual tests. From this status onwards, the scheduler ensures that all tests are executed in the required time intervals.
- Inside the Scheduler the tick is 5000 milliseconds, and test are executed in a synchronous way to prevent high system load
- cmd
- The cmd submodule (derived from "command") can communicate with devices in the installation to query a status, the config or similar. The submodule takes care of all the information that tests from an installation want to have, consolidates them so that queries do not have to be executed multiple times and caches the results.
- test
- The test submodule is instantiated per test and provides all necessary functionalities within a test.
Language
By design, only the app itself is available in multiple languages. The content of the tests and technical output is intentionally only available in english.
Tests
The app contains a bunch of embedded test, but it is also possible to create and save your own tests.
Licensing
There are no licenses needed for this app
Installation
AP - Manually Upload
- Activate the developer mode to activate the "App Upload" button
- Click on "App upload"
- App ID: techassist
- Title: TechAssist
- Add also the icon file
- Create an instance for the App
App Store
- You have to create a needed APP object via the PBX Manager-Plugin
Create your App with the PBXManager
You have to create an APP object via the PBX Manager-Plugin. Think about that, the App needs access-right to one devices-api. If you use the PBXManager to create the APP Object, the access to "devices-api" will be set automatically. If you want to have another devices-apis you have to configure it manually in the advanced UI.
Dealing with the Code Editor
The Code Editor is a tool for viewing the tests supplied and writing your own tests based on these tests.
The editor is divided into 3 basic areas.
- Left - Code Editor
- This is the code editor where you can write your test code in the browser. When you close the app, the content in the editor is saved, so that the next time you open the app you will find your old code again.
- Top right - Menu bar
- Here you will find the following functions:
- Selection of all existing Tests (predefined vendor Tests and your own custom Tests)
- Load the source code of the selected Test to the code editor
- Delete the selected test from the app (This is only possible for custom tests)
- Execute the source code in the Code Editor and show the result
- Clear the Code Editor
- Save the current Content from the code Editor as Test
- The title of your test is divided from the title property in your test (lower based without non-alphanumeric characters). This means: If you save another Test with the same title the value will be saved in the other test.
- Top right - Results
- If you execute, save or delete a test you will get the response/results in this area
Create your own Testscript
You can create your own Test scripts in a JavaScript based environment. A test itself is a JavaScript object with some configurations properties and a section of freestyle code to use JavaScript habits to perform your test. If your test will be executed, an above layer will call your test() method and want to have a return object with your result.
There already exists a global Object called tests_add. You can add your own Test as Property to this existing Object like:
tests_add.my_new_test = tests_add.my_new_test || {
title: 'Check for xyz',
// configuration properties
test: function (cmd, result) {
// your custom test code
return {
success: false,
msg: '....'
};
}
}
The way to add a test with tests_add.my_new_test = tests_add.my_new_test || {}
is important so that two tests do not overwrite each other. Please also pay attention to this. In this example, we have a logical condition in the first line. If the test my_new_test already exists in the object tests_add the left side from the OR-condition is true, and the existing test takes place. Otherwise, it is false and the right side (your new test object) will be written to tests_add.my_new_test.
Configuration Properties
You must configure certain properties in your test so that the scheduler knows exactly how to handle your test.
Example:
tests_add.my_new_test = tests_add.my_new_test || {
title: 'Unknown / New devices',
description: 'Check for unassigned devices in your installation',
todo: 'Please check the unknown devices in your installation and acceppt or delete the devices',
author: 'slu@innovaphone.com',
enabled: true,
schedule: '1h',
commands: { ... },
test: function (cmd, result) {
// your custom test code
}
}
Mandatory properties
- title
- string | The title of your test, which is displayed in the UI
- description
- string | A short description of your test, which is displayed in the UI
- todo
- string | An instruction on what the user has to do if the check fails.
- author
- string | The contact of the person who create the test
- schedule
- string | The time interval during which the test is to be carried out independently. The format is composed of a numerical value and interval value. This allows you to create combinations such as
5m (every 5 minutes), 10h (every 10 hours) or 1d (every day)
. A special case which is also allowed is the value none. With a none value, your test will not be executed automatically, but is available in the app. So the user is able to execute your test manually. - Possible interval values are:
s
(seconds)m
(minutes)h
(hours)d
(days)w
(weeks)- Please note that each execution of a test requires resources. For example, if you create a test with 1m that is executed every minute, you may well create an unpleasant impact! The system automatically checks the execution time of your test against your specified interval. If your test runs for a longer time than your schedule, your schedule parameter is automatically doubled by the system.
- Please note the Submodule Scheduler with Tick and synchronous execution. For example, it is not possible to execute a test every second.
Optional properties
- commands
- object | An object in which you can list all the commands, values, etc. that you want to use later in your test. As soon as your test() method is called, the returns of these commands are passed in as parameters so that you can analyze them. For detailed documentation, please see the commands-documentation below.
- enabled
- bool | If false the test will be loaded but not executed. The user will see the test in the list of test, and it is possible to enable it (default: true)
- hidden
- bool | If true the test will be skipped by the system and will not be loaded (default: false)
Property "commands"
The property commands is an object where you can define innovaphone specific commands that are executed before your test will be executed. The results of the given commands will be passed to your test in the parameter results. Think about that, all your commands will be cached until the schedule timeout is exceeded. This means that you will only get a new result of the command after the schedule time has elapsed in relation to the time of your last execution. The only exceptions is when the user manually re-executes a test via the UI or caching has been explicitly disabled at the command
You have to consider a special nested notation when creating your commands, so that you can use them later.
outer commands-object
Let's start with some first basic rules:
- commands is an object with one property per command. At this moment we only list commands, we don't take care about specific devices, types of models or so on.
- Every child-property (let's call it "command-object") is a new object which contains all needed information for this specific command
- The title of the property "command-object" is defined by yourself
- Spoiler: The title of your "command-object" should be used later if you want to read the result for a specific device
- You can list multiple "command-objects"
Up to here we have the following and can define it like this:
commands: {
my_command_one: {},
my_command_two: {},
},
single command-object
Then we have some new rules per "command-object":
- A property called cmd is expected. This property contains the real command which shall be executed (Remember, at this moment we only list commands, we don't take care about specific devices, types of models or so on)
- Example 1:
cmd: 'CMD0/box_info.xml'
will give you the xml output of the general overview page - Example 2:
cmd: '!mod cmd CDR0 qs'
will give you the amount of buffered CDRs in the CDR0 interface - Example 3:
cmd: 'cfg.txt'
will give you back the full configuration file - If you are already firm with the innovaphone based command syntax, you will see that you can use any HTTP command which can be sent to an innovaphone device
- Example 1:
- Some more properties can be defined optionally. See documentation below
Up to here we have an idea of a specific "command-object" and can do the following now:
commands: {
buffered_cdr0: {
cmd: '!mod cmd CDR0 qs',
},
buffered_cdr1: {
cmd: '!mod cmd CDR1 qs',
},
},
With this command, we can collect the "buffered CDRs" on both CDR Interfaces in our boxes. Maybe you see it directly... This will not make really sense in a real setup because CDRs only generated by Gateways/PBXes. So we don't need to bother phones with these requests. So from now on we take care about our defined command. There are optional parameters that we can also list inside a "command-object" to let the system know what is the to-do with this command.
- cmd
- string | Any HTTP command you can send to an innovaphone device. There is a limit of 15 seconds for every http request. If your request exceeds this limit the request will be cancelled.
- cat
- array | An optional list of device types on which the command is to be executed. If cat is empty, it means all devices in your installation.
- You can define the following device categories:
pbx
- all PBXesgw
- all Gatewaysap
- all App Platformsdect
- all Dect Gatewaysphone
- all Phones- cache
- bool | If you set it to false you will never get a cached value. Please use this option wisely and only if you really need it. (default: true)
Now let's improve our example from above and get the bufferd CDRx information only from pbxes and gateways.
commands: {
buffered_cdr0: {
cmd: '!mod cmd CDR0 qs',
cat: ['pbx', 'gw'],
},
buffered_cdr1: {
cmd: '!mod cmd CDR1 qs',
cat: ['pbx', 'gw'],
},
},
execution and results of commands
Up to now, we have only defined commands that are to be executed on certain devices. When your test is executed, the previously defined commands are executed and the system collects the returns in a corresponding object (called results
) which is passed into your test(cmd, results)
method. There you can access the results and do whatever you want to test/analyze or whatever with the return value. You will find an example and some sample-code how to use the results-object in the description of the test() method
nested commands-objects
You can now send any commands and the results will be returned to you for evaluation. As you may have already noticed, there is no possibility to place multiple commands in dependency.
For more complicated tests, it is necessary to execute several commands one after the other and to create the second command dynmaically on the basis of the result of the first command.
For such tests, there is the possibility to specify another object "commands" within a "command-object". From this level, you can define a dynamic function in which you can apply your own code based on the first result, and then return further commands as a return statement. In this return statement, you also have the option of returning another dynamic function in order to be able to create processes of any complexity.
This is certainly not easy to understand. Therefore, let us take a closer look at it with an example.
commands: {
certificates: {
cmd: 'X509/mod_cmd.xml',
cat: ['pbx', 'gw', 'dect'],
commands: function(cmd, result) {
// free style code to build the following commands
var xml = cmd.parse_xml(result);
//log(JSON.stringify(xml));
var fingerprint;
// get the fingerprint for single cert
if (typeof xml.info.servercert.certificate['@fingerprint'] !== 'undefined') {
fingerprint = xml.info.servercert.certificate['@fingerprint']
}
// use last certificates in case of multiple
else {
fingerprint = xml.info.servercert.certificate[xml.info.servercert.certificate.length - 1]['@fingerprint'];
}
cmd.log('Fingerprint: '+fingerprint);
return {
certificate: {
cmd: 'X509/mod_cmd.xml?cmd=servercert-details&fingerprint='+fingerprint,
}
}
}
}
},
Our goal is to check which key-length is used in the device certificate. But there i no direct command to get this information. So we have to do it in multipe steps.
In this sample, we request the following in the commands object:
- Execute our command "certificates" on devices (pbx, gw, dect) the command "X509/mod_cmd.xml" which will give you back the xml payload of the page Reference14r1:General/Certificates
- If the first request is done, the nested "commands" object should be called and we get the result from the first command
- Now, inside the function
commands: function(cmd, result) {
we can use some free-style code to to what we need, to build a following command - Here we convert xml to an object and extract the certificate fingerprint from the result of the first request
- then we return an object with "command-objects"
- think about that is possible to add an new
commands: function(cmd, result) {
inside the return to add a new level :-)
- think about that is possible to add an new
- this second command will be executed to for every device and the xml payload contains the device certifcate of the current device. (like the popup in the UI if you click on a specific certificate)
- Now, inside the function
- If all nested commands are processed, then your test() will be called, and you get all the results (in this case "certificates" and "certificate") in the results-object
Executable method test()
The upper layer expects a method test(cmd, results)
in your JavaScript object. This method has two parameters.
- cmd
- An object that offers you a wide range of predefined functions and options. You can see it as a kind of library, which is documented at the cmd object.
- results
- An object of the results of your commands from the given command-objects in your test definition.
// structure of results object
{
// your command is the property
'buffered_cdr0' : {
// the unique id of the device is the property
'1': 'QS messages: 0 max 2000 chars: 0 max 300000',
'4': 'QS messages: 0 max 2000 chars: 0 max 300000',
},
'buffered_cdr1' : {
// the unique id of the device is the property
'1': 'QS messages: 0 max 2000 chars: 0 max 300000',
'4': 'QS messages: 0 max 2000 chars: 0 max 300000',
}
}
return test result
Your test has to be return an object with your test result. The system expect an object with the following properties.
- success
- boolean | indicates if your test was successful or not
- msg
- string | an optional short message that will be displayed in the UI as preview
- log
- array | an optional array with lines of logfile that you can use to display detailed information in the UI at your test result. If you want to create debug output to the app-instance logfile you can use cmd.log()
test: function (cmd, results) {
// your stuff
var count = 2;
return {
success: false,
msg: 'There are '+count+' new/unassigned devices in your setup'
};
}
The full picture
Complete sample test to check buffered CDRs
tests_add.cdrs_buffered = tests_add.cdrs_buffered || {
title: 'Buffered CDRs',
description: 'This test will check if CDRs on a gateway was buffered and are not transmit to the CDR target yet',
todo: 'Check the CDR target',
author: 'slu@innovaphone.com',
enabled: true,
schedule: '10m',
commands: {
cdr0: {
cmd: '!mod cmd CDR0 qs',
cat: ['gw'],
},
cdr1: {
cmd: '!mod cmd CDR1 qs',
cat: ['gw'],
},
cdr2: {
cmd: '!mod cmd CDR2 qs',
cat: ['gw'],
},
cdr3: {
cmd: '!mod cmd CDR3 qs',
cat: ['gw'],
},
cdr4: {
cmd: '!mod cmd CDR4 qs',
cat: ['gw'],
},
},
test: function (cmd, results) {
var log = [];
var success = true;
for (var n = 0; n < 5; n++) {
Object.keys(results['cdr'+n]).forEach(function(i){
var check = calculate_cdrs(results['cdr'+n][i]);
// result was not good, lets create a human error message
if (check !== true) {
log.push(cmd.get_device(i).product+' / '+cmd.get_device(i).hwId+' / '+check+'% - '+results['cdr'+n][i]);
if (success) success = false;
}
});
}
return {
success: success,
msg: success ? '' : 'Devices with buffered CDRs exists',
log: log
};
function calculate_cdrs(str) {
// QS messages: 0 max 2000 chars: 0 max 300000
var check = 0;
str = str.split(' ');
// log.push(str);
var messages_current = str[2];
var messages_max = str[4];
var chars_current = str[6];
var chars_max = str[8];
// check messages
check = cmd.percent(messages_current, messages_max);
if (check >= 30) return check;
// check chars
check = cmd.percent(chars_current, chars_max);
if (check >= 30) return check;
return true;
}
}
}
Complete sample test to check firmware versions
tests_add.firmware_version = tests_add.firmware_version || {
title: 'Supported firmware versions',
description: 'Check if you have unsupported firmware version in use',
todo: 'Update the firmware on affected devices',
author: 'slu@innovaphone.com',
schedule: '1d',
commands: {
box_info: {
cmd: 'CMD0/box_info.xml',
},
},
test: function (cmd, results) {
var log = [];
var success = true;
var msg = ;
Object.keys(results.box_info).forEach(function(i){
var xml = cmd.parse_xml(results.box_info[i]);
if (typeof xml.info === 'undefined') return;
if (typeof xml.info.sys === 'undefined') return;
if (typeof xml.info.sys['@version'] === 'undefined') return;
if (
xml.info.sys['@version'].includes('6.00') ||
xml.info.sys['@version'].includes('7.00') ||
xml.info.sys['@version'].includes('8.00') ||
xml.info.sys['@version'].includes('9.00') ||
xml.info.sys['@version'].includes('10.00') ||
xml.info.sys['@version'].includes('11.00') ||
xml.info.sys['@version'].includes('11r1') ||
xml.info.sys['@version'].includes('12r1') ||
xml.info.sys['@version'].includes('13r1')
) {
log.push(cmd.get_device(i).product+' / '+cmd.get_device(i).hwId+' / '+xml.info.sys['@version']);
if (success) success = false;
}
});
return {
success: success,
msg: success ? : 'You use old firmware which is no longer be supported',
log: log
};
}
}
cmd object
The cmd object provides you the following methods:
get device information / system information
get_domains() : object
You will receive all existing domains
get_devices() : object
You will receive all existing devices
get_devices_from_domain(int id) : object
You will receive all devices which are assigned to the given domain
get_device(int id) : object
You will receive a specific device
helper functions
percent(int value, int total) : float
Returns the number of percent of value to total
parse_xml(string xml) : object
Returns an object representation of given XML string
debug
log(string message) : void
You can write a logfile line to the app debug log
Secret knowledge for good tests
- bool config attributes
- You will find config attributes like
no-subscribe="true"
in the config. Please note that if the attribute is missing or the value is false it is false, otherwise it is true. For example, the valueno-subscribe="on"
is also true.
Troubleshooting
Startup Logfile
If you enable the traceflag App in your App instance, you will find the following logs:
AppService::AppStart TechAssist@slu.de App instance started 14C1363 14C1363 ... // App begins to load embedded tests JavaScript: Files tests/* ... // Scheduler begins to load all the tests scheduler init init test "firmware_version" firmware_version: Test added successful init test "pbx_mediarelay" pbx_mediarelay: Test added successful