|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Implements access to the NVMe target configfs hierarchy
|
|
Packit |
01965a |
|
|
Packit |
01965a |
Copyright (c) 2011-2013 by Datera, Inc.
|
|
Packit |
01965a |
Copyright (c) 2011-2014 by Red Hat, Inc.
|
|
Packit |
01965a |
Copyright (c) 2016 by HGST, a Western Digital Company.
|
|
Packit |
01965a |
|
|
Packit |
01965a |
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
Packit |
01965a |
not use this file except in compliance with the License. You may obtain
|
|
Packit |
01965a |
a copy of the License at
|
|
Packit |
01965a |
|
|
Packit |
01965a |
http://www.apache.org/licenses/LICENSE-2.0
|
|
Packit |
01965a |
|
|
Packit |
01965a |
Unless required by applicable law or agreed to in writing, software
|
|
Packit |
01965a |
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
Packit |
01965a |
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
Packit |
01965a |
License for the specific language governing permissions and limitations
|
|
Packit |
01965a |
under the License.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
import os
|
|
Packit |
01965a |
import stat
|
|
Packit |
01965a |
import uuid
|
|
Packit |
01965a |
import json
|
|
Packit |
01965a |
from glob import iglob as glob
|
|
Packit |
01965a |
|
|
Packit |
01965a |
DEFAULT_SAVE_FILE = '/etc/nvmet/config.json'
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class CFSError(Exception):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Generic slib error.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
pass
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class CFSNotFound(CFSError):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
The underlying configfs object does not exist. Happens when
|
|
Packit |
01965a |
calling methods of an object that is instantiated but have
|
|
Packit |
01965a |
been deleted from congifs, or when trying to lookup an
|
|
Packit |
01965a |
object that does not exist.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
pass
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class CFSNode(object):
|
|
Packit |
01965a |
|
|
Packit |
01965a |
configfs_dir = '/sys/kernel/config/nvmet'
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __init__(self):
|
|
Packit |
01965a |
self._path = self.configfs_dir
|
|
Packit |
01965a |
self._enable = None
|
|
Packit |
01965a |
self.attr_groups = []
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __eq__(self, other):
|
|
Packit |
01965a |
return self._path == other._path
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __ne__(self, other):
|
|
Packit |
01965a |
return self._path != other._path
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _get_path(self):
|
|
Packit |
01965a |
return self._path
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _create_in_cfs(self, mode):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Creates the configFS node if it does not already exist, depending on
|
|
Packit |
01965a |
the mode.
|
|
Packit |
01965a |
any -> makes sure it exists, also works if the node already does exist
|
|
Packit |
01965a |
lookup -> make sure it does NOT exist
|
|
Packit |
01965a |
create -> create the node which must not exist beforehand
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
if mode not in ['any', 'lookup', 'create']:
|
|
Packit |
01965a |
raise CFSError("Invalid mode: %s" % mode)
|
|
Packit |
01965a |
if self.exists and mode == 'create':
|
|
Packit |
01965a |
raise CFSError("This %s already exists in configFS" %
|
|
Packit |
01965a |
self.__class__.__name__)
|
|
Packit |
01965a |
elif not self.exists and mode == 'lookup':
|
|
Packit |
01965a |
raise CFSNotFound("No such %s in configfs: %s" %
|
|
Packit |
01965a |
(self.__class__.__name__, self.path))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not self.exists:
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
os.mkdir(self.path)
|
|
Packit |
01965a |
except:
|
|
Packit |
01965a |
raise CFSError("Could not create %s in configFS" %
|
|
Packit |
01965a |
self.__class__.__name__)
|
|
Packit |
01965a |
self.get_enable()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _exists(self):
|
|
Packit |
01965a |
return os.path.isdir(self.path)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _check_self(self):
|
|
Packit |
01965a |
if not self.exists:
|
|
Packit |
01965a |
raise CFSNotFound("This %s does not exist in configFS" %
|
|
Packit |
01965a |
self.__class__.__name__)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def list_attrs(self, group, writable=None):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
@param group: The attribute group
|
|
Packit |
01965a |
@param writable: If None (default), returns all attributes, if True,
|
|
Packit |
01965a |
returns read-write attributes, if False, returns just the read-only
|
|
Packit |
01965a |
attributes.
|
|
Packit |
01965a |
@type writable: bool or None
|
|
Packit |
01965a |
@return: A list of existing attribute names as strings.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
names = [os.path.basename(name).split('_', 1)[1]
|
|
Packit |
01965a |
for name in glob("%s/%s_*" % (self._path, group))
|
|
Packit |
01965a |
if os.path.isfile(name)]
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if writable is True:
|
|
Packit |
01965a |
names = [name for name in names
|
|
Packit |
01965a |
if self._attr_is_writable(group, name)]
|
|
Packit |
01965a |
elif writable is False:
|
|
Packit |
01965a |
names = [name for name in names
|
|
Packit |
01965a |
if not self._attr_is_writable(group, name)]
|
|
Packit |
01965a |
|
|
Packit |
01965a |
names.sort()
|
|
Packit |
01965a |
return names
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _attr_is_writable(self, group, name):
|
|
Packit |
01965a |
s = os.stat("%s/%s_%s" % (self._path, group, name))
|
|
Packit |
01965a |
return s[stat.ST_MODE] & stat.S_IWUSR
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def set_attr(self, group, attribute, value):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Sets the value of a named attribute.
|
|
Packit |
01965a |
The attribute must exist in configFS.
|
|
Packit |
01965a |
@param group: The attribute group
|
|
Packit |
01965a |
@param attribute: The attribute's name.
|
|
Packit |
01965a |
@param value: The attribute's value.
|
|
Packit |
01965a |
@type value: string
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
path = "%s/%s_%s" % (self.path, str(group), str(attribute))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not os.path.isfile(path):
|
|
Packit |
01965a |
raise CFSError("Cannot find attribute: %s" % path)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if self._enable:
|
|
Packit |
01965a |
raise CFSError("Cannot set attribute while %s is enabled" %
|
|
Packit |
01965a |
self.__class__.__name__)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
with open(path, 'w') as file_fd:
|
|
Packit |
01965a |
file_fd.write(str(value))
|
|
Packit |
01965a |
except Exception as e:
|
|
Packit |
01965a |
raise CFSError("Cannot set attribute %s: %s" % (path, e))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def get_attr(self, group, attribute):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Gets the value of a named attribute.
|
|
Packit |
01965a |
@param group: The attribute group
|
|
Packit |
01965a |
@param attribute: The attribute's name.
|
|
Packit |
01965a |
@return: The named attribute's value, as a string.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
path = "%s/%s_%s" % (self.path, str(group), str(attribute))
|
|
Packit |
01965a |
if not os.path.isfile(path):
|
|
Packit |
01965a |
raise CFSError("Cannot find attribute: %s" % path)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
with open(path, 'r') as file_fd:
|
|
Packit |
01965a |
return file_fd.read().strip()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def get_enable(self):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
path = "%s/enable" % self.path
|
|
Packit |
01965a |
if not os.path.isfile(path):
|
|
Packit |
01965a |
return None
|
|
Packit |
01965a |
|
|
Packit |
01965a |
with open(path, 'r') as file_fd:
|
|
Packit |
01965a |
self._enable = int(file_fd.read().strip())
|
|
Packit |
01965a |
return self._enable
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def set_enable(self, value):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
path = "%s/enable" % self.path
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not os.path.isfile(path) or self._enable is None:
|
|
Packit |
01965a |
raise CFSError("Cannot enable %s" % self.path)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
with open(path, 'w') as file_fd:
|
|
Packit |
01965a |
file_fd.write(str(value))
|
|
Packit |
01965a |
except Exception as e:
|
|
Packit |
01965a |
raise CFSError("Cannot enable %s: %s (%s)" %
|
|
Packit |
01965a |
(self.path, e, value))
|
|
Packit |
01965a |
self._enable = value
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def delete(self):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
If the underlying configFS object does not exist, this method does
|
|
Packit |
01965a |
nothing. If the underlying configFS object exists, this method attempts
|
|
Packit |
01965a |
to delete it.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
if self.exists:
|
|
Packit |
01965a |
os.rmdir(self.path)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
path = property(_get_path,
|
|
Packit |
01965a |
doc="Get the configFS object path.")
|
|
Packit |
01965a |
exists = property(_exists,
|
|
Packit |
01965a |
doc="Is True as long as the underlying configFS object exists. "
|
|
Packit |
01965a |
+ "If the underlying configFS objects gets deleted "
|
|
Packit |
01965a |
+ "either by calling the delete() method, or by any "
|
|
Packit |
01965a |
+ "other means, it will be False.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = {}
|
|
Packit |
01965a |
for group in self.attr_groups:
|
|
Packit |
01965a |
a = {}
|
|
Packit |
01965a |
for i in self.list_attrs(group, writable=True):
|
|
Packit |
01965a |
a[str(i)] = self.get_attr(group, i)
|
|
Packit |
01965a |
d[str(group)] = a
|
|
Packit |
01965a |
if self._enable is not None:
|
|
Packit |
01965a |
d['enable'] = self._enable
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _setup_attrs(self, attr_dict, err_func):
|
|
Packit |
01965a |
for group in self.attr_groups:
|
|
Packit Service |
7c86dd |
for name, value in attr_dict.get(group, {}).iteritems():
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
self.set_attr(group, name, value)
|
|
Packit |
01965a |
except CFSError as e:
|
|
Packit |
01965a |
err_func(str(e))
|
|
Packit |
01965a |
enable = attr_dict.get('enable')
|
|
Packit |
01965a |
if enable is not None:
|
|
Packit |
01965a |
self.set_enable(enable)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class Root(CFSNode):
|
|
Packit |
01965a |
def __init__(self):
|
|
Packit |
01965a |
super(Root, self).__init__()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not os.path.isdir(self.configfs_dir):
|
|
Packit |
01965a |
self._modprobe('nvmet')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not os.path.isdir(self.configfs_dir):
|
|
Packit |
01965a |
raise CFSError("%s does not exist. Giving up." %
|
|
Packit |
01965a |
self.configfs_dir)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
self._path = self.configfs_dir
|
|
Packit |
01965a |
self._create_in_cfs('lookup')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _modprobe(self, modname):
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
from kmodpy import kmod
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
kmod.Kmod().modprobe(modname, quiet=True)
|
|
Packit |
01965a |
except kmod.KmodError:
|
|
Packit |
01965a |
pass
|
|
Packit |
01965a |
except ImportError:
|
|
Packit |
01965a |
pass
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_subsystems(self):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for d in os.listdir("%s/subsystems/" % self._path):
|
|
Packit |
01965a |
yield Subsystem(d, 'lookup')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
subsystems = property(_list_subsystems,
|
|
Packit |
01965a |
doc="Get the list of Subsystems.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_ports(self):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for d in os.listdir("%s/ports/" % self._path):
|
|
Packit |
01965a |
yield Port(d, 'lookup')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
ports = property(_list_ports,
|
|
Packit |
01965a |
doc="Get the list of Ports.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_hosts(self):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for h in os.listdir("%s/hosts/" % self._path):
|
|
Packit |
01965a |
yield Host(h, 'lookup')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
hosts = property(_list_hosts,
|
|
Packit |
01965a |
doc="Get the list of Hosts.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def save_to_file(self, savefile=None):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Write the configuration in json format to a file.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
if savefile:
|
|
Packit |
01965a |
savefile = os.path.expanduser(savefile)
|
|
Packit |
01965a |
else:
|
|
Packit |
01965a |
savefile = DEFAULT_SAVE_FILE
|
|
Packit |
01965a |
|
|
Packit |
01965a |
savefile_abspath = os.path.abspath(savefile)
|
|
Packit |
01965a |
savefile_dir = os.path.dirname(savefile_abspath)
|
|
Packit |
01965a |
if not os.path.exists(savefile_dir):
|
|
Packit |
01965a |
os.makedirs(savefile_dir)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
with open(savefile + ".temp", "w+") as f:
|
|
Packit |
01965a |
os.fchmod(f.fileno(), stat.S_IRUSR | stat.S_IWUSR)
|
|
Packit |
01965a |
f.write(json.dumps(self.dump(), sort_keys=True, indent=2))
|
|
Packit |
01965a |
f.write("\n")
|
|
Packit |
01965a |
f.flush()
|
|
Packit |
01965a |
os.fsync(f.fileno())
|
|
Packit |
01965a |
f.close()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
os.rename(savefile + ".temp", savefile)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def clear_existing(self):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Remove entire current configuration.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for p in self.ports:
|
|
Packit |
01965a |
p.delete()
|
|
Packit |
01965a |
for s in self.subsystems:
|
|
Packit |
01965a |
s.delete()
|
|
Packit |
01965a |
for h in self.hosts:
|
|
Packit |
01965a |
h.delete()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def restore(self, config, clear_existing=False, abort_on_error=False):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Takes a dict generated by dump() and reconfigures the target to match.
|
|
Packit |
01965a |
Returns list of non-fatal errors that were encountered.
|
|
Packit |
01965a |
Will refuse to restore over an existing configuration unless
|
|
Packit |
01965a |
clear_existing is True.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
if clear_existing:
|
|
Packit |
01965a |
self.clear_existing()
|
|
Packit |
01965a |
else:
|
|
Packit |
01965a |
if any(self.subsystems):
|
|
Packit |
01965a |
raise CFSError("subsystems present, not restoring")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
errors = []
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if abort_on_error:
|
|
Packit |
01965a |
def err_func(err_str):
|
|
Packit |
01965a |
raise CFSError(err_str)
|
|
Packit |
01965a |
else:
|
|
Packit |
01965a |
def err_func(err_str):
|
|
Packit |
01965a |
errors.append(err_str + ", skipped")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
# Create the hosts first because the subsystems reference them
|
|
Packit |
01965a |
for index, t in enumerate(config.get('hosts', [])):
|
|
Packit |
01965a |
if 'nqn' not in t:
|
|
Packit |
01965a |
err_func("'nqn' not defined in host %d" % index)
|
|
Packit |
01965a |
continue
|
|
Packit |
01965a |
|
|
Packit |
01965a |
Host.setup(t, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for index, t in enumerate(config.get('subsystems', [])):
|
|
Packit |
01965a |
if 'nqn' not in t:
|
|
Packit |
01965a |
err_func("'nqn' not defined in subsystem %d" % index)
|
|
Packit |
01965a |
continue
|
|
Packit |
01965a |
|
|
Packit |
01965a |
Subsystem.setup(t, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for index, t in enumerate(config.get('ports', [])):
|
|
Packit |
01965a |
if 'portid' not in t:
|
|
Packit |
01965a |
err_func("'portid' not defined in port %d" % index)
|
|
Packit |
01965a |
continue
|
|
Packit |
01965a |
|
|
Packit |
01965a |
Port.setup(self, t, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
return errors
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def restore_from_file(self, savefile=None, clear_existing=True,
|
|
Packit |
01965a |
abort_on_error=False):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Restore the configuration from a file in json format.
|
|
Packit |
01965a |
Returns a list of non-fatal errors. If abort_on_error is set,
|
|
Packit |
01965a |
it will raise the exception instead of continuing.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
if savefile:
|
|
Packit |
01965a |
savefile = os.path.expanduser(savefile)
|
|
Packit |
01965a |
else:
|
|
Packit |
01965a |
savefile = DEFAULT_SAVE_FILE
|
|
Packit |
01965a |
|
|
Packit |
01965a |
with open(savefile, "r") as f:
|
|
Packit |
01965a |
config = json.loads(f.read())
|
|
Packit |
01965a |
return self.restore(config, clear_existing=clear_existing,
|
|
Packit |
01965a |
abort_on_error=abort_on_error)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = super(Root, self).dump()
|
|
Packit |
01965a |
d['subsystems'] = [s.dump() for s in self.subsystems]
|
|
Packit |
01965a |
d['ports'] = [p.dump() for p in self.ports]
|
|
Packit |
01965a |
d['hosts'] = [h.dump() for h in self.hosts]
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class Subsystem(CFSNode):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
This is an interface to a NVMe Subsystem in configFS.
|
|
Packit |
01965a |
A Subsystem is identified by its NQN.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __repr__(self):
|
|
Packit |
01965a |
return "<Subsystem %s>" % self.nqn
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __init__(self, nqn=None, mode='any'):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
@param nqn: The Subsystems' NQN.
|
|
Packit |
01965a |
If no NQN is specified, one will be generated.
|
|
Packit |
01965a |
@type nqn: string
|
|
Packit |
01965a |
@param mode:An optional string containing the object creation mode:
|
|
Packit |
01965a |
- I{'any'} means the configFS object will be either looked up
|
|
Packit |
01965a |
or created.
|
|
Packit |
01965a |
- I{'lookup'} means the object MUST already exist configFS.
|
|
Packit |
01965a |
- I{'create'} means the object must NOT already exist in configFS.
|
|
Packit |
01965a |
@type mode:string
|
|
Packit |
01965a |
@return: A Subsystem object.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
super(Subsystem, self).__init__()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if nqn is None:
|
|
Packit |
01965a |
if mode == 'lookup':
|
|
Packit |
01965a |
raise CFSError("Need NQN for lookup")
|
|
Packit |
01965a |
nqn = self._generate_nqn()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
self.nqn = nqn
|
|
Packit |
01965a |
self.attr_groups = ['attr']
|
|
Packit |
01965a |
self._path = "%s/subsystems/%s" % (self.configfs_dir, nqn)
|
|
Packit |
01965a |
self._create_in_cfs(mode)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _generate_nqn(self):
|
|
Packit |
01965a |
prefix = "nqn.2014-08.org.nvmexpress:NVMf:uuid"
|
|
Packit |
01965a |
name = str(uuid.uuid4())
|
|
Packit |
01965a |
return "%s:%s" % (prefix, name)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def delete(self):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Recursively deletes a Subsystem object.
|
|
Packit |
01965a |
This will delete all attached Namespace objects and then the
|
|
Packit |
01965a |
Subsystem itself.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
for ns in self.namespaces:
|
|
Packit |
01965a |
ns.delete()
|
|
Packit |
01965a |
for h in self.allowed_hosts:
|
|
Packit |
01965a |
self.remove_allowed_host(h)
|
|
Packit |
01965a |
super(Subsystem, self).delete()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_namespaces(self):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
for d in os.listdir("%s/namespaces/" % self._path):
|
|
Packit |
01965a |
yield Namespace(self, int(d), 'lookup')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
namespaces = property(_list_namespaces,
|
|
Packit |
01965a |
doc="Get the list of Namespaces for the Subsystem.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_allowed_hosts(self):
|
|
Packit |
01965a |
return [os.path.basename(name)
|
|
Packit |
01965a |
for name in os.listdir("%s/allowed_hosts/" % self._path)]
|
|
Packit |
01965a |
|
|
Packit |
01965a |
allowed_hosts = property(_list_allowed_hosts,
|
|
Packit |
01965a |
doc="Get the list of Allowed Hosts for the Subsystem.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def add_allowed_host(self, nqn):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Enable access for the host identified by I{nqn} to the Subsystem
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
os.symlink("%s/hosts/%s" % (self.configfs_dir, nqn),
|
|
Packit |
01965a |
"%s/allowed_hosts/%s" % (self._path, nqn))
|
|
Packit |
01965a |
except Exception as e:
|
|
Packit |
01965a |
raise CFSError("Could not symlink %s in configFS: %s" % (nqn, e))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def remove_allowed_host(self, nqn):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Disable access for the host identified by I{nqn} to the Subsystem
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
os.unlink("%s/allowed_hosts/%s" % (self._path, nqn))
|
|
Packit |
01965a |
except Exception as e:
|
|
Packit |
01965a |
raise CFSError("Could not unlink %s in configFS: %s" % (nqn, e))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
@classmethod
|
|
Packit |
01965a |
def setup(cls, t, err_func):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Set up Subsystem objects based upon t dict, from saved config.
|
|
Packit |
01965a |
Guard against missing or bad dict items, but keep going.
|
|
Packit |
01965a |
Call 'err_func' for each error.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if 'nqn' not in t:
|
|
Packit |
01965a |
err_func("'nqn' not defined for Subsystem")
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
s = Subsystem(t['nqn'])
|
|
Packit |
01965a |
except CFSError as e:
|
|
Packit |
01965a |
err_func("Could not create Subsystem object: %s" % e)
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
for ns in t.get('namespaces', []):
|
|
Packit |
01965a |
Namespace.setup(s, ns, err_func)
|
|
Packit |
01965a |
for h in t.get('allowed_hosts', []):
|
|
Packit |
01965a |
s.add_allowed_host(h)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
s._setup_attrs(t, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = super(Subsystem, self).dump()
|
|
Packit |
01965a |
d['nqn'] = self.nqn
|
|
Packit |
01965a |
d['namespaces'] = [ns.dump() for ns in self.namespaces]
|
|
Packit |
01965a |
d['allowed_hosts'] = self.allowed_hosts
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class Namespace(CFSNode):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
This is an interface to a NVMe Namespace in configFS.
|
|
Packit |
01965a |
A Namespace is identified by its parent Subsystem and Namespace ID.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
MAX_NSID = 8192
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __repr__(self):
|
|
Packit |
01965a |
return "<Namespace %d>" % self.nsid
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __init__(self, subsystem, nsid=None, mode='any'):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
@param subsystem: The parent Subsystem object
|
|
Packit |
01965a |
@param nsid: The Namespace identifier
|
|
Packit |
01965a |
If no nsid is specified, the next free one will be used.
|
|
Packit |
01965a |
@type nsid: int
|
|
Packit |
01965a |
@param mode:An optional string containing the object creation mode:
|
|
Packit |
01965a |
- I{'any'} means the configFS object will be either looked up
|
|
Packit |
01965a |
or created.
|
|
Packit |
01965a |
- I{'lookup'} means the object MUST already exist configFS.
|
|
Packit |
01965a |
- I{'create'} means the object must NOT already exist in configFS.
|
|
Packit |
01965a |
@type mode:string
|
|
Packit |
01965a |
@return: A Namespace object.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
super(Namespace, self).__init__()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not isinstance(subsystem, Subsystem):
|
|
Packit |
01965a |
raise CFSError("Invalid parent class")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if nsid is None:
|
|
Packit |
01965a |
if mode == 'lookup':
|
|
Packit |
01965a |
raise CFSError("Need NSID for lookup")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
nsids = [n.nsid for n in subsystem.namespaces]
|
|
Packit |
01965a |
for index in xrange(1, self.MAX_NSID + 1):
|
|
Packit |
01965a |
if index not in nsids:
|
|
Packit |
01965a |
nsid = index
|
|
Packit |
01965a |
break
|
|
Packit |
01965a |
if nsid is None:
|
|
Packit |
01965a |
raise CFSError("All NSIDs 1-%d in use" % self.MAX_NSID)
|
|
Packit |
01965a |
else:
|
|
Packit |
01965a |
nsid = int(nsid)
|
|
Packit |
01965a |
if nsid < 1 or nsid > self.MAX_NSID:
|
|
Packit |
01965a |
raise CFSError("NSID must be 1 to %d" % self.MAX_NSID)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
self.attr_groups = ['device']
|
|
Packit |
01965a |
self._subsystem = subsystem
|
|
Packit |
01965a |
self._nsid = nsid
|
|
Packit |
01965a |
self._path = "%s/namespaces/%d" % (self.subsystem.path, self.nsid)
|
|
Packit |
01965a |
self._create_in_cfs(mode)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _get_subsystem(self):
|
|
Packit |
01965a |
return self._subsystem
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _get_nsid(self):
|
|
Packit |
01965a |
return self._nsid
|
|
Packit |
01965a |
|
|
Packit |
01965a |
subsystem = property(_get_subsystem,
|
|
Packit |
01965a |
doc="Get the parent Subsystem object.")
|
|
Packit |
01965a |
nsid = property(_get_nsid, doc="Get the NSID as an int.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
@classmethod
|
|
Packit |
01965a |
def setup(cls, subsys, n, err_func):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Set up a Namespace object based upon n dict, from saved config.
|
|
Packit |
01965a |
Guard against missing or bad dict items, but keep going.
|
|
Packit |
01965a |
Call 'err_func' for each error.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if 'nsid' not in n:
|
|
Packit |
01965a |
err_func("'nsid' not defined for Namespace")
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
ns = Namespace(subsys, n['nsid'])
|
|
Packit |
01965a |
except CFSError as e:
|
|
Packit |
01965a |
err_func("Could not create Namespace object: %s" % e)
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
ns._setup_attrs(n, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = super(Namespace, self).dump()
|
|
Packit |
01965a |
d['nsid'] = self.nsid
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class Port(CFSNode):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
This is an interface to a NVMe Port in configFS.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
MAX_PORTID = 8192
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __repr__(self):
|
|
Packit |
01965a |
return "<Port %d>" % self.portid
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __init__(self, portid, mode='any'):
|
|
Packit |
01965a |
super(Port, self).__init__()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
self.attr_groups = ['addr']
|
|
Packit |
01965a |
self._portid = int(portid)
|
|
Packit |
01965a |
self._path = "%s/ports/%d" % (self.configfs_dir, self._portid)
|
|
Packit |
01965a |
self._create_in_cfs(mode)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _get_portid(self):
|
|
Packit |
01965a |
return self._portid
|
|
Packit |
01965a |
|
|
Packit |
01965a |
portid = property(_get_portid, doc="Get the Port ID as an int.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_subsystems(self):
|
|
Packit |
01965a |
return [os.path.basename(name)
|
|
Packit |
01965a |
for name in os.listdir("%s/subsystems/" % self._path)]
|
|
Packit |
01965a |
|
|
Packit |
01965a |
subsystems = property(_list_subsystems,
|
|
Packit |
01965a |
doc="Get the list of Subsystem for this Port.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def add_subsystem(self, nqn):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Enable access to the Subsystem identified by I{nqn} through this Port.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
os.symlink("%s/subsystems/%s" % (self.configfs_dir, nqn),
|
|
Packit |
01965a |
"%s/subsystems/%s" % (self._path, nqn))
|
|
Packit |
01965a |
except Exception as e:
|
|
Packit |
01965a |
raise CFSError("Could not symlink %s in configFS: %s" % (nqn, e))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def remove_subsystem(self, nqn):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Disable access to the Subsystem identified by I{nqn} through this Port.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
os.unlink("%s/subsystems/%s" % (self._path, nqn))
|
|
Packit |
01965a |
except Exception as e:
|
|
Packit |
01965a |
raise CFSError("Could not unlink %s in configFS: %s" % (nqn, e))
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def delete(self):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Recursively deletes a Port object.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
for s in self.subsystems:
|
|
Packit |
01965a |
self.remove_subsystem(s)
|
|
Packit |
01965a |
for r in self.referrals:
|
|
Packit |
01965a |
r.delete()
|
|
Packit |
01965a |
super(Port, self).delete()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _list_referrals(self):
|
|
Packit |
01965a |
self._check_self()
|
|
Packit |
01965a |
for d in os.listdir("%s/referrals/" % self._path):
|
|
Packit |
01965a |
yield Referral(self, d, 'lookup')
|
|
Packit |
01965a |
|
|
Packit |
01965a |
referrals = property(_list_referrals,
|
|
Packit |
01965a |
doc="Get the list of Referrals for this Port.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
@classmethod
|
|
Packit |
01965a |
def setup(cls, root, n, err_func):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Set up a Namespace object based upon n dict, from saved config.
|
|
Packit |
01965a |
Guard against missing or bad dict items, but keep going.
|
|
Packit |
01965a |
Call 'err_func' for each error.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if 'portid' not in n:
|
|
Packit |
01965a |
err_func("'portid' not defined for Port")
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
port = Port(n['portid'])
|
|
Packit |
01965a |
except CFSError as e:
|
|
Packit |
01965a |
err_func("Could not create Port object: %s" % e)
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
port._setup_attrs(n, err_func)
|
|
Packit |
01965a |
for s in n.get('subsystems', []):
|
|
Packit |
01965a |
port.add_subsystem(s)
|
|
Packit |
01965a |
for r in n.get('referrals', []):
|
|
Packit |
01965a |
Referral.setup(port, r, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = super(Port, self).dump()
|
|
Packit |
01965a |
d['portid'] = self.portid
|
|
Packit |
01965a |
d['subsystems'] = self.subsystems
|
|
Packit |
01965a |
d['referrals'] = [r.dump() for r in self.referrals]
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class Referral(CFSNode):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
This is an interface to a NVMe Referral in configFS.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __repr__(self):
|
|
Packit |
01965a |
return "<Referral %d>" % self.name
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __init__(self, port, name, mode='any'):
|
|
Packit |
01965a |
super(Referral, self).__init__()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if not isinstance(port, Port):
|
|
Packit |
01965a |
raise CFSError("Invalid parent class")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
self.attr_groups = ['addr']
|
|
Packit |
01965a |
self.port = port
|
|
Packit |
01965a |
self._name = name
|
|
Packit |
01965a |
self._path = "%s/referrals/%s" % (self.port.path, self._name)
|
|
Packit |
01965a |
self._create_in_cfs(mode)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _get_name(self):
|
|
Packit |
01965a |
return self._name
|
|
Packit |
01965a |
|
|
Packit |
01965a |
name = property(_get_name, doc="Get the Referral name.")
|
|
Packit |
01965a |
|
|
Packit |
01965a |
@classmethod
|
|
Packit |
01965a |
def setup(cls, port, n, err_func):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Set up a Referral based upon n dict, from saved config.
|
|
Packit |
01965a |
Guard against missing or bad dict items, but keep going.
|
|
Packit |
01965a |
Call 'err_func' for each error.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if 'name' not in n:
|
|
Packit |
01965a |
err_func("'name' not defined for Referral")
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
r = Referral(port, n['name'])
|
|
Packit |
01965a |
except CFSError as e:
|
|
Packit |
01965a |
err_func("Could not create Referral object: %s" % e)
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
r._setup_attrs(n, err_func)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = super(Referral, self).dump()
|
|
Packit |
01965a |
d['name'] = self.name
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
class Host(CFSNode):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
This is an interface to a NVMe Host in configFS.
|
|
Packit |
01965a |
A Host is identified by its NQN.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __repr__(self):
|
|
Packit |
01965a |
return "<Host %s>" % self.nqn
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def __init__(self, nqn, mode='any'):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
@param nqn: The Hosts's NQN.
|
|
Packit |
01965a |
@type nqn: string
|
|
Packit |
01965a |
@param mode:An optional string containing the object creation mode:
|
|
Packit |
01965a |
- I{'any'} means the configFS object will be either looked up
|
|
Packit |
01965a |
or created.
|
|
Packit |
01965a |
- I{'lookup'} means the object MUST already exist configFS.
|
|
Packit |
01965a |
- I{'create'} means the object must NOT already exist in configFS.
|
|
Packit |
01965a |
@type mode:string
|
|
Packit |
01965a |
@return: A Host object.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
super(Host, self).__init__()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
self.nqn = nqn
|
|
Packit |
01965a |
self._path = "%s/hosts/%s" % (self.configfs_dir, nqn)
|
|
Packit |
01965a |
self._create_in_cfs(mode)
|
|
Packit |
01965a |
|
|
Packit |
01965a |
@classmethod
|
|
Packit |
01965a |
def setup(cls, t, err_func):
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
Set up Host objects based upon t dict, from saved config.
|
|
Packit |
01965a |
Guard against missing or bad dict items, but keep going.
|
|
Packit |
01965a |
Call 'err_func' for each error.
|
|
Packit |
01965a |
'''
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if 'nqn' not in t:
|
|
Packit |
01965a |
err_func("'nqn' not defined for Host")
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
try:
|
|
Packit |
01965a |
h = Host(t['nqn'])
|
|
Packit |
01965a |
except CFSError as e:
|
|
Packit |
01965a |
err_func("Could not create Host object: %s" % e)
|
|
Packit |
01965a |
return
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def dump(self):
|
|
Packit |
01965a |
d = super(Host, self).dump()
|
|
Packit |
01965a |
d['nqn'] = self.nqn
|
|
Packit |
01965a |
return d
|
|
Packit |
01965a |
|
|
Packit |
01965a |
|
|
Packit |
01965a |
def _test():
|
|
Packit |
01965a |
from doctest import testmod
|
|
Packit |
01965a |
testmod()
|
|
Packit |
01965a |
|
|
Packit |
01965a |
if __name__ == "__main__":
|
|
Packit |
01965a |
_test()
|