Skip to content

Commit

Permalink
CP-45747 Allow 4KN-capable SR type to be used
Browse files Browse the repository at this point in the history
The 'lvm' and 'ext' SR types will not work with 4K native disks - or
indeed any where the logical block size is not 512 bytes - but if a
suitable alternative SR type is available then this can be used instead.

* The feature flag file "large-block-capable-sr-type" may now be used to
  specify the name of an SR type that can be used for local storage on
  disks where the logical block size can be something other than 512
  bytes.
* The installer detects logical block size at points in the process
  this is necessary.
* For interactive installs, there are now separate screens for selecting
  which disks to use for local SR storage and determining what SR type
  to use, so that the options for the latter can depend on the former.
* Tweak the disk-probing logic so that it is easy for a new SR type to
  name volume groups such that the installer can detect them.

For non-interactive installs:
* Sanity checking to prevent 'lvm' or 'ext' being used on conjunction
  with disks where they won't work.
* Allow the use of an available 4KN-capable SR type even if the disk
  block size does not make this necessary.
* If the answer file doesn't specify an SR type, try to default to one
  that will work, given the block size.

Signed-off-by: Robin Newton <[email protected]>
  • Loading branch information
Robin Newton authored and liulinC committed Jul 30, 2024
1 parent 22dc7cb commit 11ff548
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 34 deletions.
30 changes: 27 additions & 3 deletions answerfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,33 @@ def parseDisks(self):
results['sr-on-primary'] = results['primary-disk'] in guest_disks
results['guest-disks'] = list(guest_disks)

results['sr-type'] = getMapAttribute(self.top_node, ['sr-type', 'srtype'],
[('lvm', SR_TYPE_LVM),
('ext', SR_TYPE_EXT)], default='lvm')
sr_type_mapping = []
for sr_type in SR_TYPE_LVM, SR_TYPE_EXT, SR_TYPE_LARGE_BLOCK:
if sr_type:
sr_type_mapping.append((sr_type, sr_type))

large_block_disks = [
disk
for disk in guest_disks
if diskutil.isLargeBlockDisk(disk)
]

if SR_TYPE_LARGE_BLOCK and len(large_block_disks) > 0:
default_sr_type = SR_TYPE_LARGE_BLOCK
else:
default_sr_type = SR_TYPE_LVM

sr_type = getMapAttribute(self.top_node,
['sr-type', 'srtype'],
sr_type_mapping,
default=default_sr_type)

if sr_type != SR_TYPE_LARGE_BLOCK and len(large_block_disks) > 0:
raise AnswerfileException("%s not compatible with SR type %s"
% (", ".join(large_block_disks), sr_type))

results['sr-type'] = sr_type

return results

