2913452ab1066ad95e03ad27f3a54c7e49236c97
[vpp.git] / src / vcl / vcl_test_server.c
1 /*
2  * Copyright (c) 2017-2018 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 <unistd.h>
17 #include <errno.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <time.h>
23 #include <ctype.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <vcl/vcl_test.h>
27 #include <sys/epoll.h>
28 #include <vppinfra/mem.h>
29 #include <pthread.h>
30
31 typedef struct
32 {
33   uint8_t is_alloc;
34   int fd;
35   uint8_t *buf;
36   uint32_t buf_size;
37   sock_test_cfg_t cfg;
38   sock_test_stats_t stats;
39   vppcom_endpt_t endpt;
40   uint8_t ip[16];
41   vppcom_data_segments_t ds;
42 } vcl_test_server_conn_t;
43
44 typedef struct
45 {
46   uint16_t port;
47   uint32_t address_ip6;
48   u8 proto;
49   u8 workers;
50   vppcom_endpt_t endpt;
51 } vcl_test_server_cfg_t;
52
53 #define SOCK_SERVER_MAX_TEST_CONN  16
54 #define SOCK_SERVER_MAX_EPOLL_EVENTS 16
55
56 typedef struct
57 {
58   uint32_t wrk_index;
59   int listen_fd;
60   int epfd;
61   struct epoll_event wait_events[SOCK_SERVER_MAX_EPOLL_EVENTS];
62   size_t conn_pool_size;
63   vcl_test_server_conn_t *conn_pool;
64   int nfds;
65   pthread_t thread_handle;
66 } vcl_test_server_worker_t;
67
68 typedef struct
69 {
70   vcl_test_server_cfg_t cfg;
71   vcl_test_server_worker_t *workers;
72
73   struct sockaddr_storage servaddr;
74   volatile int worker_fails;
75   volatile int active_workers;
76   u8 use_ds;
77 } vcl_test_server_main_t;
78
79 static __thread int __wrk_index = 0;
80
81 static vcl_test_server_main_t sock_server_main;
82
83 static inline void
84 conn_pool_expand (vcl_test_server_worker_t * wrk, size_t expand_size)
85 {
86   vcl_test_server_conn_t *conn_pool;
87   size_t new_size = wrk->conn_pool_size + expand_size;
88   int i;
89
90   conn_pool = realloc (wrk->conn_pool, new_size * sizeof (*wrk->conn_pool));
91   if (conn_pool)
92     {
93       for (i = wrk->conn_pool_size; i < new_size; i++)
94         {
95           vcl_test_server_conn_t *conn = &conn_pool[i];
96           memset (conn, 0, sizeof (*conn));
97           sock_test_cfg_init (&conn->cfg);
98           sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ ,
99                                &conn->buf, &conn->buf_size);
100           conn->cfg.txbuf_size = conn->cfg.rxbuf_size;
101         }
102
103       wrk->conn_pool = conn_pool;
104       wrk->conn_pool_size = new_size;
105     }
106   else
107     {
108       vterr ("conn_pool_expand()", -errno);
109     }
110 }
111
112 static inline vcl_test_server_conn_t *
113 conn_pool_alloc (vcl_test_server_worker_t * wrk)
114 {
115   int i, expand = 0;
116
117 again:
118   for (i = 0; i < wrk->conn_pool_size; i++)
119     {
120       if (!wrk->conn_pool[i].is_alloc)
121         {
122           wrk->conn_pool[i].endpt.ip = wrk->conn_pool[i].ip;
123           wrk->conn_pool[i].is_alloc = 1;
124           return (&wrk->conn_pool[i]);
125         }
126     }
127
128   if (expand == 0)
129     {
130       conn_pool_expand (wrk, 2 * wrk->conn_pool_size);
131       expand = 1;
132       goto again;
133     }
134   vtwrn ("Failed to allocate connection even after expand");
135   return 0;
136 }
137
138 static inline void
139 conn_pool_free (vcl_test_server_conn_t * conn)
140 {
141   conn->fd = 0;
142   conn->is_alloc = 0;
143 }
144
145 static inline void
146 sync_config_and_reply (vcl_test_server_conn_t * conn,
147                        sock_test_cfg_t * rx_cfg)
148 {
149   conn->cfg = *rx_cfg;
150   sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ ,
151                        &conn->buf, &conn->buf_size);
152   conn->cfg.txbuf_size = conn->cfg.rxbuf_size;
153
154   if (conn->cfg.verbose)
155     {
156       vtinf ("(fd %d): Replying to cfg message!\n", conn->fd);
157       sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ );
158     }
159   (void) vcl_test_write (conn->fd, (uint8_t *) & conn->cfg,
160                          sizeof (conn->cfg), NULL, conn->cfg.verbose);
161 }
162
163 static void
164 vts_server_start_stop (vcl_test_server_worker_t * wrk,
165                        vcl_test_server_conn_t * conn,
166                        sock_test_cfg_t * rx_cfg)
167 {
168   u8 is_bi = rx_cfg->test == SOCK_TEST_TYPE_BI;
169   int client_fd = conn->fd, i;
170   vcl_test_server_conn_t *tc;
171   char buf[64];
172
173   if (rx_cfg->ctrl_handle == conn->fd)
174     {
175       clock_gettime (CLOCK_REALTIME, &conn->stats.stop);
176
177       for (i = 0; i < wrk->conn_pool_size; i++)
178         {
179           tc = &wrk->conn_pool[i];
180           if (tc->cfg.ctrl_handle != conn->fd)
181             continue;
182
183           sock_test_stats_accumulate (&conn->stats, &tc->stats);
184
185           if (conn->cfg.verbose)
186             {
187               sprintf (buf, "SERVER (fd %d) RESULTS", tc->fd);
188               sock_test_stats_dump (buf, &tc->stats, 1 /* show_rx */ ,
189                                     is_bi /* show tx */ , conn->cfg.verbose);
190             }
191         }
192
193       sock_test_stats_dump ("SERVER RESULTS", &conn->stats, 1 /* show_rx */ ,
194                             is_bi /* show_tx */ , conn->cfg.verbose);
195       sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ );
196       if (conn->cfg.verbose)
197         {
198           vtinf ("  sock server main\n"
199                  SOCK_TEST_SEPARATOR_STRING
200                  "       buf:  %p\n"
201                  "  buf size:  %u (0x%08x)\n"
202                  SOCK_TEST_SEPARATOR_STRING,
203                  conn->buf, conn->buf_size, conn->buf_size);
204         }
205
206       sync_config_and_reply (conn, rx_cfg);
207       vtinf ("(fd %d): %s-directional Stream Test Complete!\n"
208              SOCK_TEST_BANNER_STRING "\n", conn->fd, is_bi ? "Bi" : "Uni");
209       memset (&conn->stats, 0, sizeof (conn->stats));
210     }
211   else
212     {
213       vtinf (SOCK_TEST_BANNER_STRING "(fd %d): %s-directional Stream Test!\n"
214              "  Sending client the test cfg to start streaming data...\n",
215              client_fd, is_bi ? "Bi" : "Uni");
216
217       if (rx_cfg->ctrl_handle == ~0)
218         rx_cfg->ctrl_handle = conn->fd;
219
220       sync_config_and_reply (conn, rx_cfg);
221
222       /* read the 1st chunk, record start time */
223       memset (&conn->stats, 0, sizeof (conn->stats));
224       clock_gettime (CLOCK_REALTIME, &conn->stats.start);
225     }
226 }
227
228 static inline void
229 vts_server_rx (vcl_test_server_conn_t * conn, int rx_bytes)
230 {
231   vcl_test_server_main_t *vts = &sock_server_main;
232   int client_fd = conn->fd;
233
234   if (conn->cfg.test == SOCK_TEST_TYPE_BI)
235     {
236       if (vts->use_ds)
237         {
238           (void) vcl_test_write (client_fd, conn->ds[0].data, conn->ds[0].len,
239                                  &conn->stats, conn->cfg.verbose);
240           if (conn->ds[1].len)
241             (void) vcl_test_write (client_fd, conn->ds[1].data,
242                                    conn->ds[1].len, &conn->stats,
243                                    conn->cfg.verbose);
244         }
245       else
246         (void) vcl_test_write (client_fd, conn->buf, rx_bytes, &conn->stats,
247                                conn->cfg.verbose);
248     }
249
250   if (vts->use_ds)
251     vppcom_session_free_segments (conn->fd, conn->ds);
252
253   if (conn->stats.rx_bytes >= conn->cfg.total_bytes)
254     clock_gettime (CLOCK_REALTIME, &conn->stats.stop);
255 }
256
257 static void
258 vts_server_echo (vcl_test_server_conn_t * conn, int rx_bytes)
259 {
260   vcl_test_server_main_t *vts = &sock_server_main;
261   int tx_bytes, nbytes, pos;
262
263   if (vts->use_ds)
264     vppcom_data_segment_copy (conn->buf, conn->ds, rx_bytes);
265
266   /* If it looks vaguely like a string, make sure it's terminated */
267   pos = rx_bytes < conn->buf_size ? rx_bytes : conn->buf_size - 1;
268   ((char *) conn->buf)[pos] = 0;
269   vtinf ("(fd %d): RX (%d bytes) - '%s'", conn->fd, rx_bytes, conn->buf);
270
271   if (conn->cfg.verbose)
272     vtinf ("(fd %d): Echoing back", conn->fd);
273
274   nbytes = strlen ((const char *) conn->buf) + 1;
275   tx_bytes = vcl_test_write (conn->fd, conn->buf, nbytes, &conn->stats,
276                              conn->cfg.verbose);
277   if (tx_bytes >= 0)
278     vtinf ("(fd %d): TX (%d bytes) - '%s'", conn->fd, tx_bytes, conn->buf);
279 }
280
281 static inline void
282 vts_new_client (vcl_test_server_worker_t * wrk)
283 {
284   int client_fd;
285   vcl_test_server_conn_t *conn;
286
287   conn = conn_pool_alloc (wrk);
288   if (!conn)
289     {
290       vtwrn ("No free connections!");
291       return;
292     }
293
294   client_fd = vppcom_session_accept (wrk->listen_fd, &conn->endpt, 0);
295   if (client_fd < 0)
296     {
297       vterr ("vppcom_session_accept()", client_fd);
298       return;
299     }
300
301   vtinf ("Got a connection -- fd = %d (0x%08x)!", client_fd, client_fd);
302
303   conn->fd = client_fd;
304
305   {
306     struct epoll_event ev;
307     int rv;
308
309     ev.events = EPOLLIN;
310     ev.data.u64 = conn - wrk->conn_pool;
311     rv = vppcom_epoll_ctl (wrk->epfd, EPOLL_CTL_ADD, client_fd, &ev);
312     if (rv < 0)
313       {
314         vterr ("vppcom_epoll_ctl()", rv);
315         return;
316       }
317     wrk->nfds++;
318   }
319 }
320
321 void
322 print_usage_and_exit (void)
323 {
324   fprintf (stderr,
325            "sock_test_server [OPTIONS] <port>\n"
326            "  OPTIONS\n"
327            "  -h               Print this message and exit.\n"
328            "  -6               Use IPv6\n"
329            "  -w <num>         Number of workers\n"
330            "  -u               Use UDP transport layer\n");
331   exit (1);
332 }
333
334 static void
335 vcl_test_init_endpoint_addr (vcl_test_server_main_t * ssm)
336 {
337   struct sockaddr_storage *servaddr = &ssm->servaddr;
338   memset (servaddr, 0, sizeof (*servaddr));
339
340   if (ssm->cfg.address_ip6)
341     {
342       struct sockaddr_in6 *server_addr = (struct sockaddr_in6 *) servaddr;
343       server_addr->sin6_family = AF_INET6;
344       server_addr->sin6_addr = in6addr_any;
345       server_addr->sin6_port = htons (ssm->cfg.port);
346     }
347   else
348     {
349       struct sockaddr_in *server_addr = (struct sockaddr_in *) servaddr;
350       server_addr->sin_family = AF_INET;
351       server_addr->sin_addr.s_addr = htonl (INADDR_ANY);
352       server_addr->sin_port = htons (ssm->cfg.port);
353     }
354
355   if (ssm->cfg.address_ip6)
356     {
357       struct sockaddr_in6 *server_addr = (struct sockaddr_in6 *) servaddr;
358       ssm->cfg.endpt.is_ip4 = 0;
359       ssm->cfg.endpt.ip = (uint8_t *) & server_addr->sin6_addr;
360       ssm->cfg.endpt.port = (uint16_t) server_addr->sin6_port;
361     }
362   else
363     {
364       struct sockaddr_in *server_addr = (struct sockaddr_in *) servaddr;
365       ssm->cfg.endpt.is_ip4 = 1;
366       ssm->cfg.endpt.ip = (uint8_t *) & server_addr->sin_addr;
367       ssm->cfg.endpt.port = (uint16_t) server_addr->sin_port;
368     }
369 }
370
371 static void
372 vcl_test_server_process_opts (vcl_test_server_main_t * ssm, int argc,
373                               char **argv)
374 {
375   int v, c;
376
377   ssm->cfg.proto = VPPCOM_PROTO_TCP;
378
379   opterr = 0;
380   while ((c = getopt (argc, argv, "6Dsw:")) != -1)
381     switch (c)
382       {
383       case '6':
384         ssm->cfg.address_ip6 = 1;
385         break;
386
387       case 'D':
388         ssm->cfg.proto = VPPCOM_PROTO_UDP;
389         break;
390
391       case 'w':
392         v = atoi (optarg);
393         if (v > 1)
394           ssm->cfg.workers = v;
395         else
396           vtwrn ("Invalid number of workers %d", v);
397         break;
398       case 's':
399         ssm->use_ds = 1;
400         break;
401       case '?':
402         switch (optopt)
403           {
404           default:
405             if (isprint (optopt))
406               vtwrn ("Unknown option `-%c'.", optopt);
407             else
408               vtwrn ("Unknown option character `\\x%x'.", optopt);
409           }
410         /* fall thru */
411       case 'h':
412       default:
413         print_usage_and_exit ();
414       }
415
416   if (argc < (optind + 1))
417     {
418       fprintf (stderr, "SERVER: ERROR: Insufficient number of arguments!\n");
419       print_usage_and_exit ();
420     }
421
422   if (sscanf (argv[optind], "%d", &v) == 1)
423     ssm->cfg.port = (uint16_t) v;
424   else
425     {
426       fprintf (stderr, "SERVER: ERROR: Invalid port (%s)!\n", argv[optind]);
427       print_usage_and_exit ();
428     }
429
430   vcl_test_init_endpoint_addr (ssm);
431 }
432
433 int
434 vts_handle_cfg (vcl_test_server_worker_t * wrk, sock_test_cfg_t * rx_cfg,
435                 vcl_test_server_conn_t * conn, int rx_bytes)
436 {
437   if (rx_cfg->verbose)
438     {
439       vtinf ("(fd %d): Received a cfg msg!", conn->fd);
440       sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
441     }
442
443   if (rx_bytes != sizeof (*rx_cfg))
444     {
445       vtinf ("(fd %d): Invalid cfg msg size %d expected %lu!", conn->fd,
446              rx_bytes, sizeof (*rx_cfg));
447       conn->cfg.rxbuf_size = 0;
448       conn->cfg.num_writes = 0;
449       if (conn->cfg.verbose)
450         {
451           vtinf ("(fd %d): Replying to cfg msg", conn->fd);
452           sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
453         }
454       vcl_test_write (conn->fd, (uint8_t *) & conn->cfg,
455                       sizeof (conn->cfg), NULL, conn->cfg.verbose);
456       return -1;
457     }
458
459   switch (rx_cfg->test)
460     {
461     case SOCK_TEST_TYPE_NONE:
462     case SOCK_TEST_TYPE_ECHO:
463       sync_config_and_reply (conn, rx_cfg);
464       break;
465
466     case SOCK_TEST_TYPE_BI:
467     case SOCK_TEST_TYPE_UNI:
468       vts_server_start_stop (wrk, conn, rx_cfg);
469       break;
470
471     case SOCK_TEST_TYPE_EXIT:
472       vtinf ("Have a great day conn %d (closing)!", conn->fd);
473       vppcom_session_close (conn->fd);
474       conn_pool_free (conn);
475       wrk->nfds--;
476       break;
477
478     default:
479       vtwrn ("Unknown test type %d", rx_cfg->test);
480       sock_test_cfg_dump (rx_cfg, 0 /* is_client */ );
481       break;
482     }
483
484   return 0;
485 }
486
487 static void
488 vts_worker_init (vcl_test_server_worker_t * wrk)
489 {
490   vcl_test_server_main_t *ssm = &sock_server_main;
491   struct epoll_event listen_ev;
492   int rv;
493
494   __wrk_index = wrk->wrk_index;
495
496   vtinf ("Initializing worker ...");
497
498   conn_pool_expand (wrk, SOCK_SERVER_MAX_TEST_CONN + 1);
499   if (wrk->wrk_index)
500     if (vppcom_worker_register ())
501       vtfail ("vppcom_worker_register()", 1);
502
503   wrk->listen_fd = vppcom_session_create (ssm->cfg.proto,
504                                           0 /* is_nonblocking */ );
505   if (wrk->listen_fd < 0)
506     vtfail ("vppcom_session_create()", wrk->listen_fd);
507
508   rv = vppcom_session_bind (wrk->listen_fd, &ssm->cfg.endpt);
509   if (rv < 0)
510     vtfail ("vppcom_session_bind()", rv);
511
512   if (!(ssm->cfg.proto == VPPCOM_PROTO_UDP))
513     {
514       rv = vppcom_session_listen (wrk->listen_fd, 10);
515       if (rv < 0)
516         vtfail ("vppcom_session_listen()", rv);
517     }
518
519   wrk->epfd = vppcom_epoll_create ();
520   if (wrk->epfd < 0)
521     vtfail ("vppcom_epoll_create()", wrk->epfd);
522
523   listen_ev.events = EPOLLIN;
524   listen_ev.data.u32 = ~0;
525   rv = vppcom_epoll_ctl (wrk->epfd, EPOLL_CTL_ADD, wrk->listen_fd,
526                          &listen_ev);
527   if (rv < 0)
528     vtfail ("vppcom_epoll_ctl", rv);
529
530   ssm->active_workers += 1;
531   vtinf ("Waiting for a client to connect on port %d ...", ssm->cfg.port);
532 }
533
534 static int
535 vts_conn_expect_config (vcl_test_server_conn_t * conn)
536 {
537   if (conn->cfg.test == SOCK_TEST_TYPE_ECHO)
538     return 1;
539
540   return (conn->stats.rx_bytes < 128
541           || conn->stats.rx_bytes >= conn->cfg.total_bytes);
542 }
543
544 static sock_test_cfg_t *
545 vts_conn_read_config (vcl_test_server_conn_t * conn)
546 {
547   vcl_test_server_main_t *vts = &sock_server_main;
548
549   if (vts->use_ds)
550     {
551       /* We could avoid the copy if the first segment is big enough but this
552        * just simplifies things */
553       vppcom_data_segment_copy (conn->buf, conn->ds,
554                                 sizeof (sock_test_cfg_t));
555       vppcom_session_free_segments (conn->fd, conn->ds);
556     }
557   return (sock_test_cfg_t *) conn->buf;
558 }
559
560 static inline int
561 vts_conn_read (vcl_test_server_conn_t * conn)
562 {
563   vcl_test_server_main_t *vts = &sock_server_main;
564   if (vts->use_ds)
565     return vcl_test_read_ds (conn->fd, conn->ds, &conn->stats);
566   else
567     return vcl_test_read (conn->fd, conn->buf, conn->buf_size, &conn->stats);
568 }
569
570 static inline int
571 vts_conn_has_ascii (vcl_test_server_conn_t * conn)
572 {
573   vcl_test_server_main_t *vts = &sock_server_main;
574
575   if (vts->use_ds)
576     return isascii (conn->ds[0].data[0]);
577   else
578     return isascii (conn->buf[0]);
579 }
580
581 static void *
582 vts_worker_loop (void *arg)
583 {
584   vcl_test_server_main_t *ssm = &sock_server_main;
585   vcl_test_server_worker_t *wrk = arg;
586   vcl_test_server_conn_t *conn;
587   int i, rx_bytes, num_ev;
588   sock_test_cfg_t *rx_cfg;
589
590   if (wrk->wrk_index)
591     vts_worker_init (wrk);
592
593   while (1)
594     {
595       num_ev = vppcom_epoll_wait (wrk->epfd, wrk->wait_events,
596                                   SOCK_SERVER_MAX_EPOLL_EVENTS, 60000.0);
597       if (num_ev < 0)
598         {
599           vterr ("vppcom_epoll_wait()", num_ev);
600           goto fail;
601         }
602       else if (num_ev == 0)
603         {
604           vtinf ("vppcom_epoll_wait() timeout!");
605           continue;
606         }
607       for (i = 0; i < num_ev; i++)
608         {
609           conn = &wrk->conn_pool[wrk->wait_events[i].data.u32];
610           if (wrk->wait_events[i].events & (EPOLLHUP | EPOLLRDHUP))
611             {
612               vppcom_session_close (conn->fd);
613               wrk->nfds -= 1;
614               if (!wrk->nfds)
615                 {
616                   vtinf ("All client connections closed\n");
617                   goto done;
618                 }
619               continue;
620             }
621           if (wrk->wait_events[i].data.u32 == ~0)
622             {
623               vts_new_client (wrk);
624               continue;
625             }
626
627           if (EPOLLIN & wrk->wait_events[i].events)
628             {
629             read_again:
630               rx_bytes = vts_conn_read (conn);
631
632               if (rx_bytes <= 0)
633                 {
634                   if (errno == ECONNRESET)
635                     {
636                       vtinf ("Connection reset by remote peer.\n");
637                       goto fail;
638                     }
639                   else
640                     continue;
641                 }
642
643               if (vts_conn_expect_config (conn))
644                 {
645                   rx_cfg = vts_conn_read_config (conn);
646                   if (rx_cfg->magic == SOCK_TEST_CFG_CTRL_MAGIC)
647                     {
648                       vts_handle_cfg (wrk, rx_cfg, conn, rx_bytes);
649                       if (!wrk->nfds)
650                         {
651                           vtinf ("All client connections closed\n");
652                           goto done;
653                         }
654                       continue;
655                     }
656                 }
657               if ((conn->cfg.test == SOCK_TEST_TYPE_UNI)
658                   || (conn->cfg.test == SOCK_TEST_TYPE_BI))
659                 {
660                   vts_server_rx (conn, rx_bytes);
661                   if (vppcom_session_attr (conn->fd, VPPCOM_ATTR_GET_NREAD, 0,
662                                            0) > 0)
663                     goto read_again;
664                   continue;
665                 }
666               if (vts_conn_has_ascii (conn))
667                 {
668                   vts_server_echo (conn, rx_bytes);
669                 }
670               else
671                 {
672                   vtwrn ("FIFO not drained! extra bytes %d", rx_bytes);
673                 }
674             }
675           else
676             {
677               vtwrn ("Unhandled event");
678               goto fail;
679             }
680         }
681     }
682
683 fail:
684   ssm->worker_fails -= 1;
685
686 done:
687   vppcom_session_close (wrk->listen_fd);
688   if (wrk->conn_pool)
689     free (wrk->conn_pool);
690   ssm->active_workers -= 1;
691   return 0;
692 }
693
694 int
695 main (int argc, char **argv)
696 {
697   vcl_test_server_main_t *vsm = &sock_server_main;
698   int rv, i;
699
700   clib_mem_init_thread_safe (0, 64 << 20);
701   vsm->cfg.port = SOCK_TEST_SERVER_PORT;
702   vsm->cfg.workers = 1;
703   vsm->active_workers = 0;
704   vcl_test_server_process_opts (vsm, argc, argv);
705
706   rv = vppcom_app_create ("vcl_test_server");
707   if (rv)
708     vtfail ("vppcom_app_create()", rv);
709
710   vsm->workers = calloc (vsm->cfg.workers, sizeof (*vsm->workers));
711   vts_worker_init (&vsm->workers[0]);
712   for (i = 1; i < vsm->cfg.workers; i++)
713     {
714       vsm->workers[i].wrk_index = i;
715       rv = pthread_create (&vsm->workers[i].thread_handle, NULL,
716                            vts_worker_loop, (void *) &vsm->workers[i]);
717     }
718   vts_worker_loop (&vsm->workers[0]);
719
720   while (vsm->active_workers > 0)
721     ;
722
723   vppcom_app_destroy ();
724   free (vsm->workers);
725
726   return vsm->worker_fails;
727 }
728
729 /*
730  * fd.io coding-style-patch-verification: ON
731  *
732  * Local Variables:
733  * eval: (c-set-style "gnu")
734  * End:
735  */