hsa: add http throughput test server app
[vpp.git] / src / plugins / hs_apps / http_cli.c
1 /*
2 * Copyright (c) 2017-2019 Cisco and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <vnet/session/application.h>
17 #include <vnet/session/application_interface.h>
18 #include <vnet/session/session.h>
19 #include <http/http.h>
20
21 typedef struct
22 {
23   u32 hs_index;
24   u32 thread_index;
25   u64 node_index;
26   u8 *buf;
27 } hcs_cli_args_t;
28
29 typedef struct
30 {
31   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
32   u32 session_index;
33   u32 thread_index;
34   u8 *tx_buf;
35   u32 tx_offset;
36   u32 vpp_session_index;
37 } hcs_session_t;
38
39 typedef struct
40 {
41   hcs_session_t **sessions;
42   u32 *free_http_cli_process_node_indices;
43   u32 app_index;
44
45   /* Cert key pair for tls */
46   u32 ckpair_index;
47
48   u32 prealloc_fifos;
49   u32 private_segment_size;
50   u32 fifo_size;
51   u8 *uri;
52   vlib_main_t *vlib_main;
53 } hcs_main_t;
54
55 static hcs_main_t hcs_main;
56
57 static hcs_session_t *
58 hcs_session_alloc (u32 thread_index)
59 {
60   hcs_main_t *hcm = &hcs_main;
61   hcs_session_t *hs;
62   pool_get (hcm->sessions[thread_index], hs);
63   memset (hs, 0, sizeof (*hs));
64   hs->session_index = hs - hcm->sessions[thread_index];
65   hs->thread_index = thread_index;
66   return hs;
67 }
68
69 static hcs_session_t *
70 hcs_session_get (u32 thread_index, u32 hs_index)
71 {
72   hcs_main_t *hcm = &hcs_main;
73   if (pool_is_free_index (hcm->sessions[thread_index], hs_index))
74     return 0;
75   return pool_elt_at_index (hcm->sessions[thread_index], hs_index);
76 }
77
78 static void
79 hcs_session_free (hcs_session_t *hs)
80 {
81   hcs_main_t *hcm = &hcs_main;
82   u32 thread = hs->thread_index;
83   if (CLIB_DEBUG)
84     memset (hs, 0xfa, sizeof (*hs));
85   pool_put (hcm->sessions[thread], hs);
86 }
87
88 static void
89 hcs_cli_process_free (hcs_cli_args_t *args)
90 {
91   vlib_main_t *vm = vlib_get_first_main ();
92   hcs_main_t *hcm = &hcs_main;
93   hcs_cli_args_t **save_args;
94   vlib_node_runtime_t *rt;
95   vlib_node_t *n;
96   u32 node_index;
97
98   node_index = args->node_index;
99   ASSERT (node_index != 0);
100
101   n = vlib_get_node (vm, node_index);
102   rt = vlib_node_get_runtime (vm, n->index);
103   save_args = vlib_node_get_runtime_data (vm, n->index);
104
105   /* Reset process session pointer */
106   clib_mem_free (*save_args);
107   *save_args = 0;
108
109   /* Turn off the process node */
110   vlib_node_set_state (vm, rt->node_index, VLIB_NODE_STATE_DISABLED);
111
112   /* add node index to the freelist */
113   vec_add1 (hcm->free_http_cli_process_node_indices, node_index);
114 }
115
116 /* Header, including incantation to suppress favicon.ico requests */
117 static const char *html_header_template =
118     "<html><head><title>%v</title></head>"
119     "<link rel=\"icon\" href=\"data:,\">"
120     "<body><pre>";
121
122 static const char *html_footer =
123     "</pre></body></html>\r\n";
124
125 static void
126 hcs_cli_output (uword arg, u8 *buffer, uword buffer_bytes)
127 {
128   u8 **output_vecp = (u8 **) arg;
129   u8 *output_vec;
130   u32 offset;
131
132   output_vec = *output_vecp;
133
134   offset = vec_len (output_vec);
135   vec_validate (output_vec, offset + buffer_bytes - 1);
136   clib_memcpy_fast (output_vec + offset, buffer, buffer_bytes);
137
138   *output_vecp = output_vec;
139 }
140
141 static void
142 start_send_data (hcs_session_t *hs, http_status_code_t status)
143 {
144   http_msg_t msg;
145   session_t *ts;
146   int rv;
147
148   msg.type = HTTP_MSG_REPLY;
149   msg.code = status;
150   msg.content_type = HTTP_CONTENT_TEXT_HTML;
151   msg.data.type = HTTP_MSG_DATA_INLINE;
152   msg.data.len = vec_len (hs->tx_buf);
153
154   ts = session_get (hs->vpp_session_index, hs->thread_index);
155   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
156   ASSERT (rv == sizeof (msg));
157
158   if (!msg.data.len)
159     goto done;
160
161   rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (hs->tx_buf), hs->tx_buf);
162
163   if (rv != vec_len (hs->tx_buf))
164     {
165       hs->tx_offset = rv;
166       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
167     }
168   else
169     {
170       vec_free (hs->tx_buf);
171     }
172
173 done:
174
175   if (svm_fifo_set_event (ts->tx_fifo))
176     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
177 }
178
179 static void
180 send_data_to_http (void *rpc_args)
181 {
182   hcs_cli_args_t *args = (hcs_cli_args_t *) rpc_args;
183   hcs_session_t *hs;
184
185   hs = hcs_session_get (args->thread_index, args->hs_index);
186   if (!hs)
187     {
188       vec_free (args->buf);
189       goto cleanup;
190     }
191
192   hs->tx_buf = args->buf;
193   start_send_data (hs, HTTP_STATUS_OK);
194
195 cleanup:
196
197   clib_mem_free (rpc_args);
198 }
199
200 static uword
201 hcs_cli_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f)
202 {
203   u8 *request = 0, *reply = 0, *html = 0;
204   hcs_cli_args_t *args, *rpc_args;
205   hcs_main_t *hcm = &hcs_main;
206   hcs_cli_args_t **save_args;
207   unformat_input_t input;
208   int i;
209
210   save_args = vlib_node_get_runtime_data (hcm->vlib_main, rt->node_index);
211   args = *save_args;
212
213   request = args->buf;
214
215   /* Replace slashes with spaces, stop at the end of the path */
216   i = 0;
217   while (i < vec_len (request))
218     {
219       if (request[i] == '/')
220         request[i] = ' ';
221       else if (request[i] == ' ')
222         {
223           /* vlib_cli_input is vector-based, no need for a NULL */
224           _vec_len (request) = i;
225           break;
226         }
227       i++;
228     }
229
230   /* Generate the html header */
231   html = format (0, html_header_template, request /* title */ );
232
233   /* Run the command */
234   unformat_init_vector (&input, vec_dup (request));
235   vlib_cli_input (vm, &input, hcs_cli_output, (uword) &reply);
236   unformat_free (&input);
237   request = 0;
238
239   /* Generate the html page */
240   html = format (html, "%v", reply);
241   html = format (html, html_footer);
242
243   /* Send it */
244   rpc_args = clib_mem_alloc (sizeof (*args));
245   clib_memcpy_fast (rpc_args, args, sizeof (*args));
246   rpc_args->buf = html;
247
248   session_send_rpc_evt_to_thread_force (args->thread_index, send_data_to_http,
249                                         rpc_args);
250
251   vec_free (reply);
252   vec_free (args->buf);
253   hcs_cli_process_free (args);
254
255   return (0);
256 }
257
258 static void
259 alloc_cli_process (hcs_cli_args_t *args)
260 {
261   hcs_main_t *hcm = &hcs_main;
262   vlib_main_t *vm = hcm->vlib_main;
263   hcs_cli_args_t **save_args;
264   vlib_node_t *n;
265   char *name;
266   uword l;
267
268   l = vec_len (hcm->free_http_cli_process_node_indices);
269   if (l > 0)
270     {
271       n = vlib_get_node (vm, hcm->free_http_cli_process_node_indices[l - 1]);
272       vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING);
273       _vec_len (hcm->free_http_cli_process_node_indices) = l - 1;
274     }
275   else
276     {
277       static vlib_node_registration_t r = {
278         .function = hcs_cli_process,
279         .type = VLIB_NODE_TYPE_PROCESS,
280         .process_log2_n_stack_bytes = 16,
281         .runtime_data_bytes = sizeof (void *),
282       };
283
284       name = (char *) format (0, "http-cli-%d", l);
285       r.name = name;
286       vlib_register_node (vm, &r);
287       vec_free (name);
288
289       n = vlib_get_node (vm, r.index);
290     }
291
292   /* Save the node index in the args. It won't be zero. */
293   args->node_index = n->index;
294
295   /* Save the args (pointer) in the node runtime */
296   save_args = vlib_node_get_runtime_data (vm, n->index);
297   *save_args = clib_mem_alloc (sizeof (*args));
298   clib_memcpy_fast (*save_args, args, sizeof (*args));
299
300   vlib_start_process (vm, n->runtime_index);
301 }
302
303 static void
304 alloc_cli_process_callback (void *cb_args)
305 {
306   alloc_cli_process ((hcs_cli_args_t *) cb_args);
307 }
308
309 static int
310 hcs_ts_rx_callback (session_t *ts)
311 {
312   hcs_cli_args_t args = {};
313   hcs_session_t *hs;
314   http_msg_t msg;
315   int rv;
316
317   hs = hcs_session_get (ts->thread_index, ts->opaque);
318
319   /* Read the http message header */
320   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
321   ASSERT (rv == sizeof (msg));
322
323   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
324     {
325       hs->tx_buf = 0;
326       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
327       return 0;
328     }
329
330   /* send the command to a new/recycled vlib process */
331   vec_validate (args.buf, msg.data.len - 1);
332   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, args.buf);
333   ASSERT (rv == msg.data.len);
334   vec_set_len (args.buf, rv);
335
336   args.hs_index = hs->session_index;
337   args.thread_index = ts->thread_index;
338
339   /* Send RPC request to main thread */
340   if (vlib_get_thread_index () != 0)
341     vlib_rpc_call_main_thread (alloc_cli_process_callback, (u8 *) &args,
342                                sizeof (args));
343   else
344     alloc_cli_process (&args);
345   return 0;
346 }
347
348 static int
349 hcs_ts_tx_callback (session_t *ts)
350 {
351   hcs_session_t *hs;
352   u32 to_send;
353   int rv;
354
355   hs = hcs_session_get (ts->thread_index, ts->opaque);
356   if (!hs || !hs->tx_buf)
357     return 0;
358
359   to_send = vec_len (hs->tx_buf) - hs->tx_offset;
360   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->tx_buf + hs->tx_offset);
361
362   if (rv <= 0)
363     {
364       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
365       return 0;
366     }
367
368   if (rv < to_send)
369     {
370       hs->tx_offset += rv;
371       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
372     }
373   else
374     {
375       vec_free (hs->tx_buf);
376     }
377
378   if (svm_fifo_set_event (ts->tx_fifo))
379     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
380
381   return 0;
382 }
383
384 static int
385 hcs_ts_accept_callback (session_t *ts)
386 {
387   hcs_session_t *hs;
388
389   hs = hcs_session_alloc (ts->thread_index);
390   hs->vpp_session_index = ts->session_index;
391
392   ts->opaque = hs->session_index;
393   ts->session_state = SESSION_STATE_READY;
394
395   return 0;
396 }
397
398 static int
399 hcs_ts_connected_callback (u32 app_index, u32 api_context, session_t *s,
400                            session_error_t err)
401 {
402   clib_warning ("called...");
403   return -1;
404 }
405
406 static void
407 hcs_ts_disconnect_callback (session_t *s)
408 {
409   hcs_main_t *hcm = &hcs_main;
410   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
411
412   a->handle = session_handle (s);
413   a->app_index = hcm->app_index;
414   vnet_disconnect_session (a);
415 }
416
417 static void
418 hcs_ts_reset_callback (session_t *s)
419 {
420   hcs_main_t *hcm = &hcs_main;
421   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
422
423   a->handle = session_handle (s);
424   a->app_index = hcm->app_index;
425   vnet_disconnect_session (a);
426 }
427
428 static void
429 hcs_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
430 {
431   hcs_session_t *hs;
432
433   if (ntf == SESSION_CLEANUP_TRANSPORT)
434     return;
435
436   hs = hcs_session_get (s->thread_index, s->opaque);
437   if (!hs)
438     return;
439
440   vec_free (hs->tx_buf);
441   hcs_session_free (hs);
442 }
443
444 static int
445 hcs_add_segment_callback (u32 client_index, u64 segment_handle)
446 {
447   return 0;
448 }
449
450 static int
451 hcs_del_segment_callback (u32 client_index, u64 segment_handle)
452 {
453   return 0;
454 }
455
456 static session_cb_vft_t hcs_session_cb_vft = {
457   .session_accept_callback = hcs_ts_accept_callback,
458   .session_disconnect_callback = hcs_ts_disconnect_callback,
459   .session_connected_callback = hcs_ts_connected_callback,
460   .add_segment_callback = hcs_add_segment_callback,
461   .del_segment_callback = hcs_del_segment_callback,
462   .builtin_app_rx_callback = hcs_ts_rx_callback,
463   .builtin_app_tx_callback = hcs_ts_tx_callback,
464   .session_reset_callback = hcs_ts_reset_callback,
465   .session_cleanup_callback = hcs_ts_cleanup_callback,
466 };
467
468 static int
469 hcs_attach ()
470 {
471   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
472   hcs_main_t *hcm = &hcs_main;
473   u64 options[APP_OPTIONS_N_OPTIONS];
474   vnet_app_attach_args_t _a, *a = &_a;
475   u32 segment_size = 128 << 20;
476
477   clib_memset (a, 0, sizeof (*a));
478   clib_memset (options, 0, sizeof (options));
479
480   if (hcm->private_segment_size)
481     segment_size = hcm->private_segment_size;
482
483   a->api_client_index = ~0;
484   a->name = format (0, "http_cli_server");
485   a->session_cb_vft = &hcs_session_cb_vft;
486   a->options = options;
487   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
488   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
489   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
490     hcm->fifo_size ? hcm->fifo_size : 8 << 10;
491   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
492     hcm->fifo_size ? hcm->fifo_size : 32 << 10;
493   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
494   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hcm->prealloc_fifos;
495
496   if (vnet_application_attach (a))
497     {
498       vec_free (a->name);
499       clib_warning ("failed to attach server");
500       return -1;
501     }
502   vec_free (a->name);
503   hcm->app_index = a->app_index;
504
505   clib_memset (ck_pair, 0, sizeof (*ck_pair));
506   ck_pair->cert = (u8 *) test_srv_crt_rsa;
507   ck_pair->key = (u8 *) test_srv_key_rsa;
508   ck_pair->cert_len = test_srv_crt_rsa_len;
509   ck_pair->key_len = test_srv_key_rsa_len;
510   vnet_app_add_cert_key_pair (ck_pair);
511   hcm->ckpair_index = ck_pair->index;
512
513   return 0;
514 }
515
516 static int
517 hcs_transport_needs_crypto (transport_proto_t proto)
518 {
519   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
520          proto == TRANSPORT_PROTO_QUIC;
521 }
522
523 static int
524 hcs_listen ()
525 {
526   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
527   hcs_main_t *hcm = &hcs_main;
528   vnet_listen_args_t _a, *a = &_a;
529   char *uri = "tcp://0.0.0.0/80";
530   u8 need_crypto;
531   int rv;
532
533   clib_memset (a, 0, sizeof (*a));
534   a->app_index = hcm->app_index;
535
536   if (hcm->uri)
537     uri = (char *) hcm->uri;
538
539   if (parse_uri (uri, &sep))
540     return -1;
541
542   need_crypto = hcs_transport_needs_crypto (sep.transport_proto);
543
544   sep.transport_proto = TRANSPORT_PROTO_HTTP;
545   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
546
547   if (need_crypto)
548     {
549       session_endpoint_alloc_ext_cfg (&a->sep_ext,
550                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
551       a->sep_ext.ext_cfg->crypto.ckpair_index = hcm->ckpair_index;
552     }
553
554   rv = vnet_listen (a);
555
556   if (need_crypto)
557     clib_mem_free (a->sep_ext.ext_cfg);
558
559   return rv;
560 }
561
562 static int
563 hcs_create (vlib_main_t *vm)
564 {
565   vlib_thread_main_t *vtm = vlib_get_thread_main ();
566   hcs_main_t *hcm = &hcs_main;
567   u32 num_threads;
568
569   num_threads = 1 /* main thread */  + vtm->n_threads;
570   vec_validate (hcm->sessions, num_threads - 1);
571
572   if (hcs_attach ())
573     {
574       clib_warning ("failed to attach server");
575       return -1;
576     }
577   if (hcs_listen ())
578     {
579       clib_warning ("failed to start listening");
580       return -1;
581     }
582
583   return 0;
584 }
585
586 static clib_error_t *
587 hcs_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
588                        vlib_cli_command_t *cmd)
589 {
590   unformat_input_t _line_input, *line_input = &_line_input;
591   hcs_main_t *hcm = &hcs_main;
592   u64 seg_size;
593   int rv;
594
595   hcm->prealloc_fifos = 0;
596   hcm->private_segment_size = 0;
597   hcm->fifo_size = 0;
598
599   /* Get a line of input. */
600   if (!unformat_user (input, unformat_line_input, line_input))
601     goto start_server;
602
603   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
604     {
605       if (unformat (line_input, "prealloc-fifos %d", &hcm->prealloc_fifos))
606         ;
607       else if (unformat (line_input, "private-segment-size %U",
608                          unformat_memory_size, &seg_size))
609         hcm->private_segment_size = seg_size;
610       else if (unformat (line_input, "fifo-size %d", &hcm->fifo_size))
611         hcm->fifo_size <<= 10;
612       else if (unformat (line_input, "uri %s", &hcm->uri))
613         ;
614       else
615         {
616           unformat_free (line_input);
617           return clib_error_return (0, "unknown input `%U'",
618                                     format_unformat_error, line_input);
619         }
620     }
621
622   unformat_free (line_input);
623
624 start_server:
625
626   if (hcm->app_index != (u32) ~0)
627     return clib_error_return (0, "test http server is already running");
628
629   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
630
631   rv = hcs_create (vm);
632   switch (rv)
633     {
634     case 0:
635       break;
636     default:
637       return clib_error_return (0, "server_create returned %d", rv);
638     }
639
640   return 0;
641 }
642
643 VLIB_CLI_COMMAND (hcs_create_command, static) = {
644   .path = "http cli server",
645   .short_help = "http cli server [uri <uri>] [fifo-size <nbytes>] "
646                 "[private-segment-size <nMG>] [prealloc-fifos <n>]",
647   .function = hcs_create_command_fn,
648 };
649
650 static clib_error_t *
651 hcs_main_init (vlib_main_t *vm)
652 {
653   hcs_main_t *hcs = &hcs_main;
654
655   hcs->app_index = ~0;
656   hcs->vlib_main = vm;
657   return 0;
658 }
659
660 VLIB_INIT_FUNCTION (hcs_main_init);
661
662 /*
663 * fd.io coding-style-patch-verification: ON
664 *
665 * Local Variables:
666 * eval: (c-set-style "gnu")
667 * End:
668 */