def parseFCoEInterface(self):
Expand Down
8 changes: 2 additions & 6 deletions backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,19 +728,15 @@ def prepareStorageRepositories(mounts, primary_disk, storage_partnum, guest_disk

partitions = getSRPhysDevs(primary_disk, storage_partnum, guest_disks)

sr_type_strings = { constants.SR_TYPE_EXT: 'ext',
constants.SR_TYPE_LVM: 'lvm' }
sr_type_string = sr_type_strings[sr_type]

# write a config file for the prepare-storage firstboot script:

links = [diskutil.idFromPartition(x) or x for x in partitions]
fd = open(os.path.join(mounts['root'], constants.FIRSTBOOT_DATA_DIR, 'default-storage.conf'), 'w')
print("XSPARTITIONS='%s'" % str.join(" ", links), file=fd)
print("XSTYPE='%s'" % sr_type_string, file=fd)
print("XSTYPE='%s'" % sr_type, file=fd)
# Legacy names
print("PARTITIONS='%s'" % str.join(" ", links), file=fd)
print("TYPE='%s'" % sr_type_string, file=fd)
print("TYPE='%s'" % sr_type, file=fd)
fd.close()

def make_free_space(mount, required):
Expand Down
9 changes: 9 additions & 0 deletions constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# sr types:
SR_TYPE_LVM = "lvm"
SR_TYPE_EXT = "ext"
# See also SR_TYPE_LARGE_BLOCK at the bottom of this file

# partition schemes:
PARTITION_DOS = "DOS"
Expand Down Expand Up @@ -183,6 +184,14 @@ def error_string(error, logname, with_hd):
# optional features
FEATURES_DIR = "/etc/xensource/features"
HAS_SUPPLEMENTAL_PACKS = os.path.exists(os.path.join(FEATURES_DIR, "supplemental-packs"))
SR_TYPE_LARGE_BLOCK = None
try:
with open(os.path.join(FEATURES_DIR, "large-block-capable-sr-type")) as f:
value = f.read().strip()
if value:
SR_TYPE_LARGE_BLOCK = value
except IOError:
pass

# Error partitioning disk as in use
PARTITIONING_ERROR = \
Expand Down
8 changes: 5 additions & 3 deletions disktools.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ class LVMTool:
# Volume group prefixes
VG_SWAP_PREFIX = 'VG_XenSwap'
VG_CONFIG_PREFIX = 'VG_XenConfig'
# Prefix used by the SR type 'lvm'
VG_SR_PREFIX = 'VG_XenStorage'
VG_EXT_SR_PREFIX = 'XSLocalEXT'
# Prefix for non-'lvm' local storage SR types
VG_OTHER_SR_PREFIX = 'XSLocal'

PVMOVE = ['pvmove']
LVCHANGE = ['lvchange']
Expand Down Expand Up @@ -348,7 +350,7 @@ def swapPartition(self, devicePrefix):
def srPartition(self, devicePrefix):
retVal = self.testPartition(devicePrefix, self.VG_SR_PREFIX)
if retVal is None:
retVal = self.testPartition(devicePrefix, self.VG_EXT_SR_PREFIX)
retVal = self.testPartition(devicePrefix, self.VG_OTHER_SR_PREFIX)
return retVal

def isPartitionConfig(self, device):
Expand All @@ -364,7 +366,7 @@ def isPartitionSwap(self, device):
def isPartitionSR(self, device):
pv = self.deviceToPVOrNone(device)
return pv is not None and (pv['vg_name'].startswith(self.VG_SR_PREFIX) or \
pv['vg_name'].startswith(self.VG_EXT_SR_PREFIX))
pv['vg_name'].startswith(self.VG_OTHER_SR_PREFIX))

def deleteDevice(self, device):
"""Deletes PVs, VGs and LVs associated with a device (partition)"""
Expand Down
50 changes: 41 additions & 9 deletions diskutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,17 @@ def getDiskDeviceSize(dev):
elif os.path.exists("/sys/block/%s/size" % dev):
return int(__readOneLineFile__("/sys/block/%s/size" % dev))

def getDiskBlockSize(dev):
if not dev.startswith("/dev/"):
dev = '/dev/' + dev
if isDeviceMapperNode(dev):
return getDiskBlockSize(getDeviceSlaves(dev)[0])
if dev.startswith("/dev/"):
dev = re.match("/dev/(.*)", dev).group(1)
dev = dev.replace("/", "!")
return int(__readOneLineFile__("/sys/block/%s/queue/logical_block_size"
% dev))

def getDiskSerialNumber(dev):
# For Multipath nodes return info about 1st slave
if not dev.startswith("/dev/"):
Expand Down Expand Up @@ -334,18 +345,38 @@ def isRemovable(path):
else:
return False

def isLargeBlockDisk(dev):
"""
Determines whether a disk's logical block size is larger than 512 bytes
(e.g. 4KB) with the consequence that "lvm" and "ext" SR types will not
be able to make use of it.
"""
return getDiskBlockSize(dev) > 512

def blockSizeToGBSize(blocks):
return (int(blocks) * 512) // (1024 * 1024 * 1024)

def blockSizeToMBSize(blocks):
return (int(blocks) * 512) // (1024 * 1024)

def getHumanDiskSize(blocks):
gb = blockSizeToGBSize(blocks)
def blockSizeToBytes(blocks):
return int(blocks) * 512

def bytesToHuman(num_bytes):
kb = num_bytes // 1024
mb = kb // 1024
gb = mb // 1024

if gb > 0:
return "%d GB" % gb
else:
return "%d MB" % blockSizeToMBSize(blocks)
if mb > 0:
return "%d MB" % mb
if kb > 0:
return "%d KB" % kb
return "%d bytes" % num_bytes

def getHumanDiskSize(blocks):
return bytesToHuman(blockSizeToBytes(blocks))

