def print_data(self): '''Print static (non-editable) data. Slices - fill the left side, then remaining slices on the right side. If for some reason not all slices fit, indicate how many more slices there area Partitions - Put standard partitions on the left, logical partitions on the right ''' part_index = 0 data = self.ui_obj.get_parts_in_use() if len(data) == 0: return # should never be this case if self.has_partition_data: max_parts = MAX_PRIMARY_PARTS else: max_parts = min(len(data), self.left_win.area.lines) win = self.left_win y_loc = 0 for next_data in data: LOGGER.debug("next_data: %s", next_data) if y_loc >= max_parts: if win is self.left_win: win = self.right_win y_loc = 0 max_parts = win.area.lines else: num_extra = len(data) - part_index if self.has_partition_data: more_parts_txt = _("%d more partitions") % num_extra else: more_parts_txt = _("%d more slices") % num_extra win.add_text(more_parts_txt, win.area.lines, 3) break x_loc = DiskWindow.SCROLL_PAD field = 0 win.add_text(next_data.get_description(), y_loc, x_loc, self.headers[field][0] - 1) x_loc += self.headers[field][0] field += 1 if not self.has_partition_data: win.add_text(str(next_data.name), y_loc, x_loc, self.headers[field][0] - 1) x_loc += self.headers[field][0] field += 1 win.add_text(locale.format("%*.1f", (self.headers[field][0] - 1, next_data.size.get(Size.gb_units))), y_loc, x_loc, self.headers[field][0] - 1) x_loc += self.headers[field][0] y_loc += 1 field += 1 part_index += 1 self.right_win.use_vert_scroll_bar = False self.no_ut_refresh()
def set_actions(self): '''Edit Screens add 'Reset' and 'Change Type' actions. Since these do not manipulate screen direction, they are captured during processing by adding them to center_win's key_dict. ''' super(PartEditScreen, self).set_actions() reset_action = Action(curses.KEY_F7, _("Reset")) change_action = Action(curses.KEY_F5, _("Change Type")) self.main_win.actions[reset_action.key] = reset_action self.main_win.actions[change_action.key] = change_action self.center_win.key_dict[curses.KEY_F7] = self.on_key_F7
def build_summary(self): '''Build a textual summary from the DOC data''' lines = [] lines.append(_("Software: %s") % self.get_release()) lines.append("") lines.append(self.get_disk_summary()) lines.append("") lines.append(self.get_tz_summary()) lines.append("") lines.append(_("Language: *The following can be changed when " "logging in.")) if self.sysconfig.system.locale is None: self.sysconfig.system.determine_locale() lines.append(_(" Default language: %s") % self.sysconfig.system.actual_lang) lines.append("") lines.append(_("Keyboard layout: *The following can be " "changed when logging in.")) lines.append(_(" Default keyboard layout: %s") % self.sysconfig.system.keyboard) lines.append("") lines.append(_("Terminal type: %s") % self.sysconfig.system.terminal_type) lines.append("") lines.append(_("Users:")) lines.extend(self.get_users()) lines.append("") lines.append(_("Network:")) lines.extend(self.get_networks()) self._get_nameservice(lines) return "\n".join(lines)
def get_users(self): '''Build a summary of the user information, and return it as a list of strings ''' root = self.sysconfig.users.root primary = self.sysconfig.users.user user_summary = [] if not root.password: user_summary.append(_(" Warning: No root password set")) if primary.login_name: user_summary.append(_(" Username: %s") % primary.login_name) else: user_summary.append(_(" No user account")) return user_summary
def set_actions(self): '''Remove all actions except Quit, and add actions for rebooting and viewing the log. ''' self.main_win.actions.pop(curses.KEY_F2) # Remove F2_Continue self.main_win.actions.pop(curses.KEY_F3) # Remove F3_Back self.main_win.actions.pop(curses.KEY_F6) # Remove F6_Help if self.install_data.install_succeeded: reboot_action = Action(curses.KEY_F8, _("Reboot"), reboot_system) self.main_win.actions[reboot_action.key] = reboot_action log_action = Action(curses.KEY_F4, _("View Log"), self.main_win.screen_list.get_next) self.main_win.actions[log_action.key] = log_action
def get_disk_summary(self): '''Return a string summary of the disk selection''' doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) disk_string = list() disk_size_str = locale.format("%.1f", disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB locale_disk_str = _("Disk: ") + disk_size_str + " " + \ str(disk.disk_prop.dev_type) disk_string.append(locale_disk_str) if not disk.whole_disk: if disk.label != "VTOC": part_data = get_solaris_gpt_partition(doc) if part_data is not None: type_str = str(part_data.part_type) else: part_data = get_solaris_partition(doc) if part_data is not None: type_str = str(libdiskmgt_const.PARTITION_ID_MAP[ part_data.part_type]) if part_data is not None: part_size_str = locale.format("%.1f", part_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_part_str = _("Partition: ") + part_size_str + " " +\ type_str disk_string.append(locale_part_str) if part_data is None or not part_data.in_zpool: slice_data = get_solaris_slice(doc) slice_num_str = _("Slice %s: ") % slice_data.name slice_size_str = locale.format("%.1f", slice_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_slice_str = slice_num_str + slice_size_str + " " +\ str(slice_data.in_zpool) disk_string.append(locale_slice_str) return "\n".join(disk_string)
def get_users(self): '''Build a summary of the user information, and return it as a list of strings ''' user_summary = [] user_summary.append(_(" root password is blank. set it at next login.")) return user_summary
def _show(self): '''Display the correct text based on whether the installation succeeded or failed. ''' self.log_locations["log_tmp"] = self.install_data.log_location self.log_locations["log_final"] = self.install_data.log_final if self.install_data.install_succeeded: self.header_text = InstallStatus.SUCCESS_HEADER paragraph_text = InstallStatus.SUCCESS_TEXT # inform the user how to set the CHAP password and username on # SPARC, if needed if platform.processor() == "sparc": doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) iscsi = doc.volatile.get_first_child(name=ISCSI_LABEL, class_type=Iscsi) if iscsi and iscsi.chap_name is not None and \ disk.ctd in iscsi.ctd_list: iscsi_string = list() iscsi_string.append("") iscsi_string.append(_("CHAP username and password must " "be set at the ok prompt:")) iscsi_string.append(_("ok set-ascii-security-key " "chap-user <chap name>")) iscsi_string.append(_("ok set-ascii-security-key " "chap-password <chap password>")) self.iscsi_paragraph = "\n".join(iscsi_string) else: self.header_text = InstallStatus.FAILED_HEADER paragraph_text = InstallStatus.FAILED_TEXT self.main_win.set_header_text(self.header_text) fmt = {} fmt.update(self.log_locations) fmt.update(RELEASE) self.center_win.add_paragraph(paragraph_text % fmt, 2) if self.iscsi_paragraph: self.center_win.add_paragraph(self.iscsi_paragraph, 10)
def get_disk_summary(self): '''Return a string summary of the disk selection''' doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) disk_string = list() disk_size_str = locale.format("%.1f", disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB locale_disk_str = _("Disk: ") + disk_size_str + " " + \ str(disk.disk_prop.dev_type) disk_string.append(locale_disk_str) if not disk.whole_disk: part_data = get_solaris_partition(doc) if part_data is not None: part_size_str = locale.format("%.1f", part_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_part_str = _("Partition: ") + part_size_str + " " +\ str(libdiskmgt_const.PARTITION_ID_MAP[part_data.part_type]) disk_string.append(locale_part_str) if part_data is None or not part_data.in_zpool: slice_data = get_solaris_slice(doc) slice_num_str = _("Slice %s: ") % slice_data.name slice_size_str = locale.format("%.1f", slice_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_slice_str = slice_num_str + slice_size_str + " " +\ str(slice_data.in_zpool) disk_string.append(locale_slice_str) return "\n".join(disk_string)
def get_log_data(self): '''Attempt to read in the install log file. If an error occurs, the log_data is set to a string explaining the cause, if possible. ''' if self.log_data is None: try: with open(self.install_data.log_location) as log_file: log_data = log_file.read() except (OSError, IOError) as error: self.log_data = _("Could not read log file:\n\t%s") % \ error.strerror max_chars = self.win_size_x - 4 self.log_data = convert_paragraph(log_data, max_chars) return self.log_data
class WelcomeScreen(BaseScreen): '''First screen of the text installer. No special __init__ needed beyond that provided by BaseScreen ''' HEADER_TEXT = _("Welcome to %(release)s") % RELEASE WELCOME_TEXT = _("Thanks for choosing to install %(release)s! This " "installer enables you to install the %(release)s " "Operating System (OS) on SPARC or x86 systems.\n\n" "The installation log will be at %(log)s.\n\n" "How to navigate through this installer:") BULLET_ITEMS = [ _("Use the function keys listed at the bottom of each " "screen to move from screen to screen and to perform " "other operations."), _("Use the up/down arrow keys to change the selection " "or to move between input fields."), _("If your keyboard does not have function keys, or " "they do not respond, press ESC; the legend at the " "bottom of the screen will change to show the ESC keys" " for navigation and other functions.") ] BULLET = "- " HELP_DATA = (TUI_HELP + "/%s/welcome.txt", _("Welcome and Navigation Instructions")) def __init__(self, main_win, install_data): super(WelcomeScreen, self).__init__(main_win) self.install_data = install_data def set_actions(self): '''Remove the F3_Back Action from the first screen''' self.main_win.actions.pop(self.main_win.back_action.key, None) def _show(self): '''Display the static paragraph WELCOME_TEXT''' log_file = self.install_data.log_location y_loc = 1 fmt = {"log": log_file} fmt.update(RELEASE) text = WelcomeScreen.WELCOME_TEXT % fmt y_loc += self.center_win.add_paragraph(text, start_y=y_loc) x_loc = len(WelcomeScreen.BULLET) for bullet in WelcomeScreen.BULLET_ITEMS: self.center_win.add_text(WelcomeScreen.BULLET, start_y=y_loc) y_loc += self.center_win.add_paragraph(bullet, start_y=y_loc, start_x=x_loc)
class LogViewer(BaseScreen): '''Screen for reading and displaying the install log''' HEADER_TEXT = _("Installation Log") def __init__(self, main_win, install_data): super(LogViewer, self).__init__(main_win) self.log_data = None self.scroll_area = None self.install_data = install_data def set_actions(self): '''Remove all actions except F3_Back''' self.main_win.actions.pop(curses.KEY_F2) self.main_win.actions.pop(curses.KEY_F6) self.main_win.actions.pop(curses.KEY_F9) def _show(self): '''Create a scrollable region and fill it with the install log''' self.center_win.border_size = (0, 0) self.scroll_area = WindowArea(self.win_size_y, self.win_size_x, 0, 0, len(self.get_log_data())) log = ScrollWindow(self.scroll_area, window=self.center_win) log.add_paragraph(self.get_log_data(), 0, 2) self.center_win.activate_object(log) def get_log_data(self): '''Attempt to read in the install log file. If an error occurs, the log_data is set to a string explaining the cause, if possible. ''' if self.log_data is None: try: with open(self.install_data.log_location) as log_file: log_data = log_file.read() except (OSError, IOError) as error: self.log_data = _("Could not read log file:\n\t%s") % \ error.strerror max_chars = self.win_size_x - 4 self.log_data = convert_paragraph(log_data, max_chars) return self.log_data
def get_networks(self): '''Build a summary of the networks from the DOC data, returned as a list of strings ''' network_summary = [] network_summary.append(_(" Computer name: %s") % self.sysconfig.system.hostname) nic = self.sysconfig.nic if nic.type == NetworkInfo.AUTOMATIC: network_summary.append(_(" Network Configuration: Automatic")) elif nic.type == NetworkInfo.NONE: network_summary.append(_(" Network Configuration: None")) elif nic.type == NetworkInfo.MANUAL: network_summary.append(_(" Manual Configuration: %s") % NetworkInfo.get_nic_desc(nic.nic_iface)) network_summary.append(_(" IP Address: %s") % nic.ip_address) network_summary.append(_(" Netmask: %s") % nic.netmask) if nic.gateway: network_summary.append(_(" Router: %s") % nic.gateway) return network_summary
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() radixchar = locale.localeconv()['decimal_point'] if text.endswith(" "): raise UIMessage(_('Only the digits 0-9 and %s are valid.') % radixchar) vals = text.split(radixchar) if len(vals) > 2: raise UIMessage(_('A number can only have one %s') % radixchar) 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 %s are valid.') % radixchar) 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(radixchar) if not text: text = "0" # encode user input per locale for floating point conversion text = text.encode(get_encoding()) new_size = Size(str(locale.atof(text)) + Size.gb_units) max_size = edit_field.data_obj.get_max_size() # 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.get(Size.gb_units), 1) max_size_rounded = round(max_size.get(Size.gb_units), 1) if new_size_rounded > max_size_rounded: locale_new_size = locale.format("%.1f", new_size_rounded) locale_max_size = locale.format("%.1f", max_size_rounded) msg = _("The new size ") + locale_new_size + \ _(" is greater than the available space ") + locale_max_size raise UIMessage(msg) return True
class PartEditScreen(BaseScreen): '''Allows user editing of partitions on a disk, or slices on a disk/partition ''' PARTITION_PARAGRAPH = _("Oracle Solaris will be installed into the Solaris" " partition. A partition's type can be changed" " using the F5 key.\n\n" "A partition's size can be increased " "up to its Avail space. Avail space can be " "increased by deleting an adjacent partition. " "Delete a partition by changing it to \"Unused\"" " using the F5 key.\n\n" "The four primary partition slots are listed on " "the left. If one is an \"Extended\" partition " "its logical partitions are listed on the " "right.") % RELEASE SLICE_PARAGRAPH = _("%(release)s will be installed in the \"%(pool)s\" " "slice. Use the F5 key to change a slice to " "\"%(pool)s.\"\n\n" "A slice's size can be increased up to its Avail " "size. Avail can be increased by deleting an adjacent" " slice. Use the F5 key to delete a slice by changing" " it to \"Unused.\"\n\n" "Slices are listed in disk layout order.") HEADER_x86_PART = _("Select Partition: ") HEADER_x86_SLICE = _("Select Slice in Fdisk Partition") HEADER_SPARC_SLICE = _("Select Slice: ") HEADER_TYPE_BOOTABLE = _(" %(type)s %(bootable)s") SLICE_DESTROY_TEXT = _("indicates the slice's current content will be " "destroyed") PART_DESTROY_TEXT = _("indicates the partition's current content will " "be destroyed") BOOTABLE = _("Boot") SPARC_HELP = (TUI_HELP + "/%s/" "sparc_solaris_slices_select.txt", _("Select Slice")) X86_PART_HELP = (TUI_HELP + "/%s/" "x86_fdisk_partitions_select.txt", _("Select Partition")) X86_SLICE_HELP = (TUI_HELP + "/%s/" "x86_fdisk_slices_select.txt", _("Select Slice")) X86_SELECTION_ERROR = _("A 'Solaris2' partition must be selected for " "installation") SPARC_SELECTION_ERROR = _("An 'rpool' slice must be selected for " "installation") MULTIPLE_SOLARIS2_ERROR = _("Invalid layout, more than one 'Solaris2' " "partition found") PART_TOO_SMALL = _("'Solaris2' partition is too small, installation " "requires at least %(size).1fGB") HELP_FORMAT = " %s" def __init__(self, main_win, target_controller, x86_slice_mode=False): super(PartEditScreen, self).__init__(main_win) global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.x86_slice_mode = x86_slice_mode self.is_x86 = platform.processor() == "i386" self.doc = InstallEngine.get_instance().doc if self.x86_slice_mode: # x86, Slice within a partition self.instance = ".slice" self.header_text = PartEditScreen.HEADER_x86_SLICE self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT self.help_data = PartEditScreen.X86_SLICE_HELP elif self.is_x86: # x86, Partition on disk self.header_text = PartEditScreen.HEADER_x86_PART self.paragraph_text = PartEditScreen.PARTITION_PARAGRAPH self.destroy_text = PartEditScreen.PART_DESTROY_TEXT self.help_data = PartEditScreen.X86_PART_HELP else: # SPARC (Slice on disk) self.header_text = PartEditScreen.HEADER_SPARC_SLICE self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT self.help_data = PartEditScreen.SPARC_HELP self.help_format = " %s" self.disk_win = None self.tc = target_controller def set_actions(self): '''Edit Screens add 'Reset' and 'Change Type' actions. Since these do not manipulate screen direction, they are captured during processing by adding them to center_win's key_dict. ''' super(PartEditScreen, self).set_actions() reset_action = Action(curses.KEY_F7, _("Reset")) change_action = Action(curses.KEY_F5, _("Change Type")) self.main_win.actions[reset_action.key] = reset_action self.main_win.actions[change_action.key] = change_action self.center_win.key_dict[curses.KEY_F7] = self.on_key_F7 def on_key_F7(self, dummy): '''F7 -> Reset the DiskWindow''' self.disk_win.reset() def _show(self): '''Display the explanatory paragraph and create the DiskWindow''' disk = get_desired_target_disk(self.doc) if disk.label == "GPT": raise SkipException if disk.whole_disk or getattr(disk, "use_whole_segment", False): LOGGER.debug("disk.whole_disk or disk.use_whole_segment is True, " "skip editing") # perform final target validation perform_final_validation(self.doc) raise SkipException if self.x86_slice_mode: LOGGER.debug("in x86 slice mode") part = get_solaris_partition(self.doc) LOGGER.debug(str(part)) if part is None: err_msg = "Critical error - no Solaris partition found" LOGGER.error(err_msg) raise ValueError(err_msg) if part.in_zpool is not None: LOGGER.debug("Whole partition selected. Skipping slice edit") LOGGER.debug(str(part)) # remove the in_zpool value from partition, delete any existing # slices, and create the needed underneath slices part.create_entire_partition_slice(in_zpool=part.in_zpool, in_vdev=DEFAULT_VDEV_NAME, tag=V_ROOT) part.bootid = Partition.ACTIVE part.in_zpool = None LOGGER.debug(str(part)) # Make sure in_zpool is not set on the Disk, target controller # puts it there in some cases disk.in_zpool = None # perform final target validation perform_final_validation(self.doc) raise SkipException else: part = disk if self.x86_slice_mode: header = self.header_text else: bootable = "" if self.is_x86 and disk.is_boot_disk(): bootable = PartEditScreen.BOOTABLE disk_size_str = locale.format( "%.1f", disk.disk_prop.dev_size.get( Size.gb_units)) + LOCALIZED_GB type_boot_str = PartEditScreen.HEADER_TYPE_BOOTABLE % \ {"type": disk.disk_prop.dev_type, "bootable": bootable} header = self.header_text + disk_size_str + type_boot_str self.main_win.set_header_text(header) y_loc = 1 fmt_dict = {'pool': ROOT_POOL} fmt_dict.update(RELEASE) y_loc += self.center_win.add_paragraph(self.paragraph_text % fmt_dict, y_loc) y_loc += 1 disk_win_area = WindowArea(6, 70, y_loc, 0) self.disk_win = DiskWindow(disk_win_area, part, window=self.center_win, editable=True, error_win=self.main_win.error_line, target_controller=self.tc) y_loc += disk_win_area.lines y_loc += 1 LOGGER.log(LOG_LEVEL_INPUT, "calling addch with params start_y=%s," "start_x=%s, ch=%c", y_loc, self.center_win.border_size[1], DiskWindow.DESTROYED_MARK) self.center_win.window.addch(y_loc, self.center_win.border_size[1], DiskWindow.DESTROYED_MARK, self.center_win.color_theme.inactive) self.center_win.add_text(self.destroy_text, y_loc, 2) self.main_win.do_update() self.center_win.activate_object(self.disk_win) def validate(self): ''' Perform final validation of the desired target ''' if self.is_x86: # check for multiple Solaris2 partitions desired_disk = get_desired_target_disk(self.doc) solaris_list = [ p for p in desired_disk.get_children(class_type=Partition) if p.is_solaris ] if len(solaris_list) > 1: raise UIMessage(PartEditScreen.MULTIPLE_SOLARIS2_ERROR) solaris_part = get_solaris_partition(self.doc) if solaris_part is None: raise UIMessage(PartEditScreen.X86_SELECTION_ERROR) # verify the size of the Solaris2 partition is large enough min_size = self.tc.minimum_target_size if min_size > solaris_part.size: raise UIMessage(PartEditScreen.PART_TOO_SMALL % {"size": min_size.get(Size.gb_units)}) disk = solaris_part.parent # unset in_zpool and in_vdev on the partiton and the parent Disk. # This is a workaround to CR 7085718 solaris_part.in_zpool = None solaris_part.in_vdev = None disk.in_zpool = None disk.in_vdev = None # create system partitions if required try: efi_sys, resv, solaris_part = \ disk.add_required_partitions(solaris_part) LOGGER.debug("EFI System Partition: %s", str(efi_sys)) # resv is always None as it is GPT specific so don't log it except NoPartitionSlotsFree: # If there are no unused partitions left we can't proceed LOGGER.warning("No free slots available for EFI system " "partition.") raise UIMessage("Too many partitions. Delete unnecessary " "partitions.") except InsufficientSpaceError as ise: raise RuntimeError("INTERNAL ERROR: Could not allocate space " "for EFI system partition on disk %s: %s" % (disk, str(ise))) if not self.x86_slice_mode: # delay final validation for x86 until slice mode is # completed. return # final target validation will be performed now solaris_part.bootid = Partition.ACTIVE else: inner_window = self.disk_win.get_active_object() edit_object = inner_window.get_active_object() ui_object = edit_object.get_active_object() if ui_object is None: raise UIMessage(PartEditScreen.SPARC_SELECTION_ERROR) perform_final_validation(self.doc)
class GPTPartEditScreen(PartEditScreen): """ subclass to handle GPT specific disks """ GPT_PARTITION_PARAGRAPH = _("Oracle Solaris will be installed into the " "highlighted Solaris partition. A partition's " "type can be changed using the F5 key.\n\n" "A partition's size can be increased up to " "its Avail space. Avail space can be " "increased by deleting an adjacent partition. " "Delete a partition by changing it to " "\"Unused\"" " using the F5 key.") % RELEASE GPT_PART_DESTROY_TEXT = _("indicates the partition's current content will " "be destroyed") HEADER_GPT = _("Select GPT Partition: ") GPT_HELP = (TUI_HELP + "/%s/gpt_partitions_select.txt", _("Select GPT Partition")) PART_TOO_SMALL = _("Selected partition is too small, installation " "requires at least %(size).1fGB") SELECTION_ERROR = _("A 'Solaris' partition must be selected for " "installation") def __init__(self, main_win, target_controller): super(GPTPartEditScreen, self).__init__(main_win, target_controller) global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.doc = InstallEngine.get_instance().doc self.header_text = GPTPartEditScreen.HEADER_GPT self.paragraph_text = GPTPartEditScreen.GPT_PARTITION_PARAGRAPH self.destroy_text = GPTPartEditScreen.GPT_PART_DESTROY_TEXT self.help_data = GPTPartEditScreen.GPT_HELP self.disk_win = None self.tc = target_controller def _show(self): '''Display the explanatory paragraph and create the DiskWindow''' disk = get_desired_target_disk(self.doc) if disk.label == "VTOC": raise SkipException if disk.whole_disk: LOGGER.debug("disk.whole_disk=True, skip editing") # perform final target validation perform_final_validation(self.doc) raise SkipException part = disk bootable = "" if self.is_x86 and disk.is_boot_disk(): bootable = PartEditScreen.BOOTABLE disk_size_str = locale.format( "%.1f", disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB type_boot_str = PartEditScreen.HEADER_TYPE_BOOTABLE % \ {"type": disk.disk_prop.dev_type, "bootable": bootable} header = self.header_text + disk_size_str + type_boot_str self.main_win.set_header_text(header) y_loc = 1 fmt_dict = {'pool': ROOT_POOL} fmt_dict.update(RELEASE) y_loc += self.center_win.add_paragraph(self.paragraph_text % fmt_dict, y_loc) y_loc += 1 disk_win_area = WindowArea(6, 70, y_loc, 0) self.disk_win = DiskWindow(disk_win_area, part, window=self.center_win, editable=True, error_win=self.main_win.error_line, target_controller=self.tc) y_loc += disk_win_area.lines y_loc += 1 LOGGER.log(LOG_LEVEL_INPUT, "calling addch with params start_y=%s," "start_x=%s, ch=%c", y_loc, self.center_win.border_size[1], DiskWindow.DESTROYED_MARK) self.center_win.window.addch(y_loc, self.center_win.border_size[1], DiskWindow.DESTROYED_MARK, self.center_win.color_theme.inactive) self.center_win.add_text(self.destroy_text, y_loc, 2) self.main_win.do_update() self.center_win.activate_object(self.disk_win) def validate(self): ''' Perform final validation of the desired target ''' # Before validation can happen, we need to set the in_zpool and in_vdev # attributes on *ONLY* the GPT partition the user selected. We provide # the ability for the user to create multiple 'solaris' GPT partitions, # but we only use one of them for the root pool. # If there are multiple Solaris partitions the user must select one # explicitly by highlighting it. If there is only one though, its # selection is implicit. gpt_partition = None disk = get_desired_target_disk(self.doc) # In order to get to the specific DOC object we need to translate # through the many window layers: # main_win.disk_win.[left | right]_win.list_obj.edit_obj.UI_obj.DOC_obj inner_window = self.disk_win.get_active_object() edit_object = inner_window.get_active_object() ui_object = edit_object.get_active_object() # trap on any non Solaris partition if ui_object is None: parts = disk.get_children(class_type=GPTPartition) if parts: solparts = [p for p in parts if p.is_solaris] # Only one Solaris partition, so its selection is implicit. if len(solparts) == 1: gpt_partition = solparts[0] if gpt_partition is None: raise UIMessage(GPTPartEditScreen.SELECTION_ERROR) else: gpt_partition = ui_object.data_obj.doc_obj # verify the size of the selected partition is large enough min_size = self.tc.minimum_target_size if min_size > gpt_partition.size: raise UIMessage(GPTPartEditScreen.PART_TOO_SMALL % {"size": min_size.get(Size.gb_units)}) # unset in_zpool and in_vdev on the parent Disk object disk.in_zpool = None disk.in_vdev = None # unset in_zpool and in_vdev on all other GPT Partitions on this disk for entry in disk.get_children(class_type=GPTPartition): if entry.name != gpt_partition.name: entry.in_zpool = None entry.in_vdev = None # Set in_zpool, in_vdev and the action for this partition gpt_partition.in_zpool = DEFAULT_ZPOOL_NAME gpt_partition.in_vdev = DEFAULT_VDEV_NAME gpt_partition.action = "create" # create required EFI System or BIOS boot partitions and reserved # partitions try: sys_part, resv_part, gpt_partition = \ disk.add_required_partitions(donor=gpt_partition) LOGGER.debug("System/Boot Partition: %s", str(sys_part)) LOGGER.debug("Reserved Partition: %s", str(resv_part)) except NoGPTPartitionSlotsFree: # If there are no unused partitions left we can't proceed LOGGER.warning("No free slots available for boot partition.") raise UIMessage("Too many partitions. Delete unnecessary " "partitions.") except InsufficientSpaceError as ise: raise RuntimeError( "INTERNAL ERROR: Could not allocate space " "for system partition or reserved partition on disk %s: " "%s" % (disk, str(ise))) perform_final_validation(self.doc)
def resize_validate(edit_field, disk_win=None): '''Check text to see if it is a decimal number of precision no greater than the tenths place. Resize the partition if everything checks out. ''' LOGGER.debug("in resize_validate()") text = edit_field.get_text().lstrip() radixchar = locale.localeconv()['decimal_point'] if text.endswith(" "): raise UIMessage( _('Only the digits 0-9 and "%s" are valid.') % radixchar) vals = text.split(radixchar) if len(vals) > 2: raise UIMessage(_('A number can only have one "%s"') % radixchar) 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 "%s" are valid.') % radixchar) if len(vals) > 1 and len(vals[1]) > 1: raise UIMessage(_("Size can be specified to only one decimal place.")) if disk_win is None: return True text = text.rstrip(radixchar) # If the user deleted all digits, leave partition size alone until user # inputs new digits if not text: LOGGER.debug("No size value digits, skipping resize") return True part_order = disk_win.ui_obj.get_parts_in_use().index(edit_field.data_obj) LOGGER.debug("Part being resized is at index: %s", part_order) # encode user input per locale for floating point conversion text = text.encode(get_encoding()) new_size = Size(str(locale.atof(text)) + Size.gb_units) max_size = edit_field.data_obj.get_max_size() # When comparing user input and display sizes, check only to the first # decimal place as that is all the user sees. new_size_rounded = round(new_size.get(Size.gb_units), 1) max_size_rounded = round(max_size.get(Size.gb_units), 1) if new_size_rounded > max_size_rounded: locale_new_size = locale.format("%.1f", new_size_rounded) locale_max_size = locale.format("%.1f", max_size_rounded) msg = _("The new size %(size)s is greater than " "the available space %(avail)s") % \ {"size": locale_new_size, "avail": locale_max_size} raise UIMessage(msg) new_size_text = text.strip() LOGGER.debug("New size text=%s", new_size_text) old_size = edit_field.data_obj.size new_size_byte = new_size.get(Size.byte_units) # Filter out edits that would result in resizing a partition to zero if new_size_byte == 0: return True old_size_byte = old_size.get(Size.byte_units) precision_bytes = Size(UI_PRECISION).get(Size.byte_units) # Ignore potential rounding artifacts. if abs(new_size_byte - old_size_byte) <= precision_bytes: return True max_size_byte = max_size.get(Size.byte_units) if new_size_byte > max_size_byte: # Allow for loss of precision from rounding errors, but no greater if (new_size_byte - max_size_byte) > precision_bytes: raise RuntimeError("Requested partition resize to %d bytes " "exceeds maximum size available: %d" % (new_size_byte, max_size_byte)) # Clamp the new size at max size otherwise the resize will throw # an InsufficientSpaceError. LOGGER.debug( "Requested partition resize exceeds maximum size: " "Clamping %d bytes to %d bytes", new_size_byte, max_size_byte) new_size = max_size parent_doc_obj = edit_field.data_obj.doc_obj.parent if isinstance(parent_doc_obj, Disk): if isinstance(edit_field.data_obj.doc_obj, GPTPartition): resized_obj = parent_doc_obj.resize_gptpartition( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) elif isinstance(edit_field.data_obj.doc_obj, Partition): resized_obj = parent_doc_obj.resize_partition( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) else: resized_obj = parent_doc_obj.resize_slice( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) else: resized_obj = parent_doc_obj.resize_slice(edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) if isinstance(resized_obj, Partition): # Don't do this for GPTPartition because there is no guarantee this # will be the installation target partition if there is more than # 1 Solaris partition resized_obj.in_zpool = ROOT_POOL elif isinstance(resized_obj, Slice): if resized_obj.in_zpool == ROOT_POOL: resized_obj.tag = V_ROOT disk_win.set_disk_info(ui_obj=disk_win.ui_obj) disk_win.activate_index(part_order) dump_doc("After resize") return True
class SummaryScreen(BaseScreen): '''Display a summary of the install profile to the user InnerWindow.__init__ is sufficient to initalize an instance of SummaryScreen ''' HEADER_TEXT = _("Installation Summary") PARAGRAPH = _("Review the settings below before installing." " Go back (F3) to make changes.") HELP_DATA = (TUI_HELP + "/%s/summary.txt", _("Installation Summary")) INDENT = 2 def set_actions(self): '''Replace the default F2_Continue with F2_Install''' install_action = Action(curses.KEY_F2, _("Install"), self.main_win.screen_list.get_next) self.main_win.actions[install_action.key] = install_action def _show(self): '''Prepare a text summary and display it to the user in a ScrollWindow ''' global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.sysconfig = solaris_install.sysconfig.profile.from_engine() y_loc = 1 y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc) y_loc += 1 summary_text = self.build_summary() LOGGER.info("The following configuration is used for " "installation: %s\n", summary_text) # Wrap the summary text, accounting for the INDENT (used below in # the call to add_paragraph) max_chars = self.win_size_x - SummaryScreen.INDENT - 1 summary_text = convert_paragraph(summary_text, max_chars) area = WindowArea(x_loc=0, y_loc=y_loc, scrollable_lines=(len(summary_text) + 1)) area.lines = self.win_size_y - y_loc area.columns = self.win_size_x scroll_region = ScrollWindow(area, window=self.center_win) scroll_region.add_paragraph(summary_text, start_x=SummaryScreen.INDENT) self.center_win.activate_object(scroll_region) def build_summary(self): '''Build a textual summary from the DOC data''' lines = [] lines.append(_("Software: %s") % self.get_release()) lines.append("") lines.append(self.get_disk_summary()) lines.append("") lines.append(self.get_tz_summary()) lines.append("") lines.append(_("Language: *The following can be changed when " "logging in.")) if self.sysconfig.system.locale is None: self.sysconfig.system.determine_locale() lines.append(_(" Default language: %s") % self.sysconfig.system.actual_lang) lines.append("") lines.append(_("Keyboard layout: *The following can be " "changed when logging in.")) lines.append(_(" Default keyboard layout: %s") % self.sysconfig.system.keyboard) lines.append("") lines.append(_("Terminal type: %s") % self.sysconfig.system.terminal_type) lines.append("") lines.append(_("Users:")) lines.extend(self.get_users()) lines.append("") lines.append(_("Network:")) lines.extend(self.get_networks()) self._get_nameservice(lines) lines.append("") lines.append(_("Support configuration:")) lines.extend(self.get_support()) return "\n".join(lines) def get_networks(self): '''Build a summary of the networks from the DOC data, returned as a list of strings ''' network_summary = [] network_summary.append(_(" Computer name: %s") % self.sysconfig.system.hostname) nic = self.sysconfig.nic if nic.type == NetworkInfo.AUTOMATIC: network_summary.append(_(" Network Configuration: Automatic")) elif nic.type == NetworkInfo.NONE: network_summary.append(_(" Network Configuration: None")) elif nic.type == NetworkInfo.MANUAL: network_summary.append(_(" Manual Configuration: %s") % NetworkInfo.get_nic_desc(nic.nic_iface)) network_summary.append(_(" IP Address: %s") % nic.ip_address) network_summary.append(_(" Netmask: %s") % nic.netmask) if nic.gateway: network_summary.append(_(" Router: %s") % nic.gateway) return network_summary def _get_nameservice(self, summary): ''' Find all name services information and append to summary ''' # append lines of name service info to summary nameservice_summary(self.sysconfig.nameservice, summary) def get_users(self): '''Build a summary of the user information, and return it as a list of strings ''' root = self.sysconfig.users.root primary = self.sysconfig.users.user user_summary = [] if not root.password: user_summary.append(_(" Warning: No root password set")) if primary.login_name: user_summary.append(_(" Username: %s") % primary.login_name) else: user_summary.append(_(" No user account")) return user_summary def get_disk_summary(self): '''Return a string summary of the disk selection''' doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) disk_string = list() disk_size_str = locale.format("%.1f", disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB locale_disk_str = _("Disk: ") + disk_size_str + " " + \ str(disk.disk_prop.dev_type) disk_string.append(locale_disk_str) if not disk.whole_disk: if disk.label != "VTOC": part_data = get_solaris_gpt_partition(doc) if part_data is not None: type_str = str(part_data.part_type) else: part_data = get_solaris_partition(doc) if part_data is not None: type_str = str(libdiskmgt_const.PARTITION_ID_MAP[ part_data.part_type]) if part_data is not None: part_size_str = locale.format("%.1f", part_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_part_str = _("Partition: ") + part_size_str + " " +\ type_str disk_string.append(locale_part_str) if part_data is None or not part_data.in_zpool: slice_data = get_solaris_slice(doc) slice_num_str = _("Slice %s: ") % slice_data.name slice_size_str = locale.format("%.1f", slice_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_slice_str = slice_num_str + slice_size_str + " " +\ str(slice_data.in_zpool) disk_string.append(locale_slice_str) return "\n".join(disk_string) def get_tz_summary(self): '''Return a string summary of the timezone selection''' timezone = self.sysconfig.system.tz_timezone return _("Time Zone: %s") % timezone @staticmethod def get_release(): '''Read in the release information from /etc/release''' try: try: release_file = open("/etc/release") except IOError: LOGGER.warn("Could not read /etc/release") release_file = None release = RELEASE['release'] else: release = release_file.readline() finally: if release_file is not None: release_file.close() return release.strip() def get_support(self): '''Return a string summary of the support selection.''' support_summary = [] support = self.sysconfig.support if support.netcfg == SupportInfo.NOSVC: support_summary.append(_(" OCM and ASR services are not " "installed.")) return support_summary ocm_level = None asr_level = None if support.mos_email: if support.ocm_mos_password or support.ocm_ciphertext: ocm_level = "auth" elif support.ocm_available: ocm_level = "unauth" if support.asr_mos_password or support.asr_private_key: asr_level = "auth" if (ocm_level == None and asr_level == None): support_summary.append(_(" No telemetry will be " "sent automatically")) elif ocm_level == "unauth": # No need to check ASR; ocm_level == unauth implies no password # given, so asr_level will never be auth here. support_summary.append(_(" Telemetry will be sent and associated " "with email address:")) support_summary.append(" %s" % support.mos_email) support_summary.append(_(" but will not be registered with My " "Oracle Support because")) support_summary.append(_(" no password was saved.")) else: # Equivalent to (ocm_level == "auth" or asr_level == "auth") support_summary.append(_(" Telemetry will be sent and will be " "registered with My Oracle Support")) support_summary.append(_(" using email address:")) support_summary.append(" %s" % support.mos_email) # Use the presence of OCM ciphertext to assume that successful OCM # validation took place. if support.ocm_ciphertext: support_summary.append(_(" MOS credentials validated " "for OCM")) elif support.ocm_available: support_summary.append(_(" MOS credentials NOT validated " "for OCM")) # Use the presence of ASR private_key to assume that successful ASR # validation took place. if support.asr_private_key: support_summary.append(_(" MOS credentials validated " "for ASR")) elif support.asr_available: support_summary.append(_(" MOS credentials NOT validated " "for ASR")) # Display different messages for different situations. if ((support.ocm_available and not support.ocm_ciphertext) or (support.asr_available and not support.asr_private_key)): # Installer environment. support_summary.append(_(" Validation will be attempted " "again when target (re)boots.")) if support.netcfg == SupportInfo.PROXY: if support.proxy_user: proxy_line = (_(" Secure proxy ")) else: proxy_line = (_(" Proxy ")) proxy_line += (_("specified: host: %s" % support.proxy_hostname)) if support.proxy_port: proxy_line += (_(" port: %s" % support.proxy_port)) if support.proxy_user: proxy_line += (_(" user: %s" % support.proxy_user)) support_summary.append(proxy_line) elif support.netcfg == SupportInfo.HUB: if support.ocm_hub: support_summary.append(_(" OCM hub: %s" % support.ocm_hub)) if support.asr_hub: support_summary.append(_(" ASR hub: %s" % support.asr_hub)) return support_summary
def get_support(self): '''Return a string summary of the support selection.''' support_summary = [] support = self.sysconfig.support if support.netcfg == SupportInfo.NOSVC: support_summary.append(_(" OCM and ASR services are not " "installed.")) return support_summary ocm_level = None asr_level = None if support.mos_email: if support.ocm_mos_password or support.ocm_ciphertext: ocm_level = "auth" elif support.ocm_available: ocm_level = "unauth" if support.asr_mos_password or support.asr_private_key: asr_level = "auth" if (ocm_level == None and asr_level == None): support_summary.append(_(" No telemetry will be " "sent automatically")) elif ocm_level == "unauth": # No need to check ASR; ocm_level == unauth implies no password # given, so asr_level will never be auth here. support_summary.append(_(" Telemetry will be sent and associated " "with email address:")) support_summary.append(" %s" % support.mos_email) support_summary.append(_(" but will not be registered with My " "Oracle Support because")) support_summary.append(_(" no password was saved.")) else: # Equivalent to (ocm_level == "auth" or asr_level == "auth") support_summary.append(_(" Telemetry will be sent and will be " "registered with My Oracle Support")) support_summary.append(_(" using email address:")) support_summary.append(" %s" % support.mos_email) # Use the presence of OCM ciphertext to assume that successful OCM # validation took place. if support.ocm_ciphertext: support_summary.append(_(" MOS credentials validated " "for OCM")) elif support.ocm_available: support_summary.append(_(" MOS credentials NOT validated " "for OCM")) # Use the presence of ASR private_key to assume that successful ASR # validation took place. if support.asr_private_key: support_summary.append(_(" MOS credentials validated " "for ASR")) elif support.asr_available: support_summary.append(_(" MOS credentials NOT validated " "for ASR")) # Display different messages for different situations. if ((support.ocm_available and not support.ocm_ciphertext) or (support.asr_available and not support.asr_private_key)): # Installer environment. support_summary.append(_(" Validation will be attempted " "again when target (re)boots.")) if support.netcfg == SupportInfo.PROXY: if support.proxy_user: proxy_line = (_(" Secure proxy ")) else: proxy_line = (_(" Proxy ")) proxy_line += (_("specified: host: %s" % support.proxy_hostname)) if support.proxy_port: proxy_line += (_(" port: %s" % support.proxy_port)) if support.proxy_user: proxy_line += (_(" user: %s" % support.proxy_user)) support_summary.append(proxy_line) elif support.netcfg == SupportInfo.HUB: if support.ocm_hub: support_summary.append(_(" OCM hub: %s" % support.ocm_hub)) if support.asr_hub: support_summary.append(_(" ASR hub: %s" % support.asr_hub)) return support_summary
class DiscoverySelection(BaseScreen): """ Allow the user to select which method of target discovery to use. NOTE: Local disk discovery will always happen, regardless of this choice """ HEADER_TEXT = _("Discovery Selection") PARAGRAPH = _("Select discovery method for disks") LOCAL_TEXT = _("Local Disks") LOCAL_DETAIL = _("Discover local disks") ISCSI_TEXT = _("iSCSI") ISCSI_DETAIL = _("Discover iSCSI LUNs") ITEM_OFFSET = 2 ITEM_MAX_WIDTH = 21 ITEM_DESC_OFFSET = ITEM_MAX_WIDTH + ITEM_OFFSET + 1 HELP_DATA = (TUI_HELP + "/%s/discovery_selection.txt", _("Discovery Selection")) def __init__(self, main_win): """ screen object containing the discovery selection choices """ global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) super(DiscoverySelection, self).__init__(main_win) self.local = None self.iscsi = None self.current_selection = 0 def _show(self): """ create the screen for the user to select a discovery method """ y_loc = 1 y_loc += self.center_win.add_paragraph(DiscoverySelection.PARAGRAPH, y_loc) y_loc += 1 item_area = WindowArea(1, DiscoverySelection.ITEM_MAX_WIDTH, y_loc, 1) self.local = ListItem(item_area, window=self.center_win, text=DiscoverySelection.LOCAL_TEXT) self.local.item_key = "local" self.center_win.add_text(DiscoverySelection.LOCAL_DETAIL, y_loc, DiscoverySelection.ITEM_DESC_OFFSET, self.win_size_x - 3) y_loc += 2 item_area.y_loc = y_loc self.iscsi = ListItem(item_area, window=self.center_win, text=DiscoverySelection.ISCSI_TEXT) self.iscsi.item_key = "iscsi" self.center_win.add_text(DiscoverySelection.ISCSI_DETAIL, y_loc, DiscoverySelection.ITEM_DESC_OFFSET, self.win_size_x - 3) self.main_win.do_update() self.center_win.activate_object(self.current_selection) def on_change_screen(self): """ save the user's choice in case they return to this screen """ choice = self.center_win.get_active_object().item_key LOGGER.debug("discovery selection: %s", choice) eng = InstallEngine.get_instance() if choice == "iscsi": # remove any existing Iscsi DOC objects so there are no duplicates prev_iscsi = eng.doc.volatile.get_first_child(name=ISCSI_LABEL, class_type=Iscsi) if prev_iscsi: prev_iscsi.delete() # create an empty Iscsi DOC object iscsi_doc_obj = Iscsi(ISCSI_LABEL) # add the object to the DOC eng.doc.volatile.insert_children(iscsi_doc_obj) else: # look for an existing iSCSI object in the DOC. If there is one, # remove it iscsi_doc_obj = eng.doc.volatile.get_first_child(class_type=Iscsi, name=ISCSI_LABEL) if iscsi_doc_obj is not None: iscsi_doc_obj.delete() self.current_selection = self.center_win.active_object
class GPTPart(BaseScreen): '''Allow user to choose to use the whole disk, or move to the GPT partition edit screen. ''' BOOT_TEXT = _("Boot") HEADER_TYPE_BOOTABLE = _(" %(type)s %(bootable)s") HEADER_GPT = _("GPT Partitions: ") PARAGRAPH = _("%(release)s can be installed on the whole disk or a " "GPT partition on the disk.") % RELEASE FOUND_PART = _("The following GPT partitions were found on the disk.") PROPOSED_PART = _("A partition table was not found. The following is " "proposed.") USE_WHOLE_DISK = _("Use the entire disk") USE_PART_IN_DISK = _("Use a GPT partition of the disk") GPT_HELP = (TUI_HELP + "/%s/gpt_partitions.txt", _("GPT Partitions")) def __init__(self, main_win, target_controller): super(GPTPart, self).__init__(main_win) global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.help_format = " %s" self.header_text = GPTPart.HEADER_GPT self.found = GPTPart.FOUND_PART self.proposed = GPTPart.PROPOSED_PART self.use_whole = GPTPart.USE_WHOLE_DISK self.use_part = GPTPart.USE_PART_IN_DISK self.help_data = GPTPart.GPT_HELP self.disk_win = None self.whole_disk_item = None self.partial_disk_item = None self.relabel_disk_item = None self.disk = None self.tc = target_controller self.use_whole_segment = True self.paragraph = GPTPart.PARAGRAPH def _show(self): '''Display partition data for selected disk, and present the two choices ''' doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) # verify the desired disk has a GPT partition table if disk.label == "VTOC": raise SkipException self.disk = disk LOGGER.debug("Working with the following disk:") LOGGER.debug(str(self.disk)) # set the header of the screen if self.disk.is_boot_disk(): bootable = GPTPart.BOOT_TEXT else: bootable = u"" disk_size_gb_str = locale.format("%.1f", self.disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB type_bootable_str = GPTPart.HEADER_TYPE_BOOTABLE % \ {"type": self.disk.disk_prop.dev_type, "bootable": bootable} header_text = self.header_text + disk_size_gb_str + type_bootable_str self.main_win.set_header_text(header_text) if self.disk.whole_disk: LOGGER.debug("disk.whole_disk=True, skip editing") raise SkipException y_loc = 1 y_loc += self.center_win.add_paragraph(self.paragraph, start_y=y_loc) y_loc += 1 gpt_partitions = self.disk.get_children(class_type=GPTPartition) if gpt_partitions: next_line = self.found else: next_line = self.proposed y_loc += self.center_win.add_paragraph(next_line, start_y=y_loc) y_loc += 1 disk_win_area = WindowArea(6, 70, y_loc, 0) self.disk_win = DiskWindow(disk_win_area, self.disk, target_controller=self.tc, window=self.center_win) y_loc += disk_win_area.lines y_loc += 3 whole_disk_width = textwidth(self.use_whole) + 3 cols = (self.win_size_x - whole_disk_width) / 2 whole_disk_item_area = WindowArea(1, whole_disk_width, y_loc, cols) self.whole_disk_item = ListItem(whole_disk_item_area, window=self.center_win, text=self.use_whole, centered=True) y_loc += 1 partial_width = textwidth(self.use_part) + 3 cols = (self.win_size_x - partial_width) / 2 partial_item_area = WindowArea(1, partial_width, y_loc, cols) self.partial_disk_item = ListItem(partial_item_area, window=self.center_win, text=self.use_part, centered=True) self.main_win.do_update() if self.use_whole_segment: self.center_win.activate_object(self.whole_disk_item) else: self.center_win.activate_object(self.partial_disk_item) def on_continue(self): '''Set the user's selection in the install target. ''' active_object = self.center_win.get_active_object() if active_object is self.whole_disk_item: LOGGER.debug("Setting whole_disk for %s", self.disk) self.use_whole_segment = True self.disk = self.tc.select_disk(self.disk, use_whole_disk=True)[0] else: LOGGER.debug("Setting whole_disk to false") self.use_whole_segment = False self.disk.whole_disk = False dump_doc("At the end of gpt_partitions.continue")
class PartEditScreen(BaseScreen): '''Allows user editing of partitions on a disk, or slices on a disk/partition ''' PARTITION_PARAGRAPH = _( "Syneto StorageOS will be installed into the Solaris" " partition. A partition's type can be changed" " using the F5 key.\n\n" "A partition's size can be increased " "up to its Avail space. Avail space can be " "increased by deleting an adjacent partition. " "Delete a partition by changing it to \"Unused\"" " using the F5 key.\n\n" "The four primary partition slots are listed on " "the left. If one is an \"Extended\" partition " "its logical partitions are listed on the " "right.") % RELEASE SLICE_PARAGRAPH = _("%(release)s will be installed in the \"%(pool)s\" " "slice. Use the F5 key to change a slice to " "\"%(pool)s.\"\n\n" "A slice's size can be increased up to its Avail " "size. Avail can be increased by deleting an adjacent" " slice. Use the F5 key to delete a slice by changing" " it to \"Unused.\"\n\n" "Slices are listed in disk layout order.") HEADER_x86_PART = _("Select Partition: ") HEADER_x86_SLICE = _("Select Slice in Fdisk Partition") HEADER_SPARC_SLICE = _("Select Slice: ") HEADER_TYPE_BOOTABLE = _(" %(type)s %(bootable)s") SLICE_DESTROY_TEXT = _("indicates the slice's current content will be " "destroyed") PART_DESTROY_TEXT = _("indicates the partition's current content will " "be destroyed") BOOTABLE = _("Boot") SPARC_HELP = (TUI_HELP + "/%s/" "sparc_solaris_slices_select.txt", _("Select Slice")) X86_PART_HELP = (TUI_HELP + "/%s/" "x86_fdisk_partitions_select.txt", _("Select Partition")) X86_SLICE_HELP = (TUI_HELP + "/%s/" "x86_fdisk_slices_select.txt", _("Select Slice")) HELP_FORMAT = " %s" def __init__(self, main_win, target_controller, x86_slice_mode=False): super(PartEditScreen, self).__init__(main_win) global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.x86_slice_mode = x86_slice_mode self.is_x86 = (platform.processor() == "i386") self.header_text = platform.processor() if self.x86_slice_mode: # x86, Slice within a partition self.instance = ".slice" self.header_text = PartEditScreen.HEADER_x86_SLICE self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT self.help_data = PartEditScreen.X86_SLICE_HELP elif self.is_x86: # x86, Partition on disk self.header_text = PartEditScreen.HEADER_x86_PART self.paragraph_text = PartEditScreen.PARTITION_PARAGRAPH self.destroy_text = PartEditScreen.PART_DESTROY_TEXT self.help_data = PartEditScreen.X86_PART_HELP else: # SPARC (Slice on disk) self.header_text = PartEditScreen.HEADER_SPARC_SLICE self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT self.help_data = PartEditScreen.SPARC_HELP self.help_format = " %s" self.disk_win = None self.tc = target_controller def set_actions(self): '''Edit Screens add 'Reset' and 'Change Type' actions. Since these do not manipulate screen direction, they are captured during processing by adding them to center_win's key_dict. ''' super(PartEditScreen, self).set_actions() reset_action = Action(curses.KEY_F7, _("Reset")) change_action = Action(curses.KEY_F5, _("Change Type")) self.main_win.actions[reset_action.key] = reset_action self.main_win.actions[change_action.key] = change_action self.center_win.key_dict[curses.KEY_F7] = self.on_key_F7 # pylint: disable-msg=C0103 # F7 is the keyname and appropriate here def on_key_F7(self, dummy): '''F7 -> Reset the DiskWindow''' self.disk_win.reset() return None def _show(self): '''Display the explanatory paragraph and create the DiskWindow''' doc = InstallEngine.get_instance().doc if self.x86_slice_mode: LOGGER.debug("in x86 slice mode") disk = get_desired_target_disk(doc) if disk.whole_disk: LOGGER.debug("disk.whole_disk=True, skip editting") disk.whole_disk = False # perform final target validation perform_final_validation(doc) raise SkipException part = get_solaris_partition(doc) LOGGER.debug(str(part)) if part is None: err_msg = "Critical error - no Solaris partition found" LOGGER.error(err_msg) raise ValueError(err_msg) if part.in_zpool is not None: LOGGER.debug("Whole partition selected. Skipping slice edit") LOGGER.debug(str(part)) # # remove the in_zpool value from partition, delete # any existing slices, and create # the needed underneath slices # # All the logic from here to the part.bootid line # can be removed when GPT partitions is supported. # part.create_entire_partition_slice(in_zpool=part.in_zpool, in_vdev=DEFAULT_VDEV_NAME, tag=V_ROOT) part.bootid = Partition.ACTIVE part.in_zpool = None LOGGER.debug(str(part)) # perform final target validation perform_final_validation(doc) raise SkipException else: # get selected disk from desired target disk = get_desired_target_disk(doc) LOGGER.debug("disk.whole_disk: %s", disk.whole_disk) LOGGER.debug(str(disk)) if disk.whole_disk: LOGGER.debug("disk.whole_disk true, skipping editing") if not self.is_x86: # Unset this so Target Instantiation works correctly disk.whole_disk = False # perform final target validation perform_final_validation(doc) raise SkipException part = disk if self.x86_slice_mode: header = self.header_text else: bootable = "" if self.is_x86 and disk.is_boot_disk(): bootable = PartEditScreen.BOOTABLE disk_size_str = locale.format( "%.1f", disk.disk_prop.dev_size.get( Size.gb_units)) + LOCALIZED_GB type_boot_str = PartEditScreen.HEADER_TYPE_BOOTABLE % \ {"type": disk.disk_prop.dev_type, "bootable": bootable} header = self.header_text + disk_size_str + type_boot_str self.main_win.set_header_text(header) y_loc = 1 fmt_dict = {'pool': ROOT_POOL} fmt_dict.update(RELEASE) y_loc += self.center_win.add_paragraph(self.paragraph_text % fmt_dict, y_loc) y_loc += 1 disk_win_area = WindowArea(6, 70, y_loc, 0) self.disk_win = DiskWindow(disk_win_area, part, window=self.center_win, editable=True, error_win=self.main_win.error_line, target_controller=self.tc) y_loc += disk_win_area.lines y_loc += 1 LOGGER.log(LOG_LEVEL_INPUT, "calling addch with params start_y=%s," "start_x=%s, ch=%c", y_loc, self.center_win.border_size[1], DiskWindow.DESTROYED_MARK) self.center_win.window.addch(y_loc, self.center_win.border_size[1], DiskWindow.DESTROYED_MARK, self.center_win.color_theme.inactive) self.center_win.add_text(self.destroy_text, y_loc, 2) self.main_win.do_update() self.center_win.activate_object(self.disk_win) def validate(self): ''' Perform final validation of the desired target ''' if self.is_x86 and not self.x86_slice_mode: # delay final validation for x86 until slice mode # is completed. return # perform final target validation doc = InstallEngine.get_instance().doc if self.is_x86: solaris_part = get_solaris_partition(doc) if solaris_part is None: raise RuntimeError("No Solaris2 partition in desired target") solaris_part.bootid = Partition.ACTIVE perform_final_validation(doc)
def set_actions(self): '''Replace the default F2_Continue with F2_Install''' install_action = Action(curses.KEY_F2, _("Install"), self.main_win.screen_list.get_next) self.main_win.actions[install_action.key] = install_action
def get_tz_summary(self): '''Return a string summary of the timezone selection''' timezone = self.sysconfig.system.tz_timezone return _("Time Zone: %s") % timezone
class DiskScreen(BaseScreen): ''' Allow the user to select a (valid) disk target for installation Display the partition/slice table for the highlighted disk ''' HEADER_TEXT = _("Disks") PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE REC_SIZE_TEXT = _("Recommended size: ") MIN_SIZE_TEXT = _(" Minimum size: ") DISK_SEEK_TEXT = _("Seeking disks on system") FOUND_x86 = _("The following partitions were found on the disk.") FOUND_SPARC = _("The following slices were found on the disk.") PROPOSED_x86 = _("A partition table was not found. The following is " "proposed.") PROPOSED_SPARC = _("A VTOC label was not found. The following " "is proposed.") PROPOSED_GPT = _("A GPT labeled disk was found. The following is " "proposed.") TOO_SMALL = _("Too small") TOO_BIG_WARN = _("Limited to %.1f TB") GPT_LABELED = _("GPT labeled disk") NO_DISKS = _("No disks found. Additional device drivers may " "be needed.") NO_TARGETS = _("%(release)s cannot be installed on any disk") % RELEASE TGT_ERROR = _("An error occurred while searching for installation" " targets. Please check the install log and file a bug" " at defect.opensolaris.org.") DISK_HEADERS = [(8, _("Type")), (10, _(" Size(GB)")), (6, _("Boot")), (26, _("Device")), (10, _("Vendor")), (3, ""), (10, _("Notes"))] #blank header for the notes column VENDOR_LEN = 15 SPINNER = ["\\", "|", "/", "-"] DISK_WARNING_HEADER = _("Warning") DISK_WARNING_TOOBIG = _("Only the first %.1fTB can be used.") DISK_WARNING_GPT = _("You have chosen a GPT labeled disk. Installing " "onto a GPT labeled disk will cause the loss " "of all existing data and the disk will be " "relabeled as SMI.") CANCEL_BUTTON = _("Cancel") CONTINUE_BUTTON = _("Continue") HELP_DATA = (TUI_HELP + "/%s/disks.txt", _("Disks")) def __init__(self, main_win, target_controller): global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) super(DiskScreen, self).__init__(main_win) if platform.processor() == "i386": self.found_text = DiskScreen.FOUND_x86 self.proposed_text = DiskScreen.PROPOSED_x86 else: self.found_text = DiskScreen.FOUND_SPARC self.proposed_text = DiskScreen.PROPOSED_SPARC disk_header_text = [] for header in DiskScreen.DISK_HEADERS: header_str = fit_text_truncate(header[1], header[0] - 1, just="left") disk_header_text.append(header_str) self.disk_header_text = " ".join(disk_header_text) max_note_size = DiskScreen.DISK_HEADERS[5][0] self.too_small_text = DiskScreen.TOO_SMALL[:max_note_size] max_disk_size = (Size(MAX_VTOC)).get(Size.tb_units) too_big_warn = DiskScreen.TOO_BIG_WARN % max_disk_size self.too_big_warn = too_big_warn[:max_note_size] self.disk_warning_too_big = \ DiskScreen.DISK_WARNING_TOOBIG % max_disk_size self.disks = [] self.existing_pools = [] self.disk_win = None self.disk_detail = None self.num_targets = 0 self.td_handle = None self._size_line = None self.selected_disk_index = 0 self._minimum_size = None self._recommended_size = None self.engine = InstallEngine.get_instance() self.doc = self.engine.data_object_cache self.tc = target_controller self._target_discovery_completed = False self._target_discovery_status = InstallEngine.EXEC_SUCCESS self._image_size = None def determine_minimum(self): '''Returns minimum install size, fetching first if needed''' self.determine_size_data() return self._minimum_size minimum_size = property(determine_minimum) def determine_recommended(self): '''Returns recommended install size, fetching first if needed''' self.determine_size_data() return self._recommended_size recommended_size = property(determine_recommended) def determine_size_data(self): '''Retrieve the minimum and recommended sizes and generate the string to present that information. ''' if self._minimum_size is None or self._recommended_size is None: self._recommended_size = get_recommended_size(self.tc) self._minimum_size = get_minimum_size(self.tc) def get_size_line(self): '''Returns the line of text displaying the min/recommended sizes''' if self._size_line is None: rec_size_str = locale.format( "%.1f", self.recommended_size.get( Size.gb_units)) + LOCALIZED_GB min_size_str = locale.format( "%.1f", self.minimum_size.get(Size.gb_units)) + LOCALIZED_GB self._size_line = DiskScreen.REC_SIZE_TEXT + rec_size_str + \ DiskScreen.MIN_SIZE_TEXT + min_size_str return self._size_line size_line = property(get_size_line) def wait_for_disks(self): '''Block while waiting for libtd to finish. Catch F9 and quit if needed ''' self.main_win.actions.pop(curses.KEY_F2, None) self.main_win.actions.pop(curses.KEY_F6, None) self.main_win.actions.pop(curses.KEY_F3, None) self.main_win.show_actions() self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1, self.win_size_x - 3) self.main_win.do_update() offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2 spin_index = 0 self.center_win.window.timeout(250) while not self._target_discovery_completed: input_key = self.main_win.getch() if input_key == curses.KEY_F9: if self.confirm_quit(): raise QuitException self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5, offset) self.center_win.no_ut_refresh() self.main_win.do_update() spin_index = (spin_index + 1) % len(DiskScreen.SPINNER) self.center_win.window.timeout(-1) self.center_win.clear() # check the result of target discovery if self._target_discovery_status is not InstallEngine.EXEC_SUCCESS: err_data = (errsvc.get_errors_by_mod_id(TARGET_DISCOVERY))[0] LOGGER.error("Target discovery failed") err = err_data.error_data[liberrsvc.ES_DATA_EXCEPTION] LOGGER.error(err) raise TargetDiscoveryError( ("Unexpected error (%s) during target " "discovery. See log for details.") % err) def _td_callback(self, status, errsvc): '''Callback function for Target Discovery checkpoint execution. The status value is saved to be interpreted later. This function sets the self._target_discovery_completed value to true so the wait_for_disks() function will know to stop displaying the spinner. ''' self._target_discovery_status = status self._target_discovery_completed = True def _show(self): '''Create a list of disks to choose from and create the window for displaying the partition/slice information from the selected disk ''' self.wait_for_disks() discovered_target = self.doc.persistent.get_first_child( \ name=Target.DISCOVERED) LOGGER.debug(discovered_target) if discovered_target is None: self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1, max_x=(self.win_size_x - 1)) return self.disks = discovered_target.get_children(class_type=Disk) if not self.disks: self.center_win.add_paragraph(DiskScreen.NO_TARGETS, 1, 1, max_x=(self.win_size_x - 1)) return if self._image_size is None: try: self._image_size = Size(str(get_image_size(LOGGER)) + \ Size.mb_units) LOGGER.debug("Image_size: %s", self._image_size) except: # Unable to get the image size for some reason, allow # the target controller to use it's default size. LOGGER.debug("Unable to get image size") self._image_size = FALLBACK_IMAGE_SIZE # initialize the target controller so the min/max size for # the installation can be calculated. Explicitly do not # want to select an initial disk at this time in case # none of the disks discovered is usable. The target controller # initialization needs to be done everytime we show the disk selection # screen so the desired target node in the DOC can be re-populated # with information from target discovery. self.tc.initialize(image_size=self._image_size, no_initial_disk=True) # Go through all the disks found and find ones that have # enough space for installation. At the same time, see if any # existing disk is the boot disk. If a boot disk is found, move # it to the front of the list num_usable_disks = 0 boot_disk = None for disk in self.disks: LOGGER.debug("size: %s, min: %s" % \ (disk.disk_prop.dev_size, self.minimum_size)) if disk.disk_prop.dev_size >= self.minimum_size: if disk.is_boot_disk(): boot_disk = disk num_usable_disks += 1 if boot_disk is not None: self.disks.remove(boot_disk) self.disks.insert(0, boot_disk) if num_usable_disks == 0: self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1, max_x=(self.win_size_x - 1)) return self.main_win.reset_actions() self.main_win.show_actions() y_loc = 1 self.center_win.add_text(DiskScreen.PARAGRAPH, y_loc, 1) y_loc += 1 self.center_win.add_text(self.size_line, y_loc, 1) y_loc += 2 self.center_win.add_text(self.disk_header_text, y_loc, 1) y_loc += 1 self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1, curses.ACS_HLINE, textwidth(self.disk_header_text)) y_loc += 1 disk_win_area = WindowArea(4, textwidth(self.disk_header_text) + 2, y_loc, 0) disk_win_area.scrollable_lines = len(self.disks) + 1 self.disk_win = ScrollWindow(disk_win_area, window=self.center_win) disk_item_area = WindowArea(1, disk_win_area.columns - 2, 0, 1) disk_index = 0 len_type = DiskScreen.DISK_HEADERS[0][0] - 1 len_size = DiskScreen.DISK_HEADERS[1][0] - 1 len_boot = DiskScreen.DISK_HEADERS[2][0] - 1 len_dev = DiskScreen.DISK_HEADERS[3][0] - 1 len_mftr = DiskScreen.DISK_HEADERS[4][0] - 1 for disk in self.disks: disk_text_fields = [] if disk.disk_prop is None or disk.disk_prop.dev_type is None: continue type_field = disk.disk_prop.dev_type[:len_type] type_field = ljust_columns(type_field, len_type) disk_text_fields.append(type_field) disk_size = disk.disk_prop.dev_size.get(Size.gb_units) size_field = locale.format("%*.1f", (len_size, disk_size)) disk_text_fields.append(size_field) if disk.is_boot_disk(): bootable_field = "+".center(len_boot) else: bootable_field = " " * (len_boot) disk_text_fields.append(bootable_field) device_field = disk.ctd[:len_dev] device_field = ljust_columns(device_field, len_dev) disk_text_fields.append(device_field) vendor = disk.disk_prop.dev_vendor if vendor is not None: mftr_field = vendor[:len_mftr] mftr_field = ljust_columns(mftr_field, len_mftr) else: mftr_field = " " * len_mftr disk_text_fields.append(mftr_field) selectable = True if disk.disk_prop.dev_size < self.minimum_size: note_field = self.too_small_text selectable = False elif disk_size > Size(MAX_VTOC).get(Size.gb_units): note_field = self.too_big_warn else: note_field = "" disk_text_fields.append(note_field) disk_text = " ".join(disk_text_fields) disk_item_area.y_loc = disk_index disk_list_item = ListItem(disk_item_area, window=self.disk_win, text=disk_text, add_obj=selectable) disk_list_item.on_make_active = on_activate disk_list_item.on_make_active_kwargs["disk"] = disk disk_list_item.on_make_active_kwargs["disk_select"] = self disk_index += 1 self.disk_win.no_ut_refresh() y_loc += 7 disk_detail_area = WindowArea(6, 70, y_loc, 1) self.disk_detail = DiskWindow(disk_detail_area, self.disks[0], target_controller=self.tc, window=self.center_win) self.main_win.do_update() self.center_win.activate_object(self.disk_win) self.disk_win.activate_object(self.selected_disk_index) def on_change_screen(self): ''' Save the index of the current selected object in case the user returns to this screen later ''' # Save the index of the selected object self.selected_disk_index = self.disk_win.active_object LOGGER.debug("disk_selection.on_change_screen, saved_index: %s", self.selected_disk_index) LOGGER.debug(self.doc.persistent) def start_discovery(self): # start target discovery if not self._target_discovery_completed: errsvc.clear_error_list() self.engine.execute_checkpoints(pause_before=TRANSFER_PREP, callback=self._td_callback) def validate(self): '''Validate the size of the disk.''' warning_txt = [] disk = self.disk_detail.ui_obj.doc_obj disk_size_gb = disk.disk_prop.dev_size.get(Size.gb_units) max_size_gb = Size(MAX_VTOC).get(Size.gb_units) if disk_size_gb > max_size_gb: warning_txt.append(self.disk_warning_too_big) warning_txt = " ".join(warning_txt) if warning_txt: # warn the user and give user a chance to change result = self.main_win.pop_up(DiskScreen.DISK_WARNING_HEADER, warning_txt, DiskScreen.CANCEL_BUTTON, DiskScreen.CONTINUE_BUTTON) if not result: raise UIMessage() # let user select different disk
class IscsiScreen(BaseScreen): """ Allow the user to specify parameters for iSCSI LUNs """ HEADER_TEXT = _("iSCSI Discovery") INTRO = _("The installer needs additional information for iSCSI LUN " "discovery") FOUND_DHCP_LABEL = _("DHCP has iSCSI parameters defined for this host") TARGET_IP_LABEL = _("Target IP:") TARGET_PORT_LABEL = _("Port:") TARGET_LUN_LABEL = _("Target LUN:") TARGET_NAME_LABEL = _("Target Name:") INITIATOR_NAME_LABEL = _("Initiator Name:") USE_CHAP_LABEL = _("Use CHAP:") CHAP_NAME_LABEL = _("CHAP Name:") CHAP_PASSWORD_LABEL = _("CHAP Password:"******"Required Field") USE_CHAP_LABEL = _("If using CHAP for authentication") MAPPING_LUN_LABEL = _("Mapping iSCSI LUN...") MAPPING_TARGET_LABEL = _("Mapping iSCSI Target...") ISCSI_BOOT_LABEL = _("iSCSI boot enabled - Initiator Name set in BIOS") # error strings MISSING_TARGET_IP = _("Target IP address not provided") INVALID_TARGET_IP = _("Invalid Target IP address") INVALID_INITIATOR_IQN = _("Invalid Initiator IQN string") INVALID_TARGET_IQN = _("Invalid Target IQN string") CHAP_USERNAME_MISSING = _("CHAP username not specified") CHAP_PASSWORD_MISSING = _("CHAP password not specified") CHAP_PASSWORD_TOO_SHORT = _("CHAP password must be between 12 and 16 " "characters") UNABLE_TO_MAP = _("Unable to map iSCSI LUN: ") HELP_DATA = (TUI_HELP + "/%s/iscsi.txt", _("iSCSI")) MAX_IP_LEN = 15 MAX_LUN_LEN = 4 MAX_PORT_LEN = 5 MAX_NAME_LEN = 223 CHAP_LEN = 16 ITEM_OFFSET = 2 DEAD_ZONE = 3 REQUIRED_MARK = EditField.ASTERISK_CHAR def __init__(self, main_win): """ screen object containing iSCSI criteria objects """ global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) super(IscsiScreen, self).__init__(main_win) self.target_ip = None self.target_port = None self.target_lun = None self.target_name = None self._initiator_name = None self.chap_name = None self.chap_password = None self.full_win_width = self.win_size_x - IscsiScreen.DEAD_ZONE self.half_win_width = (self.win_size_x - IscsiScreen.DEAD_ZONE) / 2 self.default_edit = WindowArea(y_loc=0, lines=1, columns=IscsiScreen.CHAP_LEN) self.right_edit = WindowArea(y_loc=0, lines=1, columns=IscsiScreen.MAX_PORT_LEN) self.name_edit = WindowArea(y_loc=0, lines=1, scrollable_columns=IscsiScreen.MAX_NAME_LEN + 1) self.chap_edit = WindowArea(y_loc=0, lines=1, columns=IscsiScreen.CHAP_LEN) self.target_ip_list = None self.target_ip_edit = None self.target_port_list = None self.target_port_edit = None self.target_lun_list = None self.target_lun_edit = None self.target_name_edit = None self.initiator_name_edit = None self.chap_name_edit = None self.chap_password_edit = None # key map dictionary for Target entries self.add_keys = {curses.KEY_LEFT: self.on_arrow_key, curses.KEY_RIGHT: self.on_arrow_key, curses.KEY_DOWN: self.on_arrow_key, curses.KEY_UP: self.on_arrow_key} self.iscsi_obj = None self.is_iscsiboot = is_iscsiboot() def on_arrow_key(self, input_key): """ override the default behavior of the arrow keys for specific EditFields. """ # get the active object active = self.center_win.get_active_object() # only allow the right arrow to move from IP to Port if input_key == curses.KEY_RIGHT and active is self.target_ip_list: self.center_win.activate_object(self.target_port_list) return None # only allow the left arrow to move from Port to IP elif input_key == curses.KEY_LEFT and active is self.target_port_list: self.center_win.activate_object(self.target_ip_list) return None # override the default behavior for the up and down arrows to skip the # Port field if moving from IP to LUN elif input_key == curses.KEY_DOWN and active is self.target_ip_list: self.center_win.activate_object(self.target_lun_list) return None elif input_key == curses.KEY_UP and active is self.target_lun_list: self.center_win.activate_object(self.target_ip_list) return None return input_key def check_dhcp(self): """ query the DHCP server for the Rootpath string. If present, update the proper attributes. """ dhcp_params = Iscsi.get_dhcp() if dhcp_params is not None: self.target_ip, self.target_port, self.target_lun, self.target_name = dhcp_params return True return False @property def initiator_name(self): """ property to return the initiator name """ if self._initiator_name is None: cmd = [ISCSIADM, "list", "initiator-node"] p = run(cmd) for line in p.stdout.splitlines(): if line.startswith("Initiator node name:"): self._initiator_name = line.split(": ")[1] return self._initiator_name @initiator_name.setter def initiator_name(self, name): """ property setter for _initiator_name """ self._initiator_name = name def _show(self): """ create the screen to collect user input """ # look in the DOC for an Iscsi object eng = InstallEngine.get_instance() iscsi_obj = eng.doc.volatile.get_children(name=ISCSI_LABEL, class_type=Iscsi) # If there's no iscsi object in the DOC, skip this screen if not iscsi_obj: raise SkipException else: self.iscsi_obj = iscsi_obj[0] LOGGER.debug("show Iscsi object: %s" % str(self.iscsi_obj)) y_loc = 1 self.center_win.add_paragraph(IscsiScreen.INTRO, start_y=y_loc) # look to see if DHCP is providing information if self.check_dhcp(): y_loc += 2 self.center_win.add_paragraph(IscsiScreen.FOUND_DHCP_LABEL, start_y=y_loc) # Target IP y_loc += 2 # Mark this field required self.center_win.window.addch(y_loc, 2, IscsiScreen.REQUIRED_MARK, self.center_win.color_theme.inactive) edit_start = textwidth(IscsiScreen.TARGET_IP_LABEL) + \ IscsiScreen.ITEM_OFFSET ip_area = WindowArea(y_loc=y_loc, x_loc=1, lines=1, columns=edit_start + IscsiScreen.MAX_IP_LEN + 1) self.target_ip_list = ListItem(ip_area, window=self.center_win, text=IscsiScreen.TARGET_IP_LABEL) self.target_ip_list.key_dict.update(self.add_keys) self.default_edit.x_loc = edit_start self.default_edit.columns = IscsiScreen.MAX_IP_LEN + 1 self.target_ip_edit = EditField(self.default_edit, window=self.target_ip_list, validate=incremental_validate_ip, error_win=self.main_win.error_line) self.target_ip_edit.key_dict.update(self.add_keys) if self.target_ip is not None: self.target_ip_edit.set_text(self.target_ip) # Target Port edit_start = ip_area.x_loc + \ textwidth(IscsiScreen.TARGET_PORT_LABEL) + \ IscsiScreen.ITEM_OFFSET port_area = WindowArea(y_loc=y_loc, x_loc=self.half_win_width + IscsiScreen.DEAD_ZONE, lines=1, columns=edit_start + IscsiScreen.MAX_PORT_LEN + 1) self.target_port_list = ListItem(port_area, window=self.center_win, text=IscsiScreen.TARGET_PORT_LABEL) self.target_port_list.key_dict.update(self.add_keys) self.right_edit.x_loc = edit_start self.right_edit.columns = IscsiScreen.MAX_PORT_LEN + 1 self.target_port_edit = EditField(self.right_edit, window=self.target_port_list, validate=incremental_validate_digits, error_win=self.main_win.error_line, text=Iscsi.ISCSI_DEFAULT_PORT) self.target_port_edit.key_dict.update(self.add_keys) if self.target_port is not None: self.target_port_edit.set_text(self.target_port) # Target LUN y_loc += 1 edit_start = textwidth(IscsiScreen.TARGET_LUN_LABEL) + \ IscsiScreen.ITEM_OFFSET lun_area = WindowArea(y_loc=y_loc, x_loc=1, lines=1, columns=edit_start + IscsiScreen.MAX_LUN_LEN + 1) self.target_lun_list = ListItem(lun_area, window=self.center_win, text=IscsiScreen.TARGET_LUN_LABEL) self.target_lun_list.key_dict.update(self.add_keys) self.default_edit.x_loc = edit_start self.default_edit.columns = IscsiScreen.MAX_LUN_LEN + 1 self.target_lun_edit = EditField(self.default_edit, window=self.target_lun_list, validate=incremental_validate_hex, error_win=self.main_win.error_line) self.target_lun_edit.key_dict.update(self.add_keys) if self.target_lun is not None: self.target_lun_edit.set_text(self.target_lun) # Target Name y_loc += 2 name_area = WindowArea(y_loc=y_loc, x_loc=1, lines=1, columns=self.full_win_width) name_area.y_loc = y_loc target_name_list = ListItem(name_area, window=self.center_win, text=IscsiScreen.TARGET_NAME_LABEL) self.name_edit.x_loc = textwidth(IscsiScreen.TARGET_NAME_LABEL) + \ IscsiScreen.ITEM_OFFSET self.name_edit.columns = self.full_win_width - \ textwidth(IscsiScreen.TARGET_NAME_LABEL) - \ IscsiScreen.ITEM_OFFSET self.target_name_edit = EditField(self.name_edit, window=target_name_list) if self.target_name is not None: self.target_name_edit.set_text(self.target_name) # Horizontal line y_loc += 1 self.center_win.window.hline(y_loc, 3, curses.ACS_HLINE, self.full_win_width) # Initiator Name y_loc += 1 if self.is_iscsiboot: # the system BIOS is configured for iSCSI boot. This means the # user will be unable to change the initiator-name. Display the # name, but don't allow it to be changed. text = "%s %s" % \ (IscsiScreen.INITIATOR_NAME_LABEL, self.initiator_name) self.center_win.add_text(text, start_y=y_loc, start_x=1) y_loc += 1 self.center_win.add_text(IscsiScreen.ISCSI_BOOT_LABEL, start_y=y_loc, start_x=1) else: # display the edit field as normal name_area.y_loc = y_loc initiator_name_list = ListItem(name_area, window=self.center_win, text=IscsiScreen.INITIATOR_NAME_LABEL) self.name_edit.x_loc = \ textwidth(IscsiScreen.INITIATOR_NAME_LABEL) + \ IscsiScreen.ITEM_OFFSET self.name_edit.columns = self.full_win_width - \ textwidth(IscsiScreen.INITIATOR_NAME_LABEL) - \ IscsiScreen.ITEM_OFFSET self.initiator_name_edit = EditField(self.name_edit, window=initiator_name_list, text=self.initiator_name) y_loc += 2 self.center_win.add_text(IscsiScreen.USE_CHAP_LABEL, y_loc, 1) # CHAP username y_loc += 1 edit_start = textwidth(IscsiScreen.CHAP_NAME_LABEL) + \ IscsiScreen.ITEM_OFFSET chapname_area = WindowArea(y_loc=y_loc, x_loc=15, lines=1, columns=edit_start + IscsiScreen.CHAP_LEN) chap_name_list = ListItem(chapname_area, window=self.center_win, text=IscsiScreen.CHAP_NAME_LABEL) self.chap_edit.x_loc = edit_start self.chap_edit.columns = IscsiScreen.CHAP_LEN + 1 self.chap_edit.scrollable_columns = IscsiScreen.MAX_NAME_LEN + 1 self.chap_name_edit = EditField(self.chap_edit, window=chap_name_list) if self.chap_name is not None: self.chap_name_edit.set_text(self.chap_name) # CHAP password y_loc += 1 edit_start = textwidth(IscsiScreen.CHAP_PASSWORD_LABEL) + \ IscsiScreen.ITEM_OFFSET chapname_area.y_loc = y_loc chap_password_list = ListItem(chapname_area, window=self.center_win, text=IscsiScreen.CHAP_PASSWORD_LABEL) self.chap_edit.x_loc = textwidth(IscsiScreen.CHAP_PASSWORD_LABEL) + \ IscsiScreen.ITEM_OFFSET self.chap_edit.scrollable_columns = None self.chap_password_edit = PasswordField(self.chap_edit, window=chap_password_list) if self.chap_password is not None: self.chap_password_edit.set_text(self.chap_password) # Legend y_loc += 2 self.center_win.window.addch(y_loc, 1, IscsiScreen.REQUIRED_MARK, self.center_win.color_theme.inactive) self.center_win.add_text(IscsiScreen.REQUIRED_FIELD_LABEL, y_loc, 1) self.main_win.do_update() self.center_win.activate_object() def on_change_screen(self): """ save the user's choices in case they return to this screen """ self.target_ip = self.target_ip_edit.get_text() self.target_port = self.target_port_edit.get_text() self.target_lun = self.target_lun_edit.get_text() self.target_name = self.target_name_edit.get_text() if not self.is_iscsiboot: self.initiator_name = self.initiator_name_edit.get_text() self.chap_name = self.chap_name_edit.get_text() self.chap_password = self.chap_password_edit.get_text() def validate(self): """ validate the iSCSI attributes before continuing to disk selection. """ target_ip = self.target_ip_edit.get_text() target_lun = self.target_lun_edit.get_text() target_port = self.target_port_edit.get_text() target_name = self.target_name_edit.get_text() if not self.is_iscsiboot: initiator_name = self.initiator_name_edit.get_text() chap_name = self.chap_name_edit.get_text() chap_password = self.chap_password_edit.get_text() # validate the target IP if not target_ip: raise UIMessage(IscsiScreen.MISSING_TARGET_IP) else: try: IPAddress.convert_address(target_ip) except ValueError as error: raise UIMessage("%s: %s" % \ (IscsiScreen.INVALID_TARGET_IP, str(error))) # validate the IQN strings (by default re.match only matches at the # beginning of a string) if not self.is_iscsiboot and initiator_name: if IQN_RE.match(initiator_name) is None: raise UIMessage(IscsiScreen.INVALID_INITIATOR_IQN) if target_name: if IQN_RE.match(target_name) is None: raise UIMessage(IscsiScreen.INVALID_TARGET_IQN) # validate that both CHAP username and password were specified (or not # at all) if chap_name and not chap_password: raise UIMessage(IscsiScreen.CHAP_PASSWORD_MISSING) if chap_password and not chap_name: raise UIMessage(IscsiScreen.CHAP_USERNAME_MISSING) # validate the CHAP password if chap_password: if not 12 <= len(chap_password) <= 16: raise UIMessage(IscsiScreen.CHAP_PASSWORD_TOO_SHORT) # Update the Iscsi DOC object self.iscsi_obj.target_ip = target_ip # force target_lun back to None if the user comes back and removes the # LUN entry from the screen if target_lun: self.iscsi_obj.target_lun = target_lun else: self.iscsi_obj.target_lun = None if target_name: self.iscsi_obj.target_name = target_name if target_port: self.iscsi_obj.target_port = target_port if not self.is_iscsiboot and initiator_name: self.iscsi_obj.initiator_name = initiator_name if chap_name: self.iscsi_obj.chap_name = chap_name if chap_password: self.iscsi_obj.chap_password = chap_password # attempt to connect to the LUN if target_lun: self.main_win.error_line.display_err(IscsiScreen.MAPPING_LUN_LABEL) else: self.main_win.error_line.display_err( IscsiScreen.MAPPING_TARGET_LABEL) try: self.iscsi_obj.setup_iscsi() LOGGER.debug("Iscsi object: %s" % str(self.iscsi_obj)) except (CalledProcessError, RuntimeError) as err: # remove the iSCSI configuration since it's invalid try: self.iscsi_obj.teardown() except CalledProcessError: # ignore any errors pass raise UIMessage("%s %s" % (IscsiScreen.UNABLE_TO_MAP, str(err)))
class DiskScreen(BaseScreen): ''' Allow the user to select a (valid) disk target for installation Display the partition/slice table for the highlighted disk ''' HEADER_TEXT = _("Disks") PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE REC_SIZE_TEXT = _("Recommended size: ") MIN_SIZE_TEXT = _(" Minimum size: ") DISK_SEEK_TEXT = _("Seeking disks on system") FOUND_x86 = _("The following partitions were found on the disk.") FOUND_SPARC = _("The following slices were found on the disk.") FOUND_GPT = _("The following GPT partitions were found on the disk.") PROPOSED_x86 = _("A partition table was not found. The following is " "proposed.") PROPOSED_SPARC = _("A VTOC label was not found. The following " "is proposed.") PROPOSED_GPT = _("A GPT labeled disk was not found. The following is " "proposed.") TOO_SMALL = "<" TOO_BIG = ">" INVALID_DISK = "!" GPT_LABELED = _("GPT labeled disk") NO_DISKS = _("No disks found. Additional device drivers may " "be needed.") NO_TARGETS = _("%(release)s cannot be installed on any disk") % RELEASE DISK_HEADERS = [(8, _("Type")), (10, _(" Size(GB)")), (6, _("Boot")), (44, _("Device")), (3, "")] # blank header for the notes column VENDOR_LEN = 15 SPINNER = ["\\", "|", "/", "-"] DISK_WARNING_HEADER = _("Warning") DISK_WARNING_TOOBIG = _("Only the first %.1fTB can be used.") DISK_WARNING_RELABEL = _("You have chosen a GPT labeled disk. Installing " "onto this disk requires it to be relabeled as " "SMI. This causes IMMEDIATE LOSS of all data " "on the disk. Select Continue only if you are " "prepared to erase all data on this disk now.") CANCEL_BUTTON = _("Cancel") CONTINUE_BUTTON = _("Continue") HELP_DATA = (TUI_HELP + "/%s/disks.txt", _("Disks")) def __init__(self, main_win, target_controller): """ screen object containing the disk selection choice for the user """ global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) super(DiskScreen, self).__init__(main_win) if platform.processor() == "i386": self.found_text = DiskScreen.FOUND_x86 self.proposed_text = DiskScreen.PROPOSED_x86 else: self.found_text = DiskScreen.FOUND_SPARC self.proposed_text = DiskScreen.PROPOSED_SPARC self.gpt_found_text = DiskScreen.FOUND_GPT self.gpt_proposed_text = DiskScreen.PROPOSED_GPT disk_header_text = [] for header in DiskScreen.DISK_HEADERS: header_str = fit_text_truncate(header[1], header[0] - 1, just="left") disk_header_text.append(header_str) self.disk_header_text = " ".join(disk_header_text) self.max_vtoc_disk_size = (Size(MAX_VTOC)).get(Size.tb_units) self.disk_warning_too_big = \ DiskScreen.DISK_WARNING_TOOBIG % self.max_vtoc_disk_size self.disks = [] self.existing_pools = [] self.disk_win = None self.disk_detail = None self.num_targets = 0 self.td_handle = None self._size_line = None self.selected_disk_index = 0 self._minimum_size = None self._recommended_size = None self.engine = InstallEngine.get_instance() self.doc = self.engine.data_object_cache self.tc = target_controller self._target_discovery_completed = False self._target_discovery_status = InstallEngine.EXEC_SUCCESS self._image_size = None self.iscsi = None self._iscsi_target_discovery_completed = False self._iscsi_target_discovery_status = InstallEngine.EXEC_SUCCESS def determine_minimum(self): '''Returns minimum install size, fetching first if needed''' self.determine_size_data() return self._minimum_size minimum_size = property(determine_minimum) def determine_recommended(self): '''Returns recommended install size, fetching first if needed''' self.determine_size_data() return self._recommended_size recommended_size = property(determine_recommended) def determine_size_data(self): '''Retrieve the minimum and recommended sizes and generate the string to present that information. ''' if self._minimum_size is None or self._recommended_size is None: self._recommended_size = get_recommended_size(self.tc) self._minimum_size = get_minimum_size(self.tc) def get_size_line(self): '''Returns the line of text displaying the min/recommended sizes''' if self._size_line is None: rec_size_str = locale.format( "%.1f", self.recommended_size.get( Size.gb_units)) + LOCALIZED_GB min_size_str = locale.format( "%.1f", self.minimum_size.get(Size.gb_units)) + LOCALIZED_GB self._size_line = DiskScreen.REC_SIZE_TEXT + rec_size_str + \ DiskScreen.MIN_SIZE_TEXT + min_size_str return self._size_line size_line = property(get_size_line) def wait_for_disks(self): '''Block while waiting for target discovery to finish. Catch F9 and quit if needed ''' self.main_win.actions.pop(curses.KEY_F2, None) self.main_win.actions.pop(curses.KEY_F6, None) self.main_win.actions.pop(curses.KEY_F3, None) self.main_win.show_actions() self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1, self.win_size_x - 3) self.main_win.do_update() offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2 spin_index = 0 self.center_win.window.timeout(250) while not self._target_discovery_completed: input_key = self.main_win.getch() if input_key == curses.KEY_F9: if self.confirm_quit(): raise QuitException self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5, offset) self.center_win.no_ut_refresh() self.main_win.do_update() spin_index = (spin_index + 1) % len(DiskScreen.SPINNER) self.center_win.window.timeout(-1) self.center_win.clear() # check the result of target discovery if self._target_discovery_status is not InstallEngine.EXEC_SUCCESS: err_data = errsvc.get_errors_by_mod_id(TARGET_DISCOVERY)[0] LOGGER.error("Target discovery failed") err = err_data.error_data[liberrsvc.ES_DATA_EXCEPTION] LOGGER.error(err) raise TargetDiscoveryError( ("Unexpected error (%s) during target " "discovery. See log for details.") % err) def wait_for_iscsi_disk(self): ''' Block while waiting for iSCSI discovery to finish ''' # check for the existence of an Iscsi object in the DOC. That object # is only added when the user selects 'iSCSI' as the discovery criteria self.iscsi = self.doc.volatile.get_first_child(name=ISCSI_LABEL, class_type=Iscsi) if not self.iscsi: return self.main_win.actions.pop(curses.KEY_F2, None) self.main_win.actions.pop(curses.KEY_F6, None) self.main_win.actions.pop(curses.KEY_F3, None) self.main_win.show_actions() self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1, self.win_size_x - 3) self.main_win.do_update() offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2 spin_index = 0 self.center_win.window.timeout(250) # there's an iscsi object in the DOC. Check for an existing iSCSI # target discovery checkpoint. If found, update the kwargs for # discovery to find all the information about the newest mapped LUN. # If not, register a new checkpoint. kwargs = {"search_type": "disk", "search_name": self.iscsi.ctd_list} for checkpoint in self.engine._checkpoints: if checkpoint.name == ISCSI_TARGET_DISCOVERY: checkpoint.kwargs = kwargs break else: # register a new iSCSI target discovery checkpoint self.engine.register_checkpoint(ISCSI_TARGET_DISCOVERY, "solaris_install/target/discovery", "TargetDiscovery", kwargs=kwargs, insert_before=TRANSFER_PREP) # run target discovery again against the iSCSI LUN self._iscsi_target_discovery_completed = False errsvc.clear_error_list() self.engine.execute_checkpoints(start_from=ISCSI_TARGET_DISCOVERY, pause_before=TRANSFER_PREP, callback=self._iscsi_td_callback) while not self._iscsi_target_discovery_completed: input_key = self.main_win.getch() if input_key == curses.KEY_F9: if self.confirm_quit(): raise QuitException self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5, offset) self.center_win.no_ut_refresh() self.main_win.do_update() spin_index = (spin_index + 1) % len(DiskScreen.SPINNER) self.center_win.window.timeout(-1) self.center_win.clear() # check the result of target discovery if self._iscsi_target_discovery_status is not \ InstallEngine.EXEC_SUCCESS: err_data = errsvc.get_errors_by_mod_id(ISCSI_TARGET_DISCOVERY)[0] LOGGER.error("iSCSI target discovery failed") err = err_data.error_data[liberrsvc.ES_DATA_EXCEPTION] LOGGER.error(err) raise TargetDiscoveryError( "Unexpected error (%s) during iSCSI " "target discovery. See log for details." % err) def _td_callback(self, status, errsvc): '''Callback function for Target Discovery checkpoint execution. The status value is saved to be interpreted later. This function sets the self._target_discovery_completed value to true so the wait_for_disks() function will know to stop displaying the spinner. ''' self._target_discovery_status = status self._target_discovery_completed = True def _iscsi_td_callback(self, status, errsvc): '''Callback function for iSCSI Target Discovery checkpoint execution. The status value is saved to be interpreted later. This function sets the self._iscsi_target_discovery_completed value to true so the wait_for_iscsi_disk() function will know to stop displaying the spinner. ''' self._iscsi_target_discovery_status = status self._iscsi_target_discovery_completed = True def _show(self): '''Create a list of disks to choose from and create the window for displaying the partition/slice information from the selected disk ''' self.wait_for_disks() self.wait_for_iscsi_disk() discovered_target = self.doc.persistent.get_first_child( \ name=Target.DISCOVERED) LOGGER.debug(discovered_target) if discovered_target is None: self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1, max_x=(self.win_size_x - 1)) return self.disks = discovered_target.get_children(class_type=Disk) if not self.disks: self.center_win.add_paragraph(DiskScreen.NO_TARGETS, 1, 1, max_x=(self.win_size_x - 1)) return if self._image_size is None: try: self._image_size = Size(str(get_image_size(LOGGER)) + \ Size.mb_units) LOGGER.debug("Image_size: %s", self._image_size) except: # Unable to get the image size for some reason, allow # the target controller to use it's default size. LOGGER.debug("Unable to get image size") self._image_size = FALLBACK_IMAGE_SIZE # initialize the target controller so the min/max size for the # installation can be calculated. Explicitly do not want to select an # initial disk at this time in case none of the disks discovered is # usable. The target controller initialization needs to be done # everytime we show the disk selection screen so the desired target # node in the DOC can be re-populated with information from target # discovery. self.tc.initialize(image_size=self._image_size, no_initial_disk=True) # Go through all the disks found and find ones that have enough space # for installation. At the same time, see if any existing disk is the # boot disk. If a boot disk is found, move it to the front of the list num_usable_disks = 0 boot_disk = None for disk in self.disks: LOGGER.debug("size: %s, min: %s" % \ (disk.disk_prop.dev_size, self.minimum_size)) if disk.disk_prop.dev_size >= self.minimum_size: if disk.is_boot_disk(): boot_disk = disk num_usable_disks += 1 if boot_disk is not None: self.disks.remove(boot_disk) self.disks.insert(0, boot_disk) if num_usable_disks == 0: self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1, max_x=(self.win_size_x - 1)) return self.main_win.reset_actions() self.main_win.show_actions() y_loc = 1 self.center_win.add_text(DiskScreen.PARAGRAPH, y_loc, 1) y_loc += 1 self.center_win.add_text(self.size_line, y_loc, 1) y_loc += 2 self.center_win.add_text(self.disk_header_text, y_loc, 1) y_loc += 1 self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1, curses.ACS_HLINE, textwidth(self.disk_header_text)) y_loc += 1 disk_win_area = WindowArea(4, textwidth(self.disk_header_text) + 2, y_loc, 0) disk_win_area.scrollable_lines = len(self.disks) + 1 self.disk_win = ScrollWindow(disk_win_area, window=self.center_win) disk_item_area = WindowArea(1, disk_win_area.columns - 2, 0, 1) disk_index = 0 len_type = DiskScreen.DISK_HEADERS[0][0] - 1 len_size = DiskScreen.DISK_HEADERS[1][0] - 1 len_boot = DiskScreen.DISK_HEADERS[2][0] - 1 len_dev = DiskScreen.DISK_HEADERS[3][0] - 1 len_notes = DiskScreen.DISK_HEADERS[4][0] - 1 for disk in self.disks: disk_text_fields = [] dev_type = disk.disk_prop.dev_type if dev_type is not None: type_field = dev_type[:len_type] type_field = ljust_columns(type_field, len_type) else: type_field = " " * len_type disk_text_fields.append(type_field) disk_size = disk.disk_prop.dev_size.get(Size.gb_units) size_field = locale.format("%*.1f", (len_size, disk_size)) disk_text_fields.append(size_field) if disk.is_boot_disk(): bootable_field = "+".center(len_boot) else: bootable_field = " " * (len_boot) disk_text_fields.append(bootable_field) # # Information will be displayed in the device column with # the following priority: # # First priority is to display receptacle information, # if available. If receptacle information is displayed, # ctd name will not be displayed. # # If receptacle information is not available, the ctd name # will be displayed. # # Both items above can take as much as the 44 character wide # column as needed. # # If the receptacle/ctd name is less than 30 characters, # manufacturer information will be displayed in the left # over space. There won't be a column heading for the # manufacturer information. # device = disk.receptacle or disk.ctd added_device_field = False # is there enough room to display the manufacturer? if (len_dev - len(device)) >= DiskScreen.VENDOR_LEN: vendor = disk.disk_prop.dev_vendor if vendor is not None: dev_display_len = len_dev - DiskScreen.VENDOR_LEN device_field = ljust_columns(device, dev_display_len) disk_text_fields.append(device_field) vendor_field = vendor[:DiskScreen.VENDOR_LEN - 1] vendor_field = ljust_columns(vendor_field, DiskScreen.VENDOR_LEN - 1) disk_text_fields.append(vendor_field) added_device_field = True if not added_device_field: device_field = device[:len_dev] device_field = ljust_columns(device_field, len_dev) disk_text_fields.append(device_field) # display "<" or ">" if the disk is too big or too small selectable = True if disk.disk_prop.dev_size < self.minimum_size: selectable = False notes_field = DiskScreen.TOO_SMALL.center(len_notes) disk_text_fields.append(notes_field) elif disk.disk_prop.dev_size > Size(MAX_VTOC): notes_field = DiskScreen.TOO_BIG.center(len_notes) disk_text_fields.append(notes_field) # check the blocksize of the disk. If it's not 512 bytes and we # have an EFI firmware on x86, make the disk unselectable by the # user. See PSARC 2008/769 elif platform.processor() == "i386" and \ disk.geometry.blocksize != 512: firmware = SystemFirmware.get() if firmware.fw_name == "uefi64": selectable = False notes_field = DiskScreen.INVALID_DISK.center(len_notes) disk_text_fields.append(notes_field) LOGGER.debug( "marking disk %s unselectable as its " "blocksize is not 512 bytes on an UEFI " "firmware x86 system.", disk.ctd) disk_text = " ".join(disk_text_fields) disk_item_area.y_loc = disk_index disk_list_item = ListItem(disk_item_area, window=self.disk_win, text=disk_text, add_obj=selectable) disk_list_item.on_make_active = on_activate disk_list_item.on_make_active_kwargs["disk"] = disk disk_list_item.on_make_active_kwargs["disk_select"] = self disk_index += 1 self.disk_win.no_ut_refresh() y_loc += 7 disk_detail_area = WindowArea(6, 70, y_loc, 1) self.disk_detail = DiskWindow(disk_detail_area, self.disks[0], target_controller=self.tc, window=self.center_win) self.main_win.do_update() self.center_win.activate_object(self.disk_win) self.disk_win.activate_object(self.selected_disk_index, jump=True) def on_prev(self): ''' If the user goes back a screen, teardown any existing Iscsi objects and reset iSCSI target discovery ''' if self.iscsi is not None: # remove the iscsi Disk objects from the doc and from self.disks if self.iscsi.ctd_list: discovered_target = self.doc.persistent.get_first_child( name=Target.DISCOVERED) for disk in discovered_target.get_descendants(class_type=Disk): if disk.ctd in self.iscsi.ctd_list: disk.delete() self.disks = discovered_target.get_children(class_type=Disk) # tear down the Iscsi object self.iscsi.teardown() # reset the selected disk index to 0 self.selected_disk_index = 0 # reset the iscsi_discovery status self._iscsi_target_discovery_completed = False # reset the target controller's discovered_disk self.tc._discovered_disks = None def on_change_screen(self): ''' Save the index of the current selected object in case the user returns to this screen later ''' # Save the index of the selected object self.selected_disk_index = self.disk_win.active_object LOGGER.debug("disk_selection.on_change_screen, saved_index: %s", self.selected_disk_index) LOGGER.debug(self.doc.persistent) def start_discovery(self): # start target discovery if not self._target_discovery_completed: errsvc.clear_error_list() self.engine.execute_checkpoints(pause_before=TRANSFER_PREP, callback=self._td_callback) def validate(self): '''Validate the size of the disk.''' warning_txt = list() disk = self.disk_detail.ui_obj.doc_obj disk_size_gb = disk.disk_prop.dev_size.get(Size.gb_units) max_vtoc_size_gb = Size(MAX_VTOC).get(Size.gb_units) # Disk size warning should only be displayed if we are restricted to # VTOC boot disks. if not can_use_gpt and disk_size_gb > max_vtoc_size_gb: warning_txt.append(self.disk_warning_too_big) warning_txt = " ".join(warning_txt) if warning_txt: # warn the user and give user a chance to change result = self.main_win.pop_up(DiskScreen.DISK_WARNING_HEADER, warning_txt, DiskScreen.CANCEL_BUTTON, DiskScreen.CONTINUE_BUTTON) if not result: raise UIMessage() # let user select different disk # if user didn't quit it is always OK to ignore disk size, # that will be forced less than the maximum in partitioning. warning_txt = list() # We also need to warn the user if we need to relabel the disk from # GPT to SMI-VTOC if disk.label == "GPT" and not can_use_gpt and \ disk.disk_prop.dev_type != "iSCSI": warning_txt.append(DiskScreen.DISK_WARNING_RELABEL) warning_txt = " ".join(warning_txt) if warning_txt: # warn the user and give user a chance to change result = self.main_win.pop_up(DiskScreen.DISK_WARNING_HEADER, warning_txt, DiskScreen.CANCEL_BUTTON, DiskScreen.CONTINUE_BUTTON) if not result: raise UIMessage() # let user select different disk # if user didn't Cancel it is OK to relabel the disk. # This is one of the lesser known (and potentially dangerous) # features of target controller: select_disk() with # use_whole_disk=True can force a relabeling of the disk from GPT # to VTOC is necessary for booting from the disk disk = self.tc.select_disk(disk, use_whole_disk=True)[0] # The DiskWindow object needs its disk reference updated too self.disk_detail.set_disk_info(disk_info=disk)
class InstallStatus(BaseScreen): ''' Display text to the user indicating success or failure of the installation. Also provide option for viewing the install log ''' SUCCESS_HEADER = _("Installation Complete") FAILED_HEADER = _("Installation Failed") SUCCESS_TEXT = _("The installation of %(release)s has completed " "successfully.\n\n" "Reboot to start the newly installed software " "or Quit if you wish to perform additional " "tasks before rebooting.\n\n" "The installation log is available at " "%(log_tmp)s. After reboot it can be found" " at %(log_final)s.") FAILED_TEXT = _("The installation did not complete normally.\n\n" "For more information you can review the" " installation log.\n" "The installation log is available at %(log_tmp)s") def __init__(self, main_win, install_data): super(InstallStatus, self).__init__(main_win) self.log_locations = {} self.install_data = install_data self.iscsi_paragraph = "" def set_actions(self): '''Remove all actions except Quit, and add actions for rebooting and viewing the log. ''' self.main_win.actions.pop(curses.KEY_F2) # Remove F2_Continue self.main_win.actions.pop(curses.KEY_F3) # Remove F3_Back self.main_win.actions.pop(curses.KEY_F6) # Remove F6_Help if self.install_data.install_succeeded: reboot_action = Action(curses.KEY_F8, _("Reboot"), reboot_system) self.main_win.actions[reboot_action.key] = reboot_action log_action = Action(curses.KEY_F4, _("View Log"), self.main_win.screen_list.get_next) self.main_win.actions[log_action.key] = log_action def _show(self): '''Display the correct text based on whether the installation succeeded or failed. ''' self.log_locations["log_tmp"] = self.install_data.log_location self.log_locations["log_final"] = self.install_data.log_final if self.install_data.install_succeeded: self.header_text = InstallStatus.SUCCESS_HEADER paragraph_text = InstallStatus.SUCCESS_TEXT # inform the user how to set the CHAP password and username on # SPARC, if needed if platform.processor() == "sparc": doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) iscsi = doc.volatile.get_first_child(name=ISCSI_LABEL, class_type=Iscsi) if iscsi and iscsi.chap_name is not None and \ disk.ctd in iscsi.ctd_list: iscsi_string = list() iscsi_string.append("") iscsi_string.append(_("CHAP username and password must " "be set at the ok prompt:")) iscsi_string.append(_("ok set-ascii-security-key " "chap-user <chap name>")) iscsi_string.append(_("ok set-ascii-security-key " "chap-password <chap password>")) self.iscsi_paragraph = "\n".join(iscsi_string) else: self.header_text = InstallStatus.FAILED_HEADER paragraph_text = InstallStatus.FAILED_TEXT self.main_win.set_header_text(self.header_text) fmt = {} fmt.update(self.log_locations) fmt.update(RELEASE) self.center_win.add_paragraph(paragraph_text % fmt, 2) if self.iscsi_paragraph: self.center_win.add_paragraph(self.iscsi_paragraph, 10) def confirm_quit(self): '''No need to confirm after installation is complete''' return True
class SummaryScreen(BaseScreen): '''Display a summary of the install profile to the user InnerWindow.__init__ is sufficient to initalize an instance of SummaryScreen ''' HEADER_TEXT = _("Installation Summary") PARAGRAPH = _("Review the settings below before installing." " Go back (F3) to make changes.") HELP_DATA = (TUI_HELP + "/%s/summary.txt", _("Installation Summary")) INDENT = 2 def set_actions(self): '''Replace the default F2_Continue with F2_Install''' install_action = Action(curses.KEY_F2, _("Install"), self.main_win.screen_list.get_next) self.main_win.actions[install_action.key] = install_action def _show(self): '''Prepare a text summary and display it to the user in a ScrollWindow ''' global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.sysconfig = solaris_install.sysconfig.profile.from_engine() y_loc = 1 y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc) y_loc += 1 summary_text = self.build_summary() LOGGER.info( "The following configuration is used for " "installation: %s\n", summary_text) # Wrap the summary text, accounting for the INDENT (used below in # the call to add_paragraph) max_chars = self.win_size_x - SummaryScreen.INDENT - 1 summary_text = convert_paragraph(summary_text, max_chars) area = WindowArea(x_loc=0, y_loc=y_loc, scrollable_lines=(len(summary_text) + 1)) area.lines = self.win_size_y - y_loc area.columns = self.win_size_x scroll_region = ScrollWindow(area, window=self.center_win) scroll_region.add_paragraph(summary_text, start_x=SummaryScreen.INDENT) self.center_win.activate_object(scroll_region) def build_summary(self): '''Build a textual summary from the DOC data''' lines = [] lines.append(_("Software: %s") % self.get_release()) lines.append("") lines.append(self.get_disk_summary()) lines.append("") lines.append(self.get_tz_summary()) lines.append("") lines.append( _("Language: *The following can be changed when " "logging in.")) if self.sysconfig.system.locale is None: self.sysconfig.system.determine_locale() lines.append( _(" Default language: %s") % self.sysconfig.system.actual_lang) lines.append("") lines.append( _("Keyboard layout: *The following can be " "changed when logging in.")) lines.append( _(" Default keyboard layout: %s") % self.sysconfig.system.keyboard) lines.append("") lines.append( _("Terminal type: %s") % self.sysconfig.system.terminal_type) lines.append("") lines.append(_("Users:")) lines.extend(self.get_users()) lines.append("") lines.append(_("Network:")) lines.extend(self.get_networks()) self._get_nameservice(lines) return "\n".join(lines) def get_networks(self): '''Build a summary of the networks from the DOC data, returned as a list of strings ''' network_summary = [] network_summary.append( _(" Storage Array name: %s") % self.sysconfig.system.hostname) nic = self.sysconfig.nic if nic.type == NetworkInfo.AUTOMATIC: network_summary.append(_(" Network Configuration: Automatic")) elif nic.type == NetworkInfo.NONE: network_summary.append(_(" Network Configuration: None")) elif nic.type == NetworkInfo.MANUAL: network_summary.append( _(" Manual Configuration: %s") % NetworkInfo.get_nic_desc(nic.nic_iface)) network_summary.append(_(" IP Address: %s") % nic.ip_address) network_summary.append(_(" Netmask: %s") % nic.netmask) if nic.gateway: network_summary.append(_(" Router: %s") % nic.gateway) return network_summary def _get_nameservice(self, summary): ''' Find all name services information and append to summary ''' # append lines of name service info to summary nameservice_summary(self.sysconfig.nameservice, summary) def get_users(self): '''Build a summary of the user information, and return it as a list of strings ''' user_summary = [] user_summary.append( _(" root password is blank. set it at next login.")) return user_summary def get_disk_summary(self): '''Return a string summary of the disk selection''' doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) disk_string = list() disk_size_str = locale.format( "%.1f", disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB locale_disk_str = _("Disk: ") + disk_size_str + " " + \ str(disk.disk_prop.dev_type) disk_string.append(locale_disk_str) if not disk.whole_disk: part_data = get_solaris_partition(doc) if part_data is not None: part_size_str = locale.format( "%.1f", part_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_part_str = _("Partition: ") + part_size_str + " " +\ str(libdiskmgt_const.PARTITION_ID_MAP[part_data.part_type]) disk_string.append(locale_part_str) if part_data is None or not part_data.in_zpool: slice_data = get_solaris_slice(doc) slice_num_str = _("Slice %s: ") % slice_data.name slice_size_str = locale.format( "%.1f", slice_data.size.get(Size.gb_units)) + LOCALIZED_GB locale_slice_str = slice_num_str + slice_size_str + " " +\ str(slice_data.in_zpool) disk_string.append(locale_slice_str) return "\n".join(disk_string) def get_tz_summary(self): '''Return a string summary of the timezone selection''' timezone = self.sysconfig.system.tz_timezone return _("Time Zone: %s") % timezone @staticmethod def get_release(): '''Read in the release information from /etc/release''' try: try: release_file = open("/etc/release") except IOError: LOGGER.warn("Could not read /etc/release") release_file = None release = RELEASE['release'] else: release = release_file.readline() finally: if release_file is not None: release_file.close() return release.strip()
class DiskWindow(InnerWindow): '''Display and edit disk information, including partitions and slices''' STATIC_PARTITION_HEADERS = [(12, _("Primary"), _("Logical")), (10, _(" Size(GB)"), _(" Size(GB)"))] EDIT_PARTITION_HEADERS = [(13, _("Primary"), _("Logical")), (10, _(" Size(GB)"), _(" Size(GB)")), (7, _(" Avail"), _(" Avail"))] STATIC_SLICE_HEADERS = [(13, _("Slice"), _("Slice")), (2, "#", "#"), (10, _(" Size(GB)"), _(" Size(GB)"))] EDIT_SLICE_HEADERS = [(13, _("Slice"), _("Slice")), (2, "#", "#"), (10, _(" Size(GB)"), _(" Size(GB)")), (7, _(" Avail"), _(" Avail"))] ADD_KEYS = {curses.KEY_LEFT: no_action, curses.KEY_RIGHT: no_action} DEAD_ZONE = 3 SCROLL_PAD = 2 MIN_SIZE = None REC_SIZE = None SIZE_PRECISION = Size(UI_PRECISION).get(Size.gb_units) DESTROYED_MARK = EditField.ASTERISK_CHAR def __init__(self, area, disk_info, editable=False, error_win=None, target_controller=None, **kwargs): '''See also InnerWindow.__init__ disk_info (required) - Either a Disk or Partition object containing the data to be represented. If a Partition objects is provided, it will be used for displaying slice data within that partition. If Disk has partition(s), those are displayed. If not, but it has slices, then those are displayed. If neither partition data nor slice data are available, a ValueError is raised. headers (required) - List of tuples to populate the header of this window with. The first item in each tuple should be the width of the header, the second item should be the left side header. editable (optional) - If True, the window will be created such that data is editable. target_controller(optional) - Target controller ''' global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.headers = None self.orig_ext_part_field = None self.orig_logicals_active = False self.ext_part_field = None self.error_win = error_win self.editable = editable self.win_width = None self.left_win = None self.right_win = None self.list_area = None self.edit_area = None super(DiskWindow, self).__init__(area, add_obj=editable, **kwargs) self.left_header_string = None self.right_header_string = None self._orig_data = None self.disk_info = None self.has_partition_data = False self.key_dict[curses.KEY_LEFT] = self.on_arrow_key self.key_dict[curses.KEY_RIGHT] = self.on_arrow_key if self.editable: self.key_dict[curses.KEY_F5] = self.change_type self.tc = target_controller self._ui_obj = None self.ui_obj = disk_info self.set_disk_info(ui_obj=self.ui_obj) LOGGER.debug(self.ui_obj) if platform.processor() == "sparc": self.is_x86 = False else: self.is_x86 = True @property def ui_obj(self): return self._ui_obj @ui_obj.setter def ui_obj(self, part): ''' create and set the value for ui_obj depending on type ''' if isinstance(part, Disk): self._ui_obj = UIDisk(self.tc, parent=None, doc_obj=part) elif isinstance(part, Partition): self._ui_obj = UIPartition(self.tc, parent=None, doc_obj=part) else: # Must be a either a Disk or Partition. It's an error to be here raise RuntimeError("disk_info object is invalid") def _init_win(self, window): '''Require at least 70 columns and 6 lines to fit current needs for display of partitions and slices. Builds two inner ScrollWindows for displaying/editing the data. ''' if self.area.columns < 70: raise ValueError("Insufficient space - area.columns < 70") if self.area.lines < 6: raise ValueError("Insufficient space - area.lines < 6") self.win_width = (self.area.columns - DiskWindow.DEAD_ZONE + DiskWindow.SCROLL_PAD) / 2 super(DiskWindow, self)._init_win(window) win_area = WindowArea(self.area.lines - 1, self.win_width, 2, 0) win_area.scrollable_lines = self.area.lines - 2 self.left_win = ScrollWindow(win_area, window=self, add_obj=False) self.left_win.color = None self.left_win.highlight_color = None win_area.x_loc = self.win_width + DiskWindow.DEAD_ZONE win_area.scrollable_lines = 2 * MAX_EXT_PARTS self.right_win = ScrollWindow(win_area, window=self, add_obj=False) self.right_win.color = None self.right_win.highlight_color = None def set_disk_info(self, ui_obj=None, disk_info=None, no_part_ok=False): '''Set up this DiskWindow to represent disk_info''' if ui_obj is not None: disk_info = ui_obj.doc_obj elif disk_info is not None: self.ui_obj = disk_info else: # Should never be this case raise RuntimeError("Unable to find ui_obj or disk_info") part_list = disk_info.get_children(class_type=Partition) if part_list: self.has_partition_data = True else: slice_list = disk_info.get_children(class_type=Slice) if slice_list: self.has_partition_data = False else: # No partitions and no slices if no_part_ok: if self.is_x86: self.has_partition_data = True else: self.has_partition_data = False else: return if self.has_partition_data: if self.editable: self.headers = DiskWindow.EDIT_PARTITION_HEADERS self.list_area = WindowArea(1, self.headers[0][0] + self.headers[1][0], 0, DiskWindow.SCROLL_PAD) self.edit_area = WindowArea(1, self.headers[1][0], 0, self.headers[0][0]) else: self.headers = DiskWindow.STATIC_PARTITION_HEADERS else: if self.editable: self.headers = DiskWindow.EDIT_SLICE_HEADERS self.list_area = WindowArea(1, self.headers[0][0] + self.headers[1][0] + self.headers[2][0], 0, DiskWindow.SCROLL_PAD) self.edit_area = WindowArea(1, self.headers[2][0], 0, self.headers[0][0] + self.headers[1][0]) else: self.headers = DiskWindow.STATIC_SLICE_HEADERS LOGGER.debug("have_partition: %s", self.has_partition_data) LOGGER.debug(self.ui_obj) self.ui_obj.add_unused_parts(no_part_ok=no_part_ok) self.left_win.clear() self.right_win.clear() self.window.erase() self.print_headers() if self.editable: self.active_object = None self.build_edit_fields() self.right_win.bottom = max(0, len(self.right_win.all_objects) - self.right_win.area.lines) if self.has_partition_data: self.orig_ext_part_field = None for obj in self.left_win.objects: if (obj.data_obj.is_extended()): self.orig_ext_part_field = obj self.orig_logicals_active = True break else: self.print_data() def print_headers(self): '''Print the headers for the displayed data. header[0] - The width of this column. header[1] and header[2] are trimmed to this size header[1] - The internationalized text for the left window header[2] - The internationalized text for the right window ''' self.left_header_string = [] self.right_header_string = [] for header in self.headers: left_header_str = header[1] right_header_str = header[2] # Trim the header to fit in the column width, # splitting columns with at least 1 space # Pad with extra space(s) to align the columns left_header_str = fit_text_truncate(left_header_str, header[0] - 1, just="left") self.left_header_string.append(left_header_str) right_header_str = fit_text_truncate(right_header_str, header[0] - 1, just="left") self.right_header_string.append(right_header_str) self.left_header_string = " ".join(self.left_header_string) self.right_header_string = " ".join(self.right_header_string) LOGGER.debug(self.left_header_string) self.add_text(self.left_header_string, 0, DiskWindow.SCROLL_PAD) right_win_offset = (self.win_width + DiskWindow.DEAD_ZONE + DiskWindow.SCROLL_PAD) self.add_text(self.right_header_string, 0, right_win_offset) self.window.hline(1, DiskWindow.SCROLL_PAD, curses.ACS_HLINE, textwidth(self.left_header_string)) self.window.hline(1, right_win_offset, curses.ACS_HLINE, textwidth(self.right_header_string)) self.no_ut_refresh() def print_data(self): '''Print static (non-editable) data. Slices - fill the left side, then remaining slices on the right side. If for some reason not all slices fit, indicate how many more slices there area Partitions - Put standard partitions on the left, logical partitions on the right ''' part_index = 0 data = self.ui_obj.get_parts_in_use() if len(data) == 0: return # should never be this case if self.has_partition_data: max_parts = MAX_PRIMARY_PARTS else: max_parts = min(len(data), self.left_win.area.lines) win = self.left_win y_loc = 0 for next_data in data: LOGGER.debug("next_data: %s", next_data) if y_loc >= max_parts: if win is self.left_win: win = self.right_win y_loc = 0 max_parts = win.area.lines else: num_extra = len(data) - part_index if self.has_partition_data: more_parts_txt = _("%d more partitions") % num_extra else: more_parts_txt = _("%d more slices") % num_extra win.add_text(more_parts_txt, win.area.lines, 3) break x_loc = DiskWindow.SCROLL_PAD field = 0 win.add_text(next_data.get_description(), y_loc, x_loc, self.headers[field][0] - 1) x_loc += self.headers[field][0] field += 1 if not self.has_partition_data: win.add_text(str(next_data.name), y_loc, x_loc, self.headers[field][0] - 1) x_loc += self.headers[field][0] field += 1 win.add_text(locale.format("%*.1f", (self.headers[field][0] - 1, next_data.size.get(Size.gb_units))), y_loc, x_loc, self.headers[field][0] - 1) x_loc += self.headers[field][0] y_loc += 1 field += 1 part_index += 1 self.right_win.use_vert_scroll_bar = False self.no_ut_refresh() def build_edit_fields(self): '''Build subwindows for editing partition sizes For slices, fill the left side, then the right (right side scrolling as needed, though this shouldn't happen unless the number of slices on disk exceeds 8 for some reason) For partitions, fill the left side up to MAX_PRIMARY_PARTS, and place all logical partitions on the right. ''' data = self.ui_obj.get_parts_in_use() if self.has_partition_data: max_left_parts = MAX_PRIMARY_PARTS else: if len(data) == 0: return # should never be this case max_left_parts = min(len(data), self.left_win.area.lines) part_iter = iter(data) try: next_part = part_iter.next() self.objects.append(self.left_win) for y_loc in range(max_left_parts): self.list_area.y_loc = y_loc self.create_list_item(next_part, self.left_win, self.list_area) next_part = part_iter.next() self.objects.append(self.right_win) for y_loc in range(self.right_win.area.scrollable_lines): self.list_area.y_loc = y_loc self.create_list_item(next_part, self.right_win, self.list_area) next_part = part_iter.next() if len(data) > max_left_parts: self.right_win.use_vert_scroll_bar = True except StopIteration: if len(self.right_win.all_objects) <= self.right_win.area.lines: self.right_win.use_vert_scroll_bar = False self.right_win.no_ut_refresh() else: raise ValueError("Could not fit all partitions in DiskWindow") self.no_ut_refresh() def create_list_item(self, next_part, win, list_area): '''Add an entry for next_part (a Partition or Slice) to the DiskWindow ''' list_item = ListItem(list_area, window=win, data_obj=next_part) list_item.key_dict.update(DiskWindow.ADD_KEYS) edit_field = EditField(self.edit_area, window=list_item, numeric_pad=" ", validate=decimal_valid, on_exit=on_exit_edit, error_win=self.error_win, add_obj=False, data_obj=next_part) edit_field.right_justify = True edit_field.validate_kwargs["disk_win"] = self edit_field.on_exit_kwargs["disk_win"] = self edit_field.key_dict.update(DiskWindow.ADD_KEYS) self.update_part(part_field=list_item) return list_item def update_part(self, part_info=None, part_field=None): '''Sync changed partition data to the screen.''' if part_field is None: if part_info is None: raise ValueError("Must supply either part_info or part_field") part_field = self.find_part_field(part_info)[1] elif part_info is None: part_info = part_field.data_obj elif part_field.data_obj is not part_info: raise ValueError("part_field must be a ListItem associated with " "part_info") if not isinstance(part_field, ListItem): raise TypeError("part_field must be a ListItem associated with " "part_info") if self.has_partition_data: desc_text = part_info.get_description() else: desc_length = self.headers[0][0] - 1 desc_text = "%-*.*s %s" % (desc_length, desc_length, part_info.get_description(), part_info.name) part_field.set_text(desc_text) edit_field = part_field.all_objects[0] edit_field.set_text(locale.format("%.1f", part_info.size.get(Size.gb_units))) self.mark_if_destroyed(part_field) self._update_edit_field(part_info, part_field, edit_field) self.update_avail_space(part_info=part_info) if self.has_partition_data: if part_info.is_extended(): self.ext_part_field = part_field def _update_edit_field(self, part_info, part_field, edit_field): '''If the partition/slice is editable, add it to the .objects list. If it's also the part_field that's currently selected, then activate the edit field. ''' if part_info.editable(): part_field.objects = [edit_field] active_win = self.get_active_object() if active_win is not None: if active_win.get_active_object() is part_field: part_field.activate_object(edit_field) else: edit_field.make_inactive() part_field.objects = [] part_field.active_object = None def mark_if_destroyed(self, part_field): '''Determine if the partition/slice represented by part_field has changed such that its contents will be destroyed. ''' part_info = part_field.data_obj destroyed = part_info.modified() self.mark_destroyed(part_field, destroyed) def mark_destroyed(self, part_field, destroyed): '''If destroyed is True, add an asterisk indicating that the partition or slice's content will be destroyed during installation. Otherwise, clear the asterisk ''' y_loc = part_field.area.y_loc x_loc = part_field.area.x_loc - 1 if part_field in self.right_win.objects: win = self.right_win else: win = self.left_win if destroyed: win.window.addch(y_loc, x_loc, DiskWindow.DESTROYED_MARK, win.color_theme.inactive) else: win.window.addch(y_loc, x_loc, InnerWindow.BKGD_CHAR) def update_avail_space(self, part_number=None, part_info=None): '''Update the 'Avail' column for the specified slice or partition. If no number is given, all avail columns are updated ''' if part_number is None and part_info is None: self._update_all_avail_space() else: self._update_avail_space(part_number, part_info) def _update_all_avail_space(self): '''Update the 'Avail' column for all slices or partitions.''' idx = 0 for item in self.left_win.objects: self.update_avail_space(idx) idx += 1 for item in self.right_win.objects: self.update_avail_space(idx) idx += 1 y_loc = idx - len(self.left_win.objects) if self.has_partition_data: x_loc = self.headers[0][0] + self.headers[1][0] + 1 field = 2 else: x_loc = (self.headers[0][0] + self.headers[1][0] + self.headers[2][0] + 1) field = 3 if y_loc > 0: self.right_win.add_text(" " * self.headers[field][0], y_loc, x_loc) def _update_avail_space(self, part_number=None, part_info=None): '''Update the 'Avail' column for the specified slice or partition.''' if part_number is None: win, item = self.find_part_field(part_info) elif part_number < len(self.left_win.objects): win = self.left_win item = win.objects[part_number] else: win = self.right_win item = win.objects[part_number - len(self.left_win.objects)] if self.has_partition_data: x_loc = self.headers[0][0] + self.headers[1][0] + 1 field = 2 else: x_loc = (self.headers[0][0] + self.headers[1][0] + self.headers[2][0] + 1) field = 3 y_loc = item.area.y_loc part = item.data_obj max_space = part.get_max_size() max_space = locale.format("%*.1f", (self.headers[field][0], max_space.get(Size.gb_units))) win.add_text(max_space, y_loc, x_loc) def find_part_field(self, part_info): '''Given a PartitionInfo or SliceInfo object, find the associated ListItem. This search compares by reference, and will only succeed if you have a handle to the exact object referenced by the ListItem ''' for win in [self.left_win, self.right_win]: for item in win.objects: if item.data_obj is part_info: return win, item raise ValueError("Part field not found") def reset(self, dummy=None): '''Reset ui_obj to value found from Target Discovery. Meaningful only for editable DiskWindows ''' if not self.editable: return doc = InstallEngine.get_instance().doc # "reset" the desired target reset_obj = None if isinstance(self.ui_obj, UIDisk): reset_obj = (self.tc.reset_layout(disk=self.ui_obj.doc_obj))[0] else: # reset the partition by removing the modified Partition, and # resetting it with the partition found during target discovery. discovered_obj = self.ui_obj.discovered_doc_obj desired_disk = get_desired_target_disk(doc) desired_part = get_solaris_partition(doc) desired_disk.delete_partition(desired_part) part_copy = deepcopy(discovered_obj) desired_disk.insert_children(part_copy) # get the updated reference reset_obj = get_solaris_partition(doc) dump_doc("After doing reset") self.set_disk_info(disk_info=reset_obj) self.activate_solaris_data() def activate_solaris_data(self): '''Find the Solaris Partition / ZFS Root Pool Slice and activate it. ''' if self.editable: solaris_part = self.ui_obj.get_solaris_data() if solaris_part is None: LOGGER.debug("No Solaris data, activating default") self.activate_object() self.right_win.scroll(scroll_to_line=0) return disk_order = self.ui_obj.get_parts_in_use().index(solaris_part) LOGGER.debug("solaris disk at disk_order = %s", disk_order) self.activate_index(disk_order) def make_active(self): '''On activate, select the solaris partition or ZFS root pool, instead of defaulting to 0 ''' self.set_color(self.highlight_color) self.activate_solaris_data() def on_arrow_key(self, input_key): ''' On curses.KEY_LEFT: Move from the right win to the left win On curses.KEY_RIGHT: Move from the left to the right ''' if (input_key == curses.KEY_LEFT and self.get_active_object() is self.right_win and len(self.left_win.objects) > 0): active_object = self.right_win.get_active_object().area.y_loc if (active_object >= len(self.left_win.objects)): active_object = len(self.left_win.objects) - 1 self.activate_object(self.left_win) self.left_win.activate_object(active_object) return None elif (input_key == curses.KEY_RIGHT and self.get_active_object() is self.left_win and len(self.right_win.objects) > 0): active_line = (self.left_win.active_object + self.right_win.current_line[0]) active_object = None force_to_top = False for obj in self.right_win.objects: if obj.area.y_loc >= active_line: active_object = obj off_screen = (self.right_win.current_line[0] + self.right_win.area.lines) if active_object.area.y_loc > off_screen: force_to_top = True break if active_object is None: active_object = 0 self.left_win.activate_object(-1, loop=True) self.activate_object(self.right_win) self.right_win.activate_object_force(active_object, force_to_top=force_to_top) return None return input_key def no_ut_refresh(self, abs_y=None, abs_x=None): '''Refresh self, left win and right win explicitly''' super(DiskWindow, self).no_ut_refresh() self.left_win.no_ut_refresh(abs_y, abs_x) self.right_win.no_ut_refresh(abs_y, abs_x) def change_type(self, dummy): '''Cycle the type for the currently active object, and update its field ''' LOGGER.debug("changing type") part_field = self.get_active_object().get_active_object() part_info = part_field.data_obj part_order = self.ui_obj.get_parts_in_use().index(part_info) old_obj = part_info.discovered_doc_obj old_type = list() if old_obj is not None: if self.has_partition_data: old_type.append(old_obj.part_type) else: if old_obj.in_zpool is not None: old_type.append(old_obj.in_zpool) else: in_use = part_info.doc_obj.in_use if in_use is not None: if in_use['used_name']: old_type.append((in_use['used_name'])[0]) LOGGER.debug("extra type to cycle: %s", old_type) part_info.cycle_type(extra_type=old_type) self.set_disk_info(ui_obj=self.ui_obj, no_part_ok=True) self.activate_index(part_order) return None def create_extended(self, ext_part_field): '''If this is the original extended partition, restore the original logical partitions. Otherwise, create a single unused logical partition. ''' if not ext_part_field.data_obj.modified(): self.right_win.clear() self.orig_logicals_active = True logicals = deepcopy(self._orig_data.get_logicals()) self.disk_info.partitions.extend(logicals) for idx, logical in enumerate(logicals): self.list_area.y_loc = idx self.create_list_item(logical, self.right_win, self.list_area) if self.right_win not in self.objects: self.objects.append(self.right_win) self.right_win.activate_object_force(0, force_to_top=True) self.right_win.make_inactive() self.right_win.no_ut_refresh() else: # Leave old data be, create new Unused logical partition if self.right_win not in self.objects: self.objects.append(self.right_win) self.append_unused_logical() def activate_index(self, obj_index): '''Activate the object at the specified index ''' if obj_index < len(self.left_win.objects): LOGGER.debug("activating in left_win") self.left_win.activate_object(obj_index) self.activate_object(self.left_win) self.right_win.scroll(scroll_to_line=0) else: activate = obj_index - len(self.left_win.objects) LOGGER.debug('activating in right win') self.right_win.activate_object_force(activate, force_to_top=True) self.activate_object(self.right_win) left_active = self.left_win.get_active_object() if left_active is not None: left_active.make_inactive() def append_unused_logical(self): '''Adds a single Unused logical partition to the right window''' new_part = self.disk_info.append_unused_logical() self.list_area.y_loc = len(self.right_win.all_objects) bottom = self.list_area.y_loc - self.right_win.area.lines + 1 self.right_win.bottom = max(0, bottom) self.create_list_item(new_part, self.right_win, self.list_area) scroll = len(self.right_win.all_objects) > self.right_win.area.lines self.right_win.use_vert_scroll_bar = scroll self.right_win.no_ut_refresh()
class FDiskPart(BaseScreen): '''Allow user to choose to use the whole disk, or move to the partition/slice edit screen. ''' BOOT_TEXT = _("Boot") HEADER_FDISK = _("Fdisk Partitions: ") HEADER_PART_SLICE = _("Solaris Partition Slices") HEADER_SLICE = _("Solaris Slices: ") HEADER_TYPE_BOOTABLE = _(" %(type)s %(bootable)s") PARAGRAPH_FDISK = _("%(release)s can be installed on the whole " "disk or a partition on the disk.") % RELEASE PARAGRAPH_PART_SLICE = _("%(release)s can be installed in the " "whole fdisk partition or within a " "slice in the partition") % RELEASE PARAGRAPH_SLICE = _("%(release)s can be installed on the whole" " disk or a slice on the disk.") % RELEASE FOUND_PART = _("The following partitions were found on the disk.") PROPOSED_PART = _("A partition table was not found. The following is" " proposed.") FOUND_SLICE = _("The following slices were found on the disk.") PROPOSED_SLICE = _("A VTOC label was not found. The following is " "proposed.") USE_WHOLE_DISK = _("Use the whole disk") USE_WHOLE_PARTITION = _("Use the whole partition") USE_SLICE_IN_PART = _("Use a slice in the partition") USE_PART_IN_DISK = _("Use a partition of the disk") USE_SLICE_IN_DISK = _("Use a slice on the disk") SPARC_HELP = (TUI_HELP + "/%s/sparc_solaris_slices.txt", _("Solaris Slices")) X86_PART_HELP = (TUI_HELP + "/%s/" "x86_fdisk_partitions.txt", _("Fdisk Partitions")) X86_SLICE_HELP = (TUI_HELP + "/%s/x86_fdisk_slices.txt", _("Solaris Partition Slices")) def __init__(self, main_win, target_controller, x86_slice_mode=False): '''If x86_slice_mode == True, this screen presents options for using a whole partition, or a slice within the partition. Otherwise, it presents options for using the whole disk, or using a partition (x86) or slice (SPARC) within the disk ''' super(FDiskPart, self).__init__(main_win) global LOGGER LOGGER = logging.getLogger(INSTALL_LOGGER_NAME) self.x86_slice_mode = x86_slice_mode self.is_x86 = True self.help_format = " %s" if platform.processor() == "sparc": # SPARC, slices on a disk self.is_x86 = False self.header_text = FDiskPart.HEADER_SLICE self.paragraph = FDiskPart.PARAGRAPH_SLICE self.found = FDiskPart.FOUND_SLICE self.proposed = FDiskPart.PROPOSED_SLICE self.use_whole = FDiskPart.USE_WHOLE_DISK self.use_part = FDiskPart.USE_SLICE_IN_DISK self.help_data = FDiskPart.SPARC_HELP elif self.x86_slice_mode: # x86, slices within a partition self.instance = ".slice" self.header_text = FDiskPart.HEADER_PART_SLICE self.paragraph = FDiskPart.PARAGRAPH_PART_SLICE self.found = FDiskPart.FOUND_SLICE self.proposed = FDiskPart.PROPOSED_SLICE self.use_whole = FDiskPart.USE_WHOLE_PARTITION self.use_part = FDiskPart.USE_SLICE_IN_PART self.help_data = FDiskPart.X86_SLICE_HELP self.help_format = " %s" else: # x86, partitions on a disk self.header_text = FDiskPart.HEADER_FDISK self.paragraph = FDiskPart.PARAGRAPH_FDISK self.found = FDiskPart.FOUND_PART self.proposed = FDiskPart.PROPOSED_PART self.use_whole = FDiskPart.USE_WHOLE_DISK self.use_part = FDiskPart.USE_PART_IN_DISK self.help_data = FDiskPart.X86_PART_HELP self.disk_win = None self.partial_disk_item = None self.whole_disk_item = None self.disk = None self.tc = target_controller self.use_whole_segment = True def _show(self): '''Display partition data for selected disk, and present the two choices ''' doc = InstallEngine.get_instance().doc disk = get_desired_target_disk(doc) if disk.label == "GPT": # this disk has GPT partitions, so skip this screen raise SkipException if self.x86_slice_mode: if disk.whole_disk: raise SkipException sol_partition = get_solaris_partition(doc) if sol_partition is None: # Must have a Solaris partition err_msg = "Critical error - no Solaris partition found" LOGGER.error(err_msg) raise ValueError(err_msg) LOGGER.debug("Working with the following partition:") LOGGER.debug(str(sol_partition)) # See if there are any slices in the partition all_slices = sol_partition.get_children(class_type=Slice) if not all_slices: LOGGER.info("No previous slices found") # Setting the in_zpool flag to indicate the whole # partition should be used. The needed underlying # slices will be created in the next step when # the in_zpool flag is detected. sol_partition.in_zpool = DEFAULT_ZPOOL_NAME raise SkipException LOGGER.debug("Preserved partition with existing slices, " "presenting option to install into a slice") self.disk = sol_partition else: self.disk = get_desired_target_disk(doc) LOGGER.debug("Working with the following disk:") LOGGER.debug(str(self.disk)) if self.disk.whole_disk: LOGGER.debug("disk.whole_disk=True, skip editing") raise SkipException if self.disk.is_boot_disk(): bootable = FDiskPart.BOOT_TEXT else: bootable = u"" disk_size_gb_str = locale.format("%.1f", self.disk.disk_prop.dev_size.get(Size.gb_units)) + LOCALIZED_GB type_bootable_str = FDiskPart.HEADER_TYPE_BOOTABLE % \ {"type": self.disk.disk_prop.dev_type, "bootable": bootable} header_text = self.header_text + disk_size_gb_str + \ type_bootable_str self.main_win.set_header_text(header_text) y_loc = 1 y_loc += self.center_win.add_paragraph(self.paragraph, start_y=y_loc) y_loc += 1 if self.is_x86 and not self.x86_slice_mode: all_parts = self.disk.get_children(class_type=Partition) else: all_parts = self.disk.get_children(class_type=Slice) found_parts = bool(all_parts) if found_parts: next_line = self.found else: next_line = self.proposed y_loc += self.center_win.add_paragraph(next_line, start_y=y_loc) y_loc += 1 disk_win_area = WindowArea(6, 70, y_loc, 0) self.disk_win = DiskWindow(disk_win_area, self.disk, target_controller=self.tc, window=self.center_win) y_loc += disk_win_area.lines y_loc += 3 whole_disk_width = textwidth(self.use_whole) + 3 cols = (self.win_size_x - whole_disk_width) / 2 whole_disk_item_area = WindowArea(1, whole_disk_width, y_loc, cols) self.whole_disk_item = ListItem(whole_disk_item_area, window=self.center_win, text=self.use_whole, centered=True) y_loc += 1 partial_width = textwidth(self.use_part) + 3 cols = (self.win_size_x - partial_width) / 2 partial_item_area = WindowArea(1, partial_width, y_loc, cols) self.partial_disk_item = ListItem(partial_item_area, window=self.center_win, text=self.use_part, centered=True) self.main_win.do_update() if self.use_whole_segment: self.center_win.activate_object(self.whole_disk_item) else: self.center_win.activate_object(self.partial_disk_item) def on_continue(self): '''Set the user's selection in the install target. If they chose to use the entire disk (or entire partition), define a single partition (or slice) to consume the whole disk (or partition) ''' if self.center_win.get_active_object() is self.whole_disk_item: self.use_whole_segment = True if isinstance(self.disk, Disk): LOGGER.debug("Setting whole_disk for %s", self.disk) self.disk = self.tc.select_disk(self.disk, use_whole_disk=True)[0] else: # it's a partition, set the in_zpool attribute in the object # for now. The next screen will fill in needed slices self.disk.in_zpool = DEFAULT_ZPOOL_NAME else: self.use_whole_segment = False if isinstance(self.disk, Disk): LOGGER.debug("Setting whole_disk to false for %s", self.disk) self.disk.whole_disk = False else: self.disk.in_zpool = None # set an attribute on the desired disk so we don't enter the # partition/slice edit screen. if isinstance(self.disk, Disk): self.disk.use_whole_segment = self.use_whole_segment dump_doc("At the end of fdisk_partitions.continue")