1'''
2This file is part of RTSLib.
3Copyright (c) 2011-2013 by Datera, Inc
4Copyright (c) 2011-2014 by Red Hat, Inc.
5
6Licensed under the Apache License, Version 2.0 (the "License"); you may
7not use this file except in compliance with the License. You may obtain
8a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15License for the specific language governing permissions and limitations
16under the License.
17
18
19Description
20-----------
21
22Fabrics may differ in how fabric WWNs are represented, as well as
23what capabilities they support
24
25
26Available parameters
27--------------------
28
29* features
30Lists the target fabric available features. Default value:
31("discovery_auth", "acls", "auth", "nps")
32example: features = ("discovery_auth", "acls", "auth")
33example: features = () # no features supported
34
35Detail of features:
36
37  * tpgts
38  The target fabric module is using iSCSI-style target portal group tags.
39
40  * discovery_auth
41  The target fabric module supports a fabric-wide authentication for
42  discovery.
43
44  * acls
45  The target's TPGTs support explicit initiator ACLs.
46
47  * auth
48  The target's TPGT's support per-TPG authentication, and
49  the target's TPGT's ACLs support per-ACL initiator authentication.
50  Fabrics that support auth must support acls.
51
52  * nps
53  The TPGTs support iSCSI-like IPv4/IPv6 network portals, using IP:PORT
54  group names.
55
56  * nexus
57  The TPGTs have a 'nexus' attribute that contains the local initiator
58  serial unit. This attribute must be set before being able to create any
59  LUNs.
60
61  * wwn_types
62  Sets the type of WWN expected by the target fabric. Defaults to 'free'.
63  Usually a fabric will only support one type but iSCSI supports more.
64  First entry is the "native" wwn type - i.e. if a wwn can be generated, it
65  will be of this type.
66  Example: wwn_types = ("eui",)
67  Current valid types are:
68
69    * free
70    Freeform WWN.
71
72    * iqn
73    The fabric module targets are using iSCSI-type IQNs.
74
75    * naa
76    NAA FC or SAS address type WWN.
77
78    * eui
79    EUI-64. See http://en.wikipedia.org/wiki/MAC_address for info on this format.
80
81    * unit_serial
82    Disk-type unit serial.
83
84* wwns
85This property returns an iterable (either generator or list) of valid
86target WWNs for the fabric, if WWNs should be chosen from existing
87fabric interfaces. The most common case for this is hardware-set
88WWNs. WWNs should conform to rtslib's normalized internal format: the wwn
89type (see above), a period, then the wwn with interstitial dividers like
90':' removed.
91
92* to_fabric_wwn()
93Converts WWNs from normalized format (see above) to whatever the kernel code
94expects when getting a wwn. Only needed if different from normalized format.
95
96* kernel_module
97Sets the name of the kernel module implementing the fabric modules. If
98not specified, it will be assumed to be MODNAME_target_mod, where
99MODNAME is the name of the fabric module, from the fabrics list. Note
100that you must not specify any .ko or such extension here.
101Example: self.kernel_module = "my_module"
102
103* _path
104Sets the path of the configfs group used by the fabric module. Defaults to the
105name of the module from the fabrics list.
106Example: self._path = "%s/%s" % (self.configfs_dir, "my_cfs_dir")
107
108'''
109
110from functools import partial
111from glob import iglob as glob
112import os
113import six
114
115from .node import CFSNode
116from .utils import fread, fwrite, normalize_wwn, colonize
117from .utils import RTSLibError, modprobe, ignored
118from .target import Target
119from .utils import _get_auth_attr, _set_auth_attr
120
121version_attributes = set(["lio_version", "version"])
122discovery_auth_attributes = set(["discovery_auth"])
123target_names_excludes = version_attributes | discovery_auth_attributes
124
125
126class _BaseFabricModule(CFSNode):
127    '''
128    Abstract Base clase for Fabric Modules.
129    It can load modules, provide information about them and
130    handle the configfs housekeeping. After instantiation, whether or
131    not the fabric module is loaded depends on if a method requiring
132    it (i.e. accessing configfs) is used. This helps limit loaded
133    kernel modules to just the fabrics in use.
134    '''
135
136    # FabricModule ABC private stuff
137    def __init__(self, name):
138        '''
139        Instantiate a FabricModule object, according to the provided name.
140        @param name: the name of the FabricModule object.
141        @type name: str
142        '''
143        super(_BaseFabricModule, self).__init__()
144        self.name = name
145        self.spec_file = "N/A"
146        self._path = "%s/%s" % (self.configfs_dir, self.name)
147        self.features = ('discovery_auth', 'acls', 'auth', 'nps', 'tpgts')
148        self.wwn_types = ('free',)
149        self.kernel_module = "%s_target_mod" % self.name
150
151    # FabricModule public stuff
152
153    def _check_self(self):
154        if not self.exists:
155            try:
156                self._create_in_cfs_ine('any')
157            except RTSLibError:
158                modprobe(self.kernel_module)
159                self._create_in_cfs_ine('any')
160        super(_BaseFabricModule, self)._check_self()
161
162    def has_feature(self, feature):
163        # Handle a renamed feature
164        if feature == 'acls_auth':
165            feature = 'auth'
166        return feature in self.features
167
168    def _list_targets(self):
169        if self.exists:
170            for wwn in os.listdir(self.path):
171                if os.path.isdir("%s/%s" % (self.path, wwn)) and \
172                        wwn not in target_names_excludes:
173                    yield Target(self, self.from_fabric_wwn(wwn), 'lookup')
174
175    def _get_version(self):
176        if self.exists:
177            for attr in version_attributes:
178                path = "%s/%s" % (self.path, attr)
179                if os.path.isfile(path):
180                    return fread(path)
181            else:
182                raise RTSLibError("Can't find version for fabric module %s"
183                                  % self.name)
184        else:
185            return None
186
187    def to_normalized_wwn(self, wwn):
188        '''
189        Checks whether or not the provided WWN is valid for this fabric module
190        according to the spec, and returns a tuple of our preferred string
191        representation of the wwn, and what type it turned out to be.
192        '''
193        return normalize_wwn(self.wwn_types, wwn)
194
195    def to_fabric_wwn(self, wwn):
196        '''
197        Some fabrics need WWNs in a format different than rtslib's internal
198        format. These fabrics should override this method.
199        '''
200        return wwn
201
202    def from_fabric_wwn(self, wwn):
203        '''
204        Converts from WWN format used in this fabric's LIO configfs to canonical
205        format.
206        Note: Do not call from wwns(). There's no guarantee fabric wwn format is
207        the same as wherever wwns() is reading from.
208        '''
209        return wwn
210
211    def needs_wwn(self):
212        '''
213        This fabric requires wwn to be specified when creating a target,
214        it cannot be autogenerated.
215        '''
216        return self.wwns != None
217
218    def _assert_feature(self, feature):
219        if not self.has_feature(feature):
220            raise RTSLibError("Fabric module %s does not implement "
221                              + "the %s feature" % (self.name, feature))
222
223    def clear_discovery_auth_settings(self):
224        self._check_self()
225        self._assert_feature('discovery_auth')
226        self.discovery_mutual_password = ''
227        self.discovery_mutual_userid = ''
228        self.discovery_password = ''
229        self.discovery_userid = ''
230        self.discovery_enable_auth = False
231
232    def _get_discovery_enable_auth(self):
233        self._check_self()
234        self._assert_feature('discovery_auth')
235        path = "%s/discovery_auth/enforce_discovery_auth" % self.path
236        value = fread(path)
237        return bool(int(value))
238
239    def _set_discovery_enable_auth(self, enable):
240        self._check_self()
241        self._assert_feature('discovery_auth')
242        path = "%s/discovery_auth/enforce_discovery_auth" % self.path
243        if int(enable):
244            enable = 1
245        else:
246            enable = 0
247        fwrite(path, "%s" % enable)
248
249    def _get_discovery_authenticate_target(self):
250        self._check_self()
251        self._assert_feature('discovery_auth')
252        path = "%s/discovery_auth/authenticate_target" % self.path
253        return bool(int(fread(path)))
254
255    def _get_wwns(self):
256        '''
257        Returns either iterable or None. None means fabric allows
258        arbitrary WWNs.
259        '''
260        return None
261
262    def _get_disc_attr(self, *args, **kwargs):
263        self._assert_feature('discovery_auth')
264        return _get_auth_attr(self, *args, **kwargs)
265
266    def _set_disc_attr(self, *args, **kwargs):
267        self._assert_feature('discovery_auth')
268        _set_auth_attr(self, *args, **kwargs)
269
270    discovery_enable_auth = \
271            property(_get_discovery_enable_auth,
272                     _set_discovery_enable_auth,
273                     doc="Set or get the discovery enable_auth flag.")
274    discovery_authenticate_target = property(_get_discovery_authenticate_target,
275            doc="Get the boolean discovery authenticate target flag.")
276
277    discovery_userid = property(partial(_get_disc_attr, attribute='discovery_auth/userid'),
278                                partial(_set_disc_attr, attribute='discovery_auth/userid'),
279                                doc="Set or get the initiator discovery userid.")
280    discovery_password = property(partial(_get_disc_attr, attribute='discovery_auth/password'),
281                                  partial(_set_disc_attr, attribute='discovery_auth/password'),
282                                  doc="Set or get the initiator discovery password.")
283    discovery_mutual_userid = property(partial(_get_disc_attr, attribute='discovery_auth/userid_mutual'),
284                                       partial(_set_disc_attr, attribute='discovery_auth/userid_mutual'),
285                                       doc="Set or get the mutual discovery userid.")
286    discovery_mutual_password = property(partial(_get_disc_attr, attribute='discovery_auth/password_mutual'),
287                                         partial(_set_disc_attr, attribute='discovery_auth/password_mutual'),
288                                         doc="Set or get the mutual discovery password.")
289
290    targets = property(_list_targets,
291                       doc="Get the list of target objects.")
292
293    version = property(_get_version,
294                       doc="Get the fabric module version string.")
295
296    wwns = property(_get_wwns,
297                    doc="iterable of WWNs present for this fabric")
298
299    def setup(self, fm, err_func):
300        '''
301        Setup fabricmodule with settings from fm dict.
302        '''
303        for name, value in six.iteritems(fm):
304            if name != 'name':
305                try:
306                    setattr(self, name, value)
307                except:
308                    err_func("Could not set fabric %s attribute '%s'" % (fm['name'], name))
309
310    def dump(self):
311        d = super(_BaseFabricModule, self).dump()
312        d['name'] = self.name
313        if self.has_feature("discovery_auth"):
314            for attr in ("userid", "password", "mutual_userid", "mutual_password"):
315                val = getattr(self, "discovery_" + attr, None)
316                if val:
317                    d["discovery_" + attr] = val
318            d['discovery_enable_auth'] = self.discovery_enable_auth
319        return d
320
321
322class ISCSIFabricModule(_BaseFabricModule):
323
324    def __init__(self):
325        super(ISCSIFabricModule, self).__init__('iscsi')
326        self.wwn_types = ('iqn', 'naa', 'eui')
327
328
329class LoopbackFabricModule(_BaseFabricModule):
330    def __init__(self):
331        super(LoopbackFabricModule, self).__init__('loopback')
332        self.features = ("nexus",)
333        self.wwn_types = ('naa',)
334        self.kernel_module = "tcm_loop"
335
336
337class SBPFabricModule(_BaseFabricModule):
338    def __init__(self):
339        super(SBPFabricModule, self).__init__('sbp')
340        self.features = ()
341        self.wwn_types = ('eui',)
342        self.kernel_module = "sbp_target"
343
344    def to_fabric_wwn(self, wwn):
345        return wwn[4:]
346
347    def from_fabric_wwn(self, wwn):
348        return "eui." + wwn
349
350    # return 1st local guid (is unique) so our share is named uniquely
351    @property
352    def wwns(self):
353        for fname in glob("/sys/bus/firewire/devices/fw*/is_local"):
354            if bool(int(fread(fname))):
355                guid_path = os.path.dirname(fname) + "/guid"
356                yield "eui." + fread(guid_path)[2:]
357                break
358
359
360class Qla2xxxFabricModule(_BaseFabricModule):
361    def __init__(self):
362        super(Qla2xxxFabricModule, self).__init__('qla2xxx')
363        self.features = ("acls",)
364        self.wwn_types = ('naa',)
365        self.kernel_module = "tcm_qla2xxx"
366
367    def to_fabric_wwn(self, wwn):
368        # strip 'naa.' and add colons
369        return colonize(wwn[4:])
370
371    def from_fabric_wwn(self, wwn):
372        return "naa." + wwn.replace(":", "")
373
374    @property
375    def wwns(self):
376        for wwn_file in glob("/sys/class/fc_host/host*/port_name"):
377            with ignored(IOError):
378                if not fread(os.path.dirname(wwn_file)+"/symbolic_name").startswith("fcoe"):
379                    yield "naa." + fread(wwn_file)[2:]
380
381
382class SRPTFabricModule(_BaseFabricModule):
383    def __init__(self):
384        super(SRPTFabricModule, self).__init__('srpt')
385        self.features = ("acls",)
386        self.wwn_types = ('ib',)
387        self.kernel_module = "ib_srpt"
388
389    def to_fabric_wwn(self, wwn):
390        # strip 'ib.' and re-add '0x'
391        return "0x" + wwn[3:]
392
393    def from_fabric_wwn(self, wwn):
394        return "ib." + wwn[2:]
395
396    @property
397    def wwns(self):
398        for wwn_file in glob("/sys/class/infiniband/*/ports/*/gids/0"):
399            yield "ib." + fread(wwn_file).replace(":", "")
400
401
402class FCoEFabricModule(_BaseFabricModule):
403    def __init__(self):
404        super(FCoEFabricModule, self).__init__('tcm_fc')
405
406        self.features = ("acls",)
407        self.kernel_module = "tcm_fc"
408        self.wwn_types=('naa',)
409        self._path = "%s/%s" % (self.configfs_dir, "fc")
410
411    def to_fabric_wwn(self, wwn):
412        # strip 'naa.' and add colons
413        return colonize(wwn[4:])
414
415    def from_fabric_wwn(self, wwn):
416        return "naa." + wwn.replace(":", "")
417
418    @property
419    def wwns(self):
420        for wwn_file in glob("/sys/class/fc_host/host*/port_name"):
421            with ignored(IOError):
422                if fread(os.path.dirname(wwn_file)+"/symbolic_name").startswith("fcoe"):
423                    yield "naa." + fread(wwn_file)[2:]
424
425
426class USBGadgetFabricModule(_BaseFabricModule):
427    def __init__(self):
428        super(USBGadgetFabricModule, self).__init__('usb_gadget')
429        self.features = ("nexus",)
430        self.wwn_types = ('naa',)
431        self.kernel_module = "tcm_usb_gadget"
432
433
434class VhostFabricModule(_BaseFabricModule):
435    def __init__(self):
436        super(VhostFabricModule, self).__init__('vhost')
437        self.features = ("nexus", "acls", "tpgts")
438        self.wwn_types = ('naa',)
439        self.kernel_module = "tcm_vhost"
440
441class XenPvScsiFabricModule(_BaseFabricModule):
442    def __init__(self):
443        super(XenPvScsiFabricModule, self).__init__('xen-pvscsi')
444        self._path = "%s/%s" % (self.configfs_dir, 'xen-pvscsi')
445        self.features = ("nexus", "tpgts")
446        self.wwn_types = ('naa',)
447        self.kernel_module = "xen-scsiback"
448
449
450class IbmvscsisFabricModule(_BaseFabricModule):
451    def __init__(self):
452        super(IbmvscsisFabricModule, self).__init__('ibmvscsis')
453        self.features = ()
454        self.kernel_module = "ibmvscsis"
455
456    @property
457    def wwns(self):
458        for wwn_file in glob("/sys/module/ibmvscsis/drivers/vio:ibmvscsis/*/devspec"):
459            name = fread(wwn_file)
460            yield name[name.find("@") + 1:]
461
462
463fabric_modules = {
464    "srpt": SRPTFabricModule,
465    "iscsi": ISCSIFabricModule,
466    "loopback": LoopbackFabricModule,
467    "qla2xxx": Qla2xxxFabricModule,
468    "sbp": SBPFabricModule,
469    "tcm_fc": FCoEFabricModule,
470#    "usb_gadget": USBGadgetFabricModule, # very rare, don't show
471    "vhost": VhostFabricModule,
472    "xen-pvscsi": XenPvScsiFabricModule,
473    "ibmvscsis": IbmvscsisFabricModule,
474    }
475
476#
477# Maintain compatibility with existing FabricModule(fabricname) usage
478# e.g. FabricModule('iscsi') returns an ISCSIFabricModule
479#
480class FabricModule(object):
481
482    def __new__(cls, name):
483        return fabric_modules[name]()
484
485    @classmethod
486    def all(cls):
487        for mod in six.itervalues(fabric_modules):
488            yield mod()
489
490    @classmethod
491    def list_registered_drivers(cls):
492        try:
493            return os.listdir('/sys/module/target_core_mod/holders')
494        except OSError:
495            return []
496