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