subsystems = property(_list_subsystems,
doc="Get the list of Subsystems.")
+ def _list_ports(self):
+ self._check_self()
+
+ for d in os.listdir("%s/ports/" % self._path):
+ yield Port(self, d, 'lookup')
+
+ ports = property(_list_ports,
+ doc="Get the list of Ports.")
+
def save_to_file(self, savefile=None):
'''
Write the configuration in json format to a file.
for s in self.subsystems:
s.delete()
+ for p in self.ports:
+ p.delete()
def restore(self, config, clear_existing=False, abort_on_error=False):
'''
Subsystem.setup(t, err_func)
+ for index, t in enumerate(config.get('ports', [])):
+ if 'portid' not in t:
+ err_func("'portid' not defined in port %d" % index)
+ continue
+
+ Port.setup(self, t, err_func)
+
return errors
def restore_from_file(self, savefile=None, clear_existing=True,
def dump(self):
d = super(Root, self).dump()
d['subsystems'] = [s.dump() for s in self.subsystems]
+ d['ports'] = [p.dump() for p in self.ports]
return d
return d
+class Port(CFSNode):
+ '''
+ This is an interface to a NVMe Namespace in configFS.
+ A Namespace is identified by its parent Subsystem and Namespace ID.
+ '''
+
+ MAX_PORTID = 8192
+
+ def __repr__(self):
+ return "<Port %d>" % self.portid
+
+ def __init__(self, root, portid=None, mode='any'):
+ super(Port, self).__init__()
+
+ if portid is None:
+ portids = [p.portid for p in root.ports]
+ for index in xrange(0, 1 << 16):
+ if index not in portids:
+ portid = index
+ break
+ if portid is None:
+ raise CFSError("All Port IDs 0-%d in use" % 1 << 16)
+ else:
+ portid = int(portid)
+ if portid < 0 or portid > self.MAX_PORTID:
+ raise CFSError("Port ID must be 0 to %d" % self.MAX_PORTID)
+
+ self.attr_groups = ['addr']
+ self._portid = portid
+ self._path = "%s/ports/%d" % (self.configfs_dir, portid)
+ self._create_in_cfs(mode)
+
+ def _get_portid(self):
+ return self._portid
+
+ portid = property(_get_portid,
+ doc="Get the Port ID as an int.")
+
+ @classmethod
+ def setup(cls, root, n, err_func):
+ '''
+ Set up a Namespace object based upon n dict, from saved config.
+ Guard against missing or bad dict items, but keep going.
+ Call 'err_func' for each error.
+ '''
+
+ if 'portid' not in n:
+ err_func("'portid' not defined for Port")
+ return
+
+ try:
+ port = Port(root, n['portid'])
+ except CFSError as e:
+ err_func("Could not create Port object: %s" % e)
+ return
+
+ port._setup_attrs(n, err_func)
+
+ def dump(self):
+ d = super(Port, self).dump()
+ d['portid'] = self.portid
+ return d
+
+
def _test():
from doctest import testmod
testmod()
s.delete()
self.assertEqual(len(list(root.subsystems)), 0)
+ def test_port(self):
+ root = nvme.Root()
+ root.clear_existing()
+ for p in root.ports:
+ self.assertTrue(False, 'Found Port after clear')
+
+ # create mode
+ p1 = nvme.Port(root, portid=0, mode='create')
+ self.assertIsNotNone(p1)
+ self.assertEqual(len(list(root.ports)), 1)
+
+ # any mode, should create
+ p2 = nvme.Port(root, portid=1, mode='any')
+ self.assertIsNotNone(p2)
+ self.assertEqual(len(list(root.ports)), 2)
+
+ # automatic portid
+ p3 = nvme.Port(root, mode='create')
+ self.assertIsNotNone(p3)
+ self.assertNotEqual(p3, p1)
+ self.assertNotEqual(p3, p2)
+ self.assertEqual(len(list(root.ports)), 3)
+
+ # duplicate
+ self.assertRaises(nvme.CFSError, nvme.Port,
+ root, portid=0, mode='create')
+ self.assertEqual(len(list(root.ports)), 3)
+
+ # lookup using any, should not create
+ p = nvme.Port(root, portid=0, mode='any')
+ self.assertEqual(p1, p)
+ self.assertEqual(len(list(root.ports)), 3)
+
+ # lookup only
+ p = nvme.Port(root, portid=1, mode='lookup')
+ self.assertEqual(p2, p)
+ self.assertEqual(len(list(root.ports)), 3)
+
+ # lookup without portid
+ self.assertRaises(nvme.CFSError, nvme.Port, root, mode='lookup')
+
+ # and delete them all
+ for p in root.ports:
+ p.delete()
+ self.assertEqual(len(list(root.ports)), 0)
+
+ def test_loop_port(self):
+ root = nvme.Root()
+ root.clear_existing()
+
+ p = nvme.Port(root, portid=0, mode='create')
+
+ self.assertFalse(p.get_enable())
+ self.assertTrue('addr' in p.attr_groups)
+
+ # no trtype set yet, should fail
+ self.assertRaises(nvme.CFSError, p.set_enable, 1)
+
+ # now set trtype to loop and other attrs and enable
+ p.set_attr('addr', 'trtype', 'loop')
+ p.set_attr('addr', 'adrfam', 'ipv4')
+ p.set_attr('addr', 'traddr', '192.168.0.1')
+ p.set_attr('addr', 'treq', 'not required')
+ p.set_attr('addr', 'trsvcid', '1023')
+ p.set_enable(1)
+ self.assertTrue(p.get_enable())
+
+ # test double enable
+ p.set_enable(1)
+
+ # test that we can't write to attrs while enabled
+ self.assertRaises(nvme.CFSError, p.set_attr, 'addr', 'trtype',
+ 'rdma')
+ self.assertRaises(nvme.CFSError, p.set_attr, 'addr', 'adrfam',
+ 'ipv6')
+ self.assertRaises(nvme.CFSError, p.set_attr, 'addr', 'traddr',
+ '10.0.0.1')
+ self.assertRaises(nvme.CFSError, p.set_attr, 'addr', 'treq',
+ 'required')
+ self.assertRaises(nvme.CFSError, p.set_attr, 'addr', 'trsvcid',
+ '21')
+
+ # disable: once and twice
+ p.set_enable(0)
+ p.set_enable(0)
+
+ # check that the attrs haven't been tampered with
+ self.assertEqual(p.get_attr('addr', 'trtype'), 'loop')
+ self.assertEqual(p.get_attr('addr', 'adrfam'), 'ipv4')
+ self.assertEqual(p.get_attr('addr', 'traddr'), '192.168.0.1')
+ self.assertEqual(p.get_attr('addr', 'treq'), 'not required')
+ self.assertEqual(p.get_attr('addr', 'trsvcid'), '1023')
+
+ # enable again, and remove while enabled
+ p.set_enable(1)
+ p.delete()
+
def test_invalid_input(self):
root = nvme.Root()
root.clear_existing()
self.assertRaises(nvme.CFSError, nvme.Subsystem,
nqn=discover_nqn, mode='create')
+ self.assertRaises(nvme.CFSError, nvme.Port,
+ root=root, portid=1 << 17, mode='create')
+
def test_save_restore(self):
root = nvme.Root()
root.clear_existing()
nguid = n.get_attr('device', 'nguid')
+ p = nvme.Port(root, portid=66, mode='create')
+ p.set_attr('addr', 'trtype', 'loop')
+ p.set_attr('addr', 'adrfam', 'ipv4')
+ p.set_attr('addr', 'traddr', '192.168.0.1')
+ p.set_attr('addr', 'treq', 'not required')
+ p.set_attr('addr', 'trsvcid', '1023')
+ p.set_enable(1)
+
+ # save, clear, and restore
root.save_to_file('test.json')
root.clear_existing()
root.restore_from_file('test.json')
# rebuild our view of the world
s = nvme.Subsystem(nqn='testnqn', mode='lookup')
n = nvme.Namespace(s, nsid=42, mode='lookup')
+ p = nvme.Port(root, portid=66, mode='lookup')
# and check everything is still the same
self.assertTrue(n.get_enable())
self.assertEqual(n.get_attr('device', 'path'), '/dev/ram0')
self.assertEqual(n.get_attr('device', 'nguid'), nguid)
+
+ self.assertEqual(p.get_attr('addr', 'trtype'), 'loop')
+ self.assertEqual(p.get_attr('addr', 'adrfam'), 'ipv4')
+ self.assertEqual(p.get_attr('addr', 'traddr'), '192.168.0.1')
+ self.assertEqual(p.get_attr('addr', 'treq'), 'not required')
+ self.assertEqual(p.get_attr('addr', 'trsvcid'), '1023')
def refresh(self):
self._children = set([])
UISubsystemsNode(self)
+ UIPortsNode(self)
def ui_command_restoreconfig(self, savefile=None, clear_existing=False):
'''
self.shell.log.info("The Namespace has been disabled.")
except Exception as e:
raise configshell.ExecutionError(
- "The Namespace could not be dsiabled.")
+ "The Namespace could not be disabled.")
+
+
+class UIPortsNode(UINode):
+ def __init__(self, parent):
+ UINode.__init__(self, 'ports', parent)
+
+ def refresh(self):
+ self._children = set([])
+ for port in self.parent.cfnode.ports:
+ UIPortNode(self, port)
+
+ def ui_command_create(self, portid):
+ '''
+ Creates a new NVMe port. If I{port} is ommited, then the new Port
+ will use the next available portid.
+
+ SEE ALSO
+ ========
+ B{delete}
+ '''
+ port = nvme.Port(self.parent.parent.cfnode, portid, mode='create')
+ UIPortNode(self, port)
+
+ def ui_command_delete(self, portid):
+ '''
+ Recursively deletes the NVMe Port with the specified I{port}, and all
+ objects hanging under it.
+
+ SEE ALSO
+ ========
+ B{delete}
+ '''
+ port = nvme.Port(self.parent.parent.cfnode, portid, mode='lookup')
+ port.delete()
+ self.refresh()
+
+
+class UIPortNode(UINode):
+ def __init__(self, parent, cfnode):
+ UINode.__init__(self, str(cfnode.portid), parent, cfnode)
+
+ def status(self):
+ if self.cfnode.get_enable():
+ return "enabled"
+ return "disabled"
+
+ def ui_command_enable(self):
+ '''
+ Enables the current Port.
+
+ SEE ALSO
+ ========
+ B{disable}
+ '''
+ if self.cfnode.get_enable():
+ self.shell.log.info("The Port is already enabled.")
+ else:
+ try:
+ self.cfnode.set_enable(1)
+ self.shell.log.info("The Port has been enabled.")
+ except Exception as e:
+ raise configshell.ExecutionError(
+ "The Port could not be enabled.")
+
+ def ui_command_disable(self):
+ '''
+ Disables the current Port.
+
+ SEE ALSO
+ ========
+ B{enable}
+ '''
+ if not self.cfnode.get_enable():
+ self.shell.log.info("The Port is already disabled.")
+ else:
+ try:
+ self.cfnode.set_enable(0)
+ self.shell.log.info("The Port has been disabled.")
+ except Exception as e:
+ raise configshell.ExecutionError(
+ "The Port could not be disabled.")
def usage():