]> www.infradead.org Git - users/sagi/libnvme.git/commitdiff
test: Add initial tests for mi-mctp layer
authorJeremy Kerr <jk@codeconstruct.com.au>
Thu, 16 Jun 2022 06:55:40 +0000 (14:55 +0800)
committerJeremy Kerr <jk@codeconstruct.com.au>
Wed, 29 Jun 2022 02:31:01 +0000 (10:31 +0800)
This change adds a new test module, for the MCTP transport of the MI
layer.

To do this, we hook into the socket, sendmsg and recvmsg calls from the
core mi-mctp code, allowing the test to provide a mock communication
with the kernel MCTP layer.

On top of this, we write a few simple test cases for the socket error
resonses, which we'll extend in future changes.

Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
src/nvme/mi-mctp.c
src/nvme/private.h
test/meson.build
test/mi-mctp.c [new file with mode: 0644]

index e7547ec89ce852ad5a043b736c6dbe75b2bd1e9c..05fe1b8b7caa3e6395c4844559448a47617e11ea 100644 (file)
@@ -80,6 +80,17 @@ struct nvme_mi_transport_mctp {
        int     sd;
 };
 
+static struct __mi_mctp_socket_ops ops = {
+       socket,
+       sendmsg,
+       recvmsg,
+};
+
+void __nvme_mi_mctp_set_ops(const struct __mi_mctp_socket_ops *newops)
+{
+       ops = *newops;
+}
+
 #define MCTP_DBUS_PATH "/xyz/openbmc_project/mctp"
 #define MCTP_DBUS_IFACE "xyz.openbmc_project.MCTP"
 #define MCTP_DBUS_IFACE_ENDPOINT "xyz.openbmc_project.MCTP.Endpoint"
@@ -133,7 +144,7 @@ static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep,
        req_msg.msg_iov = req_iov;
        req_msg.msg_iovlen = i;
 
