diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..d830bb97 --- /dev/null +++ b/Makefile @@ -0,0 +1,97 @@ +# destinations +DESTDIR = +INSTALLER_DIR = /opt/xensource/installer +EFI_DIR = /EFI/xenserver + +# root of a tree with sm.rpm unpacked +SM_ROOTDIR = + +INSTALL = install + +install: + $(INSTALL) -d $(DESTDIR)/usr/bin + $(INSTALL) -m755 support.sh $(DESTDIR)/usr/bin + $(INSTALL) -d $(DESTDIR)$(INSTALLER_DIR)/tui/installer/ + $(INSTALL) -m755 \ + init \ + $(DESTDIR)$(INSTALLER_DIR)/ + $(INSTALL) -m644 \ + keymaps \ + timezones \ + answerfile.py \ + backend.py \ + common_criteria_firewall_rules \ + constants.py \ + cpiofile.py \ + disktools.py \ + diskutil.py \ + driver.py \ + fcoeutil.py \ + generalui.py \ + hardware.py \ + init_constants.py \ + install.py \ + netinterface.py \ + netutil.py \ + product.py \ + report.py \ + repository.py \ + restore.py \ + scripts.py \ + snackutil.py \ + uicontroller.py \ + upgrade.py \ + util.py \ + xelogging.py \ + $(DESTDIR)$(INSTALLER_DIR)/ + $(INSTALL) -m644 \ + tui/__init__.py \ + tui/init.py \ + tui/fcoe.py \ + tui/network.py \ + tui/progress.py \ + tui/repo.py \ + $(DESTDIR)$(INSTALLER_DIR)/tui/ + $(INSTALL) -m644 \ + tui/installer/__init__.py \ + tui/installer/screens.py \ + $(DESTDIR)$(INSTALLER_DIR)/tui/installer/ + + # Startup files + $(INSTALL) -d \ + $(DESTDIR)/etc/init.d \ + $(DESTDIR)/etc/modprobe.d \ + $(DESTDIR)/etc/modules-load.d \ + $(DESTDIR)/etc/depmod.d \ + $(DESTDIR)/etc/dracut.conf.d \ + $(DESTDIR)/etc/udev/rules.d \ + $(DESTDIR)/etc/systemd/system/systemd-udevd.d + + $(INSTALL) -m755 startup/interface-rename-sideway startup/early-blacklist $(DESTDIR)/etc/init.d/ + $(INSTALL) -m644 startup/functions $(DESTDIR)/etc/init.d/installer-functions + $(INSTALL) -m644 startup/early-blacklist.conf startup/bnx2x.conf $(DESTDIR)/etc/modprobe.d/ + $(INSTALL) -m644 startup/blacklist $(DESTDIR)/etc/modprobe.d/installer-blacklist.conf + $(INSTALL) -m644 startup/modprobe.mlx4 $(DESTDIR)/etc/modprobe.d/mlx4.conf + $(INSTALL) -m644 startup/iscsi-modules $(DESTDIR)/etc/modules-load.d/iscsi.conf + $(INSTALL) -m644 startup/depmod.conf $(DESTDIR)/etc/depmod.d/ + $(INSTALL) -m755 startup/preinit startup/S05ramdisk startup/S06mount $(DESTDIR)/$(INSTALLER_DIR)/ + $(INSTALL) -m644 startup/01-installer.rules $(DESTDIR)/etc/udev/rules.d/ + $(INSTALL) -m644 startup/systemd-udevd_depmod.conf $(DESTDIR)/etc/systemd/system/systemd-udevd.d/installer.conf + + # Generate a multipath configuration from sm's copy, removing + # the blacklist and blacklist_exception sections. + sed 's/\(^[[:space:]]*find_multipaths[[:space:]]*\)yes/\1no/' \ + < $(SM_ROOTDIR)/etc/multipath.xenserver/multipath.conf \ + > $(DESTDIR)/etc/multipath.conf.disabled + + # bootloader files + $(INSTALL) -D -m644 bootloader/grub.cfg $(DESTDIR)$(EFI_DIR)/grub.cfg + $(INSTALL) -D -m644 bootloader/grub.cfg $(DESTDIR)$(EFI_DIR)/grub-usb.cfg + + sed -i '/^set timeout=[0-9]\+$/asearch --file --set /install.img' \ + $(DESTDIR)$(EFI_DIR)/grub-usb.cfg + + $(INSTALL) -D -m644 bootloader/isolinux.cfg $(DESTDIR)/boot/isolinux/isolinux.cfg + + printf "echo Skipping initrd creation in the installer\nexit 0\n" \ + > $(DESTDIR)/etc/dracut.conf.d/installer.conf diff --git a/answerfile.py b/answerfile.py index 490b49ec..2dd5360e 100644 --- a/answerfile.py +++ b/answerfile.py @@ -133,6 +133,7 @@ def parseFreshInstall(self): results['preserve-settings'] = False results['backup-existing-installation'] = False + results.update(self.parseAssembleRaid()) results.update(self.parseDisks()) results.update(self.parseInterface()) results.update(self.parseRootPassword()) @@ -170,6 +171,7 @@ def parseRestore(self): results['install-type'] = INSTALL_TYPE_RESTORE + results.update(self.parseAssembleRaid()) backups = product.findXenSourceBackups() if len(backups) == 0: raise AnswerfileException("Could not locate exsisting backup.") @@ -227,6 +229,7 @@ def parseCommon(self): def parseExistingInstallation(self): results = {} + results.update(self.parseAssembleRaid()) inst = getElementsByTagName(self.top_node, ['existing-installation'], mandatory=True) disk = normalize_disk(getText(inst[0])) @@ -293,6 +296,15 @@ def parseDriverSource(self): results['extra-repos'].append((rtype, address)) return results + def parseAssembleRaid(self): + results = {} + nodes = getElementsByTagName(self.top_node, ['assemble-raid']) + if nodes: + results['assemble-raid'] = True # possibly useless + logger.log("Assembling any RAID volumes") + rv = util.runCmd2([ 'mdadm', '--assemble', "--scan" ]) + return results + def parseDisks(self): results = {} diff --git a/constants.py b/constants.py index 7b414802..1dc3d4eb 100644 --- a/constants.py +++ b/constants.py @@ -181,3 +181,7 @@ def error_string(error, logname, with_hd): 'var/lib/misc/ran-network-init', 'var/lib/misc/ran-storage-init', ] + +# optional features +FEATURES_DIR = "/etc/xensource/features" +HAS_RAID_ASSEMBLE = os.path.exists(os.path.join(FEATURES_DIR, "raid-assemble")) diff --git a/diskutil.py b/diskutil.py index 952977b4..cac8d2a0 100644 --- a/diskutil.py +++ b/diskutil.py @@ -365,9 +365,9 @@ def getMdDeviceName(disk): rv, out = util.runCmd2(['mdadm', '--detail', '--export', disk], with_stdout=True) for line in out.split("\n"): - line = line.strip().split('=', 1) - if line[0] == 'MD_DEVNAME': - return line[1] + key, value = line.strip().split('=', 1) + if key == 'MD_DEVNAME': + return "md%s" % value return disk @@ -392,6 +392,12 @@ def getHumanDiskName(disk): return disk[5:] return disk +def getHumanDiskLabel(disk, short=False): + (vendor, model, size) = getExtendedDiskInfo(disk) + template = "{device} - {size} [{vendor} {model}]" if not short else "{device} - {size}" + return template.format(device=getHumanDiskName(disk), size=getHumanDiskSize(size), + vendor=vendor, model=model) + # given a list of disks, work out which ones are part of volume # groups that will cause a problem if we install XE to those disks: def findProblematicVGs(disks): diff --git a/doc/answerfile.txt b/doc/answerfile.txt index 76221624..a4093b62 100644 --- a/doc/answerfile.txt +++ b/doc/answerfile.txt @@ -129,6 +129,15 @@ Common Elements Discovery on. + ? + + Run `mdadm --assemble --scan` before looking for the device + specified in , , or + . + + Used to be the default behavior. + + (Re)Install Elements -------------------- diff --git a/doc/features.txt b/doc/features.txt new file mode 100644 index 00000000..20fabd43 --- /dev/null +++ b/doc/features.txt @@ -0,0 +1,18 @@ +Features flags +============== + +Some host-installer features are not enabled by default, and +downstream installers can activate them by creating a file in +/etc/xensource/features/ in their installer filesystem. + +Currently available feature flags are: + + raid-assemble + + Detect Linux software-RAID (a.k.a "md") superblocks in disks, adds + a choice for the user to activate software RAID volumes, and do + not offer the user the ability to upgrade or restore a system on a + software-RAID device. + + This only impacts the UI, the answerfile + construct does not need this feature flag. diff --git a/startup/01-installer.rules b/startup/01-installer.rules new file mode 100644 index 00000000..5267d3ef --- /dev/null +++ b/startup/01-installer.rules @@ -0,0 +1,2 @@ +# disable 65-md-incremental.rules by telling an installer is running +ENV{ANACONDA}="1" diff --git a/tui/installer/__init__.py b/tui/installer/__init__.py index 7a8f588f..9e3f46d9 100644 --- a/tui/installer/__init__.py +++ b/tui/installer/__init__.py @@ -124,10 +124,10 @@ def out_of_order_pool_upgrade_fn(answers): Step(uis.hardware_warnings, args=[ram_warning, vt_warning], predicates=[lambda _:(ram_warning or vt_warning)]), + Step(uis.scan_existing), Step(uis.overwrite_warning, predicates=[only_unupgradeable_products]), - Step(uis.get_installation_type, - predicates=[lambda _:len(results['upgradeable-products']) > 0 or len(results['backups']) > 0]), + Step(uis.get_installation_type), Step(uis.upgrade_settings_warning, predicates=[upgrade_but_no_settings_predicate]), Step(uis.ha_master_upgrade, diff --git a/tui/installer/screens.py b/tui/installer/screens.py index e7b52721..4051ba42 100644 --- a/tui/installer/screens.py +++ b/tui/installer/screens.py @@ -111,14 +111,6 @@ def fn10(): lvm.deactivateAll() del lvm - tui.progress.showMessageDialog("Please wait", "Checking for existing products...") - answers['installed-products'] = product.find_installed_products() - answers['upgradeable-products'] = upgrade.filter_for_upgradeable_products(answers['installed-products']) - answers['backups'] = product.findXenSourceBackups() - tui.progress.clearModelessDialog() - - diskutil.log_available_disks() - # CA-41142, ensure we have at least one network interface and one disk before proceeding label = None if len(diskutil.getDiskList()) == 0: @@ -160,6 +152,22 @@ def hardware_warnings(answers, ram_warning, vt_warning): if button == 'back': return LEFT_BACKWARDS return RIGHT_FORWARDS +def scan_existing(answers): + tui.progress.showMessageDialog("Please wait", "Checking for existing products...") + + if 'assemble-raid' in answers: + logger.log("Assembling any RAID volumes") + rv = util.runCmd2([ 'mdadm', '--assemble', "--scan" ]) + + answers['installed-products'] = product.find_installed_products() + answers['upgradeable-products'] = upgrade.filter_for_upgradeable_products(answers['installed-products']) + answers['backups'] = product.findXenSourceBackups() + tui.progress.clearModelessDialog() + + diskutil.log_available_disks() + + return RIGHT_FORWARDS + def overwrite_warning(answers): warning_string = "Continuing will result in a clean installation, all existing configuration will be lost." warning_string += "\n\nAlternatively, please contact a Technical Support Representative for the recommended upgrade path." @@ -216,11 +224,44 @@ def get_admin_interface_configuration(answers): return rc def get_installation_type(answers): - entries = [] + + # If we were not already told to enable RAID, build a full list of + # RAID members, for filtering out from upgradable-products and + # backups, and to decide whether to propose to activate existing RAID. + raid_members = [] + if constants.HAS_RAID_ASSEMBLE and "assemble-raid" not in answers: + for disk in diskutil.getQualifiedDiskList(): + rv, out = util.runCmd2([ 'mdadm', '--examine', disk ], with_stdout=True) + if rv == 0 and re.search("Array UUID :", out): + raid_members.append(disk) + + upgradeable_products = [] for x in answers['upgradeable-products']: - entries.append(("Upgrade %s" % str(x), (x, x.settingsAvailable()))) + if x.primary_disk in raid_members: + logger.log("%s: disk %s in %s, skipping" % (x, x.primary_disk, raid_members)) + continue + upgradeable_products.append(x) + backups = [] for b in answers['backups']: - entries.append(("Restore %s from backup" % str(b), (b, None))) + if not os.path.exists(b.root_disk): + logger.log("%s: disk %s not found, skipping" % (b, b.root_disk)) + continue + if b.root_disk in raid_members: + logger.log("%s: disk %s in %s, skipping" % (b, b.root_disk, raid_members)) + continue + backups.append(b) + + entries = [] + for x in upgradeable_products: + entries.append(("Upgrade %s on %s" % (x, diskutil.getHumanDiskLabel(x.primary_disk, short=True)), + (x, x.settingsAvailable()))) + for b in backups: + entries.append(("Restore %s from backup to %s" % (b, diskutil.getHumanDiskLabel(b.root_disk, short=True)), + (b, None))) + + if raid_members: + logger.log("Found a MD RAID on: %s" % ", ".join(raid_members)) + entries.append(("Assemble software RAID volumes", ("RAID", None))) entries.append( ("Perform clean installation", None) ) @@ -232,12 +273,21 @@ def get_installation_type(answers): else: default = None - if len(answers['upgradeable-products']) > 0: - text = "One or more existing product installations that can be upgraded have been detected." - if len(answers['backups']) > 0: - text += " In addition one or more backups have been detected." + if upgradeable_products or backups: + if upgradeable_products: + text = "One or more existing product installations that can be upgraded have been detected." + if backups: + text += " In addition one or more backups have been detected." + else: + text = "One or more backups have been detected." + if raid_members: + text += " Also, some disks have been identified as members of a sofware-RAID volume." + elif raid_members: + text = "Some disks have been identified as members of a sofware-RAID volume." else: - text = "One or more backups have been detected." + text = "No existing product installation or backup was detected." + if raid_members: + text += " RAID volumes may themselves contain more upgradeable products or backups." text += "\n\nWhat would you like to do?" tui.update_help_line([None, " more info"]) @@ -306,6 +356,10 @@ def more_info(context): elif isinstance(entry[0], product.XenServerBackup): answers['install-type'] = constants.INSTALL_TYPE_RESTORE answers['backup-to-restore'], _ = entry + elif entry[0] == "RAID": + # go rescan for products after assembling RAID volumes + answers['assemble-raid'] = True + return LEFT_BACKWARDS return RIGHT_FORWARDS @@ -556,9 +610,7 @@ def select_primary_disk(answers): (boot, root, state, storage, logs) = diskutil.probeDisk(de) if storage[0]: target_is_sr[de] = True - (vendor, model, size) = diskutil.getExtendedDiskInfo(de) - stringEntry = "%s - %s [%s %s]" % (diskutil.getHumanDiskName(de), diskutil.getHumanDiskSize(size), vendor, model) - e = (stringEntry, de) + e = (diskutil.getHumanDiskLabel(de), de) entries.append(e) # we should have at least one disk @@ -665,9 +717,7 @@ def select_guest_disks(answers): # Make a list of entries: (text, item) entries = [] for de in diskEntries: - (vendor, model, size) = diskutil.getExtendedDiskInfo(de) - entry = "%s - %s [%s %s]" % (diskutil.getHumanDiskName(de), diskutil.getHumanDiskSize(size), vendor, model) - entries.append((entry, de)) + entries.append((diskutil.getHumanDiskLabel(de), de)) text = TextboxReflowed(54, "Which disks would you like to use for %s storage? \n\nOne storage repository will be created that spans the selected disks. You can choose not to prepare any storage if you wish to create an advanced configuration after installation." % BRAND_GUEST) buttons = ButtonBar(tui.screen, [('Ok', 'ok'), ('Back', 'back')])