vcl: add http support to vcl_test_protos 00/41500/9
authorAritra Basu <[email protected]>
Wed, 28 Aug 2024 21:02:34 +0000 (14:02 -0700)
committerFlorin Coras <[email protected]>
Tue, 17 Sep 2024 15:59:00 +0000 (15:59 +0000)
Type: improvement

Change-Id: Ibb493f1d7713d0e10b8bd1d5ff17b89967b53b8a
Signed-off-by: Aritra Basu <[email protected]>
extras/hs-test/vcl_test.go
src/plugins/hs_apps/vcl/vcl_test.h
src/plugins/hs_apps/vcl/vcl_test_client.c
src/plugins/hs_apps/vcl/vcl_test_protos.c
src/vcl/vppcom.c
src/vcl/vppcom.h
test/asf/test_vcl.py

index 3b413b2..81da0c5 100644 (file)
@@ -1,14 +1,15 @@
 package main
 
 import (
-       . "fd.io/hs-test/infra"
        "fmt"
        "time"
+
+       . "fd.io/hs-test/infra"
 )
 
 func init() {
        RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest,
-               XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest)
+               XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest, VclRetryAttachTest)
 }
 
 func getVclConfig(c *Container, ns_id_optional ...string) string {
@@ -89,7 +90,7 @@ func testVclEcho(s *VethsSuite, proto string) {
 
        srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont))
        srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf")
-       srvAppCont.ExecServer("vcl_test_server " + port)
+       srvAppCont.ExecServer("vcl_test_server -p " + proto + " " + port)
 
        serverVeth := s.GetInterfaceByName(ServerInterfaceName)
        serverVethAddress := serverVeth.Ip4AddressString()
@@ -111,6 +112,10 @@ func VclEchoUdpTest(s *VethsSuite) {
        testVclEcho(s, "udp")
 }
 
+func VclHttpPostTest(s *VethsSuite) {
+       testVclEcho(s, "http")
+}
+
 func VclRetryAttachTest(s *VethsSuite) {
        testRetryAttach(s, "tcp")
 }
index 2b45339..11667fb 100644 (file)
@@ -124,7 +124,7 @@ typedef struct
 
 typedef struct
 {
-  const vcl_test_proto_vft_t *protos[VPPCOM_PROTO_SRTP + 1];
+  const vcl_test_proto_vft_t *protos[VPPCOM_PROTO_HTTP + 1];
   uint32_t ckpair_index;
   hs_test_cfg_t cfg;
   vcl_test_wrk_t *wrk;
index a4a10b5..8bac1f0 100644 (file)
@@ -419,13 +419,8 @@ vtc_worker_run_select (vcl_test_client_worker_t *wrk)
              if (vcm->incremental_stats)
                vtc_inc_stats_check (ts);
            }
-         if ((!check_rx && ts->stats.tx_bytes >= ts->cfg.total_bytes) ||
-             (check_rx && ts->stats.rx_bytes >= ts->cfg.total_bytes))
-           {
-             clock_gettime (CLOCK_REALTIME, &ts->stats.stop);
-             ts->is_done = 1;
-             n_active_sessions--;
-           }
+         if (vtc_session_check_is_done (ts, check_rx))
+           n_active_sessions -= 1;
        }
     }
 
index cd1ac2b..9c81c5f 100644 (file)
  */
 
 #include <hs_apps/vcl/vcl_test.h>
+#include <http/http.h>
+#include <http/http_header_names.h>
+#include <http/http_content_types.h>
+
+typedef enum vcl_test_http_state_
+{
+  VCL_TEST_HTTP_IDLE = 0,
+  VCL_TEST_HTTP_IN_PROGRESS,
+  VCL_TEST_HTTP_COMPLETED,
+} vcl_test_http_state_t;
+
+typedef struct vcl_test_http_ctx_t
+{
+  u8 is_server;
+  vcl_test_http_state_t test_state;
+  u64 rem_data;
+} vcl_test_http_ctx_t;
 
 static int
 vt_tcp_connect (vcl_test_session_t *ts, vppcom_endpt_t *endpt)
