def decimal_valid(edit_field, disk_win=None): '''Check text to see if it is a decimal number of precision no greater than the tenths place. ''' text = edit_field.get_text().lstrip() if text.endswith(" "): raise UIMessage(_('Only the digits 0-9 and "." are valid.')) vals = text.split(".") if len(vals) > 2: raise UIMessage(_('A number can only have one "."')) try: if len(vals[0]) > 0: int(vals[0]) if len(vals) > 1 and len(vals[1]) > 0: int(vals[1]) except ValueError: raise UIMessage(_('Only the digits 0-9 and "." are valid.')) if len(vals) > 1 and len(vals[1]) > 1: raise UIMessage(_("Size can be specified to only one decimal place.")) if disk_win is not None: text = text.rstrip(".") if not text: text = "0" new_size = DiskSpace(text + "gb") max_size = edit_field.data_obj.get_max_size(disk_win.disk_info) # When comparing sizes, check only to the first decimal place, # as that is all the user sees. (Rounding errors that could # cause the partition/slice layout to be invalid get cleaned up # prior to target instantiation) new_size_rounded = round(new_size.size_as("gb"), 1) max_size_rounded = round(max_size, 1) if new_size_rounded > max_size_rounded: raise UIMessage( _("The new size (%(size).1f) is greater than " "the available space (%(avail).1f)") % { "size": new_size_rounded, "avail": max_size_rounded }) size_diff = abs(new_size.size_as("gb") - edit_field.data_obj.orig_size) if size_diff > DiskWindow.SIZE_PRECISION: edit_field.data_obj.size = new_size edit_field.data_obj.adjust_offset(disk_win.disk_info) else: edit_field.data_obj.size = "%fgb" % edit_field.data_obj.orig_size disk_win.update_avail_space() disk_win.no_ut_refresh() part_field = disk_win.find_part_field(edit_field.data_obj)[1] disk_win.mark_if_destroyed(part_field) return True
def get_recommended_size(): '''Returns the recommended size for the installation, in GB''' if DiskWindow.REC_SIZE is None: try: swap_dump = SwapDump() rec_size = str(get_rec_install_size(swap_dump)) + "mb" DiskWindow.REC_SIZE = DiskSpace(rec_size) rec_size = DiskWindow.REC_SIZE.size_as("gb") rec_size = round_to_multiple(rec_size, 0.1) DiskWindow.REC_SIZE.size = "%sgb" % rec_size except (InstallationError): logging.warn("Unable to determine recommended install size") DiskWindow.REC_SIZE = DiskSpace("10gb") return DiskWindow.REC_SIZE
def set_size(self, size): '''Set this disk's size. size must be either a DiskSpace or a string that will be accepted by DiskSpace.__init__ ''' if isinstance(size, DiskSpace): self._size = deepcopy(size) else: self._size = DiskSpace(size)
def get_endblock(self): '''Returns the ending 'offset' of this partition, as a DiskSpace''' try: start_pt = self.offset.size_as("b") end_pt = self.size.size_as("b") return DiskSpace(str(start_pt + end_pt) + "b") except AttributeError: raise AttributeError("%s does not have valid size data" % self.__class__.__name__)
def set_offset(self, offset): '''Set this partition's offset. Must be either a DiskSpace object or a string that will be accepted by DiskSpace.__init__ ''' if isinstance(offset, DiskSpace): self._offset = deepcopy(offset) else: self._offset = DiskSpace(offset)
def get_minimum_size(): '''Returns the minimum disk space needed for installation, in GB. The value returned from get_min_install_size is rounded up to the nearest tenth of a gigabyte, so that the UI ensures enough space is allocated, given that the UI only allows for precision to tenths of a gigabyte. ''' if DiskWindow.MIN_SIZE is None: try: swap_dump = SwapDump() min_size = str(get_min_install_size(swap_dump)) + "mb" DiskWindow.MIN_SIZE = DiskSpace(min_size) min_size = DiskWindow.MIN_SIZE.size_as("gb") min_size = round_to_multiple(min_size, 0.1) DiskWindow.MIN_SIZE.size = "%sgb" % min_size except (InstallationError): logging.warn("Unable to determine minimum install size") DiskWindow.MIN_SIZE = DiskSpace("6gb") return DiskWindow.MIN_SIZE
def modified(self, off_by=UI_PRECISION): '''Returns False if and only if this SliceInfo was instantiated from a tgt.Slice, and this SliceInfo does not differ in substance from the tgt.Slice from which it was instantiated. Size, offset, type and number are compared to determine whether this slice has been modified. off_by - A string or DiskSpace indicating a rounding factor. Any size data (offset, size) that differs by less than the given amount is assumed to be unchanged. e.g., if the tgt.Slice indicates a size of 10.05GB and this SliceInfo has a size of 10.1GB, and off_by is the default of 0.1GB, then it is assumed that the represented slice has not changed. (The original tgt.Slice size should be used, for accuracy) ''' if self._tgt_slice is None: return True if not isinstance(off_by, DiskSpace): off_by = DiskSpace(off_by) off_by_bytes = off_by.size_as("b") if self.number != self._tgt_slice.number: return True if self.type[0] != self._tgt_slice.type: return True if self.type[1] != self._tgt_slice.user: return True tgt_size = self._tgt_slice.blocks * self._tgt_slice.geometry.blocksz if abs(tgt_size - self.size.size_as("b")) > off_by_bytes: return True tgt_offset = self._tgt_slice.offset * self._tgt_slice.geometry.blocksz if abs(tgt_offset - self.offset.size_as("b")) > off_by_bytes: return True return False
def get_image_size(): '''Total size of the software in the image is stored in the /.cdrom/.image_info indicated by the keywoard IMAGE_SIZE. This function retrieves that value from the .image_file The size recorded in the .image_file is in KB, other functions in this file uses the value in MB, so, this function will return the size in MB Returns: size of retrieved from the .image_info file in MB ''' img_size = 0 try: with open(IMAGE_INFO, 'r') as ih: for line in ih: (opt, val) = line.split("=") if opt == IMAGE_SIZE_KEYWORD: # Remove the '\n' character read from # the file, and convert to integer img_size = int(val.rstrip('\n')) break except IOError as ioe: logging.error("Failed to access %s", IMAGE_INFO) logging.exception(ioe) raise InstallationError except ValueError as ive: logging.error("Invalid file format in %s", IMAGE_INFO) logging.exception(ive) raise InstallationError if (img_size == 0): # We should have read in a size by now logging.error("Unable to read the image size from %s", IMAGE_INFO) raise InstallationError return (DiskSpace(str(img_size) + "kb").size_as("mb"))
just indicates that something is wrong ''' pass class NotEnoughSpaceError(Exception): '''There's not enough space in the target disk for successful installation ''' pass # All sizes are in MB MIN_SWAP_SIZE = 512 MAX_SWAP_SIZE = DiskSpace("32gb").size_as("mb") #32G MIN_DUMP_SIZE = 256 MAX_DUMP_SIZE = DiskSpace("16gb").size_as("mb") #16G OVERHEAD = 1024 FUTURE_UPGRADE_SPACE = DiskSpace("2gb").size_as("mb") #2G ZVOL_REQ_MEM = 900 # Swap ZVOL is required if memory is below this class SwapDump: ''' All information associated with swap and dump''' ''' The type of swap/dump. Define them as strings so debugging output is easier to read. ''' SLICE = "Slice" ZVOL = "ZVOL" NONE = "None"
''' Object to represent Partitions ''' from copy import copy, deepcopy from functools import cmp_to_key import logging import osol_install.tgt as tgt from osol_install.profile.disk_space import DiskSpace, round_to_multiple, \ round_down from osol_install.profile.slice_info import SliceInfo, UI_PRECISION # Minimum space between partitions before presenting it as 'unused' space # Used by PartitionInfo.add_unused_parts() MIN_GAP_SIZE = DiskSpace("1gb") # Minimum space between logical partitions before presenting it as # 'unused' space. Used by DiskInfo.add_unused_parts() MIN_LOGICAL_GAP_SIZE = DiskSpace("100mb") # Pylint gets confused and thinks PartitionInfo.size and PartitionInfo.offset # are strings, but they are actually DiskSpace objects # pylint: disable-msg=E1103 class PartitionInfo(object): '''Represents a single partition on a disk on the system. EDITABLE_PART_TYPES is a list of primary partition types for which modifying the size is supported.
class SliceInfo(object): '''Represents a single slice on a partition or slice.''' MAX_SLICES = 8 MAX_VTOC = DiskSpace("2tb") BACKUP_SLICE = 2 x86_BOOT_SLICE = 8 x86_ALT_SLICE = 9 UNUSED = (None, None) UNUSED_TEXT = "Unused" DEFAULT_POOL = UserString("") ZPOOL = tgt.Slice.AZPOOL ZPOOL_TYPES = [ tgt.Slice.AZPOOL, tgt.Slice.EZPOOL, tgt.Slice.SZPOOL, tgt.Slice.CZPOOL ] ROOT_POOL = (ZPOOL, DEFAULT_POOL) LEGACY = "legacy" UFS = tgt.Slice.FS UFS_TEXT = "UFS" UNKNOWN = "???" TYPES = [UNUSED, ROOT_POOL] def __init__(self, slice_num=0, size=None, offset=None, blocksz=512, slice_type=None, readonly=False, unmountable=True, tag=None, tgt_slice=None): '''Constructor takes either a tgt_slice, which should be a tgt.Slice object, or a set of parameters. If tgt_slice is supplied, all other parameters are ignored. ''' self._tgt_slice = tgt_slice self._size = None self._offset = None self.previous_size = None if tgt_slice: size = str(tgt_slice.blocks * tgt_slice.geometry.blocksz) + "b" self.size = size offset = str(tgt_slice.offset * tgt_slice.geometry.blocksz) + "b" self.blocksz = tgt_slice.geometry.blocksz self.offset = offset self.number = tgt_slice.number self.readonly = tgt_slice.readonly self.type = (tgt_slice.type, tgt_slice.user) self.last_mount = tgt_slice.last_mount self.unmountable = tgt_slice.unmountable self.tag = tgt_slice.tag else: self.readonly = readonly if slice_type is None: slice_type = SliceInfo.UNUSED if len(slice_type) != 2: raise TypeError("slice_type must be tuple of length 2") self.type = slice_type self.unmountable = unmountable self.size = size self.blocksz = blocksz self.offset = offset self.number = slice_num self.last_mount = None self.tag = tag self.original_type = self.type def __str__(self): result = ["Slice Info (%s):" % self.number] result.append("Type: %s:%s" % self.type) result.append("Offset: %s" % self.offset) result.append("Size: %s" % self.size) return "\n".join(result) @staticmethod def compare(left, right): '''Returns an integer such that this method can be passed to list.sort() and the list will be sorted in disk layout order. The backup slice is always listed last ''' if isinstance(left, tgt.Slice): left = SliceInfo(left) if not isinstance(left, SliceInfo): return NotImplemented if isinstance(right, tgt.Slice): right = SliceInfo(right) if not isinstance(right, SliceInfo): return NotImplemented if left.number == SliceInfo.BACKUP_SLICE: return 1 elif right.number == SliceInfo.BACKUP_SLICE: return -1 left_off = left.offset.size_as("b") right_off = right.offset.size_as("b") if left_off < right_off: return -1 elif left_off > right_off: return 1 else: return 0 def get_offset(self): '''Return this slice's offset as a DiskSpace object''' return self._offset def set_offset(self, offset): '''Set this slice's offset. Must be either a DiskSpace object or a string that will be accepted by DiskSpace.__init__ ''' if isinstance(offset, DiskSpace): self._offset = deepcopy(offset) else: self._offset = DiskSpace(offset) def get_size(self): '''Returns this slice's size as a DiskSpace object''' return self._size def set_size(self, size): '''Set this slice's size. size must be either a DiskSpace or a string that will be accepted by DiskSpace.__init__ ''' if isinstance(size, DiskSpace): self._size = deepcopy(size) else: self._size = DiskSpace(size) def get_type(self): '''Returns this SliceInfo's 'type' Here for interface compatibility with PartitionInfo.get_type()''' return self.type def get_blocks(self): '''Return the number of blocks on this slice''' return int(self.size.size_as("b") / self.blocksz) size = property(get_size, set_size) offset = property(get_offset, set_offset) def get_description(self): '''Return a string suitable for representing this slice in a UI''' description = None if self.number == SliceInfo.BACKUP_SLICE: description = tgt.Slice.BACKUP elif self.type == SliceInfo.UNUSED: description = SliceInfo.UNUSED_TEXT elif self.type[0] == SliceInfo.UFS: if self.last_mount: description = self.last_mount else: description = SliceInfo.UFS_TEXT elif self.type[0] == tgt.Slice.UNKNOWN: if self.tag == tgt.Slice.UNKNOWN: description = SliceInfo.UNKNOWN else: description = self.tag elif self.type[1] and self.type[1] != tgt.Slice.UNKNOWN: description = self.type[1] else: description = self.type[0] return str(description) def cycle_type(self, parent, extra_types=None): '''Cycle this partition's type. If extra_types is given, it should be a list of additional types - these will be considered when cycling to the next type ''' if extra_types is None: extra_types = [] if self.number == SliceInfo.BACKUP_SLICE: return has_solaris_data = (parent.get_solaris_data() is not None) types = set() types.update(SliceInfo.TYPES) types.update(extra_types) types = list(types) types.sort() if self.type in types: logging.debug("type in types, cycling next") type_index = types.index(self.type) type_index = (type_index + 1) % len(types) self.type = types[type_index] logging.debug("now %s-%s", *self.type) else: logging.debug("type NOT in types, setting to types[0]") self.original_type = self.type self.type = types[0] if self.type == SliceInfo.UNUSED: self.previous_size = self.size self.size = "0GB" elif self.is_rpool(): if has_solaris_data: self.cycle_type(parent, extra_types) def get_endblock(self): '''Returns the ending 'offset' of this slice, as a DiskSpace''' try: start_pt = self.offset.size_as("b") end_pt = self.size.size_as("b") return DiskSpace(str(start_pt + end_pt) + "b") except AttributeError: raise AttributeError("%s does not have valid size data" % self.__class__.__name__) def get_max_size(self, parent): '''Return the maximum possible size this slice could consume, in gigabytes, based on adjacent unused space ''' if self.number == SliceInfo.BACKUP_SLICE: return self.size.size_as("gb") msg_str = "get_max_size:%s:" % self.number slices = parent.slices if self not in slices: raise ValueError("This slice not in the parent!") self_idx = slices.index(self) prev_slice = None next_slice = None # Search for the slice prior to this one with the largest "endblock" # Since existing slices may overlap, this could be any slice prior # to the current one. for slice_info in reversed(slices[:self_idx]): if (slice_info.type != SliceInfo.UNUSED and slice_info.number != SliceInfo.BACKUP_SLICE): if (prev_slice is None or slice_info.get_endblock() > prev_slice.get_endblock()): prev_slice = slice_info for slice_info in slices[self_idx + 1:]: if (slice_info.type != SliceInfo.UNUSED and slice_info.number != SliceInfo.BACKUP_SLICE): next_slice = slice_info break if prev_slice is None: msg_str += "prev_part=None:start_pt=0:" start_pt = 0 else: msg_str += "prev_part=%s:" % prev_slice.number start_pt = prev_slice.get_endblock().size_as("gb") msg_str += "start_pt=" + str(start_pt) + ":" if next_slice is None: msg_str += "next_part=None:end_pt=" for slice_info in reversed(slices): # Use the backup slice to define the absolute max size # any given slice can be. (This is usually the last slice, # hence the use of a reversed iterator) if slice_info.number == SliceInfo.BACKUP_SLICE: end_pt = slice_info.size.size_as("gb") break else: # Default to the parent's size if there happens to be no S2 end_pt = parent.size.size_as("gb") msg_str += str(end_pt) + ":" else: msg_str += "next_part=%s:" % next_slice.number end_pt = next_slice.offset.size_as("gb") msg_str += "end_pt=%s:" % end_pt max_space = end_pt - start_pt if max_space < 0: max_space = 0 msg_str += "max_size=%s" % max_space logging.debug(msg_str) return max_space def editable(self, dummy): '''Returns True if the installer is capable of resizing this Slice''' return self.is_rpool() def adjust_offset(self, parent): '''Adjust this slice's offset such that it no longer overlaps with prior or subsequent slices, by comparing this slice's offset with prior slices, and its endblock with subsequent ones. Additionally, any unused slices found are shifted to align with this slice's trailing edge, if needed. This function should only be called after ensuring that this slice's size is less than or equal its max_size (as given by get_max_size); the behavior of this function when attempting to adjust in both directions is undefined. Additionally, the slices on the parent should already be sorted in disk order. ''' if self.number == SliceInfo.BACKUP_SLICE: return parts = parent.get_parts() self_idx = parts.index(self) endblock = self.get_endblock() endblock_bytes = endblock.size_as("gb") unused_parts = [] pre_shift = 0 for part in parts[:self_idx]: if (part.type == SliceInfo.UNUSED or part.number == SliceInfo.BACKUP_SLICE): continue overlap = (part.get_endblock().size_as("gb") - self.offset.size_as("gb")) pre_shift = max(pre_shift, overlap) if pre_shift > 0: new_offset = self.offset.size_as("gb") + pre_shift self.offset = str(new_offset) + "gb" post_shift = None for part in parts[self_idx + 1:]: if part.offset.size_as("gb") < endblock_bytes: if part.type == SliceInfo.UNUSED: unused_parts.append(part) elif part.number != SliceInfo.BACKUP_SLICE: post_shift = endblock_bytes - part.offset.size_as("gb") break else: break else: # Check to ensure we don't slip past the end of the disk/partition max_endblock = parent.size.size_as("gb") if endblock_bytes > max_endblock: post_shift = endblock_bytes - max_endblock if post_shift is not None: new_offset = max(0, self.offset.size_as("gb") - post_shift) self.offset = str(new_offset) + "gb" new_endblock = self.get_endblock() for part in unused_parts: part.offset = new_endblock def to_tgt(self, parent): '''Transfer the install profile information to tgt format''' # Create tgt.Slice object if self.get_type() == SliceInfo.UNUSED: return None if not self.modified(): return self._tgt_slice # Don't need to include the 'backup' slice, libti will # automatically create one appropriately if self.number == SliceInfo.BACKUP_SLICE: return None # Something changed, need to create a new one geo = tgt.Geometry(parent.cylsz, self.blocksz) # offset must be a multiple of tgt.Geometry.cylsz off = int(self.offset.size_as("b") / self.blocksz) offset = round_to_multiple(off, geo.cylsz) blocks = round_to_multiple(self.get_blocks(), geo.cylsz) tag = tgt.Slice.UNASSIGNED slice_type = self.type[0] user = self.type[1] sl = tgt.Slice(geo, self.number, tag, slice_type, offset, blocks, modified=True, user=str(user), unmountable=self.unmountable, readonly=self.readonly) return (sl) def modified(self, off_by=UI_PRECISION): '''Returns False if and only if this SliceInfo was instantiated from a tgt.Slice, and this SliceInfo does not differ in substance from the tgt.Slice from which it was instantiated. Size, offset, type and number are compared to determine whether this slice has been modified. off_by - A string or DiskSpace indicating a rounding factor. Any size data (offset, size) that differs by less than the given amount is assumed to be unchanged. e.g., if the tgt.Slice indicates a size of 10.05GB and this SliceInfo has a size of 10.1GB, and off_by is the default of 0.1GB, then it is assumed that the represented slice has not changed. (The original tgt.Slice size should be used, for accuracy) ''' if self._tgt_slice is None: return True if not isinstance(off_by, DiskSpace): off_by = DiskSpace(off_by) off_by_bytes = off_by.size_as("b") if self.number != self._tgt_slice.number: return True if self.type[0] != self._tgt_slice.type: return True if self.type[1] != self._tgt_slice.user: return True tgt_size = self._tgt_slice.blocks * self._tgt_slice.geometry.blocksz if abs(tgt_size - self.size.size_as("b")) > off_by_bytes: return True tgt_offset = self._tgt_slice.offset * self._tgt_slice.geometry.blocksz if abs(tgt_offset - self.offset.size_as("b")) > off_by_bytes: return True return False def destroyed(self, off_by=UI_PRECISION): '''Returns True if this slice previously had data, and has also been modified. ''' if self.is_rpool(): return True return (self._tgt_slice is not None and self.modified(off_by)) def is_rpool(self): '''Returns True this slice is the default pool ''' return (self.type[0] in SliceInfo.ZPOOL_TYPES and self.type[1] == SliceInfo.DEFAULT_POOL) def is_solaris_data(self): return self.is_rpool()
# # Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # ''' Object to represent Slices ''' from copy import deepcopy from collections import UserString import logging import osol_install.tgt as tgt from osol_install.profile.disk_space import DiskSpace, round_to_multiple UI_PRECISION = DiskSpace("0.05gb") # Pylint gets confused and thinks SliceInfo.size and SliceInfo.offset # are strings, but they are actually DiskSpace objects # pylint: disable-msg=E1103 class SliceInfo(object): '''Represents a single slice on a partition or slice.''' MAX_SLICES = 8 MAX_VTOC = DiskSpace("2tb") BACKUP_SLICE = 2 x86_BOOT_SLICE = 8 x86_ALT_SLICE = 9 UNUSED = (None, None) UNUSED_TEXT = "Unused"