Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tolerance of pre-existing software-RAID #38

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
12 changes: 9 additions & 3 deletions diskutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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):
Expand Down
18 changes: 18 additions & 0 deletions doc/features.txt
Original file line number Diff line number Diff line change
@@ -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 <assemble-raid/> answerfile
construct does not need this feature flag.
2 changes: 2 additions & 0 deletions startup/01-installer.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# disable 65-md-incremental.rules by telling an installer is running
ENV{ANACONDA}="1"
4 changes: 2 additions & 2 deletions tui/installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
91 changes: 69 additions & 22 deletions tui/installer/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -216,11 +224,41 @@ 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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature flag to enable from presenting the "assemble RAID" option also disables the safety measure of filtering out upgrade/restore entries that the user should not select, as they indeed live on a RAID volume. It is I think a bad idea to allow the user to do this, but then hiding those options without letting the user assemble RAID volumes looks like a bad idea too. I would rather just let the behavior added by this PR be the default, and just not add this feature flag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As XenServer doesn't support installing on software RAID at all, it is better to keep the feature flag to hide the "assemble RAID" option. The button would be too confusing for users in a XenServer context.

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 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) )

Expand All @@ -232,12 +270,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, "<F5> more info"])
Expand Down Expand Up @@ -306,6 +353,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

Expand Down Expand Up @@ -556,9 +607,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
Expand Down Expand Up @@ -665,9 +714,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')])
Expand Down