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 nsoffer@redhat.com --- 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 67d34fc..238d172 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=fd, pid=owner)\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 58a22c7..caf2f3e 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]),
NB: if somewon wants to review in web UI, see https://github.com/nirs/sanlock/commit/683b9d6d46649da9f5650c8e467d07e350d1d...
On Tuesday, 18 May 2021 15:29:02 CEST Nir Soffer wrote:
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 nsoffer@redhat.com
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 67d34fc..238d172 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;
why do we fail here? NULL is possible return value from disks_to_list(), so we should handle it and not fail (possibly return NULL?)
/* 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=fd, pid=owner)\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,
{"release", (PyCFunction) py_release, METH_VARARGS|METH_KEYWORDS, pydoc_release}, {"request", (PyCFunction) py_request,METH_VARARGS|METH_KEYWORDS, pydoc_inquire},@@ -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 58a22c7..caf2f3e 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]),
On Wed, May 19, 2021 at 11:32 AM Vojtech Juranek vjuranek@redhat.com wrote:
NB: if somewon wants to review in web UI, see https://github.com/nirs/sanlock/commit/683b9d6d46649da9f5650c8e467d07e350d1d...
Let's keep the discussion in one place. The mailing list is the best way to review and discuss stuff.
On Tuesday, 18 May 2021 15:29:02 CEST Nir Soffer wrote:
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 nsoffer@redhat.com
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 67d34fc..238d172 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;
A memory error was set in this case...
- 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;
This can fail with memory error, value error (converting offset to integer) or unicode error (decoding path).
/* Steals reference to disk. */if (PyList_SetItem(result, i, disk) != 0)goto exit_fail;
This can fail (not sure how)
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;why do we fail here? NULL is possible return value from disks_to_list(), so we should handle it and not fail (possibly return NULL?)
Getting NULL from disks_to_list() means disk_to_list() has failed and set an error. In this case we need to return NULL from py_inquire(), and on the python side the error set in disks_to_list() will be raised.
We cannot return NULL since we already created the result list, and may have already added some items to the list. We must decrease the reference count of temporary variables before we return.
This is a common pattern in python c extension code, used everywhere in this module.
/* 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=fd, pid=owner)\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,
{"release", (PyCFunction) py_release, METH_VARARGS|METH_KEYWORDS, pydoc_release}, {"request", (PyCFunction) py_request,METH_VARARGS|METH_KEYWORDS, pydoc_inquire},@@ -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 58a22c7..caf2f3e 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]),
On Wednesday, 19 May 2021 11:32:15 CEST Nir Soffer wrote:
On Wed, May 19, 2021 at 11:32 AM Vojtech Juranek vjuranek@redhat.com
wrote:
NB: if somewon wants to review in web UI, see https://github.com/nirs/sanlock/commit/683b9d6d46649da9f5650c8e467d07e350d 1d6ed
Let's keep the discussion in one place. The mailing list is the best way to review and discuss stuff.
On Tuesday, 18 May 2021 15:29:02 CEST Nir Soffer wrote:
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 nsoffer@redhat.com
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 67d34fc..238d172 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;A memory error was set in this case...
Thanks, I didn't realize this. This was the case which was unclear to me (or better to say which I misunderstood).
LGTM, +1 to the patch
- 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;This can fail with memory error, value error (converting offset to integer) or unicode error (decoding path).
/* Steals reference to disk. */if (PyList_SetItem(result, i, disk) != 0)goto exit_fail;This can fail (not sure how)
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;why do we fail here? NULL is possible return value from disks_to_list(), so we should handle it and not fail (possibly return NULL?)
Getting NULL from disks_to_list() means disk_to_list() has failed and set an error. In this case we need to return NULL from py_inquire(), and on the python side the error set in disks_to_list() will be raised.
We cannot return NULL since we already created the result list, and may have already added some items to the list. We must decrease the reference count of temporary variables before we return.
This is a common pattern in python c extension code, used everywhere in this module.
/* 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=fd, pid=owner)\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 statestring");
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 58a22c7..caf2f3e 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]),
On Wed, May 19, 2021 at 12:42 PM Vojtech Juranek vjuranek@redhat.com wrote: ...
+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;A memory error was set in this case...
Thanks, I didn't realize this. This was the case which was unclear to me (or better to say which I misunderstood).
LGTM, +1 to the patch
Thanks.
Here are some tests using ovirt:
Using inquire on idle SPM host:
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}]
After starting cold merge task on a vm with 20 disks, each on different storage domain. This starts 10 concurrent cold merge jobs on the SPM host, and every job acquire the volume lease.
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}, {'disks': [('/dev/2f8c68ef-d5c1-4f34-9a6a-f7cb2f0aef0a/leases', 108003328)], 'flags': 1, 'lockspace': b'2f8c68ef-d5c1-4f34-9a6a-f7cb2f0aef0a', 'resource': b'4b7dd8cb-f61d-4840-9f84-87135d0c4b3b', 'version': 14}, {'disks': [('/dev/747ea907-c24c-43ff-a389-660c729703b0/leases', 108003328)], 'flags': 1, 'lockspace': b'747ea907-c24c-43ff-a389-660c729703b0', 'resource': b'fc34e8b4-fe7a-4d9f-a7e9-d4e4637d24e6', 'version': 15}, {'disks': [('/dev/71702e1e-82d2-41d2-93df-678f2415da2a/leases', 108003328)], 'flags': 1, 'lockspace': b'71702e1e-82d2-41d2-93df-678f2415da2a', 'resource': b'e181720a-9edf-46cb-b5ff-83e986cb3db3', 'version': 14}, {'disks': [('/dev/31e3675e-302a-4a22-ad9a-402c0c1e9765/leases', 108003328)], 'flags': 1, 'lockspace': b'31e3675e-302a-4a22-ad9a-402c0c1e9765', 'resource': b'3003f575-c38b-4034-8c54-3f000239b5c9', 'version': 15}, {'disks': [('/dev/f5597bd4-5c4f-4c4c-bd98-e8a33dace830/leases', 108003328)], 'flags': 1, 'lockspace': b'f5597bd4-5c4f-4c4c-bd98-e8a33dace830', 'resource': b'ba28ed6c-a0d9-4f89-821a-73a992d3a074', 'version': 15}, {'disks': [('/dev/e673e145-d644-44ac-a416-a8fc14b029ca/leases', 108003328)], 'flags': 1, 'lockspace': b'e673e145-d644-44ac-a416-a8fc14b029ca', 'resource': b'bc8556b2-095a-429c-9a61-c46f6390c022', 'version': 16}, {'disks': [('/dev/06e43bfc-2ffe-419e-843b-59a5d23165e1/leases', 108003328)], 'flags': 1, 'lockspace': b'06e43bfc-2ffe-419e-843b-59a5d23165e1', 'resource': b'8fb95469-16cd-406b-863d-2b76c2393711', 'version': 16}, {'disks': [('/dev/829b9bed-1b74-4f74-9d18-1006c87ef620/leases', 108003328)], 'flags': 1, 'lockspace': b'829b9bed-1b74-4f74-9d18-1006c87ef620', 'resource': b'75b3f9c9-32e0-4b1e-a367-a3a3f1ab2950', 'version': 16}, {'disks': [('/dev/7cf51212-8247-41b0-ab38-da5954059d7a/leases', 108003328)], 'flags': 1, 'lockspace': b'7cf51212-8247-41b0-ab38-da5954059d7a', 'resource': b'350353df-0434-4668-abdf-d7d55d5f0f49', 'version': 14}]
When all jobs finished:
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}]
Timing 1000 calls to sanlock.inquire() using pid:
timeit.timeit("sanlock.inquire(pid=1956)", setup="import sanlock", number=1000)
0.04519722999998521
Timing 1000 calls to sanlock inquire using slkfd:
fd = sanlock.register() fd
4
pprint.pprint(sanlock.inquire(slkfd=4))
[]
timeit.timeit("sanlock.inquire(slkfd=4)", setup="import sanlock", number=1000)
0.04666791799991188
Testing 1000000 calls to see how sanlock is affected, the client uses about 50% cpu, and sanlock about 75% cpu.
No change seen in memory usage of the python process.
Testing overloading sanlock during acquire/release operations. I run 10000 inquires while running 10 concurrent cold merge in ovirt.
One of the inquire calls failed with EBUSY:
timeit.timeit("sanlock.inquire(pid=1956)", setup="import sanlock", number=100000)
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib64/python3.6/timeit.py", line 233, in timeit return Timer(stmt, setup, timer, globals).timeit(number) File "/usr/lib64/python3.6/timeit.py", line 178, in timeit timing = self.inner(it, self.timer) File "<timeit-src>", line 6, in inner sanlock.SanlockException: (16, 'Inquire error', 'Device or resource busy')
This is expected when using pid=. We will synchronize the calls to sanlock in ovirt so we don't expect to have this issue.
The inquire calls cause one of the sanlock.release() calls in vdsm to fail:
2021-05-19 16:46:49,120+0300 INFO (tasks/1) [storage.SANLock] Releasing Lease(name='c873c3e2-5da1-4614-b564-37324fefb9a1', path ='/rhev/data-center/mnt/storage:_export_00/b2ff7cd6-64a6-453d-b71e-22fa4b11f16e/images/d4fa7500-306b-489b-88e7-48a3e1ff1a9b/c873 c3e2-5da1-4614-b564-37324fefb9a1.lease', offset=0) (clusterlock:602) 2021-05-19 16:46:49,121+0300 ERROR (tasks/1) [storage.TaskManager.Task] (Task='352a44c2-09a2-46ae-9327-d5ae444ea312') Unexpected error (task:880) Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/vdsm/storage/task.py", line 887, in _run return fn(*args, **kargs) File "/usr/lib/python3.6/site-packages/vdsm/storage/task.py", line 350, in run return self.cmd(*self.argslist, **self.argsdict) File "/usr/lib/python3.6/site-packages/vdsm/storage/securable.py", line 79, in wrapper return method(self, *args, **kwargs) File "/usr/lib/python3.6/site-packages/vdsm/storage/sp.py", line 1856, in prepareMerge merge.prepare(subchainInfo) File "/usr/lib/python3.6/site-packages/vdsm/storage/merge.py", line 183, in prepare subchain.top_vol) File "/usr/lib/python3.6/site-packages/vdsm/storage/guarded.py", line 111, in __exit__ self._release() File "/usr/lib/python3.6/site-packages/vdsm/storage/guarded.py", line 128, in _release raise ReleaseError(errors) vdsm.storage.guarded.ReleaseError: [ReleaseLockFailure('b2ff7cd6-64a6-453d-b71e-22fa4b11f16e', SanlockException(16, 'Sanlock res ource not released', 'Device or resource busy'))]
We don't handle this in vdsm since we don't expect other processes to inquire our leases.
This left unreleased lease in vdsm:
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}, {'disks': [('/rhev/data-center/mnt/storage:_export_00/b2ff7cd6-64a6-453d-b71e-22fa4b11f16e/images/d4fa7500-306b-489b-88e7-48a3e1ff1a9b/c873c3e2-5da1-4614-b564-37324fefb9a1.lease', 0)], 'flags': 1, 'lockspace': b'b2ff7cd6-64a6-453d-b71e-22fa4b11f16e', 'resource': b'c873c3e2-5da1-4614-b564-37324fefb9a1', 'version': 17}]
I could release it from the python shell using inquire result:
res = sanlock.inquire(pid=1956)
res
[{'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'flags': 1, 'version': 30, 'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)]}, {'lockspace': b'b2ff7cd6-64a6-453d-b71e-22fa4b11f16e', 'resource': b'c873c3e2-5da1-4614-b564-37324fefb9a1', 'flags': 1, 'version': 17, 'disks': [('/rhev/data-center/mnt/storage:_export_00/b2ff7cd6-64a6-453d-b71e-22fa4b11f16e/images/d4fa7500-306b-489b-88e7-48a3e1ff1a9b/c873c3e2-5da1-4614-b564-37324fefb9a1.lease', 0)]}]
sanlock.release(res[1]["lockspace"], res[1]["resource"], res[1]["disks"], pid=1956)
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}]
Nir
On Wed, May 19, 2021 at 4:59 PM Nir Soffer nsoffer@redhat.com wrote:
On Wed, May 19, 2021 at 12:42 PM Vojtech Juranek vjuranek@redhat.com wrote: ...
+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;A memory error was set in this case...
Thanks, I didn't realize this. This was the case which was unclear to me (or better to say which I misunderstood).
LGTM, +1 to the patch
Thanks.
Here are some tests using ovirt:
Using inquire on idle SPM host:
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}]
After starting cold merge task on a vm with 20 disks, each on different storage domain. This starts 10 concurrent cold merge jobs on the SPM host, and every job acquire the volume lease.
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}, {'disks': [('/dev/2f8c68ef-d5c1-4f34-9a6a-f7cb2f0aef0a/leases', 108003328)], 'flags': 1, 'lockspace': b'2f8c68ef-d5c1-4f34-9a6a-f7cb2f0aef0a', 'resource': b'4b7dd8cb-f61d-4840-9f84-87135d0c4b3b', 'version': 14}, {'disks': [('/dev/747ea907-c24c-43ff-a389-660c729703b0/leases', 108003328)], 'flags': 1, 'lockspace': b'747ea907-c24c-43ff-a389-660c729703b0', 'resource': b'fc34e8b4-fe7a-4d9f-a7e9-d4e4637d24e6', 'version': 15}, {'disks': [('/dev/71702e1e-82d2-41d2-93df-678f2415da2a/leases', 108003328)], 'flags': 1, 'lockspace': b'71702e1e-82d2-41d2-93df-678f2415da2a', 'resource': b'e181720a-9edf-46cb-b5ff-83e986cb3db3', 'version': 14}, {'disks': [('/dev/31e3675e-302a-4a22-ad9a-402c0c1e9765/leases', 108003328)], 'flags': 1, 'lockspace': b'31e3675e-302a-4a22-ad9a-402c0c1e9765', 'resource': b'3003f575-c38b-4034-8c54-3f000239b5c9', 'version': 15}, {'disks': [('/dev/f5597bd4-5c4f-4c4c-bd98-e8a33dace830/leases', 108003328)], 'flags': 1, 'lockspace': b'f5597bd4-5c4f-4c4c-bd98-e8a33dace830', 'resource': b'ba28ed6c-a0d9-4f89-821a-73a992d3a074', 'version': 15}, {'disks': [('/dev/e673e145-d644-44ac-a416-a8fc14b029ca/leases', 108003328)], 'flags': 1, 'lockspace': b'e673e145-d644-44ac-a416-a8fc14b029ca', 'resource': b'bc8556b2-095a-429c-9a61-c46f6390c022', 'version': 16}, {'disks': [('/dev/06e43bfc-2ffe-419e-843b-59a5d23165e1/leases', 108003328)], 'flags': 1, 'lockspace': b'06e43bfc-2ffe-419e-843b-59a5d23165e1', 'resource': b'8fb95469-16cd-406b-863d-2b76c2393711', 'version': 16}, {'disks': [('/dev/829b9bed-1b74-4f74-9d18-1006c87ef620/leases', 108003328)], 'flags': 1, 'lockspace': b'829b9bed-1b74-4f74-9d18-1006c87ef620', 'resource': b'75b3f9c9-32e0-4b1e-a367-a3a3f1ab2950', 'version': 16}, {'disks': [('/dev/7cf51212-8247-41b0-ab38-da5954059d7a/leases', 108003328)], 'flags': 1, 'lockspace': b'7cf51212-8247-41b0-ab38-da5954059d7a', 'resource': b'350353df-0434-4668-abdf-d7d55d5f0f49', 'version': 14}]
When all jobs finished:
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}]
Timing 1000 calls to sanlock.inquire() using pid:
timeit.timeit("sanlock.inquire(pid=1956)", setup="import sanlock", number=1000)
0.04519722999998521
Timing 1000 calls to sanlock inquire using slkfd:
fd = sanlock.register() fd
4
pprint.pprint(sanlock.inquire(slkfd=4))
[]
timeit.timeit("sanlock.inquire(slkfd=4)", setup="import sanlock", number=1000)
0.04666791799991188
Testing 1000000 calls to see how sanlock is affected, the client uses about 50% cpu, and sanlock about 75% cpu.
No change seen in memory usage of the python process.
Testing overloading sanlock during acquire/release operations. I run 10000 inquires while running 10 concurrent cold merge in ovirt.
One of the inquire calls failed with EBUSY:
timeit.timeit("sanlock.inquire(pid=1956)", setup="import sanlock", number=100000)
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib64/python3.6/timeit.py", line 233, in timeit return Timer(stmt, setup, timer, globals).timeit(number) File "/usr/lib64/python3.6/timeit.py", line 178, in timeit timing = self.inner(it, self.timer) File "<timeit-src>", line 6, in inner sanlock.SanlockException: (16, 'Inquire error', 'Device or resource busy')
This is expected when using pid=. We will synchronize the calls to sanlock in ovirt so we don't expect to have this issue.
The inquire calls cause one of the sanlock.release() calls in vdsm to fail:
2021-05-19 16:46:49,120+0300 INFO (tasks/1) [storage.SANLock] Releasing Lease(name='c873c3e2-5da1-4614-b564-37324fefb9a1', path ='/rhev/data-center/mnt/storage:_export_00/b2ff7cd6-64a6-453d-b71e-22fa4b11f16e/images/d4fa7500-306b-489b-88e7-48a3e1ff1a9b/c873 c3e2-5da1-4614-b564-37324fefb9a1.lease', offset=0) (clusterlock:602) 2021-05-19 16:46:49,121+0300 ERROR (tasks/1) [storage.TaskManager.Task] (Task='352a44c2-09a2-46ae-9327-d5ae444ea312') Unexpected error (task:880) Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/vdsm/storage/task.py", line 887, in _run return fn(*args, **kargs) File "/usr/lib/python3.6/site-packages/vdsm/storage/task.py", line 350, in run return self.cmd(*self.argslist, **self.argsdict) File "/usr/lib/python3.6/site-packages/vdsm/storage/securable.py", line 79, in wrapper return method(self, *args, **kwargs) File "/usr/lib/python3.6/site-packages/vdsm/storage/sp.py", line 1856, in prepareMerge merge.prepare(subchainInfo) File "/usr/lib/python3.6/site-packages/vdsm/storage/merge.py", line 183, in prepare subchain.top_vol) File "/usr/lib/python3.6/site-packages/vdsm/storage/guarded.py", line 111, in __exit__ self._release() File "/usr/lib/python3.6/site-packages/vdsm/storage/guarded.py", line 128, in _release raise ReleaseError(errors) vdsm.storage.guarded.ReleaseError: [ReleaseLockFailure('b2ff7cd6-64a6-453d-b71e-22fa4b11f16e', SanlockException(16, 'Sanlock res ource not released', 'Device or resource busy'))]
We don't handle this in vdsm since we don't expect other processes to inquire our leases.
This left unreleased lease in vdsm:
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}, {'disks': [('/rhev/data-center/mnt/storage:_export_00/b2ff7cd6-64a6-453d-b71e-22fa4b11f16e/images/d4fa7500-306b-489b-88e7-48a3e1ff1a9b/c873c3e2-5da1-4614-b564-37324fefb9a1.lease', 0)], 'flags': 1, 'lockspace': b'b2ff7cd6-64a6-453d-b71e-22fa4b11f16e', 'resource': b'c873c3e2-5da1-4614-b564-37324fefb9a1', 'version': 17}]
I could release it from the python shell using inquire result:
res = sanlock.inquire(pid=1956)
res
[{'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'flags': 1, 'version': 30, 'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)]}, {'lockspace': b'b2ff7cd6-64a6-453d-b71e-22fa4b11f16e', 'resource': b'c873c3e2-5da1-4614-b564-37324fefb9a1', 'flags': 1, 'version': 17, 'disks': [('/rhev/data-center/mnt/storage:_export_00/b2ff7cd6-64a6-453d-b71e-22fa4b11f16e/images/d4fa7500-306b-489b-88e7-48a3e1ff1a9b/c873c3e2-5da1-4614-b564-37324fefb9a1.lease', 0)]}]
sanlock.release(res[1]["lockspace"], res[1]["resource"], res[1]["disks"], pid=1956)
pprint.pprint(sanlock.inquire(pid=1956))
[{'disks': [('/dev/8ef0cc64-77c5-49e0-a0f5-03cee9bbee20/leases', 1048576)], 'flags': 1, 'lockspace': b'8ef0cc64-77c5-49e0-a0f5-03cee9bbee20', 'resource': b'SDM', 'version': 30}]
More testing - trying to run concurrent cold merge in vdsm when sanlock.inquire() stress test is running (68 seconds run started 10 seconds before).
Vdsm failed to acquire a lock with EBUSY:
2021-05-19 17:07:14,237+0300 INFO (tasks/0) [storage.SANLock] Acquiring Lease(name='e181720a-9edf-46cb-b5ff-83e986cb3db3', path ='/dev/71702e1e-82d2-41d2-93df-678f2415da2a/leases', offset=108003328) for host id 2, lvb=False (clusterlock:469) 2021-05-19 17:07:14,245+0300 ERROR (tasks/0) [storage.guarded] Error acquiring lock <VolumeLease ns=04_lease_71702e1e-82d2-41d2- 93df-678f2415da2a, name=e181720a-9edf-46cb-b5ff-83e986cb3db3, mode=exclusive at 0x7f5dfc058b70> (guarded:96) 2021-05-19 17:07:14,254+0300 ERROR (tasks/0) [storage.TaskManager.Task] (Task='0e1c4eea-7b95-4d49-acc6-2f57cff88542') Unexpected error (task:880) Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/vdsm/storage/clusterlock.py", line 505, in acquire **extra_args) sanlock.SanlockException: (16, 'Sanlock resource not acquired', 'Device or resource busy')
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/vdsm/storage/task.py", line 887, in _run return fn(*args, **kargs) File "/usr/lib/python3.6/site-packages/vdsm/storage/task.py", line 350, in run return self.cmd(*self.argslist, **self.argsdict) File "/usr/lib/python3.6/site-packages/vdsm/storage/securable.py", line 79, in wrapper return method(self, *args, **kwargs) File "/usr/lib/python3.6/site-packages/vdsm/storage/sp.py", line 1856, in prepareMerge merge.prepare(subchainInfo) File "/usr/lib/python3.6/site-packages/vdsm/storage/merge.py", line 178, in prepare with guarded.context(subchain.locks): File "/usr/lib/python3.6/site-packages/vdsm/storage/guarded.py", line 102, in __enter__ six.reraise(*exc) File "/usr/lib/python3.6/site-packages/six.py", line 675, in reraise raise value File "/usr/lib/python3.6/site-packages/vdsm/storage/guarded.py", line 93, in __enter__ lock.acquire() File "/usr/lib/python3.6/site-packages/vdsm/storage/volume.py", line 1607, in acquire dom.acquireVolumeLease(self._host_id, self._img_id, self._vol_id) File "/usr/lib/python3.6/site-packages/vdsm/storage/sd.py", line 548, in acquireVolumeLease self._domainLock.acquire(hostId, lease) File "/usr/lib/python3.6/site-packages/vdsm/storage/clusterlock.py", line 510, in acquire "Cannot acquire %s" % (lease,), str(e)) vdsm.storage.exception.AcquireLockFailure: Cannot obtain lock: "id=71702e1e-82d2-41d2-93df-678f2415da2a, rc=16, out=Cannot acqui re Lease(name='e181720a-9edf-46cb-b5ff-83e986cb3db3', path='/dev/71702e1e-82d2-41d2-93df-678f2415da2a/leases', offset=108003328) , err=(16, 'Sanlock resource not acquired', 'Device or resource busy')"
So using sanlock.inquire(pid=) is risky. Only one process should manage or query resources at the same time, or all users should be prepared to retry on EBUSY.
Nir
On Tue, May 18, 2021 at 4:29 PM Nir Soffer nirsof@gmail.com wrote:
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 nsoffer@redhat.com
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 67d34fc..238d172 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=fd, pid=owner)\n\
I copied this from acquire and release, but this is wrong and confusing.
Should be:
inquire(slkfd=-1, pid=-1)
I'll send v2 later.
+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,
{"release", (PyCFunction) py_release, METH_VARARGS|METH_KEYWORDS, pydoc_release}, {"request", (PyCFunction) py_request,METH_VARARGS|METH_KEYWORDS, pydoc_inquire},@@ -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 58a22c7..caf2f3e 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.26.3
On Wed, May 19, 2021 at 8:29 PM Nir Soffer nsoffer@redhat.com wrote:
On Tue, May 18, 2021 at 4:29 PM Nir Soffer nirsof@gmail.com wrote:
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 nsoffer@redhat.com
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 67d34fc..238d172 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=fd, pid=owner)\n\
I copied this from acquire and release, but this is wrong and confusing.
Should be:
inquire(slkfd=-1, pid=-1)
I pushed the patch with this change.
I'll send v2 later.
+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,
{"release", (PyCFunction) py_release, METH_VARARGS|METH_KEYWORDS, pydoc_release}, {"request", (PyCFunction) py_request,METH_VARARGS|METH_KEYWORDS, pydoc_inquire},@@ -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 58a22c7..caf2f3e 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.26.3
sanlock-devel@lists.fedorahosted.org