def getExtendedDiskInfo(disk, inMb=0):
return (getDiskDeviceVendor(disk), getDiskDeviceModel(disk),
Expand Down Expand Up @@ -446,6 +477,8 @@ def log_available_disks():
diskSizes = [getDiskDeviceSize(x) for x in disks]
diskSizesGB = [blockSizeToGBSize(x) for x in diskSizes]
logger.log("Disk sizes: %s" % str(diskSizesGB))
logger.log("Disk block sizes: %s" % [getDiskBlockSize(x)
for x in disks])

dom0disks = [x for x in diskSizesGB if constants.min_primary_disk_size <= x]
if len(dom0disks) == 0:
Expand All @@ -463,7 +496,7 @@ def __init__(self, device):

INSTALL_RETAIL = 1
STORAGE_LVM = 1
STORAGE_EXT3 = 2
STORAGE_OTHER = 2

def probeDisk(device):
"""Examines device and reports the apparent presence of a XenServer installation and/or related usage
Expand All @@ -474,7 +507,7 @@ def probeDisk(device):
boot is a tuple of True or False and the partition device
root is a tuple of None or INSTALL_RETAIL and the partition device
state is a tuple of True or False and the partition device
storage is a tuple of None, STORAGE_LVM or STORAGE_EXT3 and the partition device
storage is a tuple of None, STORAGE_LVM or STORAGE_OTHER and the partition device
logs is a tuple of True or False and the partition device
swap is a tuple of True or False and the partition device
"""
Expand Down Expand Up @@ -519,9 +552,8 @@ def probeDisk(device):
disk.state = (True, part_device)
elif lv_tool.isPartitionSR(part_device):
pv = lv_tool.deviceToPVOrNone(part_device)
if pv is not None and pv['vg_name'].startswith(lv_tool.VG_EXT_SR_PREFIX):
# odd 'ext3 in an LV' SR
disk.storage = (STORAGE_EXT3, part_device)
if pv is not None and pv['vg_name'].startswith(lv_tool.VG_OTHER_SR_PREFIX):
disk.storage = (STORAGE_OTHER, part_device)
else:
disk.storage = (STORAGE_LVM, part_device)

Expand Down
15 changes: 12 additions & 3 deletions doc/answerfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,12 +296,21 @@ Format of 'source' and 'driver-source'
(Re)Install Attributes
----------------------

<installation sr-type="lvm|ext"?>
<installation srtype="lvm|ext"?>[D]
<installation sr-type="srtype"?>
<installation srtype="srtype"?>[D]

where srtype is one of

lvm
ext
the SR type specified in the large-block-capable-sr-type feature file
(if present)

Local SR type.

Default: lvm
Default: lvm if the disks included in local SR storage all have 512 byte
logical blocks, otherwise (when available) the SR type from
the large-block-capable-sr-type feature


Upgrade Elements
Expand Down
13 changes: 13 additions & 0 deletions doc/features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,16 @@ Currently available feature flags are:

This only impacts the UI, the <source> answerfile construct still
allows to include supplemental packs without this feature flag.

large-block-capable-sr-type

Allow the use of an SR type for local storage that (unlike "lvm" and
"ext") is not restricted to using disks with 512 byte blocks. The flag
file's content must be the name of the SR type.

The other expectations for such an SR type are:
- Its only non-optional creation parameter (as with "lvm" and "ext") is
"device", a comma-separated list devices to use.
- It should use an identifying prefix for the names of volume groups that
it creates, and this prefix should start "XSLocal". (e.g. if the SR type
were called "foo", it might use the prefix "XSLocalFOO".)
6 changes: 6 additions & 0 deletions tui/installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def out_of_order_pool_upgrade_fn(answers):

return ret

def has_guest_disks_fn(answers):
guest_disks = answers.get('guest-disks')
return guest_disks is not None and len(guest_disks) > 0

if 'install-type' not in results:
results['install-type'] = constants.INSTALL_TYPE_FRESH
results['preserve-settings'] = False
Expand Down Expand Up @@ -152,6 +156,8 @@ def out_of_order_pool_upgrade_fn(answers):
predicates=[is_reinstall_fn, requires_repartition]),
Step(uis.select_guest_disks,
predicates=[is_clean_install_fn]),
Step(uis.get_sr_type,
predicates=[is_clean_install_fn, has_guest_disks_fn]),
Step(uis.confirm_erase_volume_groups,
predicates=[is_clean_install_fn]),
Step(tui.repo.select_repo_source,
Expand Down
57 changes: 47 additions & 10 deletions tui/installer/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ def disk_more_info(context):
("Vendor:", diskutil.getDiskDeviceVendor(context)),
("Model:", diskutil.getDiskDeviceModel(context)),
("Serial:", diskutil.getDiskSerialNumber(context)),
("Block size:", diskutil.bytesToHuman(diskutil.getDiskBlockSize(context))),
("Size:", diskutil.getHumanDiskSize(diskutil.getDiskDeviceSize(context))),
("Current usage:", usage))
tui.screen.popHelpLine()
Expand Down Expand Up @@ -656,9 +657,6 @@ def select_guest_disks(answers):
currently_selected = answers['guest-disks']
else:
currently_selected = answers['primary-disk']
srtype = constants.SR_TYPE_LVM
if 'sr-type' in answers:
srtype = answers['sr-type']

