// Standard
#include <sstream> // std::istringstream
#include <iostream> // std::cout
+#include <math.h> // ceil
+#include <climits>
// Project
#include <anna/timex/Engine.hpp>
#include <anna/xml/xml.hpp>
// Process
-#include "Launcher.hpp"
-#include "RealmNode.hpp"
-#include "MyDiameterEngine.hpp"
+#include <Launcher.hpp>
+#include <RealmNode.hpp>
+#include <MyDiameterEngine.hpp>
+#include <TestManager.hpp>
+#include <TestCase.hpp>
-#define SIGUSR2_TASKS_INPUT_FILENAME "./sigusr2.tasks.input"
-#define SIGUSR2_TASKS_OUTPUT_FILENAME "./sigusr2.tasks.output"
+#define SIGUSR2_TASKS_INPUT_FILENAME "./sigusr2.in"
+#define SIGUSR2_TASKS_OUTPUT_FILENAME "./sigusr2.out"
+
const char *ServicesDTD = "\
<!--\n\
Stack record\n\
\n\
- id: Normally the id corresponds to the Application-Id for which the dictionary provided is designed.\n\
+ id: Normally the id corresponds to the Application-Id for which the dictionary provided is designed\n\
(in multistack applications, it shall be mandatory respect such association to know the stack used\n\
for processed messages).\n\
dictionary: Path to the dictionary file\n\
-->\n\
\n\
<!ELEMENT node EMPTY>\n\
-<!ATTLIST node originRealm CDATA #REQUIRED applicationId CDATA #REQUIRED originHost CDATA #IMPLIED cer CDATA #IMPLIED dwr CDATA #IMPLIED allowedInactivityTime CDATA #IMPLIED tcpConnectDelay CDATA #IMPLIED answersTimeout CDATA #IMPLIED ceaTimeout CDATA #IMPLIED watchdogPeriod CDATA #IMPLIED entity CDATA #IMPLIED entityServerSessions CDATA #IMPLIED diameterServer CDATA #IMPLIED diameterServerSessions CDATA #IMPLIED balance (yes | no) #IMPLIED sessionBasedModelsClientSocketSelection (SessionIdLowPart | SessionIdHighPart | SessionIdOptionalPart | RoundRobin) #IMPLIED retries CDATA #IMPLIED log CDATA #IMPLIED splitLog (yes | no) #IMPLIED detailedLog (yes | no) #IMPLIED dumpLog (yes | no) #IMPLIED burstLog (yes | no) #IMPLIED>\n\
+<!ATTLIST node originRealm CDATA #REQUIRED originHost CDATA #IMPLIED cer CDATA #IMPLIED dwr CDATA #IMPLIED allowedInactivityTime CDATA #IMPLIED tcpConnectDelay CDATA #IMPLIED answersTimeout CDATA #IMPLIED ceaTimeout CDATA #IMPLIED watchdogPeriod CDATA #IMPLIED entity CDATA #IMPLIED entityServerSessions CDATA #IMPLIED diameterServer CDATA #IMPLIED diameterServerSessions CDATA #IMPLIED balance (yes | no) #IMPLIED sessionBasedModelsClientSocketSelection (SessionIdLowPart | SessionIdHighPart | SessionIdOptionalPart | RoundRobin) #IMPLIED retries CDATA #IMPLIED log CDATA #IMPLIED splitLog (yes | no) #IMPLIED detailedLog (yes | no) #IMPLIED dumpLog (yes | no) #IMPLIED burstLog (yes | no) #IMPLIED>\n\
<!--\n\
Node record\n\
\n\
originRealm: Node identifier (Origin-Realm name).\n\
- applicationId: The Application-Id provided must exists as a registered 'stack id'.\n\
originHost: Diameter application host name (system name). If missing, process sets o.s. hostname\n\
Note that if you have two or more realms, the names must be different.\n\
cer: User defined CER path file to be encoded to establish diameter connections.\n\
Launcher::Launcher() : anna::comm::Application("launcher", "DiameterLauncher", "1.1"), a_communicator(NULL) {
a_codecEngine = new anna::diameter::codec::Engine("MyCodecEngine");
+ a_baseProtocolDictionary = NULL;
a_timeEngine = NULL;
a_counterRecorder = NULL;
+ a_admlMinResolution = 2 * anna::timex::Engine::minResolution; // 2*10 = 20 ms; 1000/20 = 50 ticks per second;
+ //a_admlMinResolution = (anna::Millisecond)100;
a_counterRecorderClock = NULL;
// a_nodes.clear();
-void Launcher::servicesFromXML(const anna::xml::Node* servicesNode) throw(anna::RuntimeException) {
+void Launcher::servicesFromXML(const anna::xml::Node* servicesNode, bool eventOperation) throw(anna::RuntimeException) {
//<!ATTLIST stack id CDATA #REQUIRED dictionary CDATA #REQUIRED>
const anna::xml::Attribute *id, *dictionary;
- // <!ATTLIST node originRealm CDATA #REQUIRED applicationId CDATA #REQUIRED originHost CDATA #IMPLIED cer CDATA #IMPLIED dwr CDATA #IMPLIED allowedInactivityTime CDATA #IMPLIED tcpConnectDelay CDATA #IMPLIED answersTimeout CDATA #IMPLIED ceaTimeout CDATA #IMPLIED watchdogPeriod CDATA #IMPLIED entity CDATA #IMPLIED entityServerSessions CDATA #IMPLIED diameterServer CDATA #IMPLIED diameterServerSessions CDATA #IMPLIED balance (yes | no) #IMPLIED sessionBasedModelsClientSocketSelection (SessionIdLowPart | SessionIdHighPart | SessionIdOptionalPart | RoundRobin) #IMPLIED retries CDATA #IMPLIED log CDATA #IMPLIED splitLog (yes | no) #IMPLIED detailedLog (yes | no) #IMPLIED dumpLog (yes | no) #IMPLIED burstLog (yes | no) #IMPLIED>
- const anna::xml::Attribute *originRealm, *appId, *originHost, *cer, *dwr, *allowedInactivityTime, *tcpConnectDelay,
+ // <!ATTLIST node originRealm CDATA #REQUIRED originHost CDATA #IMPLIED cer CDATA #IMPLIED dwr CDATA #IMPLIED allowedInactivityTime CDATA #IMPLIED tcpConnectDelay CDATA #IMPLIED answersTimeout CDATA #IMPLIED ceaTimeout CDATA #IMPLIED watchdogPeriod CDATA #IMPLIED entity CDATA #IMPLIED entityServerSessions CDATA #IMPLIED diameterServer CDATA #IMPLIED diameterServerSessions CDATA #IMPLIED balance (yes | no) #IMPLIED sessionBasedModelsClientSocketSelection (SessionIdLowPart | SessionIdHighPart | SessionIdOptionalPart | RoundRobin) #IMPLIED retries CDATA #IMPLIED log CDATA #IMPLIED splitLog (yes | no) #IMPLIED detailedLog (yes | no) #IMPLIED dumpLog (yes | no) #IMPLIED burstLog (yes | no) #IMPLIED>
+ const anna::xml::Attribute *originRealm, *originHost, *cer, *dwr, *allowedInactivityTime, *tcpConnectDelay,
*answersTimeout, *ceaTimeout, *watchdogPeriod, *entity, *entityServerSessions,
*diameterServer, *diameterServerSessions, *balance, *sessionBasedModelsClientSocketSelection,
*retries, *log, *splitLog, *detailedLog, *dumpLog, *burstLog;
// Stacks
anna::diameter::stack::Engine &stackEngine = anna::diameter::stack::Engine::instantiate();
anna::diameter::stack::Dictionary *d;
+ ///////////////////////////////////////////
+ // APPLICATION MESSAGE OAM MODULE SCOPES //
+ ///////////////////////////////////////////
+ // We will register a scope per stack id registered. The counters will be dynamically registered at count method.
+ anna::diameter::comm::ApplicationMessageOamModule & appMsgOamModule = anna::diameter::comm::ApplicationMessageOamModule::instantiate();
+ appMsgOamModule.enableCounters(); // this special module is disabled by default (the only)
+ static int scope_id = 3;
+ bool id_0_registered = false;
+ unsigned int id_value;
for(anna::xml::Node::const_child_iterator it = servicesNode->child_begin(); it != servicesNode->child_end(); it++) {
std::string nodeName = (*it)->getName();
// Input data:
id = (*it)->getAttribute("id");
dictionary = (*it)->getAttribute("dictionary");
+ id_value = id->getIntegerValue();
try {
- d = stackEngine.createDictionary(id->getIntegerValue(), dictionary->getValue());
+ d = stackEngine.createDictionary(id_value, dictionary->getValue());
getCodecEngine()->setDictionary(d);
+
+ // OAM module for counters:
+ appMsgOamModule.createStackCounterScope(scope_id, id_value /* application-id */);
+ scope_id++;
+
} catch(anna::RuntimeException &ex) {
//_exit(ex.asString());
throw ex;
}
+
+ if (id_value == 0)
+ id_0_registered = true;
+ a_baseProtocolDictionary = d;
}
}
+ // Show loaded stacks:
+ std::cout << "Stacks currently loaded:" << std::endl;
+ std::cout << anna::functions::tab(stackEngine.asString(false /* light */)) << std::endl;
+
+
// Codec engine adjustments:
// Auto stack selection based on Application-ID:
bool multistack = (stackEngine.stack_size() > 1);
- if (multistack) getCodecEngine()->selectStackWithApplicationId(true);
+ if (multistack) {
+ getCodecEngine()->selectStackWithApplicationId(true);
+ // In multistack, id = 0 MUST be registered:
+ if (!id_0_registered)
+ throw anna::RuntimeException("In multistack applications is mandatory register a stack id = 0 using a dictionary which contains the needed elements to build base protocol messages (CER/A, DWR/A, DPR/A, STR/A, etc.)", ANNA_FILE_LOCATION);
+ }
+ else {
+ a_baseProtocolDictionary = d;
+ }
for(anna::xml::Node::const_child_iterator it = servicesNode->child_begin(); it != servicesNode->child_end(); it++) {
std::string nodeName = (*it)->getName();
if(nodeName == "node") {
// Input data:
originRealm = (*it)->getAttribute("originRealm");
- appId = (*it)->getAttribute("applicationId");
originHost = (*it)->getAttribute("originHost", false /* no exception */);
cer = (*it)->getAttribute("cer", false /* no exception */);
dwr = (*it)->getAttribute("dwr", false /* no exception */);
burstLog = (*it)->getAttribute("burstLog", false /* no exception */); // (yes | no)
// Basic checkings:
- if (stackEngine.getDictionary(appId->getIntegerValue()) == NULL) {
- std::string msg = "Cannot found a registered stack id with the value of applicationId provided: "; msg += appId->getValue();
- throw anna::RuntimeException(msg, ANNA_FILE_LOCATION);
- }
realm_nodes_it nodeIt = a_nodes.find(originRealm->getValue());
if (nodeIt != a_nodes.end()) {
std::string msg = "Already registered node name (Origin-Realm): "; msg += originRealm->getValue();
}
// Create new Node instance /////////////////////////////////////////////////////////////////
- a_workingNode = new RealmNode(originRealm->getValue(), appId->getIntegerValue(), a_codecEngine);
+ a_workingNode = new RealmNode(originRealm->getValue(), a_codecEngine, a_baseProtocolDictionary);
MyDiameterEngine *commEngine = a_workingNode->getMyDiameterEngine();
/////////////////////////////////////////////////////////////////////////////////////////////
a_workingNode->setRequestRetransmissions(retransmissions);
a_workingNode->getEntity()->setSessionBasedModelsType(sessionBasedModelsType);
a_workingNode->getEntity()->setBalance(balance ? (balance->getValue() == "yes") : false); // for sendings
+ if (eventOperation) a_workingNode->getEntity()->bind();
}
}
a_workingNode->setLogs(s_log, b_splitLog, b_detailedLog, b_dumpLog, s_burstLog);
+ // Lazy initialization for comm engine:
+ if (eventOperation) commEngine->lazyInitialize();
+
// New Node assignment //////////////////////////////////////////////////////////////////////
a_nodes[originRealm->getValue()] = a_workingNode;
/////////////////////////////////////////////////////////////////////////////////////////////
}
}
+
+ // Diameter comm engines which are loaded after application start (via management operation 'services') are not really started,
+ // but this don't care because application startComponents() -> initialize() -> do_initialize() -> do nothing.
+ // And when stopped, running state is not checked and it will be stopped anyway.
}
-void Launcher::loadServices(const std::string & xmlPathFile) throw(anna::RuntimeException) {
+void Launcher::loadServices(const std::string & xmlPathFile, bool eventOperation) throw(anna::RuntimeException) {
+
+ if (xmlPathFile == "null" || xmlPathFile == "") {
+ LOGWARNING(anna::Logger::warning("Ignoring services configuration on start: empty or 'null' string provided as xml file. Use management interface (operation 'services') in order to add services", ANNA_FILE_LOCATION));
+ return;
+ }
LOGDEBUG(
std::string trace = "Loading ADML services file '";
trace += anna::xml::Compiler().apply(rootNode);
anna::Logger::debug(trace, ANNA_FILE_LOCATION);
);
- servicesFromXML(rootNode);
+ servicesFromXML(rootNode, eventOperation);
}
throw RuntimeException(msg, ANNA_FILE_LOCATION);
}
+RealmNode *Launcher::getWorkingNode() const throw(anna::RuntimeException) {
+
+ if (!a_workingNode)
+ throw RuntimeException("No services yet loaded. Try 'services' operation (via management interface), or restart process using command-line 'services' parameter", ANNA_FILE_LOCATION);
+
+ return a_workingNode;
+}
+
bool Launcher::setWorkingNode(const std::string &name) throw() {
bool result = false;
}
RealmNode *Launcher::getRealmNode(const std::string &realmName) const throw() {
- for (realm_nodes_it it = a_nodes.begin(); it != a_nodes.end(); it++)
- if(it->second->getMyDiameterEngine()->getRealm() == realmName) return it->second;
+ realm_nodes_it it = a_nodes.find(realmName);
+ if (it != a_nodes.end()) return it->second;
return NULL; // this never happens
}
CommandLine& cl(anna::CommandLine::instantiate());
anna::comm::Communicator::WorkMode::_v workMode(anna::comm::Communicator::WorkMode::Single);
a_communicator = new MyCommunicator(workMode);
-
- //a_timeEngine = new anna::timex::Engine((anna::Millisecond)600000, anna::timex::Engine::minResolution);
- a_timeEngine = new anna::timex::Engine((anna::Millisecond)600000, (anna::Millisecond)100); // puedo bajar hasta 10
+ a_timeEngine = new anna::timex::Engine((anna::Millisecond)600000, a_admlMinResolution);
+ TestManager::instantiate().setTimerController(a_timeEngine);
// Counters record procedure:
const char *varname = "cntRecordPeriod";
a_counterRecorder = new MyCounterRecorder(cntDir + anna::functions::asString("/Counters.Pid%d", (int)getPid()));
}
+ // Testing framework:
+ std::string tmDir = ".";
+ if(cl.exists("tmDir")) tmDir = cl.getValue("tmDir");
+ TestManager::instantiate().setReportsDirectory(tmDir);
+
+ // Tracing:
+ if(cl.exists("trace"))
+ anna::Logger::setLevel(anna::Logger::asLevel(cl.getValue("trace")));
+
// Load launcher services:
loadServices(cl.getValue("services")); // before run (have components to be created)
-
- // Show loaded stacks:
- std::cout << "Stacks provided:" << std::endl;
- std::cout << anna::functions::tab(anna::diameter::stack::Engine::instantiate().asString(false /* light */));
- std::cout << std::endl;
}
void Launcher::run()
// Statistics:
anna::statistics::Engine::instantiate().enable();
- // Tracing:
- if(cl.exists("trace"))
- anna::Logger::setLevel(anna::Logger::asLevel(cl.getValue("trace")));
-
LOGINFORMATION(
// Help on startup traces:
anna::Logger::information(help(), ANNA_FILE_LOCATION);
oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::RequestSentExpired, "", 55);
oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::RequestSentOnClientSessionExpired, "", 56);
oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::RequestSentOnServerSessionExpired, "", 57);
- oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::AnswerReceivedUnknown, "", 58);
- oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::AnswerReceivedOnClientSessionUnknown, "", 59);
- oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::AnswerReceivedOnServerSessionUnknown, "", 60);
+ oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::RequestRetransmitted, "", 58);
+ oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::RequestRetransmittedOnClientSession, "", 59);
+ oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::RequestRetransmittedOnServerSession, "", 60);
+ oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::AnswerReceivedUnknown, "", 61);
+ oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::AnswerReceivedOnClientSessionUnknown, "", 62);
+ oamDiameterComm.registerCounter(anna::diameter::comm::OamModule::Counter::AnswerReceivedOnServerSessionUnknown, "", 63);
//////////////////
// CODEC MODULE //
//////////////////
oamDiameterCodec.registerCounter(anna::diameter::codec::OamModule::Counter::LevelValidation__FailedRuleForCardinalityMoreThanNeeded, "", 17 /*2017*/);
oamDiameterCodec.registerCounter(anna::diameter::codec::OamModule::Counter::LevelValidation__FailedGenericAvpRuleForCardinalityFoundDisregardedItem, "", 18 /*2018*/);
oamDiameterCodec.registerCounter(anna::diameter::codec::OamModule::Counter::LevelValidation__FoundDisregardedItemsAndGenericAVPWasNotSpecified, "", 19 /*2019*/);
- ///////////////////////////////////////////
- // APPLICATION MESSAGE OAM MODULE SCOPES //
- ///////////////////////////////////////////
- // We will register a scope per stack id registered. The counters will be dynamically registered at count method.
- anna::diameter::comm::ApplicationMessageOamModule & appMsgOamModule = anna::diameter::comm::ApplicationMessageOamModule::instantiate();
- int scope_id = 3;
- for (anna::diameter::stack::Engine::const_stack_iterator it = stackEngine.stack_begin(); it != stackEngine.stack_end(); it++) {
- appMsgOamModule.createStackCounterScope(scope_id, it->first);
- scope_id++;
- }
- appMsgOamModule.enableCounters(); // this special module is disabled by default (the only)
-
/////////////////////////////////
if(a_counterRecorderClock) {
oamDiameterComm.setCounterRecorder(a_counterRecorder);
oamDiameterCodec.setCounterRecorder(a_counterRecorder);
- appMsgOamModule.setCounterRecorder(a_counterRecorder);
+ anna::diameter::comm::ApplicationMessageOamModule::instantiate().setCounterRecorder(a_counterRecorder);
a_timeEngine->activate(a_counterRecorderClock); // start clock
}
if (entity) entity->bind();
}
-
// Go into communicator poll
// Reconnection period (tcp reconnect retry time):
const char *varname = "reconnectionPeriod";
a_communicator->accept();
}
+
bool Launcher::getDataBlockFromHexFile(const std::string &pathfile, anna::DataBlock &db) const throw() {
// Get hex string
static char buffer[8192];
msg += "')";
anna::Logger::notice(msg, ANNA_FILE_LOCATION);
);
+
// Operation:
std::string line;
std::string response_content;
ex.trace();
}
- out_file << response_content;
+ out_file << response_content << "\n";
}
in_file.close();
result += "\n";
result += "\nStart the launcher process without arguments in order to see all the startup configuration";
result += "\n posibilities, many of which could be modified on the air through the management interface";
- result += "\n (we will talk later about this great feature). Some of the more common parameters are:";
+ result += "\n (we will talk later about this great feature). There is only one mandatory parameter which";
+ result += "\n is the services definition: --services <services xml file>. You must follow the dtd schema";
+ result += "\n to build a valid services xml file. Some basic examples are:";
+ result += "\n";
+ result += "\nClient configuration:";
result += "\n";
- result += "\nAs mandatory, the stacks enabled given through the applicationId and the xml dictionary:";
- result += "\n --stacks <appid1,dictionary1#appid2,dictionary2#...#appidN,dictionaryN>";
+ result += "\n<services>";
+ result += "\n <!-- Stacks -->";
+ result += "\n <stack id=\"0\" dictionary=\"dictionary.xml\"/>";
result += "\n";
- result += "\nActing as a diameter server (accepting i.e. 10 connections), you would have:";
- result += "\n --diameterServer localhost:3868 --diameterServerSessions 10 --entityServerSessions 0";
+ result += "\n <!-- Nodes -->";
+ result += "\n <node originRealm=\"ADML-client\" entity=\"localhost:3868\"/>";
+ result += "\n</services>";
result += "\n";
- result += "\nActing as a diameter client (launching i.e. 10 connections to each entity server), you would have:";
- result += "\n --entity 192.168.12.11:3868,192.168.12.21:3868 --entityServerSessions 10 --diameterServerSessions 0";
+ result += "\nServer configuration:";
+ result += "\n";
+ result += "\n<services>";
+ result += "\n <!-- Stacks -->";
+ result += "\n <stack id=\"0\" dictionary=\"dictionary.xml\"/>";
+ result += "\n";
+ result += "\n <!-- Nodes -->";
+ result += "\n <node originRealm=\"ADML-server\" diameterServer=\"localhost:3868\"/>";
+ result += "\n</services>";
result += "\n";
result += "\nIf you act as a proxy or a translation agent, you need to combine both former setups, and probably";
result += "\n will need to program the answers to be replied through the operations interface. To balance the";
result += "\n traffic at your client side you shall use '--balance' and '--sessionBasedModelsClientSocketSelection'";
- result += "\n arguments in order to define the balancing behaviour.";
+ result += "\n arguments in order to define the balancing behaviour. To make hybrid setups you only must mix the realms:";
result += "\n";
- result += "\nThe process builds automatically CER and DWR messages as a client, but you could specify your own";
- result += "\n customized ones using '--cer <xml message file>' and '--dwr <xml message file>'.";
- result += "\nThe process builds automatically CEA and DWA messages as a server, but you could program your own";
- result += "\n customized ones using operations interface.";
+ result += "\nClient and server configuration:";
+ result += "\n";
+ result += "\n<services>";
+ result += "\n <!-- Stacks -->";
+ result += "\n <stack id=\"16777236\" dictionary=\"dictionary_Rx.xml\"/>";
+ result += "\n <stack id=\"16777238\" dictionary=\"dictionary_Gx.xml\"/>";
+ result += "\n <stack id=\"0\" dictionary=\"dictionary_base.xml\"/>";
result += "\n";
+ result += "\n <!-- Nodes -->";
+ result += "\n <node originRealm=\"ADML-Rx-client\" entity=\"localhost:3868\" cer=\"cer_Rx.xml\"/>";
+ result += "\n <node originRealm=\"ADML-Gx-client\" entity=\"localhost:3868\" cer=\"cer_Gx.xml\"/>";
+ result += "\n</services>";
+ result += "\n";
+ result += "\n";
+ result += "\nThe process builds automatically CER and DWR messages as a client, but you could specify your own";
+ result += "\n as shown in the hybrid former example. Note that the base protocol stack must be registered because";
+ result += "\n the configuration corresponds to a multistack process which change the stack using the application-id";
+ result += "\n processed (0 in the case of base protocol messages: CER, CEA, DWR, DWA, DPR, DPA).";
result += "\n";
result += "\nDYNAMIC OPERATIONS";
result += "\n------------------";
result += "\n";
result += "\n--------------------------------------------------------------------------------------- General purpose";
result += "\n";
- result += "\nhelp This help. Startup information-level traces also dump this help.";
+ result += "\nhelp This help.";
result += "\n";
result += "\n---------------------------------------------------------------------------------------- Node selection";
result += "\n";
result += "\nnode[|<name>] Select current working node by mean the registered name.";
result += "\n All the subsequent operations will be referred to this node.";
- result += "\n Without argument, the current node is dumped on stdout.";
+ result += "\n Without argument, the current node information is retrieved.";
result += "\n";
result += "\n------------------------------------------------------------------------------------ Parsing operations";
result += "\n";
result += "\n";
result += "\n------------------------------------------------------------------------------------------- Hot changes";
result += "\n";
+ result += "\nservices[|source file] Adds and starts the services specified in the xml file provided.";
+ result += "\n (if missing, the file 'services.xml' will be used).";
+ result += "\n The last loaded realm node will be automatically the new current";
+ result += "\n working node. This is used to load new nodes once the ADML is";
+ result += "\n started, regardless if '--services' command line parameter was";
+ result += "\n used or not. Those services which are not correctly loaded, will";
+ result += "\n be ignored, keeping the process alive.";
+ result += "\n";
result += "\ndiameterServerSessions|<integer> Updates the maximum number of accepted connections to diameter";
result += "\n server socket.";
result += "\ncontext[|target file] Application context could also be written by mean this operation,";
result += "\n has been logged)";
result += "\n [retry] Request retransmission";
result += "\n";
- result += "\n-------------------------------------------------------------------------------------------- Load tests";
+ result += "\n------------------------------------------------------------------------------------------- Burst tests";
result += "\n";
result += "\nburst|<action>[|parameter] Used for performance testing, we first program diameter requests";
result += "\n messages in order to launch them from client side to the configured";
result += "\n operations is that burst won't be awaken. Externally we could control";
result += "\n sending time (no request will be sent for answers).";
result += "\n burst|goto|<order> Updates current burst pointer position.";
- result += "\n burst|look|<order> Show programmed burst message for order provided.";
+ result += "\n burst|look[|order] Show programmed burst message for order provided, current when missing.";
+ result += "\n";
+ result += "\n-------------------------------------------------------------------------------------- Advanced testing";
+ result += "\n";
+ result += "\n Burst mode only allows single interface interaction. For multiple interface";
+ result += "\n (realm) coordination, you could use the advanced test cases programming:";
+ result += "\n";
+ result += "\n";
+ result += "\n test|<id>|<command>[|parameters]";
+ result += "\n";
+ result += "\n Adds a new step to the test case with provided identifier. If provided identifier";
+ result += "\n is not registered yet, a new test case will be created with that value and the";
+ result += "\n step will be added as the first. For a specific 'id', the steps are stored in";
+ result += "\n order as they are programmed";
+ result += "\n";
+ result += "\n <id>: integer number, normally monotonically increased for each test case. Some external";
+ result += "\n script/procedure shall clone a test case template in order to build a collection";
+ result += "\n of independent and coherent test cases (normally same type) with different context";
+ result += "\n values (Session-Id, Subscriber-Id, etc.).";
+ result += "\n";
+ result += "\n <command>: commands to be executed for the test id provided. Each command programmed";
+ result += "\n constitutes a test case 'step', numbered from 1 to N.";
+ result += "\n";
+ result += "\n timeout|<msecs> Sets an asynchronous timer to restrict the maximum timeout";
+ result += "\n until last test step. Normally, this command is invoked";
+ result += "\n in the first step, anyway it measures the time from the";
+ result += "\n execution point whatever it is. The expiration will abort";
+ result += "\n the test if still running. One or more timeouts could be";
+ result += "\n programmed (not usual), but the more restrict will apply.";
+ result += "\n It is highly recommended to program a initial timeout step,";
+ result += "\n or the test case could be eternally in-progress.";
+ result += "\n";
+ result += "\n sendxml2e|<source_file>[|<step number>]";
+ result += "\n Sends xml source file (pathfile) to entity (it would be a";
+ result += "\n 'forward' event if it came through local server endpoint).";
+ result += "\n The step number should be provided for answers to indicate";
+ result += "\n the 'wait for request' corresponding step. If you miss this";
+ result += "\n reference, the sequence information (hop-by-hop, end-to-end)";
+ result += "\n will be sent as they are in the answer xml message (realize";
+ result += "\n the difficulty of predicting these information). Be sure to";
+ result += "\n refer to a 'wait for request' step. Conditions like 'regexp'";
+ result += "\n (as we will see later) are not verified.";
+ result += "\n";
+ result += "\n sendxml2c|<source_file>[|<step number>]";
+ result += "\n Sends xml source file (pathfile) to client (it would be a";
+ result += "\n 'forward' event if it came through remote server endpoint).";
+ result += "\n Same commented for 'sendxml2e' regarding the step number.";
+ result += "\n";
+ result += "\n delay|<msecs> Blocking step until the time lapse expires. Useful to give ";
+ result += "\n some cadence control and time schedule for a specific case.";
+ result += "\n wait<fe/fc>|<condition> Blocking step until condition is fulfilled. The message could";
+ result += "\n received from entity (waitfe) or from client (waitfc).";
+ result += "\n";
+ result += "\n wait<fe/fc>-regexp|<regexp>";
+ result += "\n Wait condition, from entity (waitfe-regexp) or client (waitfc-regexp)";
+ result += "\n to match the serialized xml content for received messages. CPU cost";
+ result += "\n is bigger than the former ones because the whole message must be";
+ result += "\n decoded and converted to xml instead of doing a direct hexadecimal";
+ result += "\n buffer search. The main advantage is the great flexibility to identify";
+ result += "\n any content with a regular expression.";
+ result += "\n";
+ result += "\n <condition>: Optional parameters which must be fulfilled to continue through the next step.";
+ result += "\n Any received message over diameter interfaces will be evaluated against the";
+ result += "\n corresponding test case starting from the current step until the first one";
+ result += "\n whose condition is fulfilled. If no condition is fulfilled the event will be";
+ result += "\n classified as 'uncovered' (normally a test case bad configuration, or perhaps";
+ result += "\n a real unexpected message).";
+
+ // TODO(***)
+// result += "\n The way to identify the test case, is through registered Session-Id values for";
+// result += "\n programmed requests. But this depends on the type of node. Acting as clients,";
+// result += "\n requests received have Session-Id values which are already registered with";
+// result += "\n one test case, causing an error if not found. Acting as servers, requests are";
+// result += "\n received over a diameter local server from a client which are generating that";
+// result += "\n Session-Id values. Then we know nothing about such values. The procedure in";
+// result += "\n this case is find out a test case not-started containing a condition which";
+// result += "\n comply with the incoming message, and reactivates it.";
+ // The other solution: register Session-Id values for answers send to client from a local diameter server.
+
+ result += "\n How to answer: a wait condition for a request will store the incoming message";
+ result += "\n which fulfills that condition. This message is useful together with the peer";
+ result += "\n connection source in a further send step configured with the corresponding";
+ result += "\n response. You could also insert a delay between wait and send steps to be";
+ result += "\n more realistic (processing time simulation in a specific ADML realm node).";
+ result += "\n Always, a response send step will get the needed information from the most";
+ result += "\n recent wait step finding in reverse order (note that some race conditions";
+ result += "\n could happen if your condition is not specific enough).";
+
+ result += "\n";
+ result += "\n Condition format:";
+ result += "\n";
+ result += "\n [code]|[bitR]|[ResultCode]|[sessionId]|[hopByHop]|[msisdn]|[imsi]|[serviceContextId]";
+ result += "\n";
+ result += "\n code: integer number";
+ result += "\n bitR: 1 (request), 0 (answer)";
+ result += "\n ResultCode: integer number";
+ result += "\n sessionId: string";
+ result += "\n hopByHop: integer number or request send step reference: #<step number>";
+ result += "\n";
+ result += "\n Using the hash reference, you would indicate a specific wait condition";
+ result += "\n for answers. The step number provided must correspond to any of the";
+ result += "\n previous send commands (sendxml2e/sendxml2c) configured for a request.";
+ result += "\n This 'hop-by-hop' variant eases the wait condition for answers in the";
+ result += "\n safest way.";
+ result += "\n";
+ result += "\n msisdn: string";
+ result += "\n imsi: string";
+ result += "\n serviceContextId: string";
+ result += "\n";
+ result += "\n Take into account these rules, useful in general:";
+ result += "\n";
+ result += "\n - Be as much specific as possible defining conditions to avoid ambiguity sending";
+ result += "\n messages out of context due to race conditions. Although you could program several";
+ result += "\n times similar conditions, some risky practices will throw a warning trace (if you";
+ result += "\n repeat the same condition within the same test case).";
+ result += "\n - Adding a ResultCode and/or HopByHop to the condition are only valid waiting answers.";
+ result += "\n - Requests hop-by-hop values must be different for all the test case requests.";
+ result += "\n RFC says that a hop by hop must be unique for a specific connection, something that";
+ result += "\n could be difficult to manage if we have multiple available connections from client";
+ result += "\n side endpoint (entity or local server), even if we would have only one connection but";
+ result += "\n several realm interfaces. It is enough to configure different hop-by-hop values within";
+ result += "\n each test case, because on reception, the Session-Id is used to identify that test case.";
+ result += "\n";
+ result += "\n";
+ result += "\n";
+ result += "\n";
+ result += "\n Programming example:";
+ result += "\n";
+ result += "\n Basic Rx/Gx scenary: PCEF (Gx) - PCRF - AF (Rx)";
+ result += "\n";
+ result += "\n test|1|timeout|5000 (step 1: whole time requirement is 5 seconds)";
+ result += "\n test|1|sendxml2e|CCR-I.xml (step 2: imagine this xml uses the Session-Id 'SGx')";
+ result += "\n test|1|waitfe|272|0|2001|SGx (step 3: waits the CCA for the CCR-I with Result-Code = DIAMETER_SUCCESS)";
+ result += "\n test|1|sendxml2e|AAR-flows.xml (step 4: imagine this xml uses the Session-Id 'SRx')";
+ result += "\n test|1|waitfe|265|0|2001|SRx (step 5: waits the AAA for the AAR-flows with Result-Code = DIAMETER_SUCCESS)";
+ result += "\n test|1|waitfe|258|1||SGx (step 6: waits the RAR (install policies) from the PCRF server)";
+ result += "\n test|1|sendxml2e|RAA-install.xml|6 (step 7: sends the response for the RAR)";
+ result += "\n test|1|sendxml2e|CCR-T.xml (step 8: termination of the Gx session, imagine this xml puts hop-by-hop 'H1')";
+ result += "\n test|1|waitfe|272|0|2001|SGx|H1 (step 9: waits the CCA for the CCR-T with Result-Code = DIAMETER_SUCCESS and hop-by-hop 'H1')";
+ result += "\n test|1|waitfe|258|1||SGx (step 10: waits the RAR (remove policies) from the PCRF server)";
+ result += "\n test|1|sendxml2e|RAA-remove.xml|10 (step 11: sends the response for the RAR)";
+ result += "\n";
+ result += "\n Notes: We added an additional condition in step 9: the hop-by-hop. When we program the corresponding";
+ result += "\n source request (CCR-T), we configured the value 'H1' for the hop-by-hop. This is an 'application";
+ result += "\n value' because the real hop-by-hop transported through the client connection is managed by the";
+ result += "\n diameter stack. But when returned, the transaction pool resolve the original value. This feature";
+ result += "\n is necessary to ease the implementation of certain diameter agents (proxies for example). In our";
+ result += "\n case, we could format the hop-by-hop values within the request templates with total freedom to";
+ result += "\n improve the programmed conditions.";
+ result += "\n";
+ result += "\n In the case of 'waiting for requests' is not such easy. Indeed, steps 6 and 10 will write a warning";
+ result += "\n because they are the same condition. We know that we are not going to have any problem because";
+ result += "\n such events are blocking-protected regarding logic-dependent messages (CCR-T), and race condition";
+ result += "\n is absolutely strange in this case.";
+ result += "\n";
+ result += "\n You could speed up the test case moving forward steps like 3 & 5, understood as non-strict requirements";
+ result += "\n to continue testing. Anyway, remember that test cases should be as real as possible, and that there";
+ result += "\n are many ways to increase the load rate as we will see in next section (test cases execution).";
+ result += "\n";
+ result += "\n Other simplifications: the steps 3, 5 and 9 can be replaced by";
+ result += "\n";
+ result += "\n test|1|waitfe||0|||#2";
+ result += "\n test|1|waitfe||0|||#4";
+ result += "\n test|1|waitfe||0|||#8";
+ result += "\n";
+ result += "\n which means that hop-by-hop must be retrieved from steps 2, 4 and 8 respectively,";
+ result += "\n and the expected message shall be an answer. Normally you will add other conditions,";
+ result += "\n for example a DIAMETER_SUCCESS result (adding 2001 as Result-Code).";
+ result += "\n";
+ result += "\nTest cases execution:";
+ result += "\n";
+ result += "\n";
+ result += "\n test|ttps|<amount> Starts/resume the provided number of test ticks per second (ttps). The ADML starts";
+ result += "\n with the event trigger system suspended, and this operation is neccessary to begin";
+ result += "\n those cases which need this time event (internal triggering). Some other test cases";
+ result += "\n could be started through external events (first test case event could be programmed";
+ result += "\n to wait specific message), but is not usual this external mode and neither usual to";
+ result += "\n mix triggering types. Normally, you will pause/stop new test launchs providing 0 as";
+ result += "\n ttps value, and also you could dynamically modify the load rate updating that value.";
+ result += "\n If a test case has N messages then 'ttps * N' will be the virtual number of messages";
+ result += "\n managed per second when no bottleneck exists.";
+ result += "\n";
+ result += "\n Provide 0 in order to stop the timer triggering.";
+ result += "\n";
+ result += "\n There timer manager resolution currently harcoded allows a maximum of ";
+ result += anna::functions::asString(1000/a_admlMinResolution); result += " events";
+ result += "\n per second. To reach greater rates ADML will join synchronously the needed number of";
+ result += "\n new time-triggered test cases per a single event, writting a warning-level trace to";
+ result += "\n advice about the risk of burst sendings and recommend launching multiple instances";
+ result += "\n to achieve such load with a lower rate per instance.";
+ result += "\n";
+ result += "\n test|ip-limit[|amount] In-progress limit of test cases. No new test cases will be launched over this value";
+ result += "\n (test Manager tick work will be ignored). Zero-value is equivalent to stop the clock.";
+ result += "\n tick, -1 is used to specify 'no limit' which is the default. If missing amount, the";
+ result += "\n limit and current amount of in-progress test cases will be shown.";
+ result += "\n";
+ result += "\n test|goto|<id> Updates current test pointer position.";
+ result += "\n";
+ result += "\n test|look[|id] Show programmed test case for id provided, current when missing. Test cases programmed";
+ result += "\n are not dumped on process context (too many information in general). When the test case";
+ result += "\n has been completed or initiated, it will contain context information acting as a report.";
+ result += "\n";
+ result += "\n test|report[|[yes]|no] Every time a test case is finished, its xml representation will be dump on a file under";
+ result += "\n the execution directory (or the one configured in process command-line 'tmDir') with";
+ result += "\n the name 'testcase.<id>.xml'. If repeat mode is active, new test case executions will";
+ result += "\n append its reports into the same file. This option is disabled by default to improve";
+ result += "\n performance (reducing IO). ADML process context will show test manager whole information";
+ result += "\n and test case reports will be written depending on this report option. Anyway, you could";
+ result += "\n use the 'look' operation to see the report.";
+ result += "\n";
+ result += "\n test|report-hex[|[yes]|no] Reports could include the diameter messages in hexadecimal format. Disabled by default.";
+ result += "\n";
+ result += "\n test|reset|<soft/hard>[|id] Reset the test case for id provided, all the tests when missing. It could be hard/soft:";
+ result += "\n - hard: you probably may need to stop the load rate before. This operation initializes";
+ result += "\n all test cases regardless their states.";
+ result += "\n - soft: only for finished cases (those with 'Success' or 'Failed' states). It does not";
+ result += "\n affect to test cases with 'InProgress' state.";
+ result += "\n";
+ result += "\n test|repeat[|[yes]|no] Restarts the whole programmed test list when finished, disabled by default (testing";
+ result += "\n time trigger system will enter suspended state until new ttps operation is received).";
+ result += "\n Test cases state & data will be reset (when achieved again), but general statistics";
+ result += "\n and counters will continue measuring until reset with 'collect' operation.";
+ result += "\n When the test cases pool has been processed (and this repeat option is disabled), you";
+ result += "\n could reactivate the testing by mean 'test|reset|soft' and then 'test|ttps|<value>'.";
+ result += "\n";
+ result += "\n test|clear Clears all the programmed test cases and stop testing (if in progress).";
result += "\n";
result += "\n";
result += "\nUSING OPERATIONS INTERFACE";
result += "\n interface.";
result += "\n";
result += "\n";
+
return result;
}
void Launcher::eventOperation(const std::string &operation, std::string &response_content) throw(anna::RuntimeException) {
LOGMETHOD(anna::TraceMethod tm("Launcher", "eventOperation", ANNA_FILE_LOCATION));
+ if (operation == "") return; // ignore
+
CommandLine& cl(anna::CommandLine::instantiate());
+ TestManager &testManager = TestManager::instantiate();
LOGDEBUG(anna::Logger::debug(operation, ANNA_FILE_LOCATION));
- response_content = "Operation processed with exception. See traces\n"; // supposed
- std::string result = "";
+
+ // Default response:
+ response_content = "Operation processed with exception (see traces): ";
+ response_content += operation;
+
+
+ std::string opt_response_content = ""; // aditional response content
anna::DataBlock db_aux(true);
- anna::diameter::codec::Message codecMsg(getCodecEngine());
- MyDiameterEntity *entity = getWorkingNode()->getEntity();
- MyDiameterEngine *commEngine = getWorkingNode()->getMyDiameterEngine();
- MyLocalServer *localServer = getWorkingNode()->getDiameterServer();
///////////////////////////////////////////////////////////////////
// Simple operations without arguments:
// Help:
if(operation == "help") {
- std::string s_help = help();
- std::cout << s_help << std::endl;
- LOGINFORMATION(anna::Logger::information(s_help, ANNA_FILE_LOCATION));
- response_content = "Help dumped on stdout and information-level traces (launcher.trace file)\n";
+ response_content = help();
return;
}
if(operation == "collect") {
resetCounters();
resetStatistics();
- response_content = "All process counters & statistic information have been reset\n";
+ response_content = "All process counters & statistic information have been reset";
return;
}
// Counters dump on demand:
if(operation == "forceCountersRecord") {
forceCountersRecord();
- response_content = "Current counters have been dump to disk\n";
+ response_content = "Current counters have been dump to disk";
return;
}
///////////////////////////////////////////////////////////////////
// Tokenize operation
Tokenizer params;
- params.apply(operation, "|");
+ params.apply(operation, "|", "<null>" /* allow contiguous separators */);
int numParams = params.size() - 1;
- // No operation has more than 2 arguments ...
- if(numParams > 2) {
- LOGWARNING(anna::Logger::warning(help(), ANNA_FILE_LOCATION));
- throw anna::RuntimeException("Wrong body content format on HTTP Request", ANNA_FILE_LOCATION);
- }
-
- // Get the operation type:
+ // Get the operation type and parameters:
Tokenizer::const_iterator tok_iter = params.begin();
std::string opType = Tokenizer::data(tok_iter);
+ std::string param1, param2, param3, param4, param5, param6, param7, param8, param9, param10;
+ if(numParams >= 1) { tok_iter++; param1 = Tokenizer::data(tok_iter); }
+ if(numParams >= 2) { tok_iter++; param2 = Tokenizer::data(tok_iter); }
+ if(numParams >= 3) { tok_iter++; param3 = Tokenizer::data(tok_iter); }
+ // Tests conditions
+ if(numParams >= 4) { tok_iter++; param4 = Tokenizer::data(tok_iter); }
+ if(numParams >= 5) { tok_iter++; param5 = Tokenizer::data(tok_iter); }
+ if(numParams >= 6) { tok_iter++; param6 = Tokenizer::data(tok_iter); }
+ if(numParams >= 7) { tok_iter++; param7 = Tokenizer::data(tok_iter); }
+ if(numParams >= 8) { tok_iter++; param8 = Tokenizer::data(tok_iter); }
+ if(numParams >= 9) { tok_iter++; param9 = Tokenizer::data(tok_iter); }
+ if(numParams >= 10) { tok_iter++; param10 = Tokenizer::data(tok_iter); }
+ // Remove '<null>' artificial token to ease further checkings:
+ if (param1 == "<null>") param1 = "";
+ if (param2 == "<null>") param2 = "";
+ if (param3 == "<null>") param3 = "";
+ if (param4 == "<null>") param4 = "";
+ if (param5 == "<null>") param5 = "";
+ if (param6 == "<null>") param6 = "";
+ if (param7 == "<null>") param7 = "";
+ if (param8 == "<null>") param8 = "";
+ if (param9 == "<null>") param9 = "";
+ if (param10 == "<null>") param10 = "";
+
+ // No operation has more than 2 arguments except 'test' ...
+ if(opType != "test" && numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+
// Check the number of parameters:
bool wrongBody = false;
if((opType == "burst") && (numParams < 1)) wrongBody = true;
+ if((opType == "test") && (numParams < 1)) wrongBody = true;
+
if(((opType == "sendxml2c") || (opType == "sendhex2c") || (opType == "loadxml") || (opType == "diameterServerSessions")) && (numParams != 1)) wrongBody = true;
if(wrongBody) {
// Launch exception
std::string msg = "Wrong body content format on HTTP Request for '";
msg += opType;
- msg += "' operation (missing parameter/s)";
+ msg += "' operation (missing parameter/s). Use 'help' management command to see more information.";
throw anna::RuntimeException(msg, ANNA_FILE_LOCATION);
}
- // All seems ok:
- std::string param1, param2;
-
- if(numParams >= 1) { tok_iter++; param1 = Tokenizer::data(tok_iter); }
-
- if(numParams == 2) { tok_iter++; param2 = Tokenizer::data(tok_iter); }
-
// Operations:
if(opType == "context") {
std::string contextFile = ((numParams == 1) ? param1 : anna::functions::asString("/var/tmp/anna.context.%05d", getPid()));
writeContext(contextFile);
- response_content = anna::functions::asString("Context dumped on file '%s'\n", contextFile.c_str());
+ response_content = anna::functions::asString("Context dumped on file '%s'", contextFile.c_str());
+ return;
+ }
+
+ if(opType == "services") {
+ std::string servicesFile = ((numParams == 1) ? param1 : "services.xml");
+ try {
+ loadServices(servicesFile, true /* bind entities */);
+ }
+ catch(anna::RuntimeException &ex) {
+ ex.trace();
+ response_content = anna::functions::asString("Loaded services from file '%s' with some problems (ignored ones)", servicesFile.c_str());
+ return;
+ }
+ response_content = anna::functions::asString("Loaded services from file '%s'", servicesFile.c_str());
return;
}
// Realm switch:
if(opType == "node") {
if (param1 != "") {
- if (setWorkingNode(param1)) response_content = anna::functions::asString("Current node is now '%s'\n", param1.c_str());
+ if (setWorkingNode(param1)) response_content = anna::functions::asString("Current node is now '%s'", param1.c_str());
}
else {
- std::cout << getWorkingNode()->asXMLString() << std::endl;
+ response_content = getWorkingNode()->asXMLString();
}
return;
}
+ // Diameter endpoints:
+ MyDiameterEntity *entity = getWorkingNode()->getEntity();
+ MyDiameterEngine *commEngine = getWorkingNode()->getMyDiameterEngine();
+ MyLocalServer *localServer = getWorkingNode()->getDiameterServer();
+ anna::diameter::codec::Message codecMsg(getCodecEngine());
+
+
if(opType == "code") {
codecMsg.loadXML(param1);
std::string hexString = anna::functions::asHexString(codecMsg.code());
if(opType == "show") commEngine->findClientSession(key)->show();
- if(opType == "hidden") result = commEngine->findClientSession(key)->hidden() ? "true" : "false";
+ if(opType == "hidden") opt_response_content = commEngine->findClientSession(key)->hidden() ? "true" : "false";
- if(opType == "shown") result = commEngine->findClientSession(key)->shown() ? "true" : "false";
+ if(opType == "shown") opt_response_content = commEngine->findClientSession(key)->shown() ? "true" : "false";
} else {
std::string address;
int port;
if(opType == "show") commEngine->findServer(address, port)->show();
- if(opType == "hidden") result = commEngine->findServer(address, port)->hidden() ? "true" : "false";
+ if(opType == "hidden") opt_response_content = commEngine->findServer(address, port)->hidden() ? "true" : "false";
- if(opType == "shown") result = commEngine->findServer(address, port)->shown() ? "true" : "false";
+ if(opType == "shown") opt_response_content = commEngine->findServer(address, port)->shown() ? "true" : "false";
}
} else {
if(opType == "hide") entity->hide();
if(opType == "show") entity->show();
- if(opType == "hidden") result = entity->hidden() ? "true" : "false";
+ if(opType == "hidden") opt_response_content = entity->hidden() ? "true" : "false";
- if(opType == "shown") result = entity->shown() ? "true" : "false";
+ if(opType == "shown") opt_response_content = entity->shown() ? "true" : "false";
}
} else if((opType == "sendxml") || (opType == "sendxml2e") || (opType == "sendhex") || (opType == "sendhex2e")) {
if(!entity) throw anna::RuntimeException("No entity configured to send the message", ANNA_FILE_LOCATION);
// Externally we could control sending time (no request
// will be sent for answers).
// burst|goto|<order> Updates current burst pointer position.
- // burst|look|<order> Show programmed burst message for order provided.
+ // burst|look|<order> Show programmed burst message for order provided, current when missing.
if(param1 == "clear") {
- result = "Removed ";
- result += anna::functions::asString(getWorkingNode()->clearBurst());
- result += " elements.";
+ opt_response_content = "removed ";
+ opt_response_content += anna::functions::asString(getWorkingNode()->clearBurst());
+ opt_response_content += " elements";
} else if(param1 == "load") {
if(param2 == "") throw anna::RuntimeException("Missing xml path file for burst load operation", ANNA_FILE_LOCATION);
try { codecMsg.valid(); } catch(anna::RuntimeException &ex) { ex.trace(); } // at least we need to see validation errors although it will continue loading (see validation mode configured in launcher)
int position = getWorkingNode()->loadBurstMessage(codecMsg.code());
- result = "Loaded '";
- result += param2;
- result += "' file into burst list position ";
- result += anna::functions::asString(position);
+ opt_response_content = "loaded '";
+ opt_response_content += param2;
+ opt_response_content += "' file into burst list position ";
+ opt_response_content += anna::functions::asString(position);
} else if(param1 == "start") {
if(param2 == "") throw anna::RuntimeException("Missing initial load for burst start operation", ANNA_FILE_LOCATION);
int processed = getWorkingNode()->startBurst(initialLoad);
if(processed > 0) {
- result = "Initial load completed for ";
- result += anna::functions::entriesAsString(processed, "message");
- result += ".";
+ opt_response_content = "initial load completed for ";
+ opt_response_content += anna::functions::entriesAsString(processed, "message");
}
} else if(param1 == "push") {
if(param2 == "") throw anna::RuntimeException("Missing load amount for burst push operation", ANNA_FILE_LOCATION);
int pushed = getWorkingNode()->pushBurst(atoi(param2.c_str()));
if(pushed > 0) {
- result = "Pushed ";
- result += anna::functions::entriesAsString(pushed, "message");
- result += ".";
+ opt_response_content = "pushed ";
+ opt_response_content += anna::functions::entriesAsString(pushed, "message");
}
} else if(param1 == "pop") {
if(param2 == "") throw anna::RuntimeException("Missing amount for burst pop operation", ANNA_FILE_LOCATION);
int popped = getWorkingNode()->popBurst(releaseLoad);
if(popped > 0) {
- result = "Burst popped for ";
- result += anna::functions::entriesAsString(popped, "message");
- result += ".";
+ opt_response_content = "burst popped for ";
+ opt_response_content += anna::functions::entriesAsString(popped, "message");
}
} else if(param1 == "stop") {
int left = getWorkingNode()->stopBurst();
if(left != -1) {
- result += anna::functions::entriesAsString(left, "message");
- result += " left to the end of the cycle.";
+ opt_response_content += anna::functions::entriesAsString(left, "message");
+ opt_response_content += " left to the end of the cycle";
}
} else if(param1 == "repeat") {
if(param2 == "") param2 = "yes";
bool repeat = (param2 == "yes");
getWorkingNode()->repeatBurst(repeat);
- result += (repeat ? "Mode on." : "Mode off.");
+ opt_response_content += (repeat ? "repeat enabled" : "repeat disabled");
} else if(param1 == "send") {
if(param2 == "") throw anna::RuntimeException("Missing amount for burst send operation", ANNA_FILE_LOCATION);
int sent = getWorkingNode()->sendBurst(atoi(param2.c_str()));
if(sent > 0) {
- result = "Sent ";
- result += anna::functions::entriesAsString(sent, "message");
- result += ".";
+ opt_response_content = "sent ";
+ opt_response_content += anna::functions::entriesAsString(sent, "message");
}
} else if(param1 == "goto") {
if(param2 == "") throw anna::RuntimeException("Missing order position for burst goto operation", ANNA_FILE_LOCATION);
- result = getWorkingNode()->gotoBurst(atoi(param2.c_str()));
- result += ".";
+ opt_response_content = getWorkingNode()->gotoBurst(atoi(param2.c_str()));
} else if(param1 == "look") {
- if(param2 == "") throw anna::RuntimeException("Missing order position for burst look operation", ANNA_FILE_LOCATION);
-
- result = "\n\n";
- result += getWorkingNode()->lookBurst(atoi(param2.c_str()));
- result += "\n\n";
+ int order = ((param2 != "") ? atoi(param2.c_str()) : -1);
+ opt_response_content = "\n\n";
+ opt_response_content += getWorkingNode()->lookBurst(order);
} else {
throw anna::RuntimeException("Wrong body content format on HTTP Request for 'burst' operation (unexpected action parameter). See help", ANNA_FILE_LOCATION);
}
+
+ } else if((opType == "test")) {
+ // test|<id>|<command>[|parameters] Add a new step to the test case ...
+ // test|ttps|<amount> Starts/resume the provided number of time ticks per second (ttps). The ADML starts ...
+ // test|ip-limit[|amount] In-progress limit of test cases. No new test cases will be launched over this value ...
+ // test|repeat[|[yes]|no] Restarts the programmed test cases when finished. Disabled by default: the testing ...
+ // test|report[|[yes]|no] Every time a test case is finished a report file in xml format will be created under ...
+ // test|report-hex[|[yes]|no] Reports could include the diameter messages in hexadecimal format. Disabled by default.
+ // test|goto|<id> Updates current test pointer position.
+ // test|look[|id] Show programmed test case for id provided, current when missing ...
+ // test|reset|<soft/hard>[|id] Reset the test case for id provided, all the tests when missing ...
+ // test|clear Clears all the programmed test cases.
+
+ if(param1 == "ttps") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ bool success = ((param2 != "") ? testManager.configureTTPS(atoi(param2.c_str())) : false);
+ if (success) {
+ opt_response_content = "assigned new test launch rate to ";
+ opt_response_content += anna::functions::asString(atoi(param2.c_str()));
+ opt_response_content += " events per second";
+ }
+ else {
+ opt_response_content += "unable to configure the test rate provided";
+ }
+ }
+ else if(param1 == "ip-limit") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ unsigned int limit;
+ if (param2 != "") {
+ limit = atoi(param2.c_str());
+ testManager.setInProgressLimit(limit);
+ opt_response_content = "new in-progress limit: ";
+ opt_response_content += (limit != UINT_MAX) ? anna::functions::asString(limit) : "<no limit>";
+ }
+ else {
+ opt_response_content = "in-progress limit amount: ";
+ limit = testManager.getInProgressLimit();
+ opt_response_content += (limit != UINT_MAX) ? anna::functions::asString(limit) : "<no limit>";
+ opt_response_content += "; currently there are ";
+ opt_response_content += anna::functions::asString(testManager.getInProgressCount());
+ opt_response_content += " test cases running";
+ }
+ }
+ else if(param1 == "repeat") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ if(param2 == "") param2 = "yes";
+ testManager.setPoolRepeat((param2 == "yes"));
+ opt_response_content += (testManager.getPoolRepeat() ? "repeat enabled" : "repeat disabled");
+ }
+ else if(param1 == "report") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ if(param2 == "") param2 = "yes";
+ testManager.setDumpReports((param2 == "yes"));
+ opt_response_content += (testManager.getDumpReports() ? "report enabled" : "report disabled");
+ }
+ else if(param1 == "report-hex") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ if(param2 == "") param2 = "yes";
+ testManager.setDumpHex((param2 == "yes"));
+ opt_response_content += (testManager.getDumpHex() ? "report includes hexadecimal messages" : "report excludes hexadecimal messages");
+ }
+ else if(param1 == "goto") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ if(param2 == "") throw anna::RuntimeException("Missing id for test goto operation", ANNA_FILE_LOCATION);
+ int id = atoi(param2.c_str());
+ if (testManager.gotoTestCase(id)) {
+ opt_response_content = "position updated for id provided (";
+ }
+ else {
+ opt_response_content = "cannot found test id (";
+ }
+ opt_response_content += anna::functions::asString(id);
+ opt_response_content += ")";
+ }
+ else if(param1 == "look") {
+ if (numParams > 2)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ int id = ((param2 != "") ? atoi(param2.c_str()) : -1);
+ TestCase *testCase = testManager.findTestCase(id);
+
+ if (testCase) {
+ response_content = testCase->asXMLString();
+ return;
+ }
+ else {
+ if (id == -1) {
+ opt_response_content = "no current test case detected (testing started ?)";
+ }
+ else {
+ opt_response_content = "cannot found test id (";
+ opt_response_content += anna::functions::asString(id);
+ opt_response_content += ")";
+ }
+ }
+ }
+ else if(param1 == "reset") {
+ if (numParams > 3)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ if (param2 != "soft" && param2 != "hard")
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ int id = ((param3 != "") ? atoi(param3.c_str()) : -1);
+ TestCase *testCase = ((id != -1) ? testManager.findTestCase(id) : NULL);
+
+ if (testCase) {
+ bool done = testCase->reset((param2 == "hard") ? true:false);
+ opt_response_content = "test ";
+ opt_response_content += param2;
+ opt_response_content += " reset for id ";
+ opt_response_content += anna::functions::asString(id);
+ opt_response_content += done ? ": done": ": not done";
+ }
+ else {
+ if (id == -1) {
+ bool anyReset = testManager.resetPool((param2 == "hard") ? true:false);
+ opt_response_content = "reset have been sent to all programmed tests: "; opt_response_content += anyReset ? "some/all was actually reset" : "nothing was reset";
+ }
+ else {
+ opt_response_content = "cannot found test id (";
+ opt_response_content += anna::functions::asString(id);
+ opt_response_content += ")";
+ }
+ }
+ }
+ else if(param1 == "clear") {
+ if (numParams > 1)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+
+ if (testManager.clearPool()) {
+ opt_response_content = "all the programmed test cases have been dropped";
+ }
+ else {
+ opt_response_content = "there are not programmed test cases to be removed";
+ }
+ }
+ else {
+ int id = atoi(param1.c_str());
+ if(id < 0)
+ throw anna::RuntimeException("Invalid test case identifier: must be a non-negative number", ANNA_FILE_LOCATION);
+
+ // PARAM: 1 2 3 4 5 6 7 8 9 10
+ // test|<id>|<command>
+ // timeout| <msecs>
+ // sendxml2e| <file>[|<step number>]
+ // sendxml2c| <file>[|<step number>]
+ // delay| [msecs]
+ // wait<fe/fc>|[code]|[bitR]|[ResultCode]|[sessionId]|[hopByHop]|[msisdn]|[imsi]|[serviceContextId]
+ // wait<fe/fc>-answer|<step number>
+ // wait<fe/fc>-regexp|<regexp>
+ if(param2 == "") throw anna::RuntimeException("Missing command for test id operation", ANNA_FILE_LOCATION);
+
+ // Commands:
+ if (param2 == "timeout") {
+ if (numParams > 3)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+ if(param3 == "") throw anna::RuntimeException("Missing milliseconds for 'timeout' command in test id operation", ANNA_FILE_LOCATION);
+ anna::Millisecond timeout = checkTimeMeasure("Test case timeout", param3);
+ testManager.getTestCase(id)->addTimeout(timeout); // creates / reuses
+ }
+ else if ((param2 == "sendxml2e")||(param2 == "sendxml2c")) {
+ if (numParams > 4)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+ if(param3 == "") throw anna::RuntimeException(anna::functions::asString("Missing xml file for '%s' command in test id operation", param2.c_str()), ANNA_FILE_LOCATION);
+ codecMsg.loadXML(param3);
+ if (codecMsg.isRequest()) {
+ if (param4 != "")
+ throw anna::RuntimeException("Step number is provided with answers (to resolve the corresponding 'wait for request' step), but NOT with requests", ANNA_FILE_LOCATION);
+ }
+ else {
+ if (param4 == "") LOGWARNING(anna::Logger::warning("Step number has not been provided. Take into account that this answer message will be sent 'as is' and sequence information could be wrong at the remote peer", ANNA_FILE_LOCATION));
+ }
+ int stepNumber = ((param4 != "") ? atoi(param4.c_str()):-1);
+ std::string originRealm = codecMsg.getAvp(anna::diameter::helpers::base::AVPID__Origin_Realm)->getDiameterIdentity()->getValue();
+ RealmNode *realm = getRealmNode(originRealm);
+ if (!realm)
+ throw anna::RuntimeException("Cannot identify the realm node for the manager message. Check the Origin-Realm avp value (use the realm node name)", ANNA_FILE_LOCATION);
+
+ if (param2 == "sendxml2e")
+ testManager.getTestCase(id)->addSendxml2e(codecMsg.code(), realm, stepNumber); // creates / reuses
+ else
+ testManager.getTestCase(id)->addSendxml2c(codecMsg.code(), realm, stepNumber); // creates / reuses
+ }
+ else if (param2 == "delay") {
+ if (numParams > 3)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+ if(param3 == "") throw anna::RuntimeException("Missing milliseconds for 'delay' command in test id operation", ANNA_FILE_LOCATION);
+ anna::Millisecond delay = checkTimeMeasure("Test case delay step", param3);
+ testManager.getTestCase(id)->addDelay(delay); // creates / reuses
+ }
+ else if ((param2 == "waitfe")||(param2 == "waitfc")) {
+ if (numParams > 10)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+ if (param3 != "" || param4 != "" || param5 != "" || param6 != "" || param7 != "" || param8 != "" || param9 != "" || param10 != "") {
+ bool fromEntity = (param2.substr(4,2) == "fe");
+ testManager.getTestCase(id)->addWait(fromEntity, param3, param4, param5, param6, param7, param8, param9, param10);
+ }
+ else {
+ throw anna::RuntimeException(anna::functions::asString("Missing condition for '%s' command in test id operation", param2.c_str()), ANNA_FILE_LOCATION);
+ }
+ }
+ else if ((param2 == "waitfe-regexp")||(param2 == "waitfc-regexp")) {
+ if (numParams > 3)
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+ if (param3 != "") {
+ bool fromEntity = (param2.substr(4,2) == "fe");
+ testManager.getTestCase(id)->addWaitRegexp(fromEntity, param3);
+ }
+ else {
+ throw anna::RuntimeException(anna::functions::asString("Missing condition for '%s' command in test id operation", param2.c_str()), ANNA_FILE_LOCATION);
+ }
+ }
+ else {
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
+ }
+ }
+
} else if((opType == "sendxml2c") || (opType == "sendhex2c")) {
if(!localServer) throw anna::RuntimeException("No local server configured to send the message", ANNA_FILE_LOCATION);
anna::diameter::comm::Message *msg = getWorkingNode()->createCommMessage();
}
} else if(opType == "loadxml") {
codecMsg.loadXML(param1);
- std::string xmlString = codecMsg.asXMLString();
- std::cout << xmlString << std::endl;
+ response_content = codecMsg.asXMLString();
+ return;
} else if(opType == "diameterServerSessions") {
int diameterServerSessions = atoi(param1.c_str());
throw anna::RuntimeException("Operation not applicable (no own diameter server has been configured)", ANNA_FILE_LOCATION);
if(param1 == "") { // programmed answers FIFO's to stdout
- std::cout << localServer->getReactingAnswers()->asString("ANSWERS TO CLIENT") << std::endl;
- response_content = "Programmed answers dumped on stdout\n";
+ response_content = localServer->getReactingAnswers()->asString("ANSWERS TO CLIENT");
return;
} else if (param1 == "rotate") {
localServer->getReactingAnswers()->rotate(true);
} else if (param1 == "dump") {
localServer->getReactingAnswers()->dump();
} else {
- anna::diameter::codec::Message *message = getCodecEngine()->createMessage(param1); // // XXXXXXXXXXXXXX el del nodo de trabajo
+ anna::diameter::codec::Message *message = getCodecEngine()->createMessage(param1);
LOGDEBUG
(
anna::Logger::debug(message->asXMLString(), ANNA_FILE_LOCATION);
throw anna::RuntimeException("Operation not applicable (no diameter entity has been configured)", ANNA_FILE_LOCATION);
if(param1 == "") { // programmed answers FIFO's to stdout
- std::cout << entity->getReactingAnswers()->asString("ANSWERS TO ENTITY") << std::endl;
- response_content = "Programmed answers dumped on stdout\n";
+ response_content = entity->getReactingAnswers()->asString("ANSWERS TO ENTITY");
return;
} else if (param1 == "rotate") {
entity->getReactingAnswers()->rotate(true);
} else if (param1 == "dump") {
entity->getReactingAnswers()->dump();
} else {
- anna::diameter::codec::Message *message = getCodecEngine()->createMessage(param1); // XXXXXXXXXXXXXX el del nodo de trabajo
+ anna::diameter::codec::Message *message = getCodecEngine()->createMessage(param1);
LOGDEBUG
(
anna::Logger::debug(message->asXMLString(), ANNA_FILE_LOCATION);
entity->getReactingAnswers()->addMessage(code, message);
}
} else {
- LOGWARNING(anna::Logger::warning(help(), ANNA_FILE_LOCATION));
- throw anna::RuntimeException("Wrong body content format on HTTP Request. Unsupported/unrecognized operation type", ANNA_FILE_LOCATION);
+ throw anna::RuntimeException("Wrong body content format on HTTP Request. Use 'help' management command to see more information.", ANNA_FILE_LOCATION);
}
// HTTP response
- response_content = "Operation processed; ";
-
- if((opType == "decode") || (opType == "code")) {
- response_content += "File '";
- response_content += param2;
- response_content += "' created.";
- response_content += "\n";
- } else if((opType == "hide") || (opType == "show")) {
- response_content += "Resource '";
- response_content += ((param1 != "") ? param1 : "Entity");
-
- if(param2 != "") {
- response_content += "|";
- response_content += param2;
- }
-
- response_content += "' ";
-
- if(opType == "hide") response_content += "has been hidden.";
-
- if(opType == "show") response_content += "has been shown.";
-
- response_content += "\n";
- } else if((opType == "hidden") || (opType == "shown")) {
- response_content += "Result: ";
- response_content += result;
- response_content += "\n";
- } else if((opType == "sendxml") || (opType == "sendxml2e") || (opType == "sendhex") || (opType == "sendhex2e")) {
- response_content += "Message '";
- response_content += param1;
- response_content += "' sent to entity.";
- response_content += "\n";
- } else if(opType == "burst") {
- response_content += "Burst '";
- response_content += param1;
- response_content += "' executed. ";
- response_content += result;
- response_content += "\n";
- } else if((opType == "sendxml2c") || (opType == "sendhex2c")) {
- response_content += "Message '";
- response_content += param1;
- response_content += "' sent to client.";
- response_content += "\n";
- } else if(opType == "loadxml") {
- response_content += "Message '";
- response_content += param1;
- response_content += "' loaded.";
- response_content += "\n";
- } else if((opType == "answerxml") || (opType == "answerxml2c")) {
- response_content += "'";
- response_content += param1;
- response_content += "' applied on server FIFO queue";
- response_content += "\n";
- } else if(opType == "answerxml2e") {
- response_content += "'";
- response_content += param1;
- response_content += "' applied on client FIFO queue";
- response_content += "\n";
- } else if(opType == "diameterServerSessions") {
- response_content += "Maximum server socket connections updated to '";
- response_content += param1;
- response_content += "'.";
- response_content += "\n";
+ response_content = "Operation correctly processed: "; response_content += operation;
+ if (opt_response_content != "") {
+ response_content += " => ";
+ response_content += opt_response_content;
}
}
anna::diameter::codec::OamModule::instantiate().asXML(result);
// Statistics:
anna::statistics::Engine::instantiate().asXML(result);
+
+ // Testing: could be heavy if test case reports are enabled
+ TestManager::instantiate().asXML(result);
+
return result;
}