xref: /spdk/python/spdk/spdkcli/ui_node_iscsi.py (revision fecffda6ecf8853b82edccde429b68252f0a62c5)
1#  SPDX-License-Identifier: BSD-3-Clause
2#  Copyright (C) 2018 Intel Corporation.
3#  All rights reserved.
4
5from configshell_fb import ExecutionError
6from ..rpc.client import JSONRPCException
7from .ui_node import UINode
8
9
10class UIISCSI(UINode):
11    def __init__(self, parent):
12        UINode.__init__(self, "iscsi", parent)
13        self.refresh()
14
15    def refresh(self):
16        self._children = set([])
17        UIISCSIDevices(self)
18        UIPortalGroups(self)
19        UIInitiatorGroups(self)
20        UIISCSIConnections(self)
21        UIISCSIAuthGroups(self)
22        UIISCSIGlobalParams(self)
23
24
25class UIISCSIGlobalParams(UINode):
26    def __init__(self, parent):
27        UINode.__init__(self, "global_params", parent)
28        self.refresh()
29
30    def refresh(self):
31        self._children = set([])
32        iscsi_global_params = self.get_root().iscsi_get_options()
33        if not iscsi_global_params:
34            return
35        for param, val in iscsi_global_params.items():
36            UIISCSIGlobalParam("%s: %s" % (param, val), self)
37
38    def ui_command_set_auth(self, g=None, d=None, r=None, m=None):
39        """Set CHAP authentication for discovery service.
40
41        Optional arguments:
42            g = chap_group: Authentication group ID for discovery session
43            d = disable_chap: CHAP for discovery session should be disabled
44            r = require_chap: CHAP for discovery session should be required
45            m = mutual_chap: CHAP for discovery session should be mutual
46        """
47        chap_group = self.ui_eval_param(g, "number", None)
48        disable_chap = self.ui_eval_param(d, "bool", None)
49        require_chap = self.ui_eval_param(r, "bool", None)
50        mutual_chap = self.ui_eval_param(m, "bool", None)
51        self.get_root().iscsi_set_discovery_auth(
52            chap_group=chap_group, disable_chap=disable_chap,
53            require_chap=require_chap, mutual_chap=mutual_chap)
54
55
56class UIISCSIGlobalParam(UINode):
57    def __init__(self, param, parent):
58        UINode.__init__(self, param, parent)
59
60
61class UIISCSIDevices(UINode):
62    def __init__(self, parent):
63        UINode.__init__(self, "target_nodes", parent)
64        self.scsi_devices = list()
65        self.refresh()
66
67    def refresh(self):
68        self._children = set([])
69        self.target_nodes = list(self.get_root().iscsi_get_target_nodes())
70        self.scsi_devices = list(self.get_root().scsi_get_devices())
71        for device in self.scsi_devices:
72            for node in self.target_nodes:
73                if hasattr(device, "device_name") and node['name'] \
74                        == device.device_name:
75                    UIISCSIDevice(device, node, self)
76
77    def delete(self, name):
78        self.get_root().iscsi_delete_target_node(target_node_name=name)
79
80    def ui_command_create(self, name, alias_name, bdev_name_id_pairs,
81                          pg_ig_mappings, queue_depth, g=None, d=None, r=None,
82                          m=None, h=None, t=None):
83        """Create target node
84
85        Positional args:
86           name: Target node name (ASCII)
87           alias_name: Target node alias name (ASCII)
88           bdev_name_id_pairs: List of bdev_name_id_pairs
89           pg_ig_mappings: List of pg_ig_mappings
90           queue_depth: Desired target queue depth
91        Optional args:
92           g = chap_group: Authentication group ID for this target node
93           d = disable_chap: CHAP authentication should be disabled for this target node
94           r = require_chap: CHAP authentication should be required for this target node
95           m = mutual_chap: CHAP authentication should be mutual/bidirectional
96           h = header_digest: Header Digest should be required for this target node
97           t = data_digest: Data Digest should be required for this target node
98        """
99        luns = []
100        print("bdev_name_id_pairs: %s" % bdev_name_id_pairs)
101        print("pg_ig_mappings: %s" % pg_ig_mappings)
102        for u in bdev_name_id_pairs.strip().split(" "):
103            bdev_name, lun_id = u.split(":")
104            luns.append({"bdev_name": bdev_name, "lun_id": int(lun_id)})
105        pg_ig_maps = []
106        for u in pg_ig_mappings.strip().split(" "):
107            pg, ig = u.split(":")
108            pg_ig_maps.append({"pg_tag": int(pg), "ig_tag": int(ig)})
109        queue_depth = self.ui_eval_param(queue_depth, "number", None)
110        chap_group = self.ui_eval_param(g, "number", None)
111        disable_chap = self.ui_eval_param(d, "bool", None)
112        require_chap = self.ui_eval_param(r, "bool", None)
113        mutual_chap = self.ui_eval_param(m, "bool", None)
114        header_digest = self.ui_eval_param(h, "bool", None)
115        data_digest = self.ui_eval_param(t, "bool", None)
116        self.get_root().iscsi_create_target_node(
117            name=name, alias_name=alias_name, luns=luns,
118            pg_ig_maps=pg_ig_maps, queue_depth=queue_depth,
119            chap_group=chap_group, disable_chap=disable_chap,
120            require_chap=require_chap, mutual_chap=mutual_chap,
121            header_digest=header_digest, data_digest=data_digest)
122
123    def ui_command_delete(self, name=None):
124        """Delete a target node. If name is not specified delete all target nodes.
125
126        Arguments:
127           name - Target node name.
128        """
129        self.delete(name)
130
131    def ui_command_delete_all(self):
132        """Delete all target nodes"""
133        rpc_messages = ""
134        for device in self.scsi_devices:
135            try:
136                self.delete(device.device_name)
137            except JSONRPCException as e:
138                rpc_messages += e.message
139        if rpc_messages:
140            raise JSONRPCException(rpc_messages)
141
142    def ui_command_add_lun(self, name, bdev_name, lun_id=None):
143        """Add lun to the target node.
144
145        Required args:
146           name: Target node name (ASCII)
147           bdev_name: bdev name
148        Positional args:
149           lun_id: LUN ID (integer >= 0)
150        """
151        if lun_id:
152            lun_id = self.ui_eval_param(lun_id, "number", None)
153        self.get_root().iscsi_target_node_add_lun(
154            name=name, bdev_name=bdev_name, lun_id=lun_id)
155
156    def summary(self):
157        count = 0
158        for device in self.scsi_devices:
159            for node in self.target_nodes:
160                if hasattr(device, "device_name") and node['name'] \
161                        == device.device_name:
162                    count = count + 1
163        return "Target nodes: %d" % count, None
164
165
166class UIISCSIDevice(UINode):
167    def __init__(self, device, target, parent):
168        UINode.__init__(self, device.device_name, parent)
169        self.device = device
170        self.target = target
171        self.refresh()
172
173    def ui_command_set_auth(self, g=None, d=None, r=None, m=None):
174        """Set CHAP authentication for the target node.
175
176        Optionals args:
177           g = chap_group: Authentication group ID for this target node
178           d = disable_chap: CHAP authentication should be disabled for this target node
179           r = require_chap: CHAP authentication should be required for this target node
180           m = mutual_chap: CHAP authentication should be mutual/bidirectional
181        """
182        chap_group = self.ui_eval_param(g, "number", None)
183        disable_chap = self.ui_eval_param(d, "bool", None)
184        require_chap = self.ui_eval_param(r, "bool", None)
185        mutual_chap = self.ui_eval_param(m, "bool", None)
186        self.get_root().iscsi_target_node_set_auth(
187            name=self.device.device_name, chap_group=chap_group,
188            disable_chap=disable_chap,
189            require_chap=require_chap, mutual_chap=mutual_chap)
190
191    def ui_command_iscsi_target_node_add_pg_ig_maps(self, pg_ig_mappings):
192        """Add PG-IG maps to the target node.
193
194        Args:
195           pg_ig_maps: List of pg_ig_mappings, e.g. pg_tag:ig_tag pg_tag2:ig_tag2
196        """
197        pg_ig_maps = []
198        for u in pg_ig_mappings.strip().split(" "):
199            pg, ig = u.split(":")
200            pg_ig_maps.append({"pg_tag": int(pg), "ig_tag": int(ig)})
201        self.get_root().iscsi_target_node_add_pg_ig_maps(
202            pg_ig_maps=pg_ig_maps, name=self.device.device_name)
203
204    def ui_command_iscsi_target_node_remove_pg_ig_maps(self, pg_ig_mappings):
205        """Remove PG-IG maps from the target node.
206
207        Args:
208           pg_ig_maps: List of pg_ig_mappings, e.g. pg_tag:ig_tag pg_tag2:ig_tag2
209        """
210        pg_ig_maps = []
211        for u in pg_ig_mappings.strip().split(" "):
212            pg, ig = u.split(":")
213            pg_ig_maps.append({"pg_tag": int(pg), "ig_tag": int(ig)})
214        self.get_root().iscsi_target_node_remove_pg_ig_maps(
215            pg_ig_maps=pg_ig_maps, name=self.device.device_name)
216
217    def refresh(self):
218        self._children = set([])
219        UIISCSILuns(self.target['luns'], self)
220        UIISCSIPgIgMaps(self.target['pg_ig_maps'], self)
221        auths = {"disable_chap": self.target["disable_chap"],
222                 "require_chap": self.target["require_chap"],
223                 "mutual_chap": self.target["mutual_chap"],
224                 "chap_group": self.target["chap_group"],
225                 "data_digest": self.target["data_digest"]}
226        UIISCSIAuth(auths, self)
227
228    def summary(self):
229        return "Id: %s, QueueDepth: %s" % (self.device.id,
230                                           self.target['queue_depth']), None
231
232
233class UIISCSIAuth(UINode):
234    def __init__(self, auths, parent):
235        UINode.__init__(self, "auths", parent)
236        self.auths = auths
237        self.refresh()
238
239    def summary(self):
240        return "disable_chap: %s, require_chap: %s, mutual_chap: %s, chap_group: %s" % (
241            self.auths['disable_chap'], self.auths['require_chap'],
242            self.auths['mutual_chap'], self.auths['chap_group']), None
243
244
245class UIISCSILuns(UINode):
246    def __init__(self, luns, parent):
247        UINode.__init__(self, "luns", parent)
248        self.luns = luns
249        self.refresh()
250
251    def refresh(self):
252        self._children = set([])
253        for lun in self.luns:
254            UIISCSILun(lun, self)
255
256    def summary(self):
257        return "Luns: %d" % len(self.luns), None
258
259
260class UIISCSILun(UINode):
261    def __init__(self, lun, parent):
262        UINode.__init__(self, "lun %s" % lun['lun_id'], parent)
263        self.lun = lun
264        self.refresh()
265
266    def summary(self):
267        return "%s" % self.lun['bdev_name'], None
268
269
270class UIISCSIPgIgMaps(UINode):
271    def __init__(self, pg_ig_maps, parent):
272        UINode.__init__(self, "pg_ig_maps", parent)
273        self.pg_ig_maps = pg_ig_maps
274        self.refresh()
275
276    def refresh(self):
277        self._children = set([])
278        for pg_ig in self.pg_ig_maps:
279            UIISCSIPgIg(pg_ig, self)
280
281    def summary(self):
282        return "Pg_ig_maps: %d" % len(self.pg_ig_maps), None
283
284
285class UIISCSIPgIg(UINode):
286    def __init__(self, pg_ig, parent):
287        UINode.__init__(self, "portal_group%s - initiator_group%s" %
288                        (pg_ig['pg_tag'], pg_ig['ig_tag']), parent)
289        self.pg_ig = pg_ig
290        self.refresh()
291
292
293class UIPortalGroups(UINode):
294    def __init__(self, parent):
295        UINode.__init__(self, "portal_groups", parent)
296        self.refresh()
297
298    def delete(self, tag):
299        self.get_root().iscsi_delete_portal_group(tag=tag)
300
301    def ui_command_create(self, tag, portal_list):
302        """Add a portal group.
303
304        Args:
305           portals: List of portals e.g. ip:port ip2:port2
306           tag: Portal group tag (unique, integer > 0)
307        """
308        portals = []
309        for portal in portal_list.strip().split(" "):
310            host = portal
311            cpumask = None
312            if "@" in portal:
313                host, cpumask = portal.split("@")
314            if ":" not in host:
315                raise ExecutionError("Incorrect format of portal group. Port is missing."
316                                     "Use 'help create' to see the command syntax.")
317            host, port = host.rsplit(":", -1)
318            portals.append({'host': host, 'port': port})
319            if cpumask:
320                print("WARNING: Specifying a CPU mask for portal groups is no longer supported. Ignoring.")
321        tag = self.ui_eval_param(tag, "number", None)
322        self.get_root().construct_portal_group(tag=tag, portals=portals, private=None, wait=None)
323
324    def ui_command_delete(self, tag):
325        """Delete a portal group with given tag (unique, integer > 0))"""
326        tag = self.ui_eval_param(tag, "number", None)
327        self.delete(tag)
328
329    def ui_command_delete_all(self):
330        """Delete all portal groups"""
331        rpc_messages = ""
332        for pg in self.pgs:
333            try:
334                self.delete(pg.tag)
335            except JSONRPCException as e:
336                rpc_messages += e.message
337        if rpc_messages:
338            raise JSONRPCException(rpc_messages)
339
340    def refresh(self):
341        self._children = set([])
342        self.pgs = list(self.get_root().iscsi_get_portal_groups())
343        for pg in self.pgs:
344            try:
345                UIPortalGroup(pg, self)
346            except JSONRPCException as e:
347                self.shell.log.error(e.message)
348
349    def summary(self):
350        return "Portal groups: %d" % len(self.pgs), None
351
352
353class UIPortalGroup(UINode):
354    def __init__(self, pg, parent):
355        UINode.__init__(self, "portal_group%s" % pg.tag, parent)
356        self.pg = pg
357        self.refresh()
358
359    def refresh(self):
360        self._children = set([])
361        for portal in self.pg.portals:
362            UIPortal(portal['host'], portal['port'], self)
363
364    def summary(self):
365        return "Portals: %d" % len(self.pg.portals), None
366
367
368class UIPortal(UINode):
369    def __init__(self, host, port, parent):
370        UINode.__init__(self, "host=%s, port=%s" % (
371            host, port), parent)
372        self.refresh()
373
374
375class UIInitiatorGroups(UINode):
376    def __init__(self, parent):
377        UINode.__init__(self, "initiator_groups", parent)
378        self.refresh()
379
380    def delete(self, tag):
381        self.get_root().iscsi_delete_initiator_group(tag=tag)
382
383    def ui_command_create(self, tag, initiator_list, netmask_list):
384        """Add an initiator group.
385
386        Args:
387           tag: Initiator group tag (unique, integer > 0)
388           initiators: List of initiator hostnames or IP addresses
389                       separated with whitespaces, e.g. 127.0.0.1 192.168.200.100
390           netmasks: List of initiator netmasks separated with whitespaces,
391                     e.g. 255.255.0.0 255.248.0.0
392        """
393        tag = self.ui_eval_param(tag, "number", None)
394        self.get_root().construct_initiator_group(
395            tag=tag, initiators=initiator_list.split(" "),
396            netmasks=netmask_list.split(" "))
397
398    def ui_command_delete(self, tag):
399        """Delete an initiator group.
400
401        Args:
402           tag: Initiator group tag (unique, integer > 0)
403        """
404        tag = self.ui_eval_param(tag, "number", None)
405        self.delete(tag)
406
407    def ui_command_delete_all(self):
408        """Delete all initiator groups"""
409        rpc_messages = ""
410        for ig in self.igs:
411            try:
412                self.delete(ig.tag)
413            except JSONRPCException as e:
414                rpc_messages += e.message
415        if rpc_messages:
416            raise JSONRPCException(rpc_messages)
417
418    def ui_command_add_initiator(self, tag, initiators, netmasks):
419        """Add initiators to an existing initiator group.
420
421        Args:
422           tag: Initiator group tag (unique, integer > 0)
423           initiators: List of initiator hostnames or IP addresses,
424                       e.g. 127.0.0.1 192.168.200.100
425           netmasks: List of initiator netmasks,
426                     e.g. 255.255.0.0 255.248.0.0
427        """
428        tag = self.ui_eval_param(tag, "number", None)
429        self.get_root().iscsi_initiator_group_add_initiators(
430            tag=tag, initiators=initiators.split(" "),
431            netmasks=netmasks.split(" "))
432
433    def ui_command_delete_initiator(self, tag, initiators=None, netmasks=None):
434        """Delete initiators from an existing initiator group.
435
436        Args:
437           tag: Initiator group tag (unique, integer > 0)
438           initiators: List of initiator hostnames or IP addresses, e.g. 127.0.0.1 192.168.200.100
439           netmasks: List of initiator netmasks, e.g. 255.255.0.0 255.248.0.0
440        """
441        tag = self.ui_eval_param(tag, "number", None)
442        if initiators:
443            initiators = initiators.split(" ")
444        if netmasks:
445            netmasks = netmasks.split(" ")
446        self.get_root().iscsi_initiator_group_remove_initiators(
447            tag=tag, initiators=initiators,
448            netmasks=netmasks)
449
450    def refresh(self):
451        self._children = set([])
452        self.igs = list(self.get_root().iscsi_get_initiator_groups())
453        for ig in self.igs:
454            UIInitiatorGroup(ig, self)
455
456    def summary(self):
457        return "Initiator groups: %d" % len(self.igs), None
458
459
460class UIInitiatorGroup(UINode):
461    def __init__(self, ig, parent):
462        UINode.__init__(self, "initiator_group%s" % ig.tag, parent)
463        self.ig = ig
464        self.refresh()
465
466    def refresh(self):
467        self._children = set([])
468        for initiator, netmask in zip(self.ig.initiators, self.ig.netmasks):
469            UIInitiator(initiator, netmask, self)
470
471    def summary(self):
472        return "Initiators: %d" % len(self.ig.initiators), None
473
474
475class UIInitiator(UINode):
476    def __init__(self, initiator, netmask, parent):
477        UINode.__init__(self, "hostname=%s, netmask=%s" % (initiator, netmask), parent)
478        self.refresh()
479
480
481class UIISCSIConnections(UINode):
482    def __init__(self, parent):
483        UINode.__init__(self, "iscsi_connections", parent)
484        self.refresh()
485
486    def refresh(self):
487        self._children = set([])
488        self.iscsicons = list(self.get_root().iscsi_get_connections())
489        for ic in self.iscsicons:
490            UIISCSIConnection(ic, self)
491
492    def summary(self):
493        return "Connections: %d" % len(self.iscsicons), None
494
495
496class UIISCSIConnection(UINode):
497    def __init__(self, ic, parent):
498        UINode.__init__(self, "%s" % ic['id'], parent)
499        self.ic = ic
500        self.refresh()
501
502    def refresh(self):
503        self._children = set([])
504        for key, val in self.ic.items():
505            if key == "id":
506                continue
507            UIISCSIConnectionDetails("%s: %s" % (key, val), self)
508
509
510class UIISCSIConnectionDetails(UINode):
511    def __init__(self, info, parent):
512        UINode.__init__(self, "%s" % info, parent)
513        self.refresh()
514
515
516class UIISCSIAuthGroups(UINode):
517    def __init__(self, parent):
518        UINode.__init__(self, "auth_groups", parent)
519        self.refresh()
520
521    def refresh(self):
522        self._children = set([])
523        self.iscsi_auth_groups = list(self.get_root().iscsi_get_auth_groups())
524        if self.iscsi_auth_groups is None:
525            self.iscsi_auth_groups = []
526        for ag in self.iscsi_auth_groups:
527            UIISCSIAuthGroup(ag, self)
528
529    def delete(self, tag):
530        self.get_root().iscsi_delete_auth_group(tag=tag)
531
532    def delete_secret(self, tag, user):
533        self.get_root().iscsi_auth_group_remove_secret(
534            tag=tag, user=user)
535
536    def ui_command_create(self, tag, secrets=None):
537        """Add authentication group for CHAP authentication.
538
539        Args:
540           tag: Authentication group tag (unique, integer > 0).
541        Optional args:
542           secrets: Array of secrets objects separated by comma sign,
543                    e.g. user:test secret:test muser:mutual_test msecret:mutual_test
544        """
545        tag = self.ui_eval_param(tag, "number", None)
546        if secrets:
547            secrets = [dict(u.split(":") for u in a.split(" "))
548                       for a in secrets.split(",")]
549        self.get_root().iscsi_create_auth_group(tag=tag, secrets=secrets)
550
551    def ui_command_delete(self, tag):
552        """Delete an authentication group.
553
554        Args:
555           tag: Authentication group tag (unique, integer > 0)
556        """
557        tag = self.ui_eval_param(tag, "number", None)
558        self.delete(tag)
559
560    def ui_command_delete_all(self):
561        """Delete all authentication groups."""
562        rpc_messages = ""
563        for iscsi_auth_group in self.iscsi_auth_groups:
564            try:
565                self.delete(iscsi_auth_group['tag'])
566            except JSONRPCException as e:
567                rpc_messages += e.message
568        if rpc_messages:
569            raise JSONRPCException(rpc_messages)
570
571    def ui_command_add_secret(self, tag, user, secret,
572                              muser=None, msecret=None):
573        """Add a secret to an authentication group.
574
575        Args:
576           tag: Authentication group tag (unique, integer > 0)
577           user: User name for one-way CHAP authentication
578           secret: Secret for one-way CHAP authentication
579        Optional args:
580           muser: User name for mutual CHAP authentication
581           msecret: Secret for mutual CHAP authentication
582        """
583        tag = self.ui_eval_param(tag, "number", None)
584        self.get_root().iscsi_auth_group_add_secret(
585            tag=tag, user=user, secret=secret,
586            muser=muser, msecret=msecret)
587
588    def ui_command_delete_secret(self, tag, user):
589        """Delete a secret from an authentication group.
590
591        Args:
592           tag: Authentication group tag (unique, integer > 0)
593           user: User name for one-way CHAP authentication
594        """
595        tag = self.ui_eval_param(tag, "number", None)
596        self.delete_secret(tag, user)
597
598    def ui_command_delete_secret_all(self, tag):
599        """Delete all secrets from an authentication group.
600
601        Args:
602           tag: Authentication group tag (unique, integer > 0)
603        """
604        rpc_messages = ""
605        tag = self.ui_eval_param(tag, "number", None)
606        for ag in self.iscsi_auth_groups:
607            if ag['tag'] == tag:
608                for secret in ag['secrets']:
609                    try:
610                        self.delete_secret(tag, secret['user'])
611                    except JSONRPCException as e:
612                        rpc_messages += e.message
613        if rpc_messages:
614            raise JSONRPCException(rpc_messages)
615
616    def summary(self):
617        return "Groups: %s" % len(self.iscsi_auth_groups), None
618
619
620class UIISCSIAuthGroup(UINode):
621    def __init__(self, ag, parent):
622        UINode.__init__(self, "group" + str(ag['tag']), parent)
623        self.ag = ag
624        self.refresh()
625
626    def refresh(self):
627        self._children = set([])
628        for secret in self.ag['secrets']:
629            UISCSIAuthSecret(secret, self)
630
631    def summary(self):
632        return "Secrets: %s" % len(self.ag['secrets']), None
633
634
635class UISCSIAuthSecret(UINode):
636    def __init__(self, secret, parent):
637        info_list = ["%s=%s" % (key, val)
638                     for key, val in secret.items()]
639        info_list.sort(reverse=True)
640        info = ", ".join(info_list)
641        UINode.__init__(self, info, parent)
642        self.secret = secret
643        self.refresh()
644