1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (c) 2000-2014 Ericsson Telecom AB
3 // All rights reserved. This program and the accompanying materials
4 // are made available under the terms of the Eclipse Public License v1.0
5 // which accompanies this distribution, and is available at
6 // http://www.eclipse.org/legal/epl-v10.html
7 ///////////////////////////////////////////////////////////////////////////////
8 #include "ILoggerPlugin.hh"
9 #include "TSTLogger.hh"
11 #ifndef TITAN_RUNTIME_2
12 #include "RT1/TitanLoggerApi.hh"
14 #include "RT2/TitanLoggerApi.hh"
23 #include <sys/types.h>
24 #include <netinet/in.h>
25 #include <sys/socket.h>
26 #include <arpa/inet.h>
35 ILoggerPlugin
*create_plugin() { return new TSTLogger(); }
36 void destroy_plugin(ILoggerPlugin
*plugin
) { delete plugin
; }
42 ////////////////////////////////////////////////////////////////////////////////
49 SocketException(const string p_error_msg
, const string p_reason
=""):error_msg(p_error_msg
),reason(p_reason
) {}
50 const string
getMessage() const { return error_msg
; }
51 const string
getReason() const { return reason
; }
54 class TimeoutException
: public SocketException
57 TimeoutException(const string p_error_msg
): SocketException(p_error_msg
) {}
60 ////////////////////////////////////////////////////////////////////////////////
67 enum select_readwrite_t
{ SELECT_READ
, SELECT_WRITE
};
68 void wait_for_ready(time_t end_time
, select_readwrite_t readwrite
);
70 TCPClient(): socket_fd(-1), timeout_time(30) {}
71 // opens connection and returns socket file descriptor, throws exception on error
72 void open_connection(const string host_name
, const string service_name
) throw(SocketException
);
73 // send a string to a socket, don't return until the whole string is sent
74 void send_string(const string
& str
) throw(SocketException
);
75 // receive available data to the end of the parameter string, blocks until at least wait_for_bytes chars have arrived,
76 // returns false if connection was closed
77 bool receive_string(string
& str
, const size_t wait_for_bytes
) throw(SocketException
);
79 void close_connection() throw(SocketException
);
80 time_t get_timeout() const { return timeout_time
; }
81 void set_timeout(time_t t
) { timeout_time
= t
; }
84 void TCPClient::open_connection(const string host_name
, const string service_name
) throw(SocketException
)
89 struct addrinfo
*res
, *aip
;
90 struct addrinfo hints
;
91 memset(&hints
, 0, sizeof(hints
));
92 hints
.ai_socktype
= SOCK_STREAM
;
93 int status
= getaddrinfo(host_name
.c_str(), service_name
.c_str(), &hints
, &res
);
95 throw SocketException("Cannot find host and service", gai_strerror(status
));
97 for (aip
= res
; aip
!= NULL
; aip
= aip
->ai_next
) {
98 // create socket descriptor
99 socket_fd
= socket(aip
->ai_family
, aip
->ai_socktype
, aip
->ai_protocol
);
100 if (socket_fd
==-1) continue;
102 status
= connect(socket_fd
, aip
->ai_addr
, aip
->ai_addrlen
);
103 if (status
==0) break; // connected
108 throw SocketException("Cannot connect to host and service");
112 void TCPClient::close_connection() throw(SocketException
)
117 int rv
= close(socket_fd
);
120 throw SocketException("Cannot close socket", strerror(errno
));
124 // end_time - is time in seconds (from time(NULL)+timeout, when should we give up waiting
125 // readwrite - are we checking for read or write readyness on the file descriptor
126 void TCPClient::wait_for_ready(time_t end_time
, select_readwrite_t readwrite
)
129 tv
.tv_sec
= end_time
- time(NULL
);
134 FD_SET(socket_fd
, &fds
);
135 int status
= select(socket_fd
+1,
136 (readwrite
==SELECT_READ
) ? &fds
: NULL
,
137 (readwrite
==SELECT_WRITE
) ? &fds
: NULL
,
140 if (errno
==EINTR
) { // some signal
142 tv
.tv_sec
= end_time
- time(NULL
);
144 goto wait_reset
; // wait again
146 throw SocketException("Error while waiting on socket", strerror(errno
));
148 if (FD_ISSET(socket_fd
, &fds
)==0) {
149 throw TimeoutException("Timeout while waiting on socket");
153 void TCPClient::send_string(const string
& str
) throw(SocketException
)
156 throw SocketException("Connection is not open");
158 time_t end_time
= time(NULL
) + timeout_time
;
160 size_t str_len
= str
.size();
161 while (sent_cnt
<str_len
) {
162 wait_for_ready(end_time
, SELECT_WRITE
);
163 ssize_t n
= send(socket_fd
, str
.c_str()+sent_cnt
, str_len
-sent_cnt
, 0);
165 throw SocketException("Cannot send data on socket", strerror(errno
));
171 // wait_for_bytes - wait until at least so many bytes arrived or timeout or connection closed,
172 // if zero then wait until connection is closed
173 bool TCPClient::receive_string(string
& str
, const size_t wait_for_bytes
) throw(SocketException
)
176 throw SocketException("Connection is not open");
178 time_t end_time
= time(NULL
) + timeout_time
;
181 size_t bytes_received
= 0;
182 while (wait_for_bytes
==0 || bytes_received
<wait_for_bytes
) {
183 wait_for_ready(end_time
, SELECT_READ
);
184 ssize_t recv_cnt
= recv(socket_fd
, buff
, sizeof(buff
), 0);
186 throw SocketException("Cannot read data from socket", strerror(errno
));
188 if (recv_cnt
==0) { // socket was closed by the other side
193 bytes_received
+= recv_cnt
;
194 // store received data
195 str
.append(buff
, recv_cnt
);
200 ////////////////////////////////////////////////////////////////////////////////
202 class HttpException
: public SocketException
205 HttpException(const string p_error_msg
, const string p_reason
=""):
206 SocketException(p_error_msg
, p_reason
) {}
209 typedef map
<string
,string
> string_map
;
211 class HTTPClient
: public TCPClient
214 HTTPClient(): TCPClient() {}
215 string
url_encode(const string
& str
);
216 string
post_request(const string
& host
, const string
& uri
, const string
& user_agent
, const string_map
& req_params
) throw(SocketException
);
219 string
HTTPClient::url_encode(const string
& str
)
221 static char hex
[] = "0123456789abcdef";
223 for (size_t i
=0; i
<str
.size(); i
++) {
225 if (isalnum(c
) || c
=='-' || c
=='_' || c
=='.' || c
=='~') {
230 ss
<< '%' << hex
[(c
>>4) & 15] << hex
[(c
&15) & 15];
236 string
HTTPClient::post_request(const string
& host
, const string
& uri
, const string
& user_agent
, const string_map
& req_params
) throw(SocketException
)
238 // compose request message
240 req_ss
<< "POST " << uri
<< " HTTP/1.1\r\n" << // Host: must be added if HTTP/1.1 is used
241 "Host: " << host
<< "\r\n" <<
242 "User-Agent: " << user_agent
<< "\r\n" <<
243 "Connection: Close" << "\r\n" <<
244 "Content-Type: application/x-www-form-urlencoded" << "\r\n"; // as if it were post'ed from a web browser html form
245 stringstream req_body_ss
;
246 for (string_map::const_iterator it
= req_params
.begin(); it
!=req_params
.end(); ++it
) {
247 if (it
!=req_params
.begin()) req_body_ss
<< '&';
248 req_body_ss
<< url_encode(it
->first
) << '=' << url_encode(it
->second
);
250 req_ss
<< "Content-Length: " << req_body_ss
.str().size() << "\r\n";
251 req_ss
<< "\r\n" << req_body_ss
.str();
253 //cerr << "HTTP POST REQUEST:\n[" << req_ss.str() << "]\n";
254 send_string(req_ss
.str());
255 // receive response, wait until the server closes the connection (doesn't work with Keep-Alive!)
257 receive_string(response
, 0); // receive until connection is closed by the peer or timeout
258 //cerr << "HTTP POST RESPONSE:\n[" << response << "]\n";
259 // divide into header and body parts
260 size_t pos
= response
.find("\r\n\r\n");
261 if (pos
==string::npos
) {
262 throw HttpException("Invalid HTTP response", "Cannot find body part");
264 string head
= response
.substr(0, pos
);
265 string body
= response
.substr(pos
+4, string::npos
);
266 if (head
.find("Transfer-Encoding: chunked")!=string::npos
) {
267 // remove chunked encoding stuff from body
268 // FIXME: this doesn't work if the chunks contain \r\n
271 bool chunk_line
= false;
272 for (size_t idx
= 0; idx
<body
.size()-1; idx
++) {
273 if (body
[idx
]=='\r' && body
[idx
+1]=='\n') { // end of line
281 chunk_line
= !chunk_line
;
293 ////////////////////////////////////////////////////////////////////////////////
296 string
TSTLogger::get_tst_time_str(const TitanLoggerApi::TimestampType
& timestamp
)
298 long long int t
= timestamp
.seconds().get_long_long_val() * 1000 + timestamp
.microSeconds().get_long_long_val() / 1000;
304 string
TSTLogger::get_host_name()
307 int status
= gethostname(name
, 256);
308 if (status
!=0) return "DefaultExecutingHost";
312 string
TSTLogger::get_user_name()
317 TSTLogger::TSTLogger()
319 this->major_version_
= 1;
320 this->minor_version_
= 0;
321 this->name_
= mputstr(this->name_
, "TSTLogger");
322 this->help_
= mputstr(this->help_
, "TITAN Logger Plugin for TestStatistics");
324 // parameters of this plugin
325 parameters
["tst_host_name"] = ParameterData("eta-teststatistics.rnd.ki.sw.ericsson.se", true, "TestStatistics web service host name");
326 parameters
["tst_service_name"] = ParameterData("http", true, "TestStatistics web service name or port number");
328 parameters
["tst_tcstart_url"] = ParameterData("/ts-rip/rip/tcstart");
329 parameters
["tst_tcstop_url"] = ParameterData("/ts-rip/rip/tcstop");
330 parameters
["tst_tsstart_url"] = ParameterData("/ts-rip/rip/tsstart");
331 parameters
["tst_tsstop_url"] = ParameterData("/ts-rip/rip/tsstop");
332 parameters
["tst_tcfailreason_url"] = ParameterData("/ts-rip/rip/tcfailreason");
334 parameters
["dbsUrl"] = ParameterData("esekilx0007-sql5.rnd.ki.sw.ericsson.se:3314", true, "database URL");
335 parameters
["dbUser"] = ParameterData("demo", true, "database user");
336 parameters
["dbPass"] = ParameterData("demo", true, "plain text password of the user");
337 parameters
["dbName"] = ParameterData("teststatistics_demo", true, "name of the database");
339 parameters
["log_plugin_debug"] = ParameterData("0");
341 parameters
["testConfigName"] = ParameterData("DefaultConfigName", false, "name of this specific configuration of the test suite");
342 parameters
["suiteName"] = ParameterData("DefaultSuiteName", false, "name of test suite");
343 parameters
["executingHost"] = ParameterData(get_host_name(), true, "host where the test was executed");
344 parameters
["sutId"] = ParameterData("0.0.0.0", true, "IP address of SUT");
345 parameters
["sutName"] = ParameterData("DefaultSUTName", true, "name of SUT");
346 parameters
["lsvMajor"] = ParameterData("1", true, "major version number of SUT");
347 parameters
["lsvMinor"] = ParameterData("0", true, "minor version number of SUT");
348 parameters
["runByUser"] = ParameterData(get_user_name(), true, "name of user running the tests");
349 parameters
["projectName"] = ParameterData("DefaultProjectname", true, "name of the project");
350 parameters
["productName"] = ParameterData("DefaultProductName", true, "name of the product");
351 parameters
["productVersion"] = ParameterData("0.0", true, "version of the product");
352 parameters
["configType"] = ParameterData("configType", true, "");
353 parameters
["configVersion"] = ParameterData("configVersion", true, "");
354 parameters
["testType"] = ParameterData("testType", true, "");
355 parameters
["logLink"] = ParameterData("default_log_location", false, "absolute location of log files");
356 parameters
["logEnd"] = ParameterData("default_web_log_dir", false, "log directory relative to web server root");
357 parameters
["reportEmail"] = ParameterData(get_user_name()+"@ericsson.com", false, "who is to be notified via email");
358 parameters
["reportTelnum"] = ParameterData("0", false, "where to send the SMS notification");
361 ss
<< "TITAN " << this->name_
<< ' ' << this->major_version_
<< '.' << this->minor_version_
;
362 user_agent
= ss
.str();
364 this->testcase_count_
= 0;
367 TSTLogger::~TSTLogger()
371 this->name_
= this->help_
= NULL
;
374 bool TSTLogger::log_plugin_debug()
376 return parameters
["log_plugin_debug"].get_value()!="0";
379 void TSTLogger::init(const char */
*options*/
)
381 cout
<< "Initializing `" << this->name_
<< "' (v" << this->major_version_
<< "." << this->minor_version_
<< "): " << this->help_
<< endl
;
382 this->is_configured_
= true;
385 void TSTLogger::fini()
387 if (is_main_proc()) {
388 TitanLoggerApi::TimestampType timestamp
;
390 gettimeofday(&tm
, NULL
);
391 timestamp
.seconds().set_long_long_val((long long int)tm
.tv_sec
);
392 timestamp
.microSeconds().set_long_long_val((long long int)tm
.tv_usec
);
393 log_testsuite_stop(timestamp
); // TODO: call this from log()
395 this->is_configured_
= false;
398 void TSTLogger::set_parameter(const char *parameter_name
, const char *parameter_value
)
400 map
<string
,ParameterData
>::iterator it
= parameters
.find(parameter_name
);
401 if (it
!=parameters
.end()) {
402 it
->second
.set_value(parameter_value
);
404 cerr
<< this->name_
<< ": " << "Unsupported parameter: `" << parameter_name
<< "' with value: `"
405 << parameter_value
<< "'" << endl
;
409 void TSTLogger::add_database_params(std::map
<std::string
,std::string
>& req_params
)
411 req_params
["dbsUrl"] = parameters
["dbsUrl"].get_value();
412 req_params
["dbUser"] = parameters
["dbUser"].get_value();
413 req_params
["dbPass"] = parameters
["dbPass"].get_value();
414 req_params
["dbName"] = parameters
["dbName"].get_value();
417 string
TSTLogger::post_message(std::map
<std::string
,std::string
> req_params
, const string
& TST_service_uri
)
419 add_database_params(req_params
);
422 client
.open_connection(parameters
["tst_host_name"].get_value(), parameters
["tst_service_name"].get_value());
423 string response
= client
.post_request(parameters
["tst_host_name"].get_value(), TST_service_uri
, user_agent
, req_params
);
424 client
.close_connection();
427 catch (SocketException exc
) {
428 cerr
<< this->name_
<< ": " << "HTTP error: " << exc
.getMessage() << " (" << exc
.getReason() << ")\n";
433 // most of the message types should be sent only from one main process (MTC in parallel mode)
434 bool TSTLogger::is_main_proc() const
436 return TTCN_Runtime::is_mtc() || TTCN_Runtime::is_single();
439 void TSTLogger::log(const TitanLoggerApi::TitanLogEvent
& event
, bool log_buffered
)
441 log(event
, log_buffered
, false, false);
444 void TSTLogger::log(const TitanLoggerApi::TitanLogEvent
& event
,
445 bool /*log_buffered*/, bool /*separate_file*/, bool /*use_emergency_mask*/)
447 const TitanLoggerApi::LogEventType_choice
& choice
= event
.logEvent().choice();
448 switch (choice
.get_selection()) {
449 case TitanLoggerApi::LogEventType_choice::ALT_testcaseOp
: {
450 const TitanLoggerApi::TestcaseEvent_choice
& tec
= choice
.testcaseOp().choice();
451 switch (tec
.get_selection()) {
452 case TitanLoggerApi::TestcaseEvent_choice::ALT_testcaseStarted
:
453 if (is_main_proc()) {
454 log_testcase_start(tec
.testcaseStarted(), event
.timestamp());
457 case TitanLoggerApi::TestcaseEvent_choice::ALT_testcaseFinished
:
458 if (is_main_proc()) {
459 log_testcase_stop(tec
.testcaseFinished(), event
.timestamp());
466 case TitanLoggerApi::LogEventType_choice::ALT_verdictOp
:
467 // allow these messages from PTCs
468 log_verdictop_reason(choice
.verdictOp());
475 void TSTLogger::log_testcase_start(const TitanLoggerApi::QualifiedName
& testcaseStarted
, const TitanLoggerApi::TimestampType
& timestamp
)
477 if (this->testcase_count_
== 0) {
478 log_testsuite_start(timestamp
); // TODO: call this from log()
480 ++this->testcase_count_
;
482 map
<string
,string
> req_params
;
483 req_params
["suiteId"] = this->suite_id_
;
484 req_params
["tcId"] = (const char *)testcaseStarted
.testcase__name();
485 req_params
["tcHeader"] = req_params
["tcId"];
486 req_params
["tcStartTime"] = get_tst_time_str(timestamp
);
487 req_params
["tcState"] = "0";
488 //req_params["tcHtml"] = "";
489 req_params
["tcClass"] = (const char *)testcaseStarted
.module__name();
490 req_params
["tcMethod"] = req_params
["tcId"];
492 string resp
= post_message(req_params
, parameters
["tst_tcstart_url"].get_value());
494 if ((resp
.find("done") != string::npos
) && (resp
.find("tcaseId") != string::npos
)) {
495 this->tcase_id_
= resp
.substr(resp
.find("=") + 1);
496 if (log_plugin_debug()) {
497 cout
<< this->name_
<< ": " << "Operation `log_testcase_start' successful, returned tcaseId=" << this->tcase_id_
<< endl
;
500 cerr
<< this->name_
<< ": " << "Operation `log_testcase_start' failed: " << resp
<< endl
;
501 // Add better error handling.
506 void TSTLogger::log_testcase_stop(const TitanLoggerApi::TestcaseType
& testcaseFinished
, const TitanLoggerApi::TimestampType
& timestamp
)
509 switch (testcaseFinished
.verdict()) {
510 case TitanLoggerApi::Verdict::v0none
: // start (?)
513 case TitanLoggerApi::Verdict::v1pass
: // pass
516 case TitanLoggerApi::Verdict::v2inconc
: // inconclusive
519 case TitanLoggerApi::Verdict::v3fail
: // fail
522 case TitanLoggerApi::Verdict::v4error
: // error
528 map
<string
,string
> req_params
;
529 req_params
["tcaseId"] = this->tcase_id_
;
530 req_params
["tcEndTime"] = get_tst_time_str(timestamp
);
531 req_params
["tcState"] = tc_state
;
532 req_params
["tcUndefined"] = "false"; //?
533 req_params
["tcAssertion"] = "false"; //?
534 req_params
["tcTrafficLoss"] = "false"; //?
536 string resp
= post_message(req_params
, parameters
["tst_tcstop_url"].get_value());
538 if (!resp
.compare("done")) {
539 if (log_plugin_debug()) {
540 cout
<< this->name_
<< ": " << "Operation `log_testcase_stop' successful" << endl
;
543 cerr
<< this->name_
<< ": " << "Operation `log_testcase_stop' failed: " << resp
<< endl
;
548 void TSTLogger::log_testsuite_start(const TitanLoggerApi::TimestampType
& timestamp
)
550 map
<string
,string
> req_params
;
551 req_params
["fwName"] = "TITAN";
552 stringstream titanver_ss
;
553 titanver_ss
<< TTCN3_MAJOR
<< '.' << TTCN3_MINOR
<< ".pl" << TTCN3_PATCHLEVEL
;
554 req_params
["fwVersion"] = titanver_ss
.str();
555 req_params
["suiteStartTime"] = get_tst_time_str(timestamp
);
556 req_params
["testConfigName"] = parameters
["testConfigName"].get_value();
557 req_params
["suiteName"] = parameters
["suiteName"].get_value();
558 req_params
["executingHost"] = parameters
["executingHost"].get_value();
559 req_params
["sutId"] = parameters
["sutId"].get_value();
560 req_params
["sutName"] = parameters
["sutName"].get_value();
561 req_params
["lsvMajor"] = parameters
["lsvMajor"].get_value();
562 req_params
["lsvMinor"] = parameters
["lsvMinor"].get_value();
563 req_params
["runByUser"] = parameters
["runByUser"].get_value();
564 req_params
["projectName"] = parameters
["projectName"].get_value();
565 req_params
["productName"] = parameters
["productName"].get_value();
566 req_params
["productVersion"] = parameters
["productVersion"].get_value();
567 req_params
["notRun"] = "1"; // FIXME?
568 req_params
["configType"] = parameters
["configType"].get_value();
569 req_params
["configVersion"] = parameters
["configVersion"].get_value();
570 req_params
["testType"] = parameters
["testType"].get_value();
571 req_params
["logLink"] = parameters
["logLink"].get_value();
572 req_params
["logEnd"] = parameters
["logEnd"].get_value();
573 req_params
["reportEmail"] = parameters
["reportEmail"].get_value();
574 req_params
["reportTelnum"] = parameters
["reportTelnum"].get_value();
576 string resp
= post_message(req_params
, parameters
["tst_tsstart_url"].get_value());
578 if ((resp
.find("done") != string::npos
) && (resp
.find("suiteId") != string::npos
)) {
579 this->suite_id_
= resp
.substr(resp
.find("=") + 1);
580 if (log_plugin_debug()) {
581 cout
<< this->name_
<< ": " << "Operation `log_testsuite_start' successful, returned suiteId=" << this->suite_id_
<< endl
;
584 cerr
<< this->name_
<< ": " << "Operation `log_testsuite_start' failed: " << resp
<< endl
;
589 void TSTLogger::log_testsuite_stop(const TitanLoggerApi::TimestampType
& timestamp
)
591 map
<string
,string
> req_params
;
592 req_params
["suiteId"] = this->suite_id_
;
593 req_params
["tsEndTime"] = get_tst_time_str(timestamp
);
594 req_params
["reportEmail"] = parameters
["reportEmail"].get_value();
595 req_params
["reportTelnum"] = parameters
["reportTelnum"].get_value();
597 string resp
= post_message(req_params
, parameters
["tst_tsstop_url"].get_value());
599 if (!resp
.compare("done")) {
600 if (log_plugin_debug()) {
601 cout
<< this->name_
<< ": " << "Operation `log_testsuite_stop' successful" << endl
;
604 cerr
<< this->name_
<< ": " << "Operation `log_testsuite_stop' failed: " << resp
<< endl
;
608 void TSTLogger::log_verdictop_reason(const TitanLoggerApi::VerdictOp
& verdictOp
)
610 if (verdictOp
.choice().get_selection() == TitanLoggerApi::VerdictOp_choice::ALT_setVerdict
) {
611 TitanLoggerApi::SetVerdictType setVerdict
= verdictOp
.choice().setVerdict();
612 if (setVerdict
.newReason().ispresent() && setVerdict
.newReason()().lengthof()>0) {
613 map
<string
,string
> req_params
;
614 req_params
["tcaseId"] = this->tcase_id_
;
615 req_params
["tcFailType"] = "0"; //?
616 req_params
["tcFailNum"] = "1";
617 req_params
["tcFailReason"] = (const char *)setVerdict
.newReason()();
618 string resp
= post_message(req_params
, parameters
["tst_tcfailreason_url"].get_value());
619 if (!resp
.compare("done")) {
620 if (log_plugin_debug()) {
621 cout
<< this->name_
<< ": " << "Operation log_verdictop_reason' successful" << endl
;
624 cerr
<< this->name_
<< ": " << "Operation log_verdictop_reason' failed: " << resp
<< endl
;