@@ -978,6 +995,418 @@ static const vcl_test_proto_vft_t vcl_test_srtp = {
 
 VCL_TEST_REGISTER_PROTO (VPPCOM_PROTO_SRTP, vcl_test_srtp);
 
+static void
+vt_http_session_init (vcl_test_session_t *ts, u8 is_server)
+{
+  vcl_test_http_ctx_t *http_ctx;
+
+  http_ctx = malloc (sizeof (vcl_test_http_ctx_t));
+  memset (http_ctx, 0, sizeof (*http_ctx));
+  http_ctx->is_server = is_server;
+  ts->opaque = http_ctx;
+}
+
+static inline void
+vt_http_send_reply_msg (vcl_test_session_t *ts, http_status_code_t status)
+{
+  http_msg_t msg;
+  int rv = 0;
+
+  memset (&msg, 0, sizeof (http_msg_t));
+  msg.type = HTTP_MSG_REPLY;
+  msg.code = status;
+
+  vppcom_data_segment_t segs[1] = { { (u8 *) &msg, sizeof (msg) } };
+
+  do
+    {
+      rv = vppcom_session_write_segments (ts->fd, segs, 1);
+
+      if (rv < 0)
+       {
+         errno = -rv;
+         if (errno == EAGAIN || errno == EWOULDBLOCK)
+           continue;
+
+         vterr ("vppcom_session_write()", -errno);
+         break;
+       }
+    }
+  while (rv <= 0);
+}
+
+static inline int
+vt_process_http_server_read_msg (vcl_test_session_t *ts, void *buf,
+                                uint32_t nbytes)
+{
+  http_msg_t msg;
+  u8 *target_path = 0;
+  vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque;
+  vcl_test_stats_t *stats = &ts->stats;
+  int rv = 0;
+
+  do
+    {
+      stats->rx_xacts++;
+      rv = vppcom_session_read (ts->fd, buf, nbytes);
+
+      if (rv <= 0)
+       {
+         errno = -rv;
+         if (errno == EAGAIN || errno == EWOULDBLOCK)
+           {
+             stats->rx_eagain++;
+             continue;
+           }
+
+         vterr ("vppcom_session_read()", -errno);
+         return 0;
+       }
+
+      if (PREDICT_TRUE (vcl_test_http_ctx->test_state ==
+                       VCL_TEST_HTTP_IN_PROGRESS))
+       {
+         vcl_test_http_ctx->rem_data -= rv;
+
+         if (vcl_test_http_ctx->rem_data == 0)
+           {
+             vcl_test_http_ctx->test_state = VCL_TEST_HTTP_COMPLETED;
+             vt_http_send_reply_msg (ts, HTTP_STATUS_OK);
+           }
+       }
+      else if (PREDICT_FALSE (vcl_test_http_ctx->test_state ==
+                             VCL_TEST_HTTP_IDLE))
+       {
+         msg = *(http_msg_t *) buf;
+
+         /* verify that we have received http post request from client */
+         if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_POST)
+           {
+             vt_http_send_reply_msg (ts, HTTP_STATUS_METHOD_NOT_ALLOWED);
+             vterr ("error! only POST requests allowed from client", 0);
+             return 0;
+           }
+
+         if (msg.data.target_form != HTTP_TARGET_ORIGIN_FORM)
+           {
+             vt_http_send_reply_msg (ts, HTTP_STATUS_BAD_REQUEST);
+             vterr ("error! http target not in origin form", 0);
+             return 0;
+           }
+
+         /* validate target path syntax */
+         if (msg.data.target_path_len)
+           {
+             vec_validate (target_path, msg.data.target_path_len - 1);
+             memcpy (target_path,
+                     buf + sizeof (msg) + msg.data.target_path_offset - 1,
+                     msg.data.target_path_len + 1);
+             if (http_validate_abs_path_syntax (target_path, 0))
+               {
+                 vt_http_send_reply_msg (ts, HTTP_STATUS_BAD_REQUEST);
+                 vterr ("error! target path is not absolute", 0);
+                 vec_free (target_path);
+                 return 0;
+               }
+             vec_free (target_path);
+           }
+
+         /* read body */
+         if (msg.data.body_len)
+           {
+             vcl_test_http_ctx->rem_data = msg.data.body_len;
+             /* | <http_msg_t> | <target> | <headers> | <body> | */
+             vcl_test_http_ctx->rem_data -=
+               (rv - sizeof (msg) - msg.data.body_offset);
+             vcl_test_http_ctx->test_state = VCL_TEST_HTTP_IN_PROGRESS;
+           }
+       }
+
+      if (rv < nbytes)
+       stats->rx_incomp++;
+    }
+  while (rv <= 0);
+
+  stats->rx_bytes += rv;
+  return (rv);
+}
+
+static inline int
+vt_process_http_client_read_msg (vcl_test_session_t *ts, void *buf,
+                                uint32_t nbytes)
+{
+  http_msg_t msg;
+  int rv = 0;
+
+  do
+    {
+      rv = vppcom_session_read (ts->fd, buf, nbytes);
+
+      if (rv < 0)
+       {
+         errno = -rv;
+         if (errno == EAGAIN || errno == EWOULDBLOCK)
+           continue;
+
+         vterr ("vppcom_session_read()", -errno);
+         break;
+       }
+    }
+  while (!rv);
+
+  msg = *(http_msg_t *) buf;
+
+  if (msg.type == HTTP_MSG_REPLY && msg.code == HTTP_STATUS_OK)
+    vtinf ("received 200 OK from server");
+  else
+    vterr ("received unexpected reply from server", 0);
+
+  return (rv);
+}
+
+static inline int
+vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf,
+                                 uint32_t nbytes)
+{
+  http_msg_t msg;
+  http_header_t *req_headers = 0;
+  u8 *headers_buf = 0;
+  u8 *target;
+  vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque;
+  vcl_test_stats_t *stats = &ts->stats;
+  int rv = 0;
+
+  if (PREDICT_TRUE (vcl_test_http_ctx->test_state ==
+                   VCL_TEST_HTTP_IN_PROGRESS))
+    {
+      do
+       {
+         rv = vppcom_session_write (
+           ts->fd, buf, clib_min (nbytes, vcl_test_http_ctx->rem_data));
+
+         if (rv <= 0)
+           {
+             errno = -rv;
+             if (errno == EAGAIN || errno == EWOULDBLOCK)
+               {
+                 stats->tx_eagain++;
+                 continue;
+               }
+
+             vterr ("vppcom_session_write()", -errno);
+             return 0;
+           }
+
+         vcl_test_http_ctx->rem_data -= rv;
+
+         if (vcl_test_http_ctx->rem_data == 0)
+           {
+             vcl_test_http_ctx->test_state = VCL_TEST_HTTP_COMPLETED;
+             vtinf ("client finished sending %ld bytes of data",
+                    ts->cfg.total_bytes);
+           }
+
+         if (rv < nbytes)
+           stats->tx_incomp++;
+       }
+      while (rv <= 0);
+    }
+
+  else if (PREDICT_FALSE (vcl_test_http_ctx->test_state == VCL_TEST_HTTP_IDLE))
+    {
+      http_add_header (
+       &req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE),
+       http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM));
+      headers_buf = http_serialize_headers (req_headers);
+      vec_free (req_headers);
+
+      memset (&msg, 0, sizeof (http_msg_t));
+      msg.type = HTTP_MSG_REQUEST;
+      msg.method_type = HTTP_REQ_POST;
+
+      /* target */
+      msg.data.target_form = HTTP_TARGET_ORIGIN_FORM;
+      target = (u8 *) "/vcl_test_http\0";
+      msg.data.target_path_len = strlen ((char *) target);
+
+      /* headers */
+      msg.data.headers_offset = msg.data.target_path_len;
+      msg.data.headers_len = vec_len (headers_buf);
+
+      /* body */
+      msg.data.body_offset = msg.data.headers_offset + msg.data.headers_len;
+      msg.data.body_len = ts->cfg.total_bytes;
+
+      msg.data.len =
+       msg.data.target_path_len + msg.data.headers_len + msg.data.body_len;
+      msg.data.type = HTTP_MSG_DATA_INLINE;
+
+      vppcom_data_segment_t segs[3] = { { (u8 *) &msg, sizeof (msg) },
+                                       { target, strlen ((char *) target) },
+                                       { headers_buf,
+                                         vec_len (headers_buf) } };
+
+      do
+       {
+         rv = vppcom_session_write_segments (ts->fd, segs, 3);
+
+         if (rv <= 0)
+           {
+             errno = -rv;
+             if (errno == EAGAIN || errno == EWOULDBLOCK)
+               {
+                 stats->tx_eagain++;
+                 continue;
+               }
+
+             vterr ("vppcom_session_write_segments()", -errno);
+             vec_free (headers_buf);
+             return 0;
+           }
+       }
+      while (rv <= 0);
+
+      vcl_test_http_ctx->test_state = VCL_TEST_HTTP_IN_PROGRESS;
+      vcl_test_http_ctx->rem_data = ts->cfg.total_bytes;
+      vec_free (headers_buf);
+    }
+
+  stats->tx_bytes += rv;
+  return (rv);
+}
+
+static inline int
+vt_process_http_server_write_msg (vcl_test_session_t *ts, void *buf,
+                                 uint32_t nbytes)
+{
+  return 0;
+}
+
+static inline int
+vt_http_read (vcl_test_session_t *ts, void *buf, uint32_t nbytes)
+{
+  vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque;
+
+  if (vcl_test_http_ctx->is_server)
+    return vt_process_http_server_read_msg (ts, buf, nbytes);
+  else
+    return vt_process_http_client_read_msg (ts, buf, nbytes);
+}
+
+static inline int
+vt_http_write (vcl_test_session_t *ts, void *buf, uint32_t nbytes)
+{
+  vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque;
+
+  if (vcl_test_http_ctx->is_server)
+    return vt_process_http_server_write_msg (ts, buf, nbytes);
+  else
+    return vt_process_http_client_write_msg (ts, buf, nbytes);
+}
+
+static int
+vt_http_connect (vcl_test_session_t *ts, vppcom_endpt_t *endpt)
+{
+  uint32_t flags, flen;
+  int rv;
+
+  ts->fd = vppcom_session_create (VPPCOM_PROTO_HTTP, ts->noblk_connect);
+  if (ts->fd < 0)
+    {
+      vterr ("vppcom_session_create()", ts->fd);
+      return ts->fd;
+    }
+
+  rv = vppcom_session_connect (ts->fd, endpt);
+  if (rv < 0 && rv != VPPCOM_EINPROGRESS)
+    {
+      vterr ("vppcom_session_connect()", rv);
+      return rv;
+    }
+
+  ts->read = vt_http_read;
+  ts->write = vt_http_write;
+
+  if (!ts->noblk_connect)
+    {
+      flags = O_NONBLOCK;
+      flen = sizeof (flags);
+      vppcom_session_attr (ts->fd, VPPCOM_ATTR_SET_FLAGS, &flags, &flen);
+      vtinf ("Test session %d (fd %d) connected.", ts->session_index, ts->fd);
+    }
+
+  vt_http_session_init (ts, 0 /* is_server */);
+
+  return 0;
+}
+
+static int
+vt_http_listen (vcl_test_session_t *ts, vppcom_endpt_t *endpt)
+{
+  int rv;
+
+  ts->fd = vppcom_session_create (VPPCOM_PROTO_HTTP, 1 /* is_nonblocking */);
+  if (ts->fd < 0)
+    {
+      vterr ("vppcom_session_create()", ts->fd);
+      return ts->fd;
+    }
+
+  rv = vppcom_session_bind (ts->fd, endpt);
+  if (rv < 0)
+    {
+      vterr ("vppcom_session_bind()", rv);
+      return rv;
+    }
+
+  rv = vppcom_session_listen (ts->fd, 10);
+  if (rv < 0)
+    {
+      vterr ("vppcom_session_listen()", rv);
+      return rv;
+    }
+
+  return 0;
+}
+
+static int
+vt_http_accept (int listen_fd, vcl_test_session_t *ts)
+{
+  int client_fd;
+
+  client_fd = vppcom_session_accept (listen_fd, &ts->endpt, 0);
+  if (client_fd < 0)
+    {
+      vterr ("vppcom_session_accept()", client_fd);
+      return client_fd;
+    }
+
+  ts->fd = client_fd;
+  ts->is_open = 1;
+  ts->read = vt_http_read;
+  ts->write = vt_http_write;
+
+  vt_http_session_init (ts, 1 /* is_server */);
+
+  return 0;
+}
+
+static int
+vt_http_close (vcl_test_session_t *ts)
+{
+  free (ts->opaque);
+  return 0;
+}
+
+static const vcl_test_proto_vft_t vcl_test_http = {
+  .open = vt_http_connect,
+  .listen = vt_http_listen,
+  .accept = vt_http_accept,
+  .close = vt_http_close,
+};
+
+VCL_TEST_REGISTER_PROTO (VPPCOM_PROTO_HTTP, vcl_test_http);
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
index 5408fb7..2ebb1d8 100644 (file)
@@ -1754,6 +1754,10 @@ vppcom_unformat_proto (uint8_t * proto, char *proto_str)
     *proto = VPPCOM_PROTO_SRTP;
   else if (!strcmp (proto_str, "srtp"))
     *proto = VPPCOM_PROTO_SRTP;