# Make a list of entries: (text, item)
entries = []
Expand All @@ -671,15 +669,10 @@ def select_guest_disks(answers):
cbt = CheckboxTree(3, scroll)
for (c_text, c_item) in entries:
cbt.append(c_text, c_item, c_item in currently_selected)
txt = "Enable thin provisioning"
if len(BRAND_VDI) > 0:
txt += " (Optimized storage for %s)" % BRAND_VDI
tb = Checkbox(txt, srtype == constants.SR_TYPE_EXT and 1 or 0)

gf = GridFormHelp(tui.screen, 'Virtual Machine Storage', 'guestdisk:info', 1, 4)
gf = GridFormHelp(tui.screen, 'Virtual Machine Storage', 'guestdisk:info1', 1, 4)
gf.add(text, 0, 0, padding=(0, 0, 0, 1))
gf.add(cbt, 0, 1, padding=(0, 0, 0, 1))
gf.add(tb, 0, 2, padding=(0, 0, 0, 1))
gf.add(buttons, 0, 3, growx=1)
gf.addHotKey('F5')

Expand All @@ -700,7 +693,6 @@ def select_guest_disks(answers):
if button == 'back': return LEFT_BACKWARDS

answers['guest-disks'] = cbt.getSelection()
answers['sr-type'] = tb.selected() and constants.SR_TYPE_EXT or constants.SR_TYPE_LVM
answers['sr-on-primary'] = answers['primary-disk'] in answers['guest-disks']

# if the user select no disks for guest storage, check this is what
Expand All @@ -718,6 +710,51 @@ def select_guest_disks(answers):

return RIGHT_FORWARDS

def get_sr_type(answers):
guest_disks = answers['guest-disks']
assert guest_disks is not None

need_large_block_sr_type = any(diskutil.isLargeBlockDisk(disk)
for disk in guest_disks)

if not need_large_block_sr_type:
srtype = answers.get('sr-type', constants.SR_TYPE_LVM)
txt = "Enable thin provisioning"
if len(BRAND_VDI) > 0:
txt += " (Optimized storage for %s)" % BRAND_VDI
tb = Checkbox(txt, srtype == constants.SR_TYPE_EXT and 1 or 0)
content = tb
get_type = lambda: tb.selected() and constants.SR_TYPE_EXT or constants.SR_TYPE_LVM
buttons = ButtonBar(tui.screen, [('Ok', 'ok'), ('Back', 'back')])
elif constants.SR_TYPE_LARGE_BLOCK:
content = TextboxReflowed(40,
"%s storage will be configured for"
" large disk block size."
% BRAND_GUEST)
get_type = lambda: constants.SR_TYPE_LARGE_BLOCK
buttons = ButtonBar(tui.screen, [('Ok', 'ok'), ('Back', 'back')])
else:
content = TextboxReflowed(54,
"Only disks with a block size of 512 bytes"
" can be use for %s storage. Please"
" unselect any disks where the block size"
" is different from this."
% BRAND_GUEST)
buttons = ButtonBar(tui.screen, [('Back', 'back')])
get_type = None

gf = GridFormHelp(tui.screen, 'Virtual Machine Storage Type', 'guestdisk:info2', 1, 4)
gf.add(content, 0, 0, padding=(0, 0, 0, 1))
gf.add(buttons, 0, 2, growx=1)
button = buttons.buttonPressed(gf.runOnce())

if button == 'back': return LEFT_BACKWARDS

if get_type:
answers['sr-type'] = get_type()

return RIGHT_FORWARDS

def confirm_installation(answers):
if answers['install-type'] == constants.INSTALL_TYPE_RESTORE:
backup = answers['backup-to-restore']
Expand Down

0 comments on commit 11ff548

Please sign in to comment.