summaryrefslogtreecommitdiffstats
path: root/libnvme
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2022-07-14 18:53:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2022-07-14 18:53:09 +0000
commit3945f3269b3e2763faa1ab22d225ca4dd1856b82 (patch)
tree7e96ec768baa3807ce3a1076a74037a287f4caa8 /libnvme
parentInitial commit. (diff)
downloadlibnvme-3945f3269b3e2763faa1ab22d225ca4dd1856b82.tar.xz
libnvme-3945f3269b3e2763faa1ab22d225ca4dd1856b82.zip
Adding upstream version 1.0.upstream/1.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libnvme')
-rw-r--r--libnvme/.gitignore4
-rw-r--r--libnvme/README.md46
-rw-r--r--libnvme/__init__.py0
-rw-r--r--libnvme/meson.build71
-rw-r--r--libnvme/nvme.i690
-rwxr-xr-xlibnvme/tests/create-ctrl-obj.py14
6 files changed, 825 insertions, 0 deletions
diff --git a/libnvme/.gitignore b/libnvme/.gitignore
new file mode 100644
index 0000000..e943f50
--- /dev/null
+++ b/libnvme/.gitignore
@@ -0,0 +1,4 @@
+build/
+__pycache__/
+nvme.py
+nvme_wrap.c
diff --git a/libnvme/README.md b/libnvme/README.md
new file mode 100644
index 0000000..f61e5cc
--- /dev/null
+++ b/libnvme/README.md
@@ -0,0 +1,46 @@
+# Python bindings for libnvme
+
+We use [SWIG](http://www.swig.org/) to generate Python bindings for libnvme.
+
+## How to use
+
+```python
+#!/usr/bin/env python3
+import sys
+import pprint
+from libnvme import nvme
+
+root = nvme.root() # This is a singleton
+root.log_level('debug') # Optional: extra debug info
+
+host = nvme.host(root) # This "may be" a singleton.
+sybsysnqn = [string] # e.g. 'nqn.2014-08.org.nvmexpress.discovery', nvme.NVME_DISC_SUBSYS_NAME, ...
+transport = [string] # One of: 'tcp, 'rdma', 'fc', 'loop'.
+traddr = [IPv4 or IPv6] # e.g. '192.168.10.10', 'fd2e:853b:3cad:e135:506a:65ee:29f2:1b18', ...
+trsvcid = [string] # e.g. '8009', '4420', ...
+host_iface = [interface] # e.g. 'eth1', ens256', ...
+ctrl = nvme.ctrl(subsysnqn=subsysnqn, transport=transport, traddr=traddr, trsvcid=trsvcid, host_iface=host_iface)
+
+try:
+ cfg = {
+ 'hdr_digest': True, # Enable header digests
+ 'data_digest': False, # Disable data digests
+ }
+ ctrl.connect(host, cfg)
+ print(f"connected to {ctrl.name} subsys {ctrl.subsystem.name}")
+except Exception as e:
+ sys.exit(f'Failed to connect: {e}')
+
+try:
+ log_pages = ctrl.discover()
+ print(pprint.pformat(log_pages))
+except Exception as e:
+ sys.exit(f'Failed to retrieve log pages: {e}')
+
+try:
+ ctrl.disconnect()
+except Exception as e:
+ sys.exit(f'Failed to disconnect: {e}')
+
+```
+
diff --git a/libnvme/__init__.py b/libnvme/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libnvme/__init__.py
diff --git a/libnvme/meson.build b/libnvme/meson.build
new file mode 100644
index 0000000..c95444e
--- /dev/null
+++ b/libnvme/meson.build
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of libnvme.
+# Copyright (c) 2021 Dell Inc.
+#
+# Authors: Martin Belanger <Martin.Belanger@dell.com>
+#
+want_python = get_option('python')
+if want_python != 'false'
+ python3 = import('python').find_installation('python3')
+ py3_dep = python3.dependency(required: want_python == 'true')
+ swig = find_program('swig', required: want_python == 'true')
+ header_found = cc.has_header('Python.h', dependencies: py3_dep)
+ have_python_support = py3_dep.found() and swig.found() and header_found
+else
+ have_python_support = false
+endif
+
+if have_python_support
+ pymod_swig = custom_target(
+ 'nvme.py',
+ input: ['nvme.i'],
+ output: ['nvme.py', 'nvme_wrap.c'],
+ command: [swig, '-python', '-py3', '-o', '@OUTPUT1@', '@INPUT0@'],
+ install: true,
+ install_dir: [python3.get_install_dir(pure: false, subdir: 'libnvme'), false],
+ )
+
+ pynvme_clib = python3.extension_module(
+ '_nvme',
+ pymod_swig[1],
+ dependencies : py3_dep,
+ include_directories: [incdir, internal_incdir],
+ link_with: [libnvme, libccan],
+ install: true,
+ subdir: 'libnvme',
+ )
+
+ # Little hack to copy file __init__.py to the build directory.
+ # This is needed to create the proper directory layout to run the tests.
+ # It's a hack because we don't really "configure" file __init__.py and we
+ # could simply install directly from the source tree with:
+ # python3.install_sources(['__init__.py', ], pure:false, subdir:'libnvme')
+ # However, since we need __init__.py in the build directory to run the tests
+ # we resort to this hack to copy it.
+ configure_file(
+ input: '__init__.py',
+ output: '__init__.py',
+ copy: true,
+ install_dir: python3.get_install_dir(pure: false, subdir: 'libnvme'),
+ )
+
+ # Set the PYTHONPATH so that we can run the
+ # tests directly from the build directory.
+ test_env = environment()
+ test_env.append('MALLOC_PERTURB_', '0')
+ test_env.append('PYTHONPATH', join_paths(meson.current_build_dir(), '..'))
+ test_env.append('PYTHONMALLOC', 'malloc')
+
+ # Test section
+ test('[Python] import libnvme', python3, args: ['-c', 'from libnvme import nvme'], env: test_env, depends: pynvme_clib)
+
+ py_tests = [
+ [ 'create ctrl object', files('tests/create-ctrl-obj.py') ],
+ ]
+ foreach test: py_tests
+ description = test[0]
+ py_script = test[1]
+ test('[Python] ' + description, python3, args: [py_script, ], env: test_env, depends: pynvme_clib)
+ endforeach
+endif
diff --git a/libnvme/nvme.i b/libnvme/nvme.i
new file mode 100644
index 0000000..ec680be
--- /dev/null
+++ b/libnvme/nvme.i
@@ -0,0 +1,690 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * This file is part of libnvme.
+ * Copyright (c) 2021 SUSE Software Solutions
+ *
+ * Authors: Hannes Reinecke <hare@suse.de>
+ */
+
+%module(docstring="Python bindings for libnvme") nvme
+%feature("autodoc", "1");
+
+%include "exception.i"
+
+%allowexception;
+
+%rename(root) nvme_root;
+%rename(host) nvme_host;
+%rename(ctrl) nvme_ctrl;
+%rename(subsystem) nvme_subsystem;
+%rename(ns) nvme_ns;
+
+%{
+#include <ccan/list/list.h>
+#include "nvme/tree.h"
+#include "nvme/fabrics.h"
+#include "nvme/private.h"
+#include "nvme/log.h"
+
+static int host_iter_err = 0;
+static int subsys_iter_err = 0;
+static int ctrl_iter_err = 0;
+static int ns_iter_err = 0;
+static int connect_err = 0;
+static int discover_err = 0;
+%}
+
+%inline %{
+ struct host_iter {
+ struct nvme_root *root;
+ struct nvme_host *pos;
+ };
+
+ struct subsystem_iter {
+ struct nvme_host *host;
+ struct nvme_subsystem *pos;
+ };
+
+ struct ctrl_iter {
+ struct nvme_subsystem *subsystem;
+ struct nvme_ctrl *pos;
+ };
+
+ struct ns_iter {
+ struct nvme_subsystem *subsystem;
+ struct nvme_ctrl *ctrl;
+ struct nvme_ns *pos;
+ };
+%}
+
+%exception host_iter::__next__ {
+ host_iter_err = 0;
+ $action /* $action sets host_iter_err to non-zero value on failure */
+ if (host_iter_err) {
+ PyErr_SetString(PyExc_StopIteration, "End of list");
+ return NULL;
+ }
+}
+
+%exception subsystem_iter::__next__ {
+ subsys_iter_err = 0;
+ $action /* $action sets subsys_iter_err to non-zero value on failure */
+ if (subsys_iter_err) {
+ PyErr_SetString(PyExc_StopIteration, "End of list");
+ return NULL;
+ }
+}
+
+%exception ctrl_iter::__next__ {
+ ctrl_iter_err = 0;
+ $action /* $action sets ctrl_iter_err to non-zero value on failure */
+ if (ctrl_iter_err) {
+ PyErr_SetString(PyExc_StopIteration, "End of list");
+ return NULL;
+ }
+}
+
+%exception ns_iter::__next__ {
+ ns_iter_err = 0;
+ $action /* $action sets ns_iter_err to non-zero value on failure */
+ if (ns_iter_err) {
+ PyErr_SetString(PyExc_StopIteration, "End of list");
+ return NULL;
+ }
+}
+
+%exception nvme_ctrl::connect {
+ connect_err = 0;
+ errno = 0;
+ $action /* $action sets connect_err to non-zero value on failure */
+ if (connect_err == 1) {
+ SWIG_exception(SWIG_AttributeError, "Existing controller connection");
+ } else if (connect_err) {
+ const char *errstr = nvme_errno_to_string(errno);
+ if (errstr) {
+ SWIG_exception(SWIG_RuntimeError, errstr);
+ } else {
+ SWIG_exception(SWIG_RuntimeError, "Connect failed");
+ }
+ }
+}
+
+%exception nvme_ctrl::discover {
+ discover_err = 0;
+ $action /* $action sets discover_err to non-zero value on failure */
+ if (discover_err) {
+ SWIG_exception(SWIG_RuntimeError,"Discover failed");
+ }
+}
+
+#include "tree.h"
+#include "fabrics.h"
+
+%typemap(in) struct nvme_fabrics_config * ($*1_type temp) {
+ Py_ssize_t pos = 0;
+ PyObject *key, *value;
+ memset(&temp, 0, sizeof(temp));
+ temp.tos = -1;
+ temp.ctrl_loss_tmo = NVMF_DEF_CTRL_LOSS_TMO;
+ while (PyDict_Next($input, &pos, &key, &value)) {
+ if (!PyUnicode_CompareWithASCIIString(key, "host_traddr"))
+ temp.host_traddr = PyBytes_AsString(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "host_iface"))
+ temp.host_iface = PyBytes_AsString(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "nr_io_queues"))
+ temp.nr_io_queues = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "reconnect_delay"))
+ temp.reconnect_delay = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "ctrl_loss_tmo"))
+ temp.ctrl_loss_tmo = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "keep_alive_tmo"))
+ temp.keep_alive_tmo = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "nr_write_queues"))
+ temp.nr_write_queues = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "nr_poll_queues"))
+ temp.nr_poll_queues = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "tos"))
+ temp.tos = PyLong_AsLong(value);
+ if (!PyUnicode_CompareWithASCIIString(key, "duplicate_connect"))
+ temp.duplicate_connect = PyObject_IsTrue(value) ? true : false;
+ if (!PyUnicode_CompareWithASCIIString(key, "disable_sqflow"))
+ temp.disable_sqflow = PyObject_IsTrue(value) ? true : false;
+ if (!PyUnicode_CompareWithASCIIString(key, "hdr_digest"))
+ temp.hdr_digest = PyObject_IsTrue(value) ? true : false;
+ if (!PyUnicode_CompareWithASCIIString(key, "data_digest"))
+ temp.data_digest = PyObject_IsTrue(value) ? true : false;
+ }
+ $1 = &temp;
+ };
+
+%typemap(out) uint8_t [8] {
+ $result = PyBytes_FromStringAndSize((char *)$1, 8);
+};
+
+%typemap(out) uint8_t [16] {
+ $result = PyBytes_FromStringAndSize((char *)$1, 16);
+};
+
+%typemap(newfree) struct nvmf_discovery_log * {
+ if ($1) free($1);
+}
+
+%{
+static void PyDict_SetItemStringDecRef(PyObject *p, const char *key, PyObject *val) {
+ PyDict_SetItemString(p, key, val); /* Does NOT steal reference to val .. */
+ Py_XDECREF(val); /* .. therefore decrement ref. count. */
+}
+%}
+
+%typemap(out) struct nvmf_discovery_log * {
+ struct nvmf_discovery_log *log = $1;
+ int numrec = log? log->numrec : 0, i;
+ PyObject *obj = PyList_New(numrec);
+ if (!obj)
+ return NULL;
+ for (i = 0; i < numrec; i++) {
+ struct nvmf_disc_log_entry *e = &log->entries[i];
+ PyObject *entry = PyDict_New(), *val;
+
+ switch (e->trtype) {
+ case NVMF_TRTYPE_UNSPECIFIED:
+ val = PyUnicode_FromString("unspecified");
+ break;
+ case NVMF_TRTYPE_RDMA:
+ val = PyUnicode_FromString("rdma");
+ break;
+ case NVMF_TRTYPE_FC:
+ val = PyUnicode_FromString("fc");
+ break;
+ case NVMF_TRTYPE_TCP:
+ val = PyUnicode_FromString("tcp");
+ break;
+ case NVMF_TRTYPE_LOOP:
+ val = PyUnicode_FromString("loop");
+ break;
+ default:
+ val = PyLong_FromLong(e->trtype);
+ }
+ PyDict_SetItemStringDecRef(entry, "trtype", val);
+ switch (e->adrfam) {
+ case NVMF_ADDR_FAMILY_PCI:
+ val = PyUnicode_FromString("pci");
+ break;
+ case NVMF_ADDR_FAMILY_IP4:
+ val = PyUnicode_FromString("ipv4");
+ break;
+ case NVMF_ADDR_FAMILY_IP6:
+ val = PyUnicode_FromString("ipv6");
+ break;
+ case NVMF_ADDR_FAMILY_IB:
+ val = PyUnicode_FromString("infiniband");
+ break;
+ case NVMF_ADDR_FAMILY_FC:
+ val = PyUnicode_FromString("fc");
+ break;
+ default:
+ val = PyLong_FromLong(e->adrfam);
+ }
+ PyDict_SetItemStringDecRef(entry, "adrfam", val);
+ val = PyUnicode_FromString(e->traddr);
+ PyDict_SetItemStringDecRef(entry, "traddr", val);
+ val = PyUnicode_FromString(e->trsvcid);
+ PyDict_SetItemStringDecRef(entry, "trsvcid", val);
+ val = PyUnicode_FromString(e->subnqn);
+ PyDict_SetItemStringDecRef(entry, "subnqn", val);
+ switch (e->subtype) {
+ case NVME_NQN_DISC:
+ val = PyUnicode_FromString("referral");
+ break;
+ case NVME_NQN_NVME:
+ val = PyUnicode_FromString("nvme");
+ break;
+ case NVME_NQN_CURR:
+ val = PyUnicode_FromString("discovery");
+ break;
+ default:
+ val = PyLong_FromLong(e->subtype);
+ }
+ PyDict_SetItemStringDecRef(entry, "subtype", val);
+ switch (e->treq) {
+ case NVMF_TREQ_NOT_SPECIFIED:
+ val = PyUnicode_FromString("not specified");
+ break;
+ case NVMF_TREQ_REQUIRED:
+ val = PyUnicode_FromString("required");
+ break;
+ case NVMF_TREQ_NOT_REQUIRED:
+ val = PyUnicode_FromString("not required");
+ break;
+ case NVMF_TREQ_DISABLE_SQFLOW:
+ val = PyUnicode_FromString("disable sqflow");
+ break;
+ default:
+ val = PyLong_FromLong(e->treq);
+ }
+ PyDict_SetItemStringDecRef(entry, "treq", val);
+ val = PyLong_FromLong(e->portid);
+ PyDict_SetItemStringDecRef(entry, "portid", val);
+ val = PyLong_FromLong(e->cntlid);
+ PyDict_SetItemStringDecRef(entry, "cntlid", val);
+ val = PyLong_FromLong(e->asqsz);
+ PyDict_SetItemStringDecRef(entry, "asqsz", val);
+ PyList_SetItem(obj, i, entry); /* steals ref. to entry */
+ }
+ $result = obj;
+ };
+struct nvme_root {
+ %immutable config_file;
+ char *config_file;
+};
+
+struct nvme_host {
+ %immutable hostnqn;
+ %immutable hostid;
+ %immutable hostsymname;
+ char *hostnqn;
+ char *hostid;
+ char *hostsymname;
+ char *dhchap_key;
+};
+
+struct nvme_subsystem {
+ %immutable subsysnqn;
+ %immutable model;
+ %immutable serial;
+ %immutable firmware;
+ char *subsysnqn;
+ char *model;
+ char *serial;
+ char *firmware;
+};
+
+struct nvme_ctrl {
+ %immutable transport;
+ %immutable subsysnqn;
+ %immutable traddr;
+ %immutable trsvcid;
+ %immutable address;
+ %immutable firmware;
+ %immutable model;
+ %immutable numa_node;
+ %immutable queue_count;
+ %immutable serial;
+ %immutable sqsize;
+ %immutable persistent;
+ %immutable discovery_ctrl;
+ char *transport;
+ char *subsysnqn;
+ char *traddr;
+ char *trsvcid;
+ char *dhchap_key;
+ char *address;
+ char *firmware;
+ char *model;
+ char *numa_node;
+ char *queue_count;
+ char *serial;
+ char *sqsize;
+ bool persistent;
+ bool discovery_ctrl;
+};
+
+struct nvme_ns {
+ %immutable nsid;
+ %immutable eui64;
+ %immutable nguid;
+ %immutable uuid;
+ unsigned int nsid;
+ uint8_t eui64[8];
+ uint8_t nguid[16];
+ uint8_t uuid[16];
+};
+
+%extend nvme_root {
+ nvme_root(const char *config_file = NULL) {
+ return nvme_scan(config_file);
+ }
+ ~nvme_root() {
+ nvme_free_tree($self);
+ }
+ void log_level(const char *level) {
+ int log_level = DEFAULT_LOGLEVEL;
+ if (!strcmp(level,"debug"))
+ log_level = LOG_DEBUG;
+ else if (!strcmp(level, "info"))
+ log_level = LOG_INFO;
+ else if (!strcmp(level, "notice"))
+ log_level = LOG_NOTICE;
+ else if (!strcmp(level, "warning"))
+ log_level = LOG_WARNING;
+ else if (!strcmp(level, "err"))
+ log_level = LOG_ERR;
+ else if (!strcmp(level, "crit"))
+ log_level = LOG_CRIT;
+ else if (!strcmp(level, "alert"))
+ log_level = LOG_ALERT;
+ else if (!strcmp(level, "emerg"))
+ log_level = LOG_EMERG;
+ nvme_init_logging($self, log_level, false, false);
+ }
+ struct nvme_host *hosts() {
+ return nvme_first_host($self);
+ }
+ void refresh_topology() {
+ nvme_refresh_topology($self);
+ }
+ void update_config() {
+ nvme_update_config($self);
+ }
+ void dump_config() {
+ nvme_dump_config($self);
+ }
+}
+
+%extend host_iter {
+ struct host_iter *__iter__() {
+ return $self;
+ }
+ struct nvme_host *__next__() {
+ struct nvme_host *this = $self->pos;
+
+ if (!this) {
+ host_iter_err = 1;
+ return NULL;
+ }
+ $self->pos = nvme_next_host($self->root, this);
+ return this;
+ }
+}
+
+%extend nvme_host {
+ nvme_host(struct nvme_root *r, const char *hostnqn = NULL,
+ const char *hostid = NULL, const char *hostsymname = NULL) {
+
+ nvme_host_t h = hostnqn ? nvme_lookup_host(r, hostnqn, hostid) : nvme_default_host(r);
+ if (hostsymname)
+ nvme_host_set_hostsymname(h, hostsymname);
+ return h;
+ }
+ ~nvme_host() {
+ nvme_free_host($self);
+ }
+%define SET_SYMNAME_DOCSTRING
+"@brief Set or Clear Host's Symbolic Name
+
+@param hostsymname: A symbolic name, or None to clear the symbolic name.
+@type hostsymname: str|None
+
+@return: None"
+%enddef
+ %feature("autodoc", SET_SYMNAME_DOCSTRING) set_symname;
+ void set_symname(const char *hostsymname) {
+ nvme_host_set_hostsymname($self, hostsymname);
+ }
+ char *__str__() {
+ static char tmp[2048];
+
+ sprintf(tmp, "nvme_host(%s,%s)", $self->hostnqn, $self->hostid);
+ return tmp;
+ }
+ struct host_iter __iter__() {
+ struct host_iter ret = { .root = nvme_host_get_root($self),
+ .pos = $self };
+ return ret;
+ }
+ struct nvme_subsystem *subsystems() {
+ return nvme_first_subsystem($self);
+ }
+}
+
+%extend subsystem_iter {
+ struct subsystem_iter *__iter__() {
+ return $self;
+ }
+ struct nvme_subsystem *__next__() {
+ struct nvme_subsystem *this = $self->pos;
+
+ if (!this) {
+ subsys_iter_err = 1;
+ return NULL;
+ }
+ $self->pos = nvme_next_subsystem($self->host, this);
+ return this;
+ }
+}
+
+%extend ns_iter {
+ struct ns_iter *__iter__() {
+ return $self;
+ }
+ struct nvme_ns *__next__() {
+ struct nvme_ns *this = $self->pos;
+
+ if (!this) {
+ ns_iter_err = 1;
+ return NULL;
+ }
+ if ($self->ctrl)
+ $self->pos = nvme_ctrl_next_ns($self->ctrl, this);
+ else
+ $self->pos = nvme_subsystem_next_ns($self->subsystem, this);
+ return this;
+ }
+}
+
+%extend nvme_subsystem {
+ nvme_subsystem(struct nvme_host *host, const char *subsysnqn,
+ const char *name = NULL) {
+ return nvme_lookup_subsystem(host, name, subsysnqn);
+ }
+ ~nvme_subsystem() {
+ nvme_free_subsystem($self);
+ }
+ char *__str__() {
+ static char tmp[1024];
+
+ sprintf(tmp, "nvme_subsystem(%s,%s)", $self->name,$self->subsysnqn);
+ return tmp;
+ }
+ struct subsystem_iter __iter__() {
+ struct subsystem_iter ret = { .host = nvme_subsystem_get_host($self),
+ .pos = $self };
+ return ret;
+ }
+ struct nvme_ctrl *controllers() {
+ return nvme_subsystem_first_ctrl($self);
+ }
+ struct nvme_ns *namespaces() {
+ return nvme_subsystem_first_ns($self);
+ }
+ %immutable name;
+ const char *name;
+ %immutable host;
+ struct nvme_host *host;
+}
+
+%{
+ const char *nvme_subsystem_name_get(struct nvme_subsystem *s) {
+ return nvme_subsystem_get_name(s);
+ }
+ struct nvme_host *nvme_subsystem_host_get(struct nvme_subsystem *s) {
+ return nvme_subsystem_get_host(s);
+ }
+%};
+
+%extend ctrl_iter {
+ struct ctrl_iter *__iter__() {
+ return $self;
+ }
+ struct nvme_ctrl *__next__() {
+ struct nvme_ctrl *this = $self->pos;
+
+ if (!this) {
+ ctrl_iter_err = 1;
+ return NULL;
+ }
+ $self->pos = nvme_subsystem_next_ctrl($self->subsystem, this);
+ return this;
+ }
+}
+
+%extend nvme_ctrl {
+ nvme_ctrl(struct nvme_root *r, const char *subsysnqn, const char *transport,
+ const char *traddr = NULL, const char *host_traddr = NULL,
+ const char *host_iface = NULL, const char *trsvcid = NULL) {
+ return nvme_create_ctrl(r, subsysnqn, transport, traddr,
+ host_traddr, host_iface, trsvcid);
+ }
+ ~nvme_ctrl() {
+ nvme_free_ctrl($self);
+ }
+
+ void discovery_ctrl_set(bool discovery) {
+ nvme_ctrl_set_discovery_ctrl($self, discovery);
+ }
+
+ bool init(struct nvme_host *h, int instance) {
+ return nvme_init_ctrl(h, $self, instance) == 0;
+ }
+
+ void connect(struct nvme_host *h, struct nvme_fabrics_config *cfg = NULL) {
+ int ret;
+ const char *dev;
+
+ dev = nvme_ctrl_get_name($self);
+ if (dev && !cfg->duplicate_connect) {
+ connect_err = 1;
+ return;
+ }
+ ret = nvmf_add_ctrl(h, $self, cfg);
+ if (ret < 0) {
+ connect_err = 2;
+ return;
+ }
+ }
+ bool connected() {
+ return nvme_ctrl_get_name($self) != NULL;
+ }
+ void persistent_set(bool persistent) {
+ nvme_ctrl_set_persistent($self, persistent);
+ }
+ void rescan() {
+ nvme_rescan_ctrl($self);
+ }
+ void disconnect() {
+ nvme_disconnect_ctrl($self);
+ }
+
+ %feature("autodoc", "@return: True if controller supports explicit registration. False otherwise.") is_registration_supported;
+ bool is_registration_supported() {
+ return nvmf_is_registration_supported($self);
+ }
+
+ %feature("autodoc", "@return None on success or Error string on error.") registration_ctlr;
+ PyObject *registration_ctlr(enum nvmf_dim_tas tas) {
+ __u32 result;
+ int status;
+
+ status = nvmf_register_ctrl($self, NVMF_DIM_TAS_REGISTER, &result);
+ if (status != NVME_SC_SUCCESS) {
+ /* On error, return an error message */
+ if (status < 0)
+ return PyUnicode_FromFormat("Status:0x%04x - %s", status, nvme_status_to_string(status, false));
+ else
+ return PyUnicode_FromFormat("Result:0x%04x, Status:0x%04x - %s", result, status, nvme_status_to_string(status, false));
+ }
+
+ /* On success, return None */
+ Py_RETURN_NONE;
+ }
+
+ %newobject discover;
+ struct nvmf_discovery_log *discover(int max_retries = 6) {
+ struct nvmf_discovery_log *logp = NULL;
+ int ret = 0;
+ ret = nvmf_get_discovery_log($self, &logp, max_retries);
+ if (ret < 0) {
+ discover_err = 1;
+ return NULL;
+ }
+ return logp;
+ }
+ char *__str__() {
+ static char tmp[1024];
+
+ if ($self->address)
+ sprintf(tmp, "nvme_ctrl(transport=%s,%s)", $self->transport,
+ $self->address);
+ else
+ sprintf(tmp, "nvme_ctrl(transport=%s)", $self->transport);
+ return tmp;
+ }
+ struct ctrl_iter __iter__() {
+ struct ctrl_iter ret = { .subsystem = nvme_ctrl_get_subsystem($self),
+ .pos = $self };
+ return ret;
+ }
+ struct nvme_ns *namespaces() {
+ return nvme_ctrl_first_ns($self);
+ }
+ %immutable name;
+ const char *name;
+ %immutable subsystem;
+ struct nvme_subsystem *subsystem;
+ %immutable state;
+ const char *state;
+}
+
+%{
+ const char *nvme_ctrl_name_get(struct nvme_ctrl *c) {
+ return nvme_ctrl_get_name(c);
+ }
+ struct nvme_subsystem *nvme_ctrl_subsystem_get(struct nvme_ctrl *c) {
+ return nvme_ctrl_get_subsystem(c);
+ }
+ const char *nvme_ctrl_state_get(struct nvme_ctrl *c) {
+ return nvme_ctrl_get_state(c);
+ }
+%};
+
+%extend nvme_ns {
+ nvme_ns(struct nvme_subsystem *s, unsigned int nsid) {
+ return nvme_subsystem_lookup_namespace(s, nsid);
+ }
+ ~nvme_ns() {
+ nvme_free_ns($self);
+ }
+ char *__str__() {
+ static char tmp[1024];
+
+ sprintf(tmp, "nvme_ns(%u)", $self->nsid);
+ return tmp;
+ }
+ struct ns_iter __iter__() {
+ struct ns_iter ret = { .ctrl = nvme_ns_get_ctrl($self),
+ .subsystem = nvme_ns_get_subsystem($self),
+ .pos = $self };
+ return ret;
+ }
+ %immutable name;
+ const char *name;
+}
+
+%{
+ const char *nvme_ns_name_get(struct nvme_ns *n) {
+ return nvme_ns_get_name(n);
+ }
+%};
+
+
+// We want to swig all the #define and enum from types.h, but none of the structs.
+%{
+#include "nvme/types.h"
+%}
+#define __attribute__(x)
+%rename($ignore, %$isclass) ""; // ignore all classes/structs
+%rename($ignore, %$isfunction) ""; // ignore all functions
+%rename($ignore, %$isunion) ""; // ignore all unions
+%rename($ignore, %$isvariable ) ""; // ignore all variables
+
+%include "../src/nvme/types.h"
diff --git a/libnvme/tests/create-ctrl-obj.py b/libnvme/tests/create-ctrl-obj.py
new file mode 100755
index 0000000..51915cc
--- /dev/null
+++ b/libnvme/tests/create-ctrl-obj.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+import sys
+import pprint
+from libnvme import nvme
+
+root = nvme.root()
+root.log_level('debug')
+
+host = nvme.host(root)
+subsysnqn = nvme.NVME_DISC_SUBSYS_NAME
+transport = 'loop'
+traddr = '127.0.0.1'
+trsvcid = '8009'
+ctrl = nvme.ctrl(root, subsysnqn=subsysnqn, transport=transport, traddr=traddr, trsvcid=trsvcid)