-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #573 from mwhudson/raid-size-calculation
More accurate estimation of the size of a RAID
- Loading branch information
Showing
3 changed files
with
243 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
#!/usr/bin/python3 | ||
|
||
# The fine details of how big a RAID device ends up as a function of the sizes | ||
# of its components is somewhat hairier than one might think, with a certain | ||
# fraction of each component device being given over to metadata storage. This | ||
# script tests the estimates subiquity uses against reality by creating actual | ||
# raid devices (backed by sparse files in a tmpfs) and comparing their sizes | ||
# with the estimates. It must be run as root. | ||
|
||
import os | ||
import random | ||
import subprocess | ||
import sys | ||
import tempfile | ||
import uuid | ||
|
||
|
||
from subiquity.models.filesystem import ( | ||
align_down, | ||
dehumanize_size, | ||
get_raid_size, | ||
humanize_size, | ||
raidlevels, | ||
) | ||
from subiquity.models.tests.test_filesystem import ( | ||
FakeDev, | ||
) | ||
|
||
|
||
tmpdir = tempfile.mkdtemp() | ||
|
||
def run(cmd): | ||
try: | ||
subprocess.run( | ||
cmd, check=True, | ||
stdout=subprocess.PIPE, stdin=subprocess.DEVNULL, | ||
stderr=subprocess.STDOUT) | ||
except subprocess.CalledProcessError as e: | ||
print(e.stdout) | ||
raise | ||
|
||
raids = [] | ||
loopdevs = [] | ||
|
||
def cleanraids(): | ||
for raid in raids: | ||
run(['mdadm', '--verbose', '--stop', raid]) | ||
del raids[:] | ||
|
||
def cleanloops(): | ||
for loopdev in loopdevs: | ||
subprocess.run( | ||
['losetup', '-d', loopdev]) | ||
del loopdevs[:] | ||
|
||
def cleanup(): | ||
cleanraids() | ||
cleanloops() | ||
|
||
|
||
def create_devices_for_sizes(sizes): | ||
devs = [] | ||
for size in sizes: | ||
fd, name = tempfile.mkstemp(dir=tmpdir) | ||
os.ftruncate(fd, size) | ||
os.close(fd) | ||
dev = subprocess.run( | ||
['losetup', '-f', '--show', name], | ||
stdout=subprocess.PIPE, encoding='ascii').stdout.strip() | ||
devs.append(dev) | ||
loopdevs.append(dev) | ||
return devs | ||
|
||
|
||
def create_raid(level, images): | ||
name = '/dev/md/test-{}'.format(uuid.uuid4()) | ||
cmd = [ | ||
'mdadm', | ||
'--verbose', | ||
'--create', | ||
'--metadata', 'default', | ||
'--level', level, | ||
'--run', | ||
'-n', str(len(images)), | ||
'--assume-clean', | ||
name, | ||
] + images | ||
run(cmd) | ||
raids.append(name) | ||
return name | ||
|
||
|
||
def get_real_raid_size(raid): | ||
return int(subprocess.run( | ||
['blockdev', '--getsize64', raid], | ||
stdout=subprocess.PIPE, encoding='ascii').stdout.strip()) | ||
|
||
|
||
def verify_size_ok(level, sizes): | ||
r = False | ||
try: | ||
devs = create_devices_for_sizes(sizes) | ||
raid = create_raid(level, devs) | ||
devs = [FakeDev(size) for size in sizes] | ||
calc_size = get_raid_size(level, devs) | ||
real_size = get_real_raid_size(raid) | ||
if len(set(sizes)) == 1: | ||
sz = '[{}]*{}'.format(humanize_size(sizes[0]), len(sizes)) | ||
else: | ||
sz = str([humanize_size(s) for s in sizes]) | ||
print("level {} sizes {} -> calc_size {} real_size {}".format( | ||
level, sz , calc_size, real_size), end=' ') | ||
if calc_size > real_size: | ||
print("BAAAAAAAAAAAD", real_size - calc_size) | ||
if os.environ.get('DEBUG'): | ||
print(raid) | ||
input('waiting: ') | ||
elif calc_size == real_size: | ||
print("exactly right!") | ||
r = True | ||
else: | ||
print("subiquity wasted space", real_size - calc_size) | ||
r = True | ||
finally: | ||
cleanup() | ||
return r | ||
|
||
|
||
fails = 0 | ||
run(['mount', '-t', 'tmpfs', 'tmpfs', tmpdir]) | ||
try: | ||
for size in '1G', '10G', '100G', '1T', '10T': | ||
size = dehumanize_size(size) | ||
for level in raidlevels: | ||
for count in range(2, 10): | ||
if count >= level.min_devices: | ||
if not verify_size_ok(level.value, [size]*count): | ||
fails += 1 | ||
if not verify_size_ok(level.value, [align_down(random.randrange(size, 10*size))]*count): | ||
fails += 1 | ||
sizes = [align_down(random.randrange(size, 10*size)) for _ in range(count)] | ||
if not verify_size_ok(level.value, sizes): | ||
fails += 1 | ||
finally: | ||
run(['umount', '-l', tmpdir]) | ||
|
||
if fails > 0: | ||
print("{} fails".format(fails)) | ||
sys.exit(1) | ||
else: | ||
print("all ok!!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters