Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ using setup.py.

Common Package Dependencies and Problems
-----------------------------------------
Both python2 and python3 are supported via use of the 'python-six'
package.
Python3 is supported.

nvmetcli uses the 'pyparsing' package -- running nvmetcli without this
package may produce hard-to-decipher errors.
Expand Down
2 changes: 1 addition & 1 deletion nvmet/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .nvme import Root, Subsystem, Namespace, Port, Host, Referral, ANAGroup,\
from .nvme import Root, Subsystem, Namespace, Port, Host, Referral, ANAGroup, Passthru, \
DEFAULT_SAVE_FILE
134 changes: 126 additions & 8 deletions nvmet/nvme.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
import stat
import uuid
import json
import subprocess
import shlex
from glob import iglob as glob
from six import iteritems, moves

DEFAULT_SAVE_FILE = '/etc/nvmet/config.json'

Expand Down Expand Up @@ -220,7 +221,7 @@ def dump(self):

def _setup_attrs(self, attr_dict, err_func):
for group in self.attr_groups:
for name, value in iteritems(attr_dict.get(group, {})):
for name, value in attr_dict.get(group, {}).items():
try:
self.set_attr(group, name, value)
except CFSError as e:
Expand All @@ -234,6 +235,7 @@ class Root(CFSNode):
def __init__(self):
super(Root, self).__init__()

self.attr_groups = ['discovery']
if not os.path.isdir(self.configfs_dir):
self._modprobe('nvmet')

Expand All @@ -256,9 +258,22 @@ def _modprobe(self, modname):
# Try the ctypes library included with the libkmod itself.
try:
import kmod
kmod.Kmod().modprobe(modname)
except Exception as e:
pass

try:
kmod.Kmod().modprobe(modname)
except Exception as e:
pass
except ImportError:
# Try the binary specified in /proc
try:
modprobe_cmd = None
with open('/proc/sys/kernel/modprobe', 'r') as f:
modprobe_cmd = f.read()
if modprobe_cmd:
subprocess.run(shlex.split(modprobe_cmd) + [modname],
check=False)
except Exception as e:
pass

def _list_subsystems(self):
self._check_self()
Expand Down Expand Up @@ -462,6 +477,13 @@ def _list_namespaces(self):
namespaces = property(_list_namespaces,
doc="Get the list of Namespaces for the Subsystem.")

def _get_passthru(self):
self._check_self()
return Passthru(self)

passthru = property(_get_passthru,
doc="Get the passthru node for the subsystem")

def _list_allowed_hosts(self):
return [os.path.basename(name)
for name in os.listdir("%s/allowed_hosts/" % self._path)]
Expand Down Expand Up @@ -510,6 +532,8 @@ def setup(cls, t, err_func):
Namespace.setup(s, ns, err_func)
for h in t.get('allowed_hosts', []):
s.add_allowed_host(h)
for pt in t.get('passthru', []):
Passthru.setup(s, pt, err_func)

s._setup_attrs(t, err_func)

Expand All @@ -518,6 +542,8 @@ def dump(self):
d['nqn'] = self.nqn
d['namespaces'] = [ns.dump() for ns in self.namespaces]
d['allowed_hosts'] = self.allowed_hosts
if os.path.isdir(os.path.join(self.path, "passthru")):
d['passthru'] = [self.passthru.dump()]
return d


Expand Down Expand Up @@ -556,7 +582,7 @@ def __init__(self, subsystem, nsid=None, mode='any'):
raise CFSError("Need NSID for lookup")

nsids = [n.nsid for n in subsystem.namespaces]
for index in moves.xrange(1, self.MAX_NSID + 1):
for index in range(1, self.MAX_NSID + 1):
if index not in nsids:
nsid = index
break
Expand All @@ -567,7 +593,7 @@ def __init__(self, subsystem, nsid=None, mode='any'):
if nsid < 1 or nsid > self.MAX_NSID:
raise CFSError("NSID must be 1 to %d" % self.MAX_NSID)

self.attr_groups = ['device', 'ana']
self.attr_groups = ['device', 'ana', 'resv']
self._subsystem = subsystem
self._nsid = nsid
self._path = "%s/namespaces/%d" % (self.subsystem.path, self.nsid)
Expand Down Expand Up @@ -629,6 +655,98 @@ def dump(self):
d['ana_grpid'] = self.grpid
return d

class Passthru(CFSNode):
'''
This is an interface to a NVMe passthru in ConfigFS.
'''

def __init__(self, subsystem):
'''
@param subsystem: The parent Subsystem object.
@return: A Passthru object.
'''
super(Passthru, self).__init__()
self._path = "%s/passthru" % (subsystem.path)
self.attr_groups = ['device']

def _get_clear_ids(self):
self._check_self()
path = "%s/clear_ids" % self.path
_ids = 0
if os.path.isfile(path):
with open(path, 'r') as file_fd:
_ids = int(file_fd.read().strip())
return _ids

ids = property(_get_clear_ids,
doc = "Get the passthru namespace clear_ids attribute.")

def set_clear_ids(self, clear):
self._check_self()
path = "%s/clear_ids" % self.path
if os.path.isfile(path):
with open(path, 'w') as file_fd:
file_fd.write(str(clear))

def _get_admin_timeout(self):
self._check_self()
path = "%s/admin_timeout" % self.path
_timeout = 0
if os.path.isfile(path):
with open(path, 'r') as file_fd:
_timeout = int(file_fd.read().strip())
return _timeout

admin_timeout = property(_get_admin_timeout,
doc = "Get the passthru admin command timeout.")

def set_admin_timeout(self, timeout):
self._check_self()
path = "%s/admin_timeout" % self.path
if os.path.isfile(path):
with open(path, 'w') as file_fd:
file_fd.write(str(timeout))

def _get_io_timeout(self):
self._check_self()
path = "%s/io_timeout" % self.path
_timeout = 0
if os.path.isfile(path):
with open(path, 'r') as file_fd:
_timeout = int(file_fd.read().strip())
return _timeout

io_timeout = property(_get_io_timeout,
doc = "Get the passthru IO command timeout.")

def set_io_timeout(self, timeout):
self._check_self()
path = "%s/io_timeout" % self.path
if os.path.isfile(path):
with open(path, 'w') as file_fd:
file_fd.write(str(timeout))

@classmethod
def setup(cls, subsys, p, err_func):
try:
pt = Passthru(subsys)
except CFSError as e:
err_func("Could not create Passthru object: %s" % e)
return
pt._setup_attrs(p, err_func)
if 'clear_ids' in p:
pt.set_clear_ids(int(p['clear_ids']))
if 'admin_timeout' in p:
pt.set_admin_timeout(int(p['admin_timeout']))
if 'io_timeout' in p:
pt.set_io_timeout(int(p['io_timeout']))

def dump(self):
d = super(Passthru, self).dump()
d['clear_ids'] = self.ids
d['admin_timeout'] = self.admin_timeout
d['io_timeout'] = self.io_timeout
return d

