1 '''
2 Implements the RTS Target backstore and storage object classes.
3
4 This file is part of RTSLib.
5 Copyright (c) 2011-2013 by Datera, Inc
6
7 Licensed under the Apache License, Version 2.0 (the "License"); you may
8 not use this file except in compliance with the License. You may obtain
9 a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 License for the specific language governing permissions and limitations
17 under the License.
18 '''
19
20 import os
21 import stat
22 import re
23 import glob
24 import resource
25
26 from node import CFSNode
27 from utils import fread, fwrite, RTSLibError, generate_wwn
28 from utils import convert_scsi_path_to_hctl, convert_scsi_hctl_to_path
29 from utils import is_dev_in_use, get_blockdev_type, get_blockdev_size
33 '''
34 This is an interface to storage objects in configFS. A StorageObject is
35 identified by its backstore and its name.
36 '''
37
38
40 return "<%s %s/%s>" % (self.__class__.__name__, self.plugin, self.name)
41
57
64
67
69 return not self == other
70
72 """
73 LIO actually *writes* pr aptpl info to the filesystem, so we
74 need to read it in and squirt it back into configfs when we configure
75 the storage object. BLEH.
76 """
77 aptpl_dir = "/var/target/pr"
78
79 try:
80 lines = fread("%s/aptpl_%s" % (aptpl_dir, self.wwn)).split()
81 except:
82 return
83
84 if not lines[0].startswith("PR_REG_START:"):
85 return
86
87 reservations = []
88 for line in lines:
89 if line.startswith("PR_REG_START:"):
90 res_list = []
91 elif line.startswith("PR_REG_END:"):
92 reservations.append(res_list)
93 else:
94 res_list.append(line.strip())
95
96 for res in reservations:
97 fwrite(self.path + "/pr/res_aptpl_metadata", ",".join(res))
98
99 @classmethod
104
105 @classmethod
119
121 self._check_self()
122 if self.is_configured():
123 path = "%s/wwn/vpd_unit_serial" % self.path
124 return fread(path).partition(":")[2].strip()
125 else:
126 raise RTSLibError("Cannot read a T10 WWN Unit Serial from "
127 + "an unconfigured StorageObject.")
128
137
142
150
153
156
161
166
171
173 self._check_self()
174 info = fread("%s/info" % self.path)
175 return re.search(".*%s: ([^: ]+).*" \
176 % key, ' '.join(info.split())).group(1).lower()
177
181
183 '''
184 Fast scan of luns attached to a storage object. This is an order of
185 magnitude faster than using root.luns and matching path on them.
186 '''
187 isdir = os.path.isdir
188 islink = os.path.islink
189 listdir = os.listdir
190 realpath = os.path.realpath
191 path = self.path
192 from root import RTSRoot
193 from target import LUN, TPG, Target
194 from fabric import target_names_excludes
195
196 for base, fm in ((fm.path, fm) for fm in RTSRoot().fabric_modules if fm.exists):
197 for tgt_dir in listdir(base):
198 if tgt_dir not in target_names_excludes:
199 tpgts_base = "%s/%s" % (base, tgt_dir)
200 for tpgt_dir in listdir(tpgts_base):
201 luns_base = "%s/%s/lun" % (tpgts_base, tpgt_dir)
202 if isdir(luns_base):
203 for lun_dir in listdir(luns_base):
204 links_base = "%s/%s" % (luns_base, lun_dir)
205 for lun_file in listdir(links_base):
206 link = "%s/%s" % (links_base, lun_file)
207 if islink(link) and realpath(link) == path:
208 val = (tpgt_dir + "_" + lun_dir)
209 val = val.split('_')
210 target = Target(fm, tgt_dir)
211 yield LUN(TPG(target, val[1]), val[3])
212
220
221
222
224 '''
225 Recursively deletes a StorageObject object.
226 This will delete all attached LUNs currently using the StorageObject
227 object, and then the StorageObject itself. The underlying file and
228 block storages will not be touched, but all ramdisk data will be lost.
229 '''
230 self._check_self()
231
232
233 if self.is_configured():
234 for lun in self._gen_attached_luns():
235 if self.status != 'activated':
236 break
237 else:
238 lun.delete()
239
240 super(StorageObject, self).delete()
241 self._backstore.delete()
242
256
257 version = property(_get_version,
258 doc="Get the version of the StorageObject's backstore")
259 name = property(_get_name,
260 doc="Get the StorageObject name as a string.")
261 udev_path = property(_get_udev_path,
262 doc="Get the StorageObject udev_path as a string.")
263 wwn = property(_get_wwn, _set_wwn,
264 doc="Get or set the StorageObject T10 WWN Serial as a string.")
265 status = property(_get_status,
266 doc="Get the storage object status, depending on whether or not it"\
267 + "is used by any LUN")
268 attached_luns = property(_list_attached_luns,
269 doc="Get the list of all LUN objects attached.")
270
276
279 '''
280 An interface to configFS storage objects for pscsi backstore.
281 '''
282
283
284
286 '''
287 A PSCSIStorageObject can be instantiated in two ways:
288 - B{Creation mode}: If I{dev} is specified, the underlying configFS
289 object will be created with that parameter. No PSCSIStorageObject
290 with the same I{name} can pre-exist in the parent PSCSIBackstore
291 in that mode, or instantiation will fail.
292 - B{Lookup mode}: If I{dev} is not set, then the PSCSIStorageObject
293 will be bound to the existing configFS object in the parent
294 PSCSIBackstore having the specified I{name}. The underlying
295 configFS object must already exist in that mode, or instantiation
296 will fail.
297
298 @param name: The name of the PSCSIStorageObject.
299 @type name: string
300 @param dev: You have two choices:
301 - Use the SCSI id of the device: I{dev="H:C:T:L"}.
302 - Use the path to the SCSI device: I{dev="/path/to/dev"}.
303 @type dev: string
304 @return: A PSCSIStorageObject object.
305 '''
306 if dev is not None:
307 super(PSCSIStorageObject, self).__init__(name, 'create')
308 try:
309 self._configure(dev)
310 except:
311 self.delete()
312 raise
313 else:
314 super(PSCSIStorageObject, self).__init__(name, 'lookup')
315
360
364
366 self._check_self()
367 info = fread("%s/info" % self.path)
368 return str(re.search(".*Model:(.*)Rev:",
369 ' '.join(info.split())).group(1)).strip()
370
372 self._check_self()
373 info = fread("%s/info" % self.path)
374 return str(re.search(".*Vendor:(.*)Model:",
375 ' '.join(info.split())).group(1)).strip()
376
380
384
388
392
396
397
398
399 wwn = property(StorageObject._get_wwn, _set_wwn,
400 doc="Get the StorageObject T10 WWN Unit Serial as a string."
401 + " You cannot set it for pscsi-backed StorageObjects.")
402 model = property(_get_model,
403 doc="Get the SCSI device model string")
404 vendor = property(_get_vendor,
405 doc="Get the SCSI device vendor string")
406 revision = property(_get_revision,
407 doc="Get the SCSI device revision string")
408 host_id = property(_get_host_id,
409 doc="Get the SCSI device host id")
410 channel_id = property(_get_channel_id,
411 doc="Get the SCSI device channel id")
412 target_id = property(_get_target_id,
413 doc="Get the SCSI device target id")
414 lun = property(_get_lun,
415 doc="Get the SCSI device LUN")
416
421
424 '''
425 An interface to configFS storage objects for rd_mcp backstore.
426 '''
427
428
429
430 - def __init__(self, name, size=None, wwn=None, nullio=False):
431 '''
432 A RDMCPStorageObject can be instantiated in two ways:
433 - B{Creation mode}: If I{size} is specified, the underlying
434 configFS object will be created with that parameter.
435 No RDMCPStorageObject with the same I{name} can pre-exist in the
436 parent Backstore in that mode, or instantiation will fail.
437 - B{Lookup mode}: If I{size} is not set, then the
438 RDMCPStorageObject will be bound to the existing configFS object
439 in the parent Backstore having the specified I{name}.
440 The underlying configFS object must already exist in that mode,
441 or instantiation will fail.
442
443 @param name: The name of the RDMCPStorageObject.
444 @type name: string
445 @param size: The size of the ramdrive to create, in bytes.
446 @type size: int
447 @param wwn: T10 WWN Unit Serial, will generate if None
448 @type wwn: string
449 @param nullio: If rd should be created w/o backing page store.
450 @type nullio: boolean
451 @return: A RDMCPStorageObject object.
452 '''
453
454 if size is not None:
455 super(RDMCPStorageObject, self).__init__(name, 'create')
456 try:
457 self._configure(size, wwn, nullio)
458 except:
459 self.delete()
460 raise
461 else:
462 super(RDMCPStorageObject, self).__init__(name, 'lookup')
463
477
478 - def _get_page_size(self):
479 self._check_self()
480 return int(self._parse_info("PAGES/PAGE_SIZE").split('*')[1])
481
482 - def _get_pages(self):
483 self._check_self()
484 return int(self._parse_info("PAGES/PAGE_SIZE").split('*')[0])
485
490
492 self._check_self()
493
494 try:
495 return bool(int(self._parse_info('nullio')))
496 except AttributeError:
497 return False
498
499
500
501 page_size = property(_get_page_size,
502 doc="Get the ramdisk page size.")
503 pages = property(_get_pages,
504 doc="Get the ramdisk number of pages.")
505 size = property(_get_size,
506 doc="Get the ramdisk size in bytes.")
507 nullio = property(_get_nullio,
508 doc="Get the nullio status.")
509
518
521 '''
522 An interface to configFS storage objects for fileio backstore.
523 '''
524
525
526
527 - def __init__(self, name, dev=None, size=None,
528 wwn=None, write_back=False):
529 '''
530 A FileIOStorageObject can be instantiated in two ways:
531 - B{Creation mode}: If I{dev} and I{size} are specified, the
532 underlying configFS object will be created with those parameters.
533 No FileIOStorageObject with the same I{name} can pre-exist in the
534 parent Backstore in that mode, or instantiation will fail.
535 - B{Lookup mode}: If I{dev} and I{size} are not set, then the
536 FileIOStorageObject will be bound to the existing configFS object
537 in the parent Backstore having the specified I{name}.
538 The underlying configFS object must already exist in that mode,
539 or instantiation will fail.
540
541 @param name: The name of the FileIOStorageObject.
542 @type name: string
543 @param dev: The path to the backend file or block device to be used.
544 - Examples: I{dev="/dev/sda"}, I{dev="/tmp/myfile"}
545 - The only block device type that is accepted I{TYPE_DISK}, or
546 partitions of a I{TYPE_DISK} device.
547 For other device types, use pscsi.
548 @type dev: string
549 @param size: Size of the object, if not a block device
550 @type size: int
551 @param wwn: T10 WWN Unit Serial, will generate if None
552 @type wwn: string
553 @param write_back: Should we create the StorageObject with
554 write caching enabled? Disabled by default
555 @type write_back: bool
556 @return: A FileIOStorageObject object.
557 '''
558
559 if dev is not None:
560 super(FileIOStorageObject, self).__init__(name, 'create')
561 try:
562 self._configure(dev, size, wwn, write_back)
563 except:
564 self.delete()
565 raise
566 else:
567 super(FileIOStorageObject, self).__init__(name, 'lookup')
568
603
607
616
619
620
621
622 write_back = property(_get_wb_enabled,
623 doc="True if write-back, False if write-through (write cache disabled)")
624 size = property(_get_size,
625 doc="Get the current FileIOStorage size in bytes")
626 is_block = property(_is_block,
627 doc="True if FileIoStorage is backed by a block device instead of a file")
628
636
639 '''
640 An interface to configFS storage objects for block backstore.
641 '''
642
643
644
645 - def __init__(self, name, dev=None, wwn=None, readonly=False,
646 write_back=False):
647 '''
648 A BlockIOStorageObject can be instantiated in two ways:
649 - B{Creation mode}: If I{dev} is specified, the underlying configFS
650 object will be created with that parameter.
651 No BlockIOStorageObject with the same I{name} can pre-exist in
652 the parent Backstore in that mode.
653 - B{Lookup mode}: If I{dev} is not set, then the
654 BlockIOStorageObject will be bound to the existing configFS
655 object in the parent Backstore having the specified
656 I{name}. The underlying configFS object must already exist in
657 that mode, or instantiation will fail.
658
659 @param name: The name of the BlockIOStorageObject.
660 @type name: string
661 @param dev: The path to the backend block device to be used.
662 - Example: I{dev="/dev/sda"}.
663 - The only device type that is accepted I{TYPE_DISK}.
664 For other device types, use pscsi.
665 @type dev: string
666 @param wwn: T10 WWN Unit Serial, will generate if None
667 @type wwn: string
668 @return: A BlockIOStorageObject object.
669 '''
670
671 if dev is not None:
672 super(BlockStorageObject, self).__init__(name, 'create')
673 try:
674 self._configure(dev, wwn, readonly, write_back)
675 except:
676 self.delete()
677 raise
678 else:
679 super(BlockStorageObject, self).__init__(name, 'lookup')
680
697
701
705
709
713
715 self._check_self()
716
717 try:
718 return bool(int(self._parse_info('readonly')))
719 except AttributeError:
720 return False
721
722
723
724 major = property(_get_major,
725 doc="Get the block device major number")
726 minor = property(_get_minor,
727 doc="Get the block device minor number")
728 size = property(_get_size,
729 doc="Get the block device size")
730 write_back = property(_get_wb_enabled,
731 doc="True if write-back, False if write-through (write cache disabled)")
732 readonly = property(_get_readonly,
733 doc="True if the device is read-only, False if read/write")
734
742
745 """
746 Create a storage object based on a given path.
747 Only works for file & block.
748 """
749
761
762
763 bs_params = {
764 PSCSIStorageObject: dict(name='pscsi'),
765 RDMCPStorageObject: dict(name='ramdisk', alt_dirprefix='rd_mcp'),
766 FileIOStorageObject: dict(name='fileio'),
767 BlockStorageObject: dict(name='block', alt_dirprefix='iblock'),
768 }
769
770 bs_cache = {}
773 """
774 Backstore is needed as a level in the configfs hierarchy, but otherwise useless.
775 1:1 so:backstore.
776 Created by storageobject ctor before SO configfs entry.
777 """
778
779 - def __init__(self, name, storage_object_cls, mode):
780 super(_Backstore, self).__init__()
781 self._so_cls = storage_object_cls
782 self._plugin = bs_params[self._so_cls]['name']
783
784 dirp = bs_params[self._so_cls].get("alt_dirprefix", self._plugin)
785
786 global bs_cache
787 if not bs_cache:
788 for dir in glob.iglob("%s/core/*_*/*/" % self.configfs_dir):
789 parts = dir.split("/")
790 bs_name = parts[-2]
791 bs_dirp, bs_index = parts[-3].rsplit("_", 1)
792 current_key = "%s/%s" % (bs_dirp, bs_name)
793 bs_cache[current_key] = int(bs_index)
794
795
796 self._lookup_key = "%s/%s" % (dirp, name)
797 self._index = bs_cache.get(self._lookup_key, None)
798
799 if self._index != None and mode == 'create':
800 raise RTSLibError("Storage object %s/%s exists" %
801 (self._plugin, name))
802 elif self._index == None:
803 if mode == 'lookup':
804 raise RTSLibError("Storage object %s/%s not found" %
805 (self._plugin, name))
806 else:
807
808 for index in xrange(1048576):
809 if index not in bs_cache.values():
810 self._index = index
811 bs_cache[self._lookup_key] = self._index
812 break
813 else:
814 raise RTSLibError("No available backstore index")
815
816 self._path = "%s/core/%s_%d" % (self.configfs_dir,
817 dirp,
818 self._index)
819 self._create_in_cfs_ine(mode)
820
824
827
829 self._check_self()
830 info = fread("%s/hba_info" % self.path)
831 return re.search(".*%s: ([^: ]+).*" \
832 % key, ' '.join(info.split())).group(1).lower()
833
837
841
845
846 plugin = property(_get_plugin,
847 doc="Get the backstore plugin name.")
848 index = property(_get_index,
849 doc="Get the backstore index as an int.")
850 version = property(_get_version,
851 doc="Get the Backstore plugin version string.")
852 name = property(_get_name,
853 doc="Get the backstore name.")
854
857 import doctest
858 doctest.testmod()
859
860 if __name__ == "__main__":
861 _test()
862