diff --git a/nvmet/nvme.py b/nvmet/nvme.py index b5e6e4f661d5be20e1fd052e7c2be80699844b4e..f8b1ee97527ec591165ad03b6835f357de2f2f2e 100644 --- a/nvmet/nvme.py +++ b/nvmet/nvme.py @@ -659,8 +659,18 @@ class Port(CFSNode): self._check_self() for s in self.subsystems: self.remove_subsystem(s) + for r in self.referrals: + r.delete() super(Port, self).delete() + def _list_referrals(self): + self._check_self() + for d in os.listdir("%s/referrals/" % self._path): + yield Referral(self, d, 'lookup') + + referrals = property(_list_referrals, + doc="Get the list of Referrals for this Port.") + @classmethod def setup(cls, root, n, err_func): ''' @@ -682,11 +692,65 @@ class Port(CFSNode): port._setup_attrs(n, err_func) for s in n.get('subsystems', []): port.add_subsystem(s) + for r in n.get('referrals', []): + Referral.setup(port, r, err_func) def dump(self): d = super(Port, self).dump() d['portid'] = self.portid d['subsystems'] = self.subsystems + d['referrals'] = [r.dump() for r in self.referrals] + return d + + +class Referral(CFSNode): + ''' + This is an interface to a NVMe Referral in configFS. + ''' + + def __repr__(self): + return "<Referral %d>" % self.name + + def __init__(self, port, name, mode='any'): + super(Referral, self).__init__() + + if not isinstance(port, Port): + raise CFSError("Invalid parent class") + + self.attr_groups = ['addr'] + self.port = port + self._name = name + self._path = "%s/referrals/%s" % (self.port.path, self._name) + self._create_in_cfs(mode) + + def _get_name(self): + return self._name + + name = property(_get_name, doc="Get the Referral name.") + + @classmethod + def setup(cls, port, n, err_func): + ''' + Set up a Referral based upon n dict, from saved config. + Guard against missing or bad dict items, but keep going. + Call 'err_func' for each error. + ''' + + if 'name' not in n: + err_func("'name' not defined for Referral") + return + + try: + r = Referral(port, n['name']) + except CFSError as e: + err_func("Could not create Referral object: %s" % e) + return + + r._setup_attrs(n, err_func) + + def dump(self): + d = super(Referral, self).dump() + d['name'] = self.name return d diff --git a/nvmet/test_nvmet.py b/nvmet/test_nvmet.py index 38de70db9c94b7afc42c9986c9b0e7667a72c53a..445050cc4fe6f84bfffacbbc577956602c1fd5de 100644 --- a/nvmet/test_nvmet.py +++ b/nvmet/test_nvmet.py @@ -283,6 +283,89 @@ class TestNvmet(unittest.TestCase): h.delete() self.assertEqual(len(list(root.hosts)), 0) + def test_referral(self): + root = nvme.Root() + root.clear_existing() + + # create port + p = nvme.Port(root, mode='create') + self.assertEqual(len(list(p.referrals)), 0) + + # create mode + r1 = nvme.Referral(p, name="1", mode='create') + self.assertIsNotNone(r1) + self.assertEqual(len(list(p.referrals)), 1) + + # any mode, should create + r2 = nvme.Referral(p, name="2", mode='any') + self.assertIsNotNone(r2) + self.assertEqual(len(list(p.referrals)), 2) + + # duplicate + self.assertRaises(nvme.CFSError, nvme.Referral, + p, name="2", mode='create') + self.assertEqual(len(list(p.referrals)), 2) + + # lookup using any, should not create + r = nvme.Referral(p, name="1", mode='any') + self.assertEqual(r1, r) + self.assertEqual(len(list(p.referrals)), 2) + + # lookup only + r = nvme.Referral(p, name="2", mode='lookup') + self.assertEqual(r2, r) + self.assertEqual(len(list(p.referrals)), 2) + + # non-existant lookup + self.assertRaises(nvme.CFSError, nvme.Referral, p, name="foo", + mode='lookup') + + # basic state + self.assertTrue('addr' in r.attr_groups) + self.assertFalse(r.get_enable()) + + # now set trtype to loop and other attrs and enable + r.set_attr('addr', 'trtype', 'loop') + r.set_attr('addr', 'adrfam', 'ipv4') + r.set_attr('addr', 'traddr', '192.168.0.1') + r.set_attr('addr', 'treq', 'not required') + r.set_attr('addr', 'trsvcid', '1023') + r.set_enable(1) + + # test double enable + r.set_enable(1) + + # test that we can't write to attrs while enabled + self.assertRaises(nvme.CFSError, r.set_attr, 'addr', 'trtype', + 'rdma') + self.assertRaises(nvme.CFSError, r.set_attr, 'addr', 'adrfam', + 'ipv6') + self.assertRaises(nvme.CFSError, r.set_attr, 'addr', 'traddr', + '10.0.0.1') + self.assertRaises(nvme.CFSError, r.set_attr, 'addr', 'treq', + 'required') + self.assertRaises(nvme.CFSError, r.set_attr, 'addr', 'trsvcid', + '21') + + # disable: once and twice + r.set_enable(0) + r.set_enable(0) + + # check that the attrs haven't been tampered with + self.assertEqual(r.get_attr('addr', 'trtype'), 'loop') + self.assertEqual(r.get_attr('addr', 'adrfam'), 'ipv4') + self.assertEqual(r.get_attr('addr', 'traddr'), '192.168.0.1') + self.assertEqual(r.get_attr('addr', 'treq'), 'not required') + self.assertEqual(r.get_attr('addr', 'trsvcid'), '1023') + + # enable again, and try to remove while enabled + r.set_enable(1) + r.delete() + + # remove the other one while disabled: + r1.delete() + self.assertEqual(len(list(p.referrals)), 0) + def test_allowed_hosts(self): root = nvme.Root() diff --git a/nvmetcli b/nvmetcli index 35c5e676e4e0c5e8f6358f2d5f8723320376be7c..58c8d27ba35a4ccdf9869aa142874a478a1cf1f2 100755 --- a/nvmetcli +++ b/nvmetcli @@ -340,6 +340,7 @@ class UIPortNode(UINode): def __init__(self, parent, cfnode): UINode.__init__(self, str(cfnode.portid), parent, cfnode) UIPortSubsystemsNode(self) + UIReferralsNode(self) def status(self): if self.cfnode.get_enable(): @@ -408,6 +409,85 @@ class UIPortSubsystemNode(UINode): UINode.__init__(self, nqn, parent) +class UIReferralsNode(UINode): + def __init__(self, parent): + UINode.__init__(self, 'referrals', parent) + + def refresh(self): + self._children = set([]) + for r in self.parent.cfnode.referrals: + UIReferralNode(self, r) + + def ui_command_create(self, name): + ''' + Creates a new referral. + + SEE ALSO + ======== + B{delete} + ''' + r = nvme.Referral(self.parent.cfnode, name, mode='create') + UIReferralNode(self, r) + + def ui_command_delete(self, name): + ''' + Deletes the referral with the specified I{name}. + + SEE ALSO + ======== + B{create} + ''' + r = nvme.Referral(self.parent.cfnode, name, mode='lookup') + r.delete() + self.refresh() + + +class UIReferralNode(UINode): + def __init__(self, parent, cfnode): + UINode.__init__(self, cfnode.name, parent, cfnode) + + def status(self): + if self.cfnode.get_enable(): + return "enabled" + return "disabled" + + def ui_command_enable(self): + ''' + Enables the current Referral. + + SEE ALSO + ======== + B{disable} + ''' + if self.cfnode.get_enable(): + self.shell.log.info("The Referral is already enabled.") + else: + try: + self.cfnode.set_enable(1) + self.shell.log.info("The Referral has been enabled.") + except Exception as e: + raise configshell.ExecutionError( + "The Referral could not be enabled.") + + def ui_command_disable(self): + ''' + Disables the current Referral. + + SEE ALSO + ======== + B{enable} + ''' + if not self.cfnode.get_enable(): + self.shell.log.info("The Referral is already disabled.") + else: + try: + self.cfnode.set_enable(0) + self.shell.log.info("The Referral has been disabled.") + except Exception as e: + raise configshell.ExecutionError( + "The Referral could not be disabled.") + + class UIHostsNode(UINode): def __init__(self, parent): UINode.__init__(self, 'hosts', parent)