class Port(CFSNode):
'''
Expand Down Expand Up @@ -816,7 +934,7 @@ def __init__(self, port, grpid, mode='any'):
raise CFSError("Need grpid for lookup")

grpids = [n.grpid for n in port.ana_groups]
for index in moves.xrange(2, self.MAX_GRPID + 1):
for index in range(2, self.MAX_GRPID + 1):
if index not in grpids:
grpid = index
break
Expand Down
103 changes: 98 additions & 5 deletions nvmetcli
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ from __future__ import print_function

import os
import sys
import configshell_fb as configshell
import configshell
import nvmet as nvme
import errno
from string import hexdigits
Expand All @@ -33,9 +33,9 @@ def ngiud_set(nguid):
return any(c in hexdigits and c != '0' for c in nguid)


class UINode(configshell.node.ConfigNode):
class UINode(configshell.ConfigNode):
def __init__(self, name, parent=None, cfnode=None, shell=None):
configshell.node.ConfigNode.__init__(self, name, parent, shell)
configshell.ConfigNode.__init__(self, name, parent, shell)
self.cfnode = cfnode
if self.cfnode:
if self.cfnode.attr_groups:
Expand Down Expand Up @@ -94,10 +94,21 @@ class UINode(configshell.node.ConfigNode):


class UIRootNode(UINode):
ui_desc_discovery = {
'nqn': ('string', 'Discovery NQN'),
}
def __init__(self, shell):
UINode.__init__(self, '/', parent=None, cfnode=nvme.Root(),
shell=shell)

def summary(self):
info = []
try:
info.append("discovery=" + self.cfnode.get_attr("discovery", "nqn"))
except nvme.nvme.CFSError:
pass
return (", ".join(info), True)

def refresh(self):
self._children = set([])
UISubsystemsNode(self)
Expand Down Expand Up @@ -166,6 +177,7 @@ class UISubsystemNode(UINode):
self._children = set([])
UINamespacesNode(self)
UIAllowedHostsNode(self)
UIPassthruNode(self)

def summary(self):
info = []
Expand All @@ -175,6 +187,82 @@ class UISubsystemNode(UINode):
info.append("serial=" + self.cfnode.get_attr("attr", "serial"))
return (", ".join(info), True)

class UIPassthruNode(UINode):
ui_desc_device = {
'path' : ('string', 'Passthru device path')
}

def __init__(self, parent):
passthru = nvme.Passthru(parent.cfnode)
UINode.__init__(self, 'passthru', parent, passthru)

def refresh(self):
pass

Comment on lines +199 to +201
def ui_command_enable(self):
if self.cfnode.get_enable():
self.shell.log.info("The passthru is already enabled.")
else:
try:
self.cfnode.set_enable(1)
self.shell.log.info("The passthru has been enabled.")
except Exception as e:
raise configshell.ExecutionError(
"The passthru could not be enabled.")

def ui_command_disable(self):
if not self.cfnode.get_enable():
self.shell.log.info("The passthru is already disabled.")
else:
try:
self.cfnode.set_enable(0)
self.shell.log.info("The passthru has been disabled.")
except Exception as e:
raise configshell.ExecutionError(
"The passthru could not be disabled.")

def ui_command_clear_ids(self, clear):
'''
If I{clear} is set to non-zero then clears the passthru namespace
unique identifiers EUI/GUID/UUID.
'''
try:
self.cfnode.set_clear_ids(clear)
except Exception as e:
raise configshell.ExecutionError(
"Failed to set clear_ids for this passthru target.")

def ui_command_admin_timeout(self, timeout):
'''
Sets the timeout of admin passthru command.
'''
try:
self.cfnode.set_admin_timeout(timeout)
except Exception as e:
raise configshell.ExecutionError(
"Failed to set the admin passthru command timeout.")

def ui_command_io_timeout(self, timeout):
'''
Sets the timeout of IO passthru command.
'''
try:
self.cfnode.set_io_timeout(timeout)
except Exception as e:
raise configshell.ExecutionError(
"Failed to set the IO passthru command timeout.")

def summary(self):
info = []
info.append("path=" + self.cfnode.get_attr("device", "path"))
if self.cfnode.ids != 0:
info.append("clear_ids=" + str(self.cfnode.ids))
if self.cfnode.admin_timeout != 0:
info.append("admin_timeout=" + str(self.cfnode.admin_timeout))
if self.cfnode.io_timeout != 0:
info.append("io_timeout=" + str(self.cfnode.io_timeout))
info.append("enabled" if self.cfnode.get_enable() else "disabled")
return (", ".join(info), True)

class UINamespacesNode(UINode):
def __init__(self, parent):
Expand Down Expand Up @@ -283,6 +371,11 @@ class UINamespaceNode(UINode):
info.append("nguid=" + ns_nguid)
if self.cfnode.grpid != 0:
info.append("grpid=" + str(self.cfnode.grpid))
try:
resv_enable = self.cfnode.get_attr("resv", "enable")
info.append("resv_enable=" + str(resv_enable))
except nvme.nvme.CFSError:
pass
info.append("enabled" if self.cfnode.get_enable() else "disabled")
ns_enabled = self.cfnode.get_enable()
return (", ".join(info), True if ns_enabled == 1 else ns_enabled)
Expand Down Expand Up @@ -704,7 +797,7 @@ def clear(unused):


def ls(unused):
shell = configshell.shell.ConfigShell('~/.nvmetcli')
shell = configshell.ConfigShell('~/.nvmetcli')
UIRootNode(shell)
shell.run_cmdline("ls")
sys.exit(0)
Expand Down Expand Up @@ -737,7 +830,7 @@ def main():
return

try:
shell = configshell.shell.ConfigShell('~/.nvmetcli')
shell = configshell.ConfigShell('~/.nvmetcli')
UIRootNode(shell)
except Exception as msg:
shell.log.error(str(msg))
Comment on lines 832 to 836
Expand Down
2 changes: 1 addition & 1 deletion rpm/nvmetcli.spec.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Source: nvmetcli-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-rpmroot
BuildArch: noarch
BuildRequires: python-devel python-setuptools systemd-units
Requires: python-configshell python-kmod python-six
Requires: python-configshell python-kmod
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
Expand Down
Loading