Fix rmr_rpobe command line bug; add test coverage
[ric-plt/lib/rmr.git] / src / support / rmr_probe.c
1 // :vim ts=4 sw=4 noet:
2 /*
3 ==================================================================================
4         Copyright (c) 2020 Nokia
5         Copyright (c) 2020 AT&T Intellectual Property.
6
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10
11            http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 ==================================================================================
19 */
20
21 /*
22         Mnemonic:       rmr_probe.c
23         Abstract:       This sends probe messages to the indicated applications. The only
24                                 use currently is to send health check messages and wait for a
25                                 response. It might be extended later and would cause a bit of
26                                 a redesign, but from the outside the switch to the name rmr_probe
27                                 makes sense.
28
29                                 Original abstract which stands until the probe does more:
30                                 This is a generic, and very simplistic, health check application
31                                 which will send n RMR messages to an application (localhost:4560
32                                 by default) and expect to receive n responses.  Messages are sent
33                                 with the message type RIC_HEALTH_CHECK_REQ and responses are
34                                 expected to have a message type RIC_HEALTH_CHECK_RESP (defined in
35                                 the RIC message type header file).
36
37                                 Responses are expected to have a payload containing ASCII data.
38                                 The first, space separated token, is expected to be one of:
39                                         OK
40                                         ERR
41
42                                 to indicate the state of the response. The ERR token may optionally
43                                 be followed with a text string; when present it will be written on
44                                 standard error as an aide to problem determination if needed.
45
46                                 The programme exit code will be 0 on success (all received messages
47                                 had the OK token), or 1 to indicate failure. A failure reason will
48                                 also be written to standard error.
49
50                                 Command line options and parameters:
51                                         [-h host:port]          target application to "ping"
52                                         [-n num-msgs]           total number of "pings" to send
53                                         [-p port]                       specific port to open and use for responses
54                                         [-t seconds]            max timeout (default 3 seconds)
55
56                                 Route table:  While we don't need a route table to do wormhole sends we
57                                 do need for RMR to initialise an empty one. To avoid having to have a
58                                 dummy table on disk somewhere, we'll create one and "point" RMR at it.
59
60         Date:           26 February 2020
61         Author:         E. Scott Daniels
62 */
63
64 #include <unistd.h>
65 #include <errno.h>
66 #include <string.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <sys/epoll.h>
70 #include <time.h>
71 #include <fcntl.h>
72
73 /*
74    CAUTION:
75                 This is built as a part of the RMR project by CMake, not as a standalone binary.
76                 As such the includes do NOT have the leading 'rmr/' as a normal application
77                 might have.  (there is no such thing as /usr/local/include/rmr while building
78                 the project!)
79 */
80 #include <rmr.h>
81 #include <RIC_message_types.h>
82
83 #define MAX_MSG_SZ      2048            // max size we'll expect back
84 #define PAYLOAD_SZ      1024            // size of payload we will send
85
86 #define VERBOSE(...) if(verbose){ fprintf( stderr,  "[INFO] " __VA_ARGS__);}
87
88 /*
89         we can compute the latency.
90 typedef struct trace_data {
91         char    id[10];                                 // allows us to verify that it's valid
92         struct  timespec out_ts;                // time this payload was sent
93 } trace_data_t;
94 */
95
96 // ---------------------------------------------------------------------------
97
98 /*
99         Compute the elapsed time between ts1 and ts2.
100         Returns mu-seconds.
101 */
102 static int elapsed( struct timespec* start_ts, struct timespec* end_ts ) {
103         long long start;
104         long long end;
105         int bin;
106
107         start = ( start_ts->tv_sec * 1000000000) + start_ts->tv_nsec;
108         end = ( end_ts->tv_sec * 1000000000) + end_ts->tv_nsec;
109
110         bin = (end - start) / 1000;                     // to mu-sec
111         //bin = (end - start);
112
113         return bin;
114 }
115
116 static void usage( char* arg0 ) {
117
118         fprintf( stderr,        "version 1.0.0\n"
119                                                 "usage: %s [-h host:port] [-n msg-count] [-p port | -r] [-t seconds] [-v]\n"
120                                                 "\thost:port may be ip-address:port or name:port of the application\n"
121                                                 "\tmsg-count is the number of health check requests sent; default is 1\n"
122                                                 "\t-p uses the given port instead of assigning a random port (-r ignored if given)\n"
123                                                 "\t-r causes a random listen port NOT to be used; 43086 is used instead\n"
124                                                 "\t-v enables some amount of extra verbose output to stderr\n", arg0 );
125 }
126
127 /*
128         This validates the arg index is in range (< argc). If it is not
129         valid, the a message is issued and we abort.
130 */
131 static void vet_ai( int ai, int argc, char* arg0 ) {
132         if( ai < argc && ai > 0 ) {
133                 return;
134         }
135
136         fprintf( stderr, "abort: command line parameter(s) missing\n" );
137         usage( arg0 );
138         exit( 1 );
139 }
140
141 int main( int argc, char** argv ) {
142         int             ai = 1;                                                 // arg index
143         int             i;
144
145         void* mrc;                                                              // msg router context
146         rmr_mbuf_t*             mbuf;                                   // message buffer
147         char*   payload;                                                // direct reference to msg payload
148         long    expiry;                                                 // point at which we give up (expire)
149         long    now;                                                    // current time
150         long    max_timeout = 3;                                // (seconds) -t to overrride
151         char*   target = "localhost:4560";              // address of target to ping
152         char*   listen_port = NULL;                             // the port we open for "backhaul" connections (somewhat random by default)
153         int             rand_port = 1;                                  // -r turns off and we use a default port value
154         char*   tok;                                                    // pointer at token in a buffer
155         char    wbuf[MAX_MSG_SZ];                               // work buffer
156         char*   rbuf;                                                   // spot to work on the response
157         int             whid;                                                   // id of wormhole
158         int             num2send = 1;                                   // number of messages to send
159         int             count = 0;
160         int             good = 0;                                               // num good messages back
161         int             verbose = 0;
162         int             et;                                                             // elapsed time
163         struct  timespec out_ts;                                // time we sent the message
164         struct  timespec in_ts;                                 // time we got response
165
166         // ---- simple arg parsing ------
167         while( ai < argc ) {
168                 if( *argv[ai] == '-' ) {
169                         switch( argv[ai][1] ) {
170                                 case 'h':                                       // host:port
171                                         ai++;
172                                         vet_ai( ai, argc, argv[0] );
173                                         target = strdup( argv[ai] );
174                                         break;
175
176                                 case 'n':                                       // num to send
177                                         ai++;
178                                         vet_ai( ai, argc, argv[0] );
179                                         num2send = atoi( argv[ai] );
180                                         break;
181
182                                 case 'p':                                       // num to send
183                                         ai++;
184                                         vet_ai( ai, argc, argv[0] );
185                                         listen_port = strdup( argv[ai] );
186                                         break;
187
188                                 case 'r':                                       // generate random listen port
189                                         rand_port = 0;
190                                         break;
191
192                                 case 't':                                       // timeout
193                                         ai++;
194                                         vet_ai( ai, argc, argv[0] );
195                                         max_timeout = atoi( argv[ai] );
196                                         break;
197
198                                 case 'v':
199                                         verbose = 1;
200                                         break;
201
202                                 case '?':       usage( argv[0] );
203                                                         exit( 0 );
204
205                                 default:
206                                         fprintf( stderr, "[FAIL] unrecognised option: %s\n", argv[ai] );
207                                         usage( argv[0] );
208                                         exit( 1 );
209                         }
210
211                         ai++;
212                 } else {
213                         break;          // not an option, leave with a1 @ first positional parm
214                 }
215         }
216
217
218         if( listen_port == NULL ) {
219                 if( rand_port ) {                               // generate a somewhat random listen port (RMR needs)
220                         srand( time( NULL ) );
221                         snprintf( wbuf, sizeof( wbuf ), "%d", 43000 + (rand() % 1000) );
222                         listen_port = strdup( wbuf );
223                 } else {
224                         listen_port = "43086";          // -r given to disable random; go with static value
225                 }
226         }
227
228
229         VERBOSE( "listen port: %s; sending %d messages\n", listen_port, num2send );
230
231         if( (mrc = rmr_init( listen_port, MAX_MSG_SZ, RMRFL_NOTHREAD )) == NULL ) {             // start without route table listener thread
232                 fprintf( stderr, "[FAIL] unable to initialise RMR\n" );
233                 exit( 1 );
234         }
235         while( ! rmr_ready( mrc ) ) {
236                 VERBOSE( "waiting for RMR to show ready\n" );
237                 sleep( 1 );
238         }
239         VERBOSE( "RMR initialised\n" );
240
241         mbuf = rmr_alloc_msg( mrc, MAX_MSG_SZ );                // enough room for us, and provide app with extra room for response
242
243         VERBOSE( "starting session with %s, starting to send\n", target );
244         whid = rmr_wh_open( mrc, target );                                                              // open a wormhole directly to the target
245         if( whid < 0 ) {
246                 fprintf( stderr, "[FAIL] unable to connect to %s\n", target );
247                 exit( 1 );
248         }
249
250         VERBOSE( "connected to %s, sending %d pings\n", target, num2send );
251
252         now = time( NULL );
253         expiry =  now + max_timeout;                                            // when we can end this madness
254
255         while( count < num2send && now < expiry ) {                     // until we've sent and recevied n things, or we expire
256                 if( !mbuf ) {
257                         fprintf( stderr, "[FAIL] internal mishap: mbuf is nil?\n" );
258                         exit( 1 );
259                 }
260
261                 payload = mbuf->payload;
262                 snprintf( payload, PAYLOAD_SZ-1, "health check request prev=%d <eom>", count );
263
264                 mbuf->mtype = RIC_HEALTH_CHECK_REQ;
265                 mbuf->sub_id = -1;
266                 mbuf->len =  PAYLOAD_SZ;                // yes; we're sending more than we filled so xapp has good size for response data if needed
267                 mbuf->state = 0;
268
269                 VERBOSE( "sending message: %s\n", payload );
270                 clock_gettime( CLOCK_MONOTONIC, &out_ts );              // mark time out
271                 mbuf = rmr_wh_send_msg( mrc, whid, mbuf );
272
273                 if( mbuf->state == RMR_OK ) {                                                   // good send, wait for response
274                         do {
275                                 mbuf = rmr_torcv_msg( mrc, mbuf, 250 );                                 // wait for a response, but break often to check for timeout
276                                 clock_gettime( CLOCK_MONOTONIC, &in_ts );                               // mark time in (assuming there is a message)
277                                 now = time( NULL );
278                         } while( mbuf->state == RMR_ERR_TIMEOUT && now < expiry );
279
280                         if( mbuf->state == RMR_OK ) {
281                                 if( mbuf->mtype == RIC_HEALTH_CHECK_RESP ) {
282                                         payload = mbuf->payload;
283                                         memset( wbuf, 0, sizeof( wbuf ) );
284                                         memcpy( wbuf, payload, mbuf->len > sizeof( wbuf ) ? sizeof(wbuf)-1 : mbuf->len );
285                                         VERBOSE( "got: (%s) state=%d\n", wbuf, mbuf->state );
286
287                                         if( strncmp( payload, "OK", 2 ) == 0 ) {
288                                                 good++;
289                                                 et = elapsed( &out_ts, &in_ts );
290                                                 VERBOSE( "good response received; elapsed time = %d mu-sec\n", et );
291                                         } else {
292                                                 fprintf( stderr, "[WARN] xAPP response: %s\n", wbuf );
293                                         }
294                                 } else {
295                                         fprintf( stderr, "[WARN] invalid message type received: %d\n", mbuf->mtype );
296                                 }
297
298                                 count++;
299                         } else {
300                                 fprintf( stderr, "[FAIL] too few messages recevied during timeout window: wanted %d got %d\n", num2send, count );
301                                 break;
302                         }
303                 } else {
304                         fprintf( stderr, "[FAIL] send failed: %d %d %s\n", mbuf->state, errno, strerror( errno ) );
305                         break;
306                 }
307
308                 now = time( NULL );
309         }
310
311         rmr_wh_close( mrc, whid );
312
313         return good == 0;                       // if none were good, then exit 1
314 }
315