Blame nvmet/nvme.py

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 01965a
            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()