+  else if (!strcmp (proto_str, "HTTP"))
+    *proto = VPPCOM_PROTO_HTTP;
+  else if (!strcmp (proto_str, "http"))
+    *proto = VPPCOM_PROTO_HTTP;
   else
     return 1;
   return 0;
@@ -4708,6 +4712,9 @@ vppcom_proto_str (vppcom_proto_t proto)
     case VPPCOM_PROTO_SRTP:
       proto_str = "SRTP";
       break;
+    case VPPCOM_PROTO_HTTP:
+      proto_str = "HTTP";
+      break;
     default:
       proto_str = "UNKNOWN";
       break;
index 3de2934..164dc37 100644 (file)
@@ -58,6 +58,7 @@ typedef enum vppcom_proto_
   VPPCOM_PROTO_QUIC,
   VPPCOM_PROTO_DTLS,
   VPPCOM_PROTO_SRTP,
+  VPPCOM_PROTO_HTTP,
 } vppcom_proto_t;
 
 typedef enum
index 8368a9f..124ea14 100644 (file)
@@ -757,6 +757,59 @@ class VCLThruHostStackQUIC(VCLTestCase):
         self.logger.debug(self.vapi.cli("show app mq"))
 
 
+    "hs_apps" in config.excluded_plugins, "Exclude tests requiring hs_apps plugin"
+)
+class VCLThruHostStackHTTPPost(VCLTestCase):
+    """VCL Thru Host Stack HTTP Post"""
+
+    @classmethod
+    def setUpClass(cls):
+        cls.extra_vpp_plugin_config.append("plugin http_plugin.so { enable }")
+        super(VCLThruHostStackHTTPPost, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(VCLThruHostStackHTTPPost, cls).tearDownClass()
+
+    def setUp(self):
+        super(VCLThruHostStackHTTPPost, self).setUp()
+
+        self.thru_host_stack_setup()
+        self.client_uni_dir_http_post_timeout = 20
+        self.server_http_post_args = ["-p", "http", self.server_port]
+        self.client_uni_dir_http_post_test_args = [
+            "-N",
+            "10000",
+            "-U",
+            "-X",
+            "-p",
+            "http",
+            self.loop0.local_ip4,
+            self.server_port,
+        ]
+
+    def test_vcl_thru_host_stack_http_post_uni_dir(self):
+        """run VCL thru host stack uni-directional HTTP POST test"""
+
+        self.timeout = self.client_uni_dir_http_post_timeout
+        self.thru_host_stack_test(
+            "vcl_test_server",
+            self.server_http_post_args,
+            "vcl_test_client",
+            self.client_uni_dir_http_post_test_args,
+        )
+
+    def tearDown(self):
+        self.thru_host_stack_tear_down()
+        super(VCLThruHostStackHTTPPost, self).tearDown()
+
+    def show_commands_at_teardown(self):
+        self.logger.debug(self.vapi.cli("show app server"))
+        self.logger.debug(self.vapi.cli("show session verbose 2"))
+        self.logger.debug(self.vapi.cli("show app mq"))
+
+
 @unittest.skipIf(
     "hs_apps" in config.excluded_plugins, "Exclude tests requiring hs_apps plugin"
 )