diff --git a/.gitignore b/.gitignore index 3fa2074..b4350b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,30 @@ -SOURCES/sanlock-3.8.4.tar.gz +/sanlock-1.0.tar.gz +/sanlock-1.1.0.tar.bz2 +/sanlock-1.2.0.tar.bz2 +/sanlock-1.3.tar.gz +/sanlock-1.4.tar.gz +/sanlock-1.6.tar.gz +/sanlock-1.8.tar.gz +/sanlock-2.1.tar.gz +/sanlock-2.2.tar.gz +/sanlock-2.3.tar.gz +/sanlock-2.4.tar.gz +/sanlock-2.5.tar.gz +/sanlock-2.6.tar.gz +/sanlock-2.7.tar.gz +/sanlock-2.8.tar.gz +/sanlock-3.0.0.tar.gz +/sanlock-3.1.0.tar.gz +/sanlock-3.2.0.tar.gz +/sanlock-3.2.1.tar.gz +/sanlock-3.2.2.tar.gz +/sanlock-3.2.4.tar.gz +/sanlock-3.3.0.tar.gz +/sanlock-3.4.0.tar.gz +/sanlock-3.5.0.tar.gz +/sanlock-3.6.0.tar.gz +/sanlock-3.8.0.tar.gz +/sanlock-3.8.1.tar.gz +/sanlock-3.8.2.tar.gz +/sanlock-3.8.3.tar.gz /sanlock-3.8.4.tar.gz diff --git a/python-Add-inquire.patch b/python-Add-inquire.patch new file mode 100644 index 0000000..0b86194 --- /dev/null +++ b/python-Add-inquire.patch @@ -0,0 +1,342 @@ +From 2d3e2fceb615a5bd12d26b09fe95668152fb0743 Mon Sep 17 00:00:00 2001 +From: Nir Soffer +Date: Wed, 28 Apr 2021 02:20:28 +0300 +Subject: [PATCH] python: Add inquire() + +Use sanlock_inquire() to query the resource held by the current process +(using the slkfd= argument) or held by another program (using the pid= +argument). + +When using the slkfd= argument, we communicate with sanlock daemon using +slkfd, ensuring that the current process is connected to sanlock. If the +current process is not connected, sanlock assumes that the process is +dead, and release all the leases acquired by the process. + +When using the pid= argument, the function opens a new socket to sanlock +daemon and query the status of resources owned by specified pid. + +In both cases the information comes from sanlock daemon, without +accessing storage. To verify storage content, the caller should use +read_resource() and read_resource_owners(). + +The call returns list of resources dicts that can be used for verifying +that sanlock state matches the program state. + +sanlock_inquire() reports the SANLOCK_RES_LVER or sanlock.RES_SHARED +flags in the resource flags field. Add the field to the returned dict +and add sanlock constants for the flag. + +The resource flags are needed if you want to restore a lease after it +was released, ensuring that nobody else acquired the lease after it was +released. This flow is used by libvirt using libsanlock. With this +change we can implement the same flow using the python binding. + +Signed-off-by: Nir Soffer +--- + python/sanlock.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/python_test.py | 84 +++++++++++++++++++++++++ + 2 files changed, 257 insertions(+) + +diff --git a/python/sanlock.c b/python/sanlock.c +index 67d34fc23a20..c4814640c874 100644 +--- a/python/sanlock.c ++++ b/python/sanlock.c +@@ -323,6 +323,88 @@ exit_fail: + return NULL; + } + ++/* Convert disks array to list of tuples. */ ++static PyObject * ++disks_to_list(struct sanlk_disk *disks, uint32_t disks_count) ++{ ++ PyObject *result = NULL; ++ PyObject *disk = NULL; ++ ++ result = PyList_New(disks_count); ++ if (result == NULL) ++ return NULL; ++ ++ for (uint32_t i = 0; i < disks_count; i++) { ++ disk = Py_BuildValue( ++ "(s,K)", ++ disks[i].path, ++ disks[i].offset); ++ if (disk == NULL) ++ goto exit_fail; ++ ++ /* Steals reference to disk. */ ++ if (PyList_SetItem(result, i, disk) != 0) ++ goto exit_fail; ++ ++ disk = NULL; ++ } ++ ++ return result; ++ ++exit_fail: ++ Py_XDECREF(result); ++ Py_XDECREF(disk); ++ ++ return NULL; ++} ++ ++/* Convert resources array returned from sanlock_inquire() to list of resource ++ * dicts. */ ++static PyObject * ++resources_to_list(struct sanlk_resource **res, int res_count) ++{ ++ PyObject *result = NULL; ++ PyObject *info = NULL; ++ PyObject *disks = NULL; ++ ++ if ((result = PyList_New(res_count)) == NULL) ++ return NULL; ++ ++ for (int i = 0; i < res_count; i++) { ++ disks = disks_to_list(res[i]->disks, res[i]->num_disks); ++ if (disks == NULL) ++ goto exit_fail; ++ ++ /* Steals reference to disks. */ ++ info = Py_BuildValue( ++ "{s:y,s:y,s:k,s:K,s:N}", ++ "lockspace", res[i]->lockspace_name, ++ "resource", res[i]->name, ++ "flags", res[i]->flags, ++ "version", res[i]->lver, ++ "disks", disks); ++ if (info == NULL) ++ goto exit_fail; ++ ++ disks = NULL; ++ ++ /* Steals reference to info. */ ++ if (PyList_SetItem(result, i, info) != 0) ++ goto exit_fail; ++ ++ info = NULL; ++ } ++ ++ return result; ++ ++exit_fail: ++ Py_XDECREF(result); ++ Py_XDECREF(info); ++ Py_XDECREF(disks); ++ ++ return NULL; ++} ++ + /* register */ + PyDoc_STRVAR(pydoc_register, "\ + register() -> int\n\ +@@ -1062,6 +1144,89 @@ finally: + Py_RETURN_NONE; + } + ++/* inquire */ ++PyDoc_STRVAR(pydoc_inquire, "\ ++inquire(slkfd=-1, pid=-1)\n\ ++Return list of resources held by current process (using the slkfd \n\ ++argument to specify the sanlock file descriptor) or for another \n\ ++process (using the pid argument).\n\ ++\n\ ++Does not access storage. To learn about resource state on storage,\n\ ++use sanlock.read_resource() and sanlock.read_resource_owners().\n\ ++\n\ ++Arguments\n\ ++ slkfd (int): The file descriptor returned from sanlock.register().\n\ ++ pid (int): The program pid to query.\n\ ++\n\ ++Returns\n\ ++ List of resource dicts with the following keys:\n\ ++ lockspace (bytes): lockspace name\n\ ++ resource (bytes): resource name\n\ ++ flags (int): resource flags (sanlock.RES_*)\n\ ++ version (int): resource version\n\ ++ disks (list): list of disk tuples (path, offset)\n\ ++"); ++ ++static PyObject * ++py_inquire(PyObject *self __unused, PyObject *args, PyObject *keywds) ++{ ++ int sanlockfd = -1; ++ int pid = -1; ++ char *kwlist[] = {"slkfd", "pid", NULL}; ++ int rv = -1; ++ ++ /* sanlock_inquire() return values. */ ++ int res_count = 0; ++ char *res_state = NULL; ++ ++ /* Array of resoruces parsed from res_state. */ ++ struct sanlk_resource **res_arr = NULL; ++ ++ /* List of resource dicts. */ ++ PyObject *result = NULL; ++ ++ if (!PyArg_ParseTupleAndKeywords( ++ args, keywds, "|ii", kwlist, &sanlockfd, &pid)) { ++ return NULL; ++ } ++ ++ /* Check if any of the slkfd or pid parameters was given. */ ++ if (sanlockfd == -1 && pid == -1) { ++ set_sanlock_error(-EINVAL, "Invalid slkfd and pid values"); ++ return NULL; ++ } ++ ++ /* Inquire sanlock (gil disabled) */ ++ Py_BEGIN_ALLOW_THREADS ++ rv = sanlock_inquire(sanlockfd, pid, 0, &res_count, &res_state); ++ Py_END_ALLOW_THREADS ++ ++ if (rv != 0) { ++ set_sanlock_error(rv, "Inquire error"); ++ return NULL; ++ } ++ ++ if (res_count > 0) { ++ rv = sanlock_state_to_args(res_state, &res_count, &res_arr); ++ if (rv != 0) { ++ /* TODO: Include res_state in the error. */ ++ set_sanlock_error(rv, "Error parsing inquire state string"); ++ goto finally; ++ } ++ } ++ ++ result = resources_to_list(res_arr, res_count); ++ ++finally: ++ free(res_state); ++ ++ for (int i = 0; i < res_count; i++) ++ free(res_arr[i]); ++ free(res_arr); ++ ++ return result; ++} ++ + /* release */ + PyDoc_STRVAR(pydoc_release, "\ + release(lockspace, resource, disks [, slkfd=fd, pid=owner])\n\ +@@ -1752,6 +1917,8 @@ sanlock_methods[] = { + METH_VARARGS|METH_KEYWORDS, pydoc_read_resource_owners}, + {"acquire", (PyCFunction) py_acquire, + METH_VARARGS|METH_KEYWORDS, pydoc_acquire}, ++ {"inquire", (PyCFunction) py_inquire, ++ METH_VARARGS|METH_KEYWORDS, pydoc_inquire}, + {"release", (PyCFunction) py_release, + METH_VARARGS|METH_KEYWORDS, pydoc_release}, + {"request", (PyCFunction) py_request, +@@ -1850,6 +2017,12 @@ module_init(PyObject* m) + if (PyModule_AddIntConstant(m, "SETEV_ALL_HOSTS", SANLK_SETEV_ALL_HOSTS)) + return -1; + ++ /* sanlock_inquire() result resource flags */ ++ if (PyModule_AddIntConstant(m, "RES_LVER", SANLK_RES_LVER)) ++ return -1; ++ if (PyModule_AddIntConstant(m, "RES_SHARED", SANLK_RES_SHARED)) ++ return -1; ++ + /* Tuples with supported sector size and alignment values */ + + PyObject *sector = Py_BuildValue("ii", SECTOR_SIZE_512, SECTOR_SIZE_4K); +diff --git a/tests/python_test.py b/tests/python_test.py +index 58a22c71995c..caf2f3e7594a 100644 +--- a/tests/python_test.py ++++ b/tests/python_test.py +@@ -479,6 +479,90 @@ def test_acquire_release_resource(tmpdir, sanlock_daemon, size, offset): + assert owners == [] + + ++@pytest.mark.parametrize("res_name", [ ++ "ascii", ++ "\u05d0", # Hebrew Alef ++]) ++def test_inquire(tmpdir, sanlock_daemon, res_name): ++ ls_path = str(tmpdir.join("ls_name")) ++ util.create_file(ls_path, MiB) ++ ++ res_path = str(tmpdir.join(res_name)) ++ util.create_file(res_path, 10 * MiB) ++ ++ fd = sanlock.register() ++ ++ # No lockspace yet. ++ assert sanlock.inquire(slkfd=fd) == [] ++ ++ sanlock.write_lockspace(b"ls_name", ls_path, offset=0, iotimeout=1) ++ sanlock.add_lockspace(b"ls_name", 1, ls_path, offset=0, iotimeout=1) ++ ++ # No resources created yet. ++ assert sanlock.inquire(slkfd=fd) == [] ++ ++ resources = [ ++ # name, offset, acquire ++ (b"res-0", 0 * MiB, True), ++ (b"res-1", 1 * MiB, False), ++ (b"res-2", 2 * MiB, True), ++ (b"res-8", 8 * MiB, False), ++ (b"res-9", 9 * MiB, True), ++ ] ++ ++ for res_name, res_offset, acquire in resources: ++ sanlock.write_resource(b"ls_name", res_name, [(res_path, res_offset)]) ++ ++ # No resource acquired yet. ++ assert sanlock.inquire(slkfd=fd) == [] ++ ++ # Acquire resources. ++ for res_name, res_offset, acquire in resources: ++ if acquire: ++ sanlock.acquire( ++ b"ls_name", res_name, [(res_path, res_offset)], slkfd=fd) ++ ++ time.sleep(1) ++ ++ expected = [ ++ { ++ "lockspace": b"ls_name", ++ "resource": b"res-0", ++ "flags": sanlock.RES_LVER, ++ "version": 1, ++ "disks": [(res_path, 0 * MiB)], ++ }, ++ { ++ "lockspace": b"ls_name", ++ "resource": b"res-2", ++ "flags": sanlock.RES_LVER, ++ "version": 1, ++ "disks": [(res_path, 2 * MiB)], ++ }, ++ { ++ "lockspace": b"ls_name", ++ "resource": b"res-9", ++ "flags": sanlock.RES_LVER, ++ "version": 1, ++ "disks": [(res_path, 9 * MiB)], ++ }, ++ ] ++ ++ # Check acquired resources using snlkfd. ++ assert sanlock.inquire(slkfd=fd) == expected ++ ++ # Check acquired resources using pid. ++ assert sanlock.inquire(pid=os.getpid()) == expected ++ ++ for res_name, res_offset, acquire in resources: ++ if acquire: ++ sanlock.release( ++ b"ls_name", res_name, [(res_path, res_offset)], slkfd=fd) ++ ++ # All resource released. ++ assert sanlock.inquire(slkfd=fd) == [] ++ ++ + @pytest.mark.parametrize("align, sector", [ + # Invalid alignment + (KiB, sanlock.SECTOR_SIZE[0]), +-- +2.7.5 + diff --git a/sanlock-do-not-close-connection-in-error-handling.patch b/sanlock-do-not-close-connection-in-error-handling.patch new file mode 100644 index 0000000..ec326a8 --- /dev/null +++ b/sanlock-do-not-close-connection-in-error-handling.patch @@ -0,0 +1,452 @@ +From bb70c220b51720a46a1bdc6b824936fc7269d5d8 Mon Sep 17 00:00:00 2001 +From: David Teigland +Date: Mon, 3 May 2021 12:52:17 -0500 +Subject: [PATCH] sanlock: do not close connection in error handling + +When processing a client connection, a problem with the message +or with the client handling would cause the sanlock daemon to +close the client connection (the socket fd) and release any +leases if the connection was "registered". If this happened, +the client may be unaware of it, and may continue running, +using the leases that have been dropped. This could lead to +different clients on different hosts believing that they hold +the same lease concurrently. + +A known cause of this is when a sanlock client program makes +libsanlock calls on a registered connection concurrently +from multiple threads without serialization. These calls are: +sanlock_acquire, sanlock_release, sanlock_inquire, +sanlock_convert, sanlock_restrict, sanlock_killpath. + +These calls involve: +- sending a header to the sanlock daemon +- sending a body to the sanlock daemon +- receving a reply from the sanlock daemon + +If these steps are interleaved from multiple threads, the +sanlock daemon will read incorrect data when processing +a request, or the wrong result could be received by the caller. + +A specific example that's been seen involves two concurrent +sanlock_release calls from different threads. The proper +sequence would be: + +sanlock_release_1 + send header_1 + send body_1 + recv result_1 +sanlock_release_2 + send header_2 + send body_2 + recv result_2 + +Without locking, data from both requests are interleaved, +causing a sequence to be received in the daemon: + + header_1 + header_2 + body_1 + +The sanlock daemon expects the correct sequence of data, +so it misinterprets the mixed data and reports errors when +it finds unexpected fields in what it thinks are headers +and body structs. + +The sanlock daemon recvs header_1, then recvs header_2, +and thinks header_2 is body_1. When it finds an unknown +resource name in what it thinks is body_1 (really header_2), +it logs an error to sanlock.log: + +cmd_release 19,86,41799 no resource ... + +Then the sanlock dameon recvs data from body_1, and thinks +it is header_2. When it finds an invalid magic number in +header_2 (really body_1), it logs an error to sanlock.log: + +ci 19 recv 32 magic 0 vs 4282010 + +The error path after seeing a bad magic number in a message +header is to call deadfn(). For a registered connection, +this is client_pid_dead() which closes the socket fd for +the client and drops leases held by the client. + +Closing the connection is the wrong way to handle a bad message +because the client is still running, and a closed connection +implies that a registered client has exited. + +The fix is to simply ignore the bad data (logging an error). +By ignoring the messages, the sanlock clients will likely be +stuck waiting for replies, or possibly receive errors from +their calls. So, this fix only prevents leases from being +dropped incorrectly. Clients must still serialize access +to sockets. + +Other error conditions in processing a client connection also +use this same incorrect error handling, and they are also +changed to simply ignore the issue and log an error. +--- + src/main.c | 44 ++++++----- + src/sanlock_resource.h | 3 + + tests/Makefile | 9 ++- + tests/sanlk_mixmsg.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 242 insertions(+), 22 deletions(-) + create mode 100644 tests/sanlk_mixmsg.c + +diff --git a/src/main.c b/src/main.c +index 622dc8e39f2a..026f7dae7d6a 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -1235,33 +1235,33 @@ static void process_connection(int ci) + if (!rv) + goto dead; + +- log_client(ci, client[ci].fd, "recv %d %d", rv, h.cmd); ++ log_client(ci, client[ci].fd, "recv %d %u", rv, h.cmd); + + if (rv < 0) { +- log_error("ci %d fd %d pid %d recv errno %d", +- ci, client[ci].fd, client[ci].pid, errno); +- goto dead; ++ log_error("client connection %d %d %d recv msg header rv %d errno %d", ++ ci, client[ci].fd, client[ci].pid, rv, errno); ++ goto bad; + } + if (rv != sizeof(h)) { +- log_error("ci %d fd %d pid %d recv size %d", +- ci, client[ci].fd, client[ci].pid, rv); +- goto dead; ++ log_error("client connection %d %d %d recv msg header rv %d cmd %u len %u", ++ ci, client[ci].fd, client[ci].pid, rv, h.cmd, h.length); ++ goto bad; + } + if (h.magic != SM_MAGIC) { +- log_error("ci %d recv %d magic %x vs %x", +- ci, rv, h.magic, SM_MAGIC); +- goto dead; ++ log_error("client connection %d %d %d recv msg header rv %d cmd %u len %u magic %x vs %x", ++ ci, client[ci].fd, client[ci].pid, rv, h.cmd, h.length, h.magic, SM_MAGIC); ++ goto bad; + } + if (client[ci].restricted & SANLK_RESTRICT_ALL) { +- log_error("ci %d fd %d pid %d cmd %d restrict all", +- ci, client[ci].fd, client[ci].pid, h.cmd); +- goto dead; ++ log_error("client connection %d %d %d recv msg header rv %d cmd %u len %u restrict all", ++ ci, client[ci].fd, client[ci].pid, rv, h.cmd, h.length); ++ goto bad; + } + if (h.version && (h.cmd != SM_CMD_VERSION) && + (h.version & 0xFFFF0000) > (SM_PROTO & 0xFFFF0000)) { +- log_error("ci %d recv %d proto %x vs %x", +- ci, rv, h.version , SM_PROTO); +- goto dead; ++ log_error("client connection %d %d %d recv msg header rv %d cmd %u len %u version %x", ++ ci, client[ci].fd, client[ci].pid, rv, h.cmd, h.length, h.version); ++ goto bad; + } + + client[ci].cmd_last = h.cmd; +@@ -1306,7 +1306,7 @@ static void process_connection(int ci) + case SM_CMD_DELETE_RESOURCE: + rv = client_suspend(ci); + if (rv < 0) +- goto dead; ++ goto bad; + process_cmd_thread_unregistered(ci, &h); + break; + case SM_CMD_ACQUIRE: +@@ -1318,16 +1318,20 @@ static void process_connection(int ci) + while the thread is working on it */ + rv = client_suspend(ci); + if (rv < 0) +- goto dead; ++ goto bad; + process_cmd_thread_registered(ci, &h); + break; + default: +- log_error("process_connection ci %d fd %d cmd %d unknown", ci, client[ci].fd, h.cmd); +- goto dead; ++ log_error("client connection ci %d fd %d pid %d cmd %d unknown", ++ ci, client[ci].fd, client[ci].pid, h.cmd); ++ goto bad; + }; + + return; + ++ bad: ++ return; ++ + dead: + log_client(ci, client[ci].fd, "recv dead"); + deadfn = client[ci].deadfn; +diff --git a/src/sanlock_resource.h b/src/sanlock_resource.h +index 80178d194b6b..48e448969b3c 100644 +--- a/src/sanlock_resource.h ++++ b/src/sanlock_resource.h +@@ -15,6 +15,9 @@ + * process creates registered connection and acquires/releases leases on + * that connection for itself + * ++ * A threaded sanlock client must serialize libsanlock calls that are ++ * made using a registered socket connection. ++ * + * sock == -1, pid is used: + * process asks daemon to acquire/release leases for another separately + * registered pid +diff --git a/tests/Makefile b/tests/Makefile +index 1e7f7f487915..80123d3dc633 100644 +--- a/tests/Makefile ++++ b/tests/Makefile +@@ -5,6 +5,7 @@ TARGET4 = killpath + TARGET5 = sanlk_path + TARGET6 = sanlk_testr + TARGET7 = sanlk_events ++TARGET8 = sanlk_mixmsg + + SOURCE1 = devcount.c + SOURCE2 = sanlk_load.c +@@ -13,6 +14,7 @@ SOURCE4 = killpath.c + SOURCE5 = sanlk_path.c + SOURCE6 = sanlk_testr.c + SOURCE7 = sanlk_events.c ++SOURCE8 = sanlk_mixmsg.c + + CFLAGS += -D_GNU_SOURCE -g \ + -Wall \ +@@ -36,7 +38,7 @@ CFLAGS += -D_GNU_SOURCE -g \ + + LDFLAGS = -lrt -laio -lblkid -lsanlock + +-all: $(TARGET1) $(TARGET2) $(TARGET3) $(TARGET4) $(TARGET5) $(TARGET6) $(TARGET7) ++all: $(TARGET1) $(TARGET2) $(TARGET3) $(TARGET4) $(TARGET5) $(TARGET6) $(TARGET7) $(TARGET8) + + $(TARGET1): $(SOURCE1) + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ -L. -I../src -L../src +@@ -59,6 +61,9 @@ $(TARGET6): $(SOURCE6) + $(TARGET7): $(SOURCE7) + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ -L. -I../src -L../src + ++$(TARGET8): $(SOURCE8) ++ $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ -L. -I../src -L../src ++ + clean: +- rm -f *.o *.so *.so.* $(TARGET) $(TARGET2) $(TARGET3) $(TARGET4) $(TARGET5) $(TARGET6) $(TARGET7) ++ rm -f *.o *.so *.so.* $(TARGET) $(TARGET2) $(TARGET3) $(TARGET4) $(TARGET5) $(TARGET6) $(TARGET7) $(TARGET8) + +diff --git a/tests/sanlk_mixmsg.c b/tests/sanlk_mixmsg.c +new file mode 100644 +index 000000000000..1b9376e981b6 +--- /dev/null ++++ b/tests/sanlk_mixmsg.c +@@ -0,0 +1,208 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "sanlock.h" ++#include "sanlock_resource.h" ++#include "sanlock_admin.h" ++#include "sanlock_sock.h" ++ ++/* gcc with -lsanlock */ ++ ++/* ++ * sanlock direct init -s 1271384c-24db-4c9b-bebf-61a1916b6cb1:0:/dev/test/main:0 ++ * sanlock add_lockspace -s 1271384c-24db-4c9b-bebf-61a1916b6cb1:1:/dev/test/main:0 ++ */ ++ ++/* copied from client.c */ ++static int send_header(int sock, int cmd, uint32_t cmd_flags, int datalen, ++ uint32_t data, uint32_t data2) ++{ ++ struct sm_header header; ++ int rv; ++ ++ memset(&header, 0, sizeof(header)); ++ header.magic = SM_MAGIC; ++ header.version = SM_PROTO; ++ header.cmd = cmd; ++ header.cmd_flags = cmd_flags; ++ header.length = sizeof(header) + datalen; ++ header.data = data; ++ header.data2 = data2; ++ ++retry: ++ rv = send(sock, (void *) &header, sizeof(header), 0); ++ if (rv == -1 && errno == EINTR) ++ goto retry; ++ ++ if (rv < 0) ++ return -errno; ++ ++ return 0; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ char rd1[sizeof(struct sanlk_resource) + sizeof(struct sanlk_disk)]; ++ char rd2[sizeof(struct sanlk_resource) + sizeof(struct sanlk_disk)]; ++ struct sanlk_resource *res1; ++ struct sanlk_resource *res2; ++ const char *lsname; ++ const char *resname1; ++ const char *resname2; ++ char *path; ++ int fd, rv; ++ ++ if (argc < 2) { ++ printf("%s \n", argv[0]); ++ return -1; ++ } ++ ++ path = argv[1]; ++ ++ lsname = "1271384c-24db-4c9b-bebf-61a1916b6cb1"; ++ resname1 = "2e794e7a-5a9c-4617-8cd0-dc03c917d7a1"; ++ resname2 = "2e794e7a-5a9c-4617-8cd0-dc03c917d7a2"; ++ ++ memset(rd1, 0, sizeof(rd1)); ++ memset(rd2, 0, sizeof(rd2)); ++ ++ res1 = (struct sanlk_resource *)&rd1; ++ res2 = (struct sanlk_resource *)&rd2; ++ ++ strcpy(res1->lockspace_name, lsname); ++ sprintf(res1->name, "%s", resname1); ++ res1->num_disks = 1; ++ strcpy(res1->disks[0].path, path); ++ res1->disks[0].offset = 1048576; ++ ++ strcpy(res2->lockspace_name, lsname); ++ sprintf(res2->name, "%s", resname2); ++ res2->num_disks = 1; ++ strcpy(res2->disks[0].path, path); ++ res2->disks[0].offset = 2 * 1048576; ++ ++ /* ++ struct sanlk_lockspace ls = { 0 }; ++ sprintf(ls.name, lsname); ++ sprintf(ls.host_id_disk.path, path); ++ ++ rv = sanlock_write_lockspace(&ls, 0, 0, 0); ++ if (rv < 0) { ++ printf("write_lockspace error %d\n", rv); ++ return -1; ++ } ++ */ ++ ++ rv = sanlock_write_resource(res1, 0, 0, 0); ++ if (rv < 0) { ++ printf("write_resource1 error %d\n", rv); ++ return -1; ++ } ++ rv = sanlock_write_resource(res2, 0, 0, 0); ++ if (rv < 0) { ++ printf("write_resource2 error %d\n", rv); ++ return -1; ++ } ++ ++ fd = sanlock_register(); ++ if (fd < 0) { ++ printf("register error %d\n", fd); ++ return -1; ++ } ++ ++ printf("acquiring both leases for registered fd %d\n", fd); ++ ++ rv = sanlock_acquire(fd, -1, 0, 1, &res1, NULL); ++ if (rv < 0) { ++ printf("acquire res1 error %d\n", rv); ++ return -1; ++ } ++ ++ rv = sanlock_acquire(fd, -1, 0, 1, &res2, NULL); ++ if (rv < 0) { ++ printf("acquire res2 error %d\n", rv); ++ return -1; ++ } ++ ++ printf("sleeping... check that both leases are held\n"); ++ sleep(20); ++ ++ printf("sending res1 release header only\n"); ++ rv = send_header(fd, SM_CMD_RELEASE, 0, sizeof(struct sanlk_resource), 1, -1); ++ if (rv < 0) ++ printf("send bad header error %d\n", rv); ++ else ++ printf("send bad header ok\n"); ++ ++ printf("sending res2 release interleaved\n"); ++ rv = sanlock_release(fd, -1, 0, 1, &res2); ++ if (rv < 0) ++ printf("odd release res2 error %d\n", rv); ++ else ++ printf("odd release res2 ok\n"); ++ ++ printf("sending res1 release body only\n"); ++ rv = send(fd, res1, sizeof(struct sanlk_resource), 0); ++ if (rv < 0) ++ printf("send bad body error %d\n", rv); ++ else ++ printf("send bad body ok\n"); ++ ++ /* ++ * This is not simulating the recv() that each sanlock_release ++ * would do in libsanlock to get a result for each release. ++ * These would likely just cause the client block indefinitely ++ * waiting for a reply that won't come because the bad release ++ * calls were ignored. ++ */ ++ ++ printf("sleeping... check which leases are held\n"); ++ sleep(20); ++ ++ printf("releasing both leases normally\n"); ++ rv = sanlock_release(fd, -1, 0, 1, &res1); ++ if (rv < 0) ++ printf("release res1 error %d\n", rv); ++ else ++ printf("release res1 ok\n"); ++ ++ rv = sanlock_release(fd, -1, 0, 1, &res2); ++ if (rv < 0) ++ printf("release res2 error %d\n", rv); ++ else ++ printf("release res2 ok\n"); ++ ++ printf("sleeping... check that both leases are released\n"); ++ sleep(20); ++ ++ printf("acquiring lease res1\n"); ++ rv = sanlock_acquire(fd, -1, 0, 1, &res1, NULL); ++ if (rv < 0) ++ printf("acquire res1 error %d\n", rv); ++ else ++ printf("acquire res1 ok\n"); ++ ++ /* exit should close our registered connection and ++ automatically release res1 */ ++ ++ printf("exiting... check if held lease is released after exit\n"); ++ ++ return 0; ++} ++ +-- +2.7.5 + diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh new file mode 100755 index 0000000..a0b4711 --- /dev/null +++ b/tests/scripts/run_tests.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +sanlock daemon -w 0 + +gcc sanlk_load.c -lrt -laio -lblkid -lsanlock -o sanlk_load + +dd if=/dev/zero of=loopfile0 bs=1M count=10 oflag=direct +dd if=/dev/zero of=loopfile1 bs=1M count=10 oflag=direct + +losetup /dev/loop0 loopfile0 +losetup /dev/loop1 loopfile1 + +./sanlk_load init /dev/loop 2 8 + +./sanlk_load rand /dev/loop -s 2 -r 8 -S 30 -e 1 -i 1 + +[ $? -ne 0 ] && echo "sanlk_load error" >&2 && exit 1 + +sanlock shutdown -f 1 + +losetup -d /dev/loop0 +losetup -d /dev/loop1 + +rm loopfile0 +rm loopfile1 + diff --git a/tests/scripts/sanlk_load.c b/tests/scripts/sanlk_load.c new file mode 100644 index 0000000..926ccd2 --- /dev/null +++ b/tests/scripts/sanlk_load.c @@ -0,0 +1,1111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sanlock.h" +#include "sanlock_admin.h" +#include "sanlock_resource.h" +#include "sanlock_direct.h" + +#define ONEMB 1048576 +#define LEASE_SIZE ONEMB + +#define MAX_LS_COUNT 64 +#define MAX_RES_COUNT 512 +#define MAX_PID_COUNT 256 +#define DEFAULT_LS_COUNT 4 +#define DEFAULT_RES_COUNT 4 +#define DEFAULT_PID_COUNT 4 +#define MAX_RV 300 + +#define IV -1 +#define UN 0 +#define SH 3 +#define EX 5 + +int prog_stop; +int debug = 0; +int debug_verbose = 0; +char error_buf[4096]; +char lock_disk_base[PATH_MAX]; +int lock_state[MAX_LS_COUNT][MAX_RES_COUNT]; +int ls_count = DEFAULT_LS_COUNT; +int res_count = DEFAULT_RES_COUNT; +int pid_count = DEFAULT_PID_COUNT; +int one_mode = 0; +int our_hostid; +int run_sec; +int count_sec; +int error_count; +int error_range = 1; +int acquire_rv[MAX_RV]; +int release_rv[MAX_RV]; + + +#define log_debug(fmt, args...) \ +do { \ + if (debug) printf("%lu " fmt "\n", time(NULL), ##args); \ +} while (0) + +#define log_error(fmt, args...) \ +do { \ + memset(error_buf, 0, sizeof(error_buf)); \ + snprintf(error_buf, 4095, "%ld " fmt "\n", time(NULL), ##args); \ + printf("ERROR: %s\n", error_buf); \ + syslog(LOG_ERR, "%s", error_buf); \ + error_count++; \ +} while (0) + + +static void sigterm_handler(int sig) +{ + if (sig == SIGTERM) + prog_stop = 1; +} + +static int get_rand(int a, int b) +{ + return a + (int) (((float)(b - a + 1)) * random() / (RAND_MAX+1.0)); +} + +static int get_rand_sh_ex(void) +{ + unsigned int n; + + if (one_mode == SH) + return SH; + if (one_mode == EX) + return EX; + + n = (unsigned int)random();; + + if (n % 2) + return SH; + return EX; +} + +static void save_rv(int pid, int rv, int acquire) +{ + if (rv > 0) + goto fail; + if (-rv > MAX_RV) + goto fail; + + if (acquire) { + if (!rv) + acquire_rv[0]++; + else + acquire_rv[-rv]++; + } else { + if (!rv) + release_rv[0]++; + else + release_rv[-rv]++; + } + + if (error_range == 1) { + switch (rv) { + case 0: + case -EBUSY: + /* -16 */ + case -EEXIST: + /* -17 */ + case -EAGAIN: + /* -11 */ + break; + default: + log_error("%d ERROR range %d save_rv %d %d", pid, error_range, rv, acquire); + break; + }; + + } else if (error_range == 2) { + switch (rv) { + case 0: + case -EBUSY: + /* -16 */ + case -EEXIST: + /* -17 */ + case -EAGAIN: + /* -11 */ + break; + case -243: + case -244: + case -245: + break; + default: + log_error("%d ERROR range %d save_rv %d %d", pid, error_range, rv, acquire); + break; + }; + } + + return; + + fail: + log_error("%d save_rv %d %d", pid, rv, acquire); + printf("%lu %d ERROR save_rv %d %d", time(NULL), pid, rv, acquire); +} + +static void display_rv(int pid) +{ + int i; + + printf("%lu %d results acquire ", time(NULL), pid); + for (i = 0; i < MAX_RV; i++) { + if (acquire_rv[i]) + printf("%d:%d ", i, acquire_rv[i]); + } + + printf("release "); + for (i = 0; i < MAX_RV; i++) { + if (release_rv[i]) + printf("%d:%d ", i, release_rv[i]); + } + printf("\n"); +} + +static void dump_lock_state(int pid) +{ + int i, j; + + for (i = 0; i < ls_count; i++) { + for (j = 0; j < res_count; j++) { + if (!lock_state[i][j]) + continue; + log_error("%d lockspace%d:resource%d", pid, i, j); + } + } +} + +static void dump_inquire_state(int pid, char *state) +{ + char *p = state; + int len = strlen(state); + int i; + + if (!len) + return; + + for (i = 0; i < len; i++) { + if (state[i] == ' ') { + state[i] = '\0'; + if (!i) + log_debug("%d leading space", pid); + else + log_debug("%d %s", pid, p); + p = state + i + 1; + } + } + log_debug("%d %s", pid, p); +} + +static int check_lock_state(int pid, int result, int count, char *res_state) +{ + char buf[128]; + char *found = NULL; + int found_count = 0; + int none_count = 0; + int bad_count = 0; + int i, j; + + memset(buf, 0, sizeof(buf)); + + if (result < 0) + goto fail; + + if (!count) { + if (res_state) { + log_error("%d check_lock_state zero count res_state %s", + pid, res_state); + } + for (i = 0; i < ls_count; i++) { + for (j = 0; j < res_count; j++) { + if (lock_state[i][j]) { + bad_count++; + log_error("%d check_lock_state zero count %d %d lock", pid, i, j); + } + } + } + + if (bad_count) + goto fail; + return 0; + } + + for (i = 0; i < ls_count; i++) { + for (j = 0; j < res_count; j++) { + memset(buf, 0, sizeof(buf)); + sprintf(buf, "lockspace%d:resource%d:", i, j); + + found = strstr(res_state, buf); + + if (found && lock_state[i][j]) { + found_count++; + } else if (!found && !lock_state[i][j]) { + none_count++; + } else { + bad_count++; + log_error("%d check_lock_state %s lock_state %d res_state %s", + pid, buf, lock_state[i][j], res_state); + } + } + } + + if ((found_count != count) || bad_count) + goto fail; + + return 0; + + fail: + log_error("%d check_lock_state result %d count %d res_state %s", + pid, result, count, res_state); + + log_error("%d check_lock_state found %d none %d bad %d", + pid, found_count, none_count, bad_count); + + dump_lock_state(pid); + + printf("%lu %d ERROR check_lock_state result %d count %d found %d bad %d res_state %s", + time(NULL), pid, result, count, found_count, bad_count, res_state); + + return -1; +} + +#if 0 +static int remove_lockspace(int i) +{ + struct sanlk_lockspace ls; + int rv; + + memset(&ls, 0, sizeof(ls)); + sprintf(ls.host_id_disk.path, "%s%d", lock_disk_base, i); + sprintf(ls.name, "lockspace%d", i); + ls.host_id = our_hostid; + + printf("rem lockspace%d...\n", i); + + rv = sanlock_rem_lockspace(&ls, 0); + if (rv < 0) { + log_error("sanlock_rem_lockspace error %d %s", rv, + ls.host_id_disk.path); + return -1; + } + + printf("rem done\n"); + return 0; +} +#endif + +static int add_lockspace(int i) +{ + struct sanlk_lockspace ls; + int rv; + int async = !(i % 2); + uint32_t flags = 0; + + memset(&ls, 0, sizeof(ls)); + sprintf(ls.host_id_disk.path, "%s%d", lock_disk_base, i); + sprintf(ls.name, "lockspace%d", i); + ls.host_id = our_hostid; + + if (async) + flags = SANLK_ADD_ASYNC; + + printf("add lockspace%d...\n", i); + + rv = sanlock_add_lockspace(&ls, flags); + if (rv == -EEXIST) + return 0; + + if (rv < 0) { + log_error("sanlock_add_lockspace error %d %s", rv, + ls.host_id_disk.path); + return -1; + } + + if (!async) + goto out; + + while (1) { + rv = sanlock_inq_lockspace(&ls, 0); + if (!rv) + goto out; + + if (rv == -EINPROGRESS) { + sleep(2); + continue; + } + + log_error("sanlock_inq_lockspace error %d", rv); + return -1; + } + + out: + printf("add done\n"); + return 0; +} + +static int add_lockspaces(void) +{ + int i, rv; + + for (i = 0; i < ls_count; i++) { + rv = add_lockspace(i); + if (rv < 0) + return rv; + } + return 0; +} + +static const char *mode_str(int n) +{ + if (n == SH) + return "sh"; + if (n == EX) + return "ex"; + if (n == UN) + return "un"; + if (n == IV) + return "iv"; + return "er"; +} + +static int do_one(int pid, int fd, int _s1, int _r1, int _n1, int *full) +{ + char buf1[sizeof(struct sanlk_resource) + sizeof(struct sanlk_disk)]; + struct sanlk_resource *r1; + int acquire = (_n1 != UN); + int rv; + + memset(buf1, 0, sizeof(buf1)); + r1 = (struct sanlk_resource *)&buf1; + + sprintf(r1->lockspace_name, "lockspace%d", _s1); + sprintf(r1->name, "resource%d", _r1); + sprintf(r1->disks[0].path, "%s%d", lock_disk_base, _s1); + r1->disks[0].offset = (_r1+1)*LEASE_SIZE; + r1->num_disks = 1; + if (_n1 == SH) + r1->flags |= SANLK_RES_SHARED; + + if (acquire) { + rv = sanlock_acquire(fd, -1, 0, 1, &r1, NULL); + + if (rv == -E2BIG || rv == -ENOENT) + *full = 1; + } else { + rv = sanlock_release(fd, -1, 0, 1, &r1); + } + + log_debug("%d %s %d,%d %s = %d", + pid, + acquire ? "acquire" : "release", + _s1, _r1, mode_str(_n1), + rv); + + save_rv(pid, rv, acquire); + + return rv; +} + +static int do_two(int pid, int fd, int _s1, int _r1, int _n1, int _s2, int _r2, int _n2, int *full) +{ + char buf1[sizeof(struct sanlk_resource) + sizeof(struct sanlk_disk)]; + char buf2[sizeof(struct sanlk_resource) + sizeof(struct sanlk_disk)]; + struct sanlk_resource *r1; + struct sanlk_resource *r2; + struct sanlk_resource **res_args; + int acquire = (_n1 != UN); + int rv; + + res_args = malloc(2 * sizeof(struct sanlk_resource *)); + if (!res_args) + return -ENOMEM; + + memset(buf1, 0, sizeof(buf1)); + memset(buf2, 0, sizeof(buf2)); + r1 = (struct sanlk_resource *)&buf1; + r2 = (struct sanlk_resource *)&buf2; + res_args[0] = r1; + res_args[1] = r2; + + sprintf(r1->lockspace_name, "lockspace%d", _s1); + sprintf(r1->name, "resource%d", _r1); + sprintf(r1->disks[0].path, "%s%d", lock_disk_base, _s1); + r1->disks[0].offset = (_r1+1)*LEASE_SIZE; + r1->num_disks = 1; + if (_n1 == SH) + r1->flags |= SANLK_RES_SHARED; + + sprintf(r2->lockspace_name, "lockspace%d", _s2); + sprintf(r2->name, "resource%d", _r2); + sprintf(r2->disks[0].path, "%s%d", lock_disk_base, _s2); + r2->disks[0].offset = (_r2+1)*LEASE_SIZE; + r2->num_disks = 1; + if (_n2 == SH) + r2->flags |= SANLK_RES_SHARED; + + if (acquire) { + rv = sanlock_acquire(fd, -1, 0, 2, res_args, NULL); + + if (rv == -E2BIG || rv == -ENOENT) + *full = 1; + } else { + rv = sanlock_release(fd, -1, 0, 2, res_args); + } + + log_debug("%d %s %d,%d %s %d,%d %s = %d", + pid, + acquire ? "acquire" : "release", + _s1, _r1, mode_str(_n1), + _s2, _r2, mode_str(_n2), + rv); + + save_rv(pid, rv, acquire); + + free(res_args); + return rv; +} + +static int acquire_one(int pid, int fd, int s1, int r1, int n1, int *full) +{ + return do_one(pid, fd, s1, r1, n1, full); +} + +static int acquire_two(int pid, int fd, int s1, int r1, int n1, int s2, int r2, int n2, int *full) +{ + return do_two(pid, fd, s1, r1, n1, s2, r2, n2, full); +} + +static int release_one(int pid, int fd, int s1, int r1) +{ + return do_one(pid, fd, s1, r1, UN, NULL); +} + +static int release_two(int pid, int fd, int s1, int r1, int s2, int r2) +{ + return do_two(pid, fd, s1, r1, UN, s2, r2, UN, NULL); +} + +static int release_all(int pid, int fd) +{ + int rv; + + rv = sanlock_release(fd, -1, SANLK_REL_ALL, 0, NULL); + + log_debug("%d release all = %d", pid, rv); + + save_rv(pid, rv, 0); + + return rv; +} + +static void inquire_all(int pid, int fd) +{ + int rv, count = 0; + char *state = NULL; + + if (prog_stop) + return; + + rv = sanlock_inquire(fd, -1, 0, &count, &state); + + log_debug("%d inquire all = %d %d", pid, rv, count); + + if (prog_stop) + return; + + check_lock_state(pid, rv, count, state); + + if (count && debug_verbose) + dump_inquire_state(pid, state); + + if (state) + free(state); +} + +int do_rand_child(void) +{ + int s1, s2, r1, r2, m1, m2, n1, n2, full; + int fd, rv; + int iter = 1; + int pid = getpid(); + + error_count = 0; + + srandom(pid); + + memset(lock_state, 0, sizeof(lock_state)); + + fd = sanlock_register(); + if (fd < 0) { + log_error("%d sanlock_register error %d", pid, fd); + exit(-1); + } + + while (!prog_stop) { + s1 = get_rand(0, ls_count-1); + r1 = get_rand(0, res_count-1); + m1 = lock_state[s1][r1]; + + s2 = -1; + r2 = -1; + m2 = IV; + + if (get_rand(1, 3) == 2) { + s2 = get_rand(0, ls_count-1); + r2 = get_rand(0, res_count-1); + m2 = lock_state[s2][r2]; + + if (s1 == s2 && r1 == r2) { + s2 = -1; + r2 = -1; + m2 = IV; + } + } + + full = 0; + + if (m1 == UN && m2 == UN) { + /* both picks are unlocked, lock both together */ + + n1 = get_rand_sh_ex(); + n2 = get_rand_sh_ex(); + + rv = acquire_two(pid, fd, s1, r1, n1, s2, r2, n2, &full); + if (!rv) { + lock_state[s1][r1] = n1; + lock_state[s2][r2] = n2; + } + + m1 = IV; + m2 = IV; + } + if (m1 > UN && m2 > UN) { + /* both picks are locked, unlock both together */ + + release_two(pid, fd, s1, r1, s2, r2); + lock_state[s1][r1] = UN; + lock_state[s2][r2] = UN; + + m1 = IV; + m2 = IV; + } + if (m1 == UN) { + n1 = get_rand_sh_ex(); + + rv = acquire_one(pid, fd, s1, r1, n1, &full); + if (!rv) + lock_state[s1][r1] = n1; + } + if (m2 == UN) { + n2 = get_rand_sh_ex(); + + rv = acquire_one(pid, fd, s2, r2, n2, &full); + if (!rv) + lock_state[s2][r2] = n2; + } + if (m1 > UN) { + release_one(pid, fd, s1, r1); + lock_state[s1][r1] = UN; + } + if (m2 > UN) { + release_one(pid, fd, s2, r2); + lock_state[s2][r2] = UN; + } + if (full) { + release_all(pid, fd); + memset(lock_state, 0, sizeof(lock_state)); + } + if ((iter % 10) == 0) { + display_rv(pid); + inquire_all(pid, fd); + } + iter++; + } + display_rv(pid); + + if (error_count) { + printf("pid %d done error_count %d\n", pid, error_count); + exit(EXIT_FAILURE); + } + + printf("pid %d done\n", pid); + exit(EXIT_SUCCESS); +} + +int do_all_child(void) +{ + int sx, rx, full; + int fd, rv; + int pid = getpid(); + + srandom(pid); + + memset(lock_state, 0, sizeof(lock_state)); + + fd = sanlock_register(); + if (fd < 0) { + log_error("%d sanlock_register error %d", pid, fd); + exit(-1); + } + + while (!prog_stop) { + for (sx = 0; sx < ls_count; sx++) { + for (rx = 0; rx < res_count; rx++) { + rv = acquire_one(pid, fd, sx, rx, EX, &full); + if (!rv) + lock_state[sx][rx] = EX; + } + + inquire_all(pid, fd); + + for (rx = 0; rx < res_count-1; rx++) { + rv = release_one(pid, fd, sx, rx); + lock_state[sx][rx] = UN; + } + + inquire_all(pid, fd); + } + } + + display_rv(pid); + return 0; +} + +/* + * sanlk_load rand -i [-D -s -r -p ] + */ + +void get_options(int argc, char *argv[]) +{ + char optchar; + char *optionarg; + char *p; + int i = 3; + + for (; i < argc; ) { + p = argv[i]; + + if ((p[0] != '-') || (strlen(p) != 2)) { + log_error("unknown option %s", p); + log_error("space required before option value"); + exit(EXIT_FAILURE); + } + + optchar = p[1]; + i++; + + if (optchar == 'D') { + debug = 1; + continue; + } + + if (optchar == 'V') { + debug_verbose = 1; + continue; + } + + if (i >= argc) { + log_error("option '%c' requires arg", optchar); + exit(EXIT_FAILURE); + } + + optionarg = argv[i]; + + switch (optchar) { + case 'i': + our_hostid = atoi(optionarg); + break; + case 'S': + run_sec = atoi(optionarg); + break; + case 's': + ls_count = atoi(optionarg); + if (ls_count > MAX_LS_COUNT) { + log_error("max ls_count %d", MAX_LS_COUNT); + exit(-1); + } + break; + case 'r': + res_count = atoi(optionarg); + if (res_count > MAX_RES_COUNT) { + log_error("max res_count %d", MAX_RES_COUNT); + exit(-1); + } + break; + case 'p': + pid_count = atoi(optionarg); + if (pid_count > MAX_PID_COUNT) { + log_error("max pid_count %d", MAX_PID_COUNT); + exit(-1); + } + break; + case 'm': + one_mode = atoi(optionarg); + break; + case 'e': + error_range = atoi(optionarg); + break; + default: + log_error("unknown option: %c", optchar); + exit(EXIT_FAILURE); + } + + i++; + } +} + +int find_pid(int *kids, int pid) +{ + int i; + + for (i = 0; i < pid_count; i++) { + if (kids[i] == pid) + return i; + } + return -1; +} + +int do_rand(int argc, char *argv[]) +{ + struct sigaction act; + int children[MAX_PID_COUNT]; + int run_count = 0; + int i, rv, pid, status; + + if (argc < 5) + return -1; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sigterm_handler; + sigaction(SIGTERM, &act, NULL); + + strcpy(lock_disk_base, argv[2]); + + get_options(argc, argv); + + rv = add_lockspaces(); + if (rv < 0) + return rv; + + printf("forking %d pids\n", pid_count); + + for (i = 0; i < pid_count; i++) { + pid = fork(); + + if (pid < 0) { + log_error("fork %d failed %d run_count %d", i, errno, run_count); + break; + } + if (!pid) { + do_rand_child(); + exit(-1); + } + children[i] = pid; + run_count++; + } + + printf("children running\n"); + + while (!prog_stop) { +#if 0 + /* + * kill and replace a random pid + */ + + sleep(get_rand(1, 60)); + if (prog_stop) + break; + + i = get_rand(0, pid_count); + pid = children[i]; + + printf("kill pid %d\n", pid); + kill(pid, SIGKILL); + + rv = waitpid(pid, &status, 0); + if (rv <= 0) + continue; + + pid = fork(); + if (pid < 0) { + log_error("fork failed %d", errno); + break; + } else if (!pid) { + do_rand_child(); + exit(-1); + } else { + children[i] = pid; + } +#endif + +#if 0 + /* + * remove a random lockspace, replace any pids that were using + * it, replace the lockspace + */ + + sleep(get_rand(1, 60)); + if (prog_stop) + break; + + lsi = get_rand(0, ls_count-1); + + remove_lockspace(lsi); + + while (1) { + rv = waitpid(-1, &status, WNOHANG); + if (rv <= 0) + break; + + if (!WIFEXITED(status)) + continue; + + printf("exit pid %d\n", pid); + + i = find_pid(children, rv); + if (i < 0) + continue; + + pid = fork(); + if (pid < 0) { + log_error("fork failed %d", errno); + break; + } else if (!pid) { + do_rand_child(); + exit(-1); + } else { + children[i] = pid; + } + } + + add_lockspace(lsi); +#endif + + if (run_sec && (count_sec >= run_sec)) + break; + count_sec++; + sleep(1); + } + + printf("stopping pids "); + + for (i = 0; i < pid_count; i++) + kill(children[i], SIGTERM); + + while (run_count) { + status = 0; + + pid = wait(&status); + if (pid > 0) { + run_count--; + + if (!WIFEXITED(status)) { + error_count++; + printf("-"); + } else if (WEXITSTATUS(status)) { + error_count++; + printf("x"); + } else { + printf("."); + } + } + } + printf("\n"); + + if (error_count) { + printf("child errors %d\n", error_count); + exit(EXIT_FAILURE); + } + + return 0; +} + +int do_all(int argc, char *argv[]) +{ + struct sigaction act; + int children[MAX_PID_COUNT]; + int run_count = 0; + int i, rv, pid, status; + + if (argc < 5) + return -1; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sigterm_handler; + sigaction(SIGTERM, &act, NULL); + + strcpy(lock_disk_base, argv[2]); + + get_options(argc, argv); + + rv = add_lockspaces(); + if (rv < 0) + return rv; + + printf("forking %d pids\n", pid_count); + + for (i = 0; i < pid_count; i++) { + pid = fork(); + + if (pid < 0) { + log_error("fork %d failed %d run_count %d", i, errno, run_count); + break; + } + if (!pid) { + do_all_child(); + exit(-1); + } + children[i] = pid; + run_count++; + } + + printf("children running\n"); + + while (!prog_stop) { + sleep(1); + } + + printf("stopping pids"); + + for (i = 0; i < pid_count; i++) + kill(children[i], SIGTERM); + + while (run_count) { + pid = wait(&status); + if (pid > 0) { + run_count--; + printf("."); + } + } + printf("\n"); + + if (error_count) { + printf("error_count %d\n", error_count); + exit(EXIT_FAILURE); + } + + return 0; +} + +/* + * sanlk_load init [ ] + * lock_disk_base = /dev/vg/foo + * + * sanlock direct init -s lockspace0:0:/dev/vg/foo0:0 + * sanlock direct init -r lockspace0:resource0:/dev/vg/foo0:1M + * sanlock direct init -r lockspace0:resource1:/dev/vg/foo0:2M + * ... + * sanlock direct init -s lockspace1:0:/dev/vg/foo1:0 + * sanlock direct init -r lockspace1:resource0:/dev/vg/foo1:1M + * sanlock direct init -r lockspace1:resource1:/dev/vg/foo1:2M + * ... + */ + +#define INIT_NUM_HOSTS 64 + +int do_init(int argc, char *argv[]) +{ + char resbuf[sizeof(struct sanlk_resource) + sizeof(struct sanlk_disk)]; + struct sanlk_resource *res; + struct sanlk_lockspace ls; + int i, j, rv; + + if (argc < 3) + return -1; + + strcpy(lock_disk_base, argv[2]); + + if (argc > 3) + ls_count = atoi(argv[3]); + if (argc > 4) + res_count = atoi(argv[4]); + + for (i = 0; i < ls_count; i++) { + + memset(&ls, 0, sizeof(ls)); + sprintf(ls.host_id_disk.path, "%s%d", lock_disk_base, i); + sprintf(ls.name, "lockspace%d", i); + + rv = sanlock_direct_init(&ls, NULL, 0, INIT_NUM_HOSTS, 1); + if (rv < 0) { + printf("sanlock_direct_init lockspace error %d %s\n", rv, + ls.host_id_disk.path); + return -1; + } + + for (j = 0; j < res_count; j++) { + + memset(resbuf, 0, sizeof(resbuf)); + res = (struct sanlk_resource *)&resbuf; + + strcpy(res->lockspace_name, ls.name); + sprintf(res->name, "resource%d", j); + res->num_disks = 1; + strcpy(res->disks[0].path, ls.host_id_disk.path); + res->disks[0].offset = (j+1)*LEASE_SIZE; + + rv = sanlock_direct_init(NULL, res, 0, INIT_NUM_HOSTS, 0); + if (rv < 0) { + printf("sanlock_direct_init resource error %d\n", rv); + return -1; + } + } + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int rv = -1; + + if (argc < 2) + goto out; + + if (!strcmp(argv[1], "init")) + rv = do_init(argc, argv); + + else if (!strcmp(argv[1], "rand")) + rv = do_rand(argc, argv); + + else if (!strcmp(argv[1], "all")) + rv = do_all(argc, argv); + + if (!rv) + return 0; + + out: + printf("sanlk_load init [ ]\n"); + printf(" init ls_count lockspaces, each with res_count resources\n"); + printf(" devices for lockspaces 0..N are disk_base0..disk_baseN\n"); + printf(" e.g. /dev/lock0, /dev/lock1, ... /dev/lockN\n"); + printf("\n"); + printf("sanlk_load rand -i [options]\n"); + printf(" -s number of lockspaces\n"); + printf(" -r number of resources per lockspace\n"); + printf(" -p number of processes\n"); + printf(" -m use one mode for all locks, 3 = SH, 5 = EX\n"); + printf(" -S seconds to run (0 unlimited)\n"); + printf(" -e error range expected (1 single node, 2 multi node)\n"); + printf(" -D debug output\n"); + printf(" -V verbose debug output\n"); + printf("\n"); + return -1; +} + diff --git a/tests/tests.yml b/tests/tests.yml new file mode 100644 index 0000000..9312189 --- /dev/null +++ b/tests/tests.yml @@ -0,0 +1,15 @@ +- hosts: localhost + roles: + - role: standard-test-basic # this is a standard test role, it takes care of the test environment, logging, archiving results.. + tags: + - classic + tests: + - simple: + dir: scripts + run: ./run_tests.sh + required_packages: + - sanlock + - sanlock-devel + - gcc + - libaio-devel + - libblkid-devel