-       len = sendmsg(mctp->sd, &req_msg, 0);
+       len = ops.sendmsg(mctp->sd, &req_msg, 0);
        if (len < 0) {
                nvme_msg(ep->root, LOG_ERR,
                         "Failure sending MCTP message: %m\n");
@@ -159,7 +170,7 @@ static int nvme_mi_mctp_submit(struct nvme_mi_ep *ep,
        resp_msg.msg_iov = resp_iov;
        resp_msg.msg_iovlen = 2;
 
-       len = recvmsg(mctp->sd, &resp_msg, 0);
+       len = ops.recvmsg(mctp->sd, &resp_msg, 0);
 
        if (len < 0) {
                nvme_msg(ep->root, LOG_ERR,
@@ -242,7 +253,7 @@ nvme_mi_ep_t nvme_mi_open_mctp(nvme_root_t root, unsigned int netid, __u8 eid)
        mctp->net = netid;
        mctp->eid = eid;
 
-       mctp->sd = socket(AF_MCTP, SOCK_DGRAM, 0);
+       mctp->sd = ops.socket(AF_MCTP, SOCK_DGRAM, 0);
        if (mctp->sd < 0)
                goto err_free_ep;
 
index a276d545ee6a4ac0628a99e872122f6f08fc8acb..12215d2a0a2ea17821d425d101cdac4a508d79b7 100644 (file)
@@ -10,6 +10,7 @@
 #define _LIBNVME_PRIVATE_H
 
 #include <ccan/list/list.h>
+#include <sys/socket.h>
 
 #include "fabrics.h"
 #include "mi.h"
@@ -204,4 +205,14 @@ struct nvme_mi_ep *nvme_mi_init_ep(struct nvme_root *root);
 /* for tests, we need to calculate the correct MICs */
 __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
 
+/* we have a facility to mock MCTP socket operations in the mi-mctp transport,
+ * using this ops type. This should only be used for test, and isn't exposed
+ * in the shared lib */;
+struct __mi_mctp_socket_ops {
+       int (*socket)(int, int, int);
+       ssize_t (*sendmsg)(int, const struct msghdr *, int);
+       ssize_t (*recvmsg)(int, struct msghdr *, int);
+};
+void __nvme_mi_mctp_set_ops(const struct __mi_mctp_socket_ops *newops);
+
 #endif /* _LIBNVME_PRIVATE_H */
index 4776bcbcb88c1698a88d202fd071395a66ce827c..00c9ceb2486802bc4d9d19c061e5ed9927e9cda6 100644 (file)
@@ -47,3 +47,12 @@ mi = executable(
 )
 
 test('mi', mi)
+
+mi_mctp = executable(
+    'test-mi-mctp',
+    ['mi-mctp.c', 'utils.c'],
+    dependencies: libnvme_mi_test_dep,
+    include_directories: [incdir, internal_incdir],
+)
+
+test('mi-mctp', mi_mctp)
diff --git a/test/mi-mctp.c b/test/mi-mctp.c
new file mode 100644 (file)
index 0000000..a5c4ec6
--- /dev/null
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * This file is part of libnvme.
+ * Copyright (c) 2022 Code Construct
+ */
+
+#undef NDEBUG
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+
+#include <ccan/array_size/array_size.h>
+#include <ccan/endian/endian.h>
+
+#include "libnvme-mi.h"
+#include "nvme/private.h"
+#include "utils.h"
+
+/* 4096 byte max MCTP message, plus space for header data */
+#define MAX_BUFSIZ 8192
+
+struct test_peer;
+
+typedef int (*rx_test_fn)(struct test_peer *peer, void *buf, size_t len);
+
+/* Our fake MCTP "peer".
+ *
+ * The terms TX (transmit) and RX (receive) are from the perspective of
+ * the NVMe device. TX is device-to-libnvme, RX is libnvme-to-device.
+ *
+ * The RX and TX buffers are linear versions of the data sent and received by
+ * libnvme-mi, and *include* the MCTP message type byte (even though it's
+ * omitted in the sendmsg/recvmsg interface), so that the buffer inspection
+ * in the tests can exactly match the NVMe-MI spec packet diagrams.
+ */
+static struct test_peer {
+       /* rx (sendmsg) data sent from libnvme, and return value */
+       unsigned char   rx_buf[MAX_BUFSIZ];
+       size_t          rx_buf_len;
+       ssize_t         rx_rc; /* if zero, return the sendmsg len */
+       int             rx_errno;
+
+       /* tx (recvmsg) data to be received by libnvme and return value */
+       unsigned char   tx_buf[MAX_BUFSIZ];
+       size_t          tx_buf_len;
+       ssize_t         tx_rc; /* if zero, return the recvmsg len */
+       int             tx_errno;
+
+       /* Optional, called after RX, may set tx_buf according to request.
+        * Return value stored in rx_res, may be used by test */
+       rx_test_fn      rx_fn;
+       void            *rx_data;
+       int             rx_res;
+
+       /* store sd from socket() setup */
+       int             sd;
+} test_peer;
+
+/* ensure tests start from a standard state */
+void reset_test_peer(void)
+{
+       int tmp = test_peer.sd;
+       memset(&test_peer, 0, sizeof(test_peer));
+       test_peer.tx_buf[0] = NVME_MI_MSGTYPE_NVME;
+       test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
+       test_peer.sd = tmp;
+}
+
+/* calculate MIC of peer-to-libnvme data, expand buf by 4 bytes and insert
+ * the new MIC */
+static void test_set_tx_mic(struct test_peer *peer)
+{
+       extern __u32 nvme_mi_crc32_update(__u32 crc, void *data, size_t len);
+       __u32 crc = 0xffffffff;
+
+       assert(peer->tx_buf_len + sizeof(crc) <= MAX_BUFSIZ);
+
+       crc = nvme_mi_crc32_update(crc, peer->tx_buf, peer->tx_buf_len);
+       *(uint32_t *)(peer->tx_buf + peer->tx_buf_len) = cpu_to_le32(~crc);
+       peer->tx_buf_len += sizeof(crc);
+}
+
+int __wrap_socket(int family, int type, int protocol)
+{
+       /* we do an open here to give the mi-mctp code something to close() */
+       test_peer.sd = open("/dev/null", 0);
+       return test_peer.sd;
+}
+
+ssize_t __wrap_sendmsg(int sd, const struct msghdr *hdr, int flags)
+{
+       size_t i, pos;
+
+       assert(sd == test_peer.sd);
+
+       test_peer.rx_buf[0] = NVME_MI_MSGTYPE_NVME;
+
+       /* gather iovec into buf */
+       for (i = 0, pos = 1; i < hdr->msg_iovlen; i++) {
+               struct iovec *iov = &hdr->msg_iov[i];
+
+               assert(pos + iov->iov_len < MAX_BUFSIZ - 1);
+               memcpy(test_peer.rx_buf + pos, iov->iov_base, iov->iov_len);
+               pos += iov->iov_len;
+       }
+
+       test_peer.rx_buf_len = pos;
+
+       if (test_peer.rx_fn)
+               test_peer.rx_res = test_peer.rx_fn(&test_peer,
+                                                  test_peer.rx_buf,
+                                                  test_peer.rx_buf_len);
+
+       errno = test_peer.rx_errno;
+
+       return test_peer.rx_rc ?: (pos - 1);
+}
+
+ssize_t __wrap_recvmsg(int sd, struct msghdr *hdr, int flags)
+{
+       size_t i, pos, len;
+
+       assert(sd == test_peer.sd);
+
+       /* scatter buf into iovec */
+       for (i = 0, pos = 1; i < hdr->msg_iovlen && pos < test_peer.tx_buf_len;
+            i++) {
+               struct iovec *iov = &hdr->msg_iov[i];
+
+               len = iov->iov_len;
+               if (len > test_peer.tx_buf_len - pos)
+                       len = test_peer.tx_buf_len - pos;
+
+               memcpy(iov->iov_base, test_peer.tx_buf + pos, len);
+               pos += len;
+       }
+
+       errno = test_peer.tx_errno;
+
+       return test_peer.tx_rc ?: (pos - 1);
+}
+
+static struct __mi_mctp_socket_ops ops = {
+       __wrap_socket,
+       __wrap_sendmsg,
+       __wrap_recvmsg,
+};
+
+/* tests */
+static void test_rx_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+       struct nvme_mi_read_nvm_ss_info ss_info;
+       int rc;
+
+       peer->rx_rc = -1;
+
+       rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+       assert(rc != 0);
+}
+
+static void test_tx_err(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+       struct nvme_mi_read_nvm_ss_info ss_info;
+       int rc;
+
+       peer->tx_rc = -1;
+
+       rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+       assert(rc != 0);
+}
+
+static void test_tx_short(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+       struct nvme_mi_read_nvm_ss_info ss_info;
+       int rc;
+
+       peer->tx_buf_len = 11;
+
+       rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+       assert(rc != 0);
+}
+
+static void test_read_mi_data(nvme_mi_ep_t ep, struct test_peer *peer)
+{
+       struct nvme_mi_read_nvm_ss_info ss_info;
+       int rc;
+
+       /* empty response data, but with correct MIC */
+       peer->tx_buf_len = 8 + 32;
+       test_set_tx_mic(peer);
+
+       rc = nvme_mi_mi_read_mi_data_subsys(ep, &ss_info);
+       assert(rc == 0);
+}
+
+#define DEFINE_TEST(name) { #name, test_ ## name }
+struct test {
+       const char *name;
+       void (*fn)(nvme_mi_ep_t, struct test_peer *);
+} tests[] = {
+       DEFINE_TEST(rx_err),
+       DEFINE_TEST(tx_err),
+       DEFINE_TEST(tx_short),
+       DEFINE_TEST(read_mi_data),
+};
+
+static void run_test(struct test *test, FILE *logfd, nvme_mi_ep_t ep,
+                    struct test_peer *peer)
+{
+       printf("Running test %s...", test->name);
+       fflush(stdout);
+       test->fn(ep, peer);
+       printf("  OK\n");
+       test_print_log_buf(logfd);
+}
+
+int main(void)
+{
+       nvme_root_t root;
+       nvme_mi_ep_t ep;
+       unsigned int i;
+       FILE *fd;
+
+       fd = test_setup_log();
+
+       __nvme_mi_mctp_set_ops(&ops);
+
+       root = nvme_mi_create_root(fd, DEFAULT_LOGLEVEL);
+       assert(root);
+
+       ep = nvme_mi_open_mctp(root, 0, 0);
+       assert(ep);
+
+       for (i = 0; i < ARRAY_SIZE(tests); i++) {
+               reset_test_peer();
+               run_test(&tests[i], fd, ep, &test_peer);
+       }
+
+       nvme_mi_close(ep);
+       nvme_mi_free_root(root);
+
+       test_close_log(fd);
+
+       return EXIT_SUCCESS;
+}