Skip to content

Commit edcc28a

Browse files
committed
Allow image size specification using any SI or IEC unit
Currently, oz only allows image sizes to be specified as integer amounts of gibibytes or tebibytes (that's IEC power-of-two units). This is unfortunately inflexible. Consider that storage media are typically specified in SI power-of-ten sizes, so a USB stick may be 16 gigabytes (SI power-of-ten GB) in size - that's 16,000,000,000 bytes. Let's say we want to build an image that will fit on such a USB stick. oz will only allow us to build an image of 15,032,385,536 bytes (14 gibibytes) or 16,106,127,360 bytes (15 gibibytes). So we're either slightly too big, or leaving nearly a gigabyte on the table. This allows the image size to be specified in the TDL with most any IEC or SI unit suffix, from B (bytes) all the way up to YiB (yobibytes). A size with no suffix or the suffix "G" is taken as gibibytes and a size with the suffix "T" is taken as tebibytes, as before, but other ambiguous suffixes are not accepted. All casing is accepted. Behind the scenes, we convert the size to bytes and specify it that way in the libvirt XML when creating the image in _internal_generate_diskimage. This does change the interface of generate_diskimage(), by making the unit for the size argument bytes instead of gibibytes. I can't see a clean way to avoid this while allowing flexibility. I have checked, and AFAICT, among active projects, only oz itself and the ImageFactory TinMan plugin call this function. The TinMan plugin will need a trivial change to its fallback default value. Signed-off-by: Adam Williamson <[email protected]>
1 parent d0e1d9a commit edcc28a

11 files changed

+116
-31
lines changed

oz/Fedora.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,11 @@ def _modify_iso(self):
298298
initrdline += " inst.nosave=output_ks"
299299
self._modify_isolinux(initrdline)
300300

301-
def generate_diskimage(self, size=10, force=False):
301+
def generate_diskimage(self, size=10*1024*1024*1024, force=False):
302302
"""
303303
Method to generate a diskimage. By default, a blank diskimage of
304-
10GB will be created; the caller can override this with the size
305-
parameter, specified in GB. If force is False (the default), then
304+
10 GiB will be created; the caller can override this with the size
305+
parameter, specified in bytes. If force is False (the default), then
306306
a diskimage will not be created if a cached JEOS is found. If
307307
force is True, a diskimage will be created regardless of whether a
308308
cached JEOS exists. See the oz-install man page for more

oz/Guest.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ def _generate_xml(self, bootdev, installdev, kernel=None, initrd=None,
571571

572572
return xml
573573

574-
def _internal_generate_diskimage(self, size=10, force=False,
574+
def _internal_generate_diskimage(self, size=10*1024*1024*1024, force=False,
575575
create_partition=False,
576576
image_filename=None,
577577
backing_filename=None):
@@ -587,7 +587,7 @@ def _internal_generate_diskimage(self, size=10, force=False,
587587
# we'll copy the JEOS itself later on
588588
return
589589

590-
self.log.info("Generating %dGB diskimage for %s", size, self.tdl.name)
590+
self.log.info("Generating %s diskimage for %s", oz.ozutil.sizeof_fmt(int(size)), self.tdl.name)
591591

592592
diskimage = self.diskimage
593593
if image_filename:
@@ -628,17 +628,17 @@ def _internal_generate_diskimage(self, size=10, force=False,
628628
# allows creation without an explicit capacity element.
629629
qcow_size = oz.ozutil.check_qcow_size(backing_filename)
630630
if qcow_size:
631-
capacity = qcow_size / 1024 / 1024 / 1024
631+
capacity = qcow_size
632632
backing_format = 'qcow2'
633633
else:
634-
capacity = os.path.getsize(backing_filename) / 1024 / 1024 / 1024
634+
capacity = os.path.getsize(backing_filename)
635635
backing_format = 'raw'
636636
backing = oz.ozutil.lxml_subelement(vol, "backingStore")
637637
oz.ozutil.lxml_subelement(backing, "path", backing_filename)
638638
oz.ozutil.lxml_subelement(backing, "format", None,
639639
{"type": backing_format})
640640

641-
oz.ozutil.lxml_subelement(vol, "capacity", str(int(capacity)), {'unit': 'G'})
641+
oz.ozutil.lxml_subelement(vol, "capacity", str(int(capacity)), {'unit': 'B'})
642642
vol_xml = lxml.etree.tostring(vol, pretty_print=True, encoding="unicode")
643643

644644
# sigh. Yes, this is racy; if a pool is defined during this loop, we
@@ -718,11 +718,11 @@ def _vol_create_cb(args):
718718
g_handle.create_msdos_partition_table()
719719
g_handle.cleanup()
720720

721-
def generate_diskimage(self, size=10, force=False):
721+
def generate_diskimage(self, size=10*1024*1024*1024, force=False):
722722
"""
723723
Method to generate a diskimage. By default, a blank diskimage of
724-
10GB will be created; the caller can override this with the size
725-
parameter, specified in GB. If force is False (the default), then
724+
10 GiB will be created; the caller can override this with the size
725+
parameter, specified in bytes. If force is False (the default), then
726726
a diskimage will not be created if a cached JEOS is found. If
727727
force is True, a diskimage will be created regardless of whether a
728728
cached JEOS exists. See the oz-install man page for more

oz/TDL.py

+33-14
Original file line numberDiff line numberDiff line change
@@ -327,20 +327,39 @@ def _parse_disksize(self):
327327
# a sensible default
328328
return None
329329

330-
match = re.match(r'([0-9]*) *([GT]?)$', size)
331-
if not match or len(match.groups()) != 2:
332-
raise oz.OzException.OzException("Invalid disk size; it must be specified as a size in gigabytes, optionally suffixed with 'G' or 'T'")
333-
334-
number = match.group(1)
335-
suffix = match.group(2)
336-
337-
if not suffix or suffix == 'G':
338-
# for backwards compatibility, we assume G when there is no suffix
339-
size = number
340-
elif suffix == 'T':
341-
size = str(int(number) * 1024)
342-
343-
return size
330+
# drop spaces and downcase
331+
size = size.replace(" ", "").lower()
332+
# isolate digits
333+
number = ""
334+
suffix = ""
335+
for (idx, char) in enumerate(size):
336+
if char.isdigit():
337+
number += char
338+
else:
339+
suffix = size[idx:]
340+
break
341+
342+
if not suffix:
343+
# for backwards compatibility, we assume GiB when there is no suffix
344+
suffix = "gib"
345+
346+
# also for backwards compatibility with an earlier attempt to support
347+
# suffixes, treat "T" and "G" as "TiB" and "GiB"
348+
units = {"b": 1, "g": 2**30, "t": 2**40}
349+
tenscale = 3
350+
twoscale = 10
351+
for (i, pref) in enumerate(("k", "m", "g", "t", "p", "e", "z", "y"), start=1):
352+
# this is giving us {"gib": 2 ** 30, "gb": 10 ** 9}, etc
353+
units[pref + "b"] = (10 ** (i*tenscale))
354+
units[pref + "ib"] = (2 ** (i*twoscale))
355+
356+
factor = units.get(suffix)
357+
if not number or not factor:
358+
raise oz.OzException.OzException(
359+
"Invalid disk size; it must be specified as an integer size with an optional SI or IEC unit suffix, e.g. '10TB' or '16GiB'"
360+
)
361+
362+
return str(int(number) * factor)
344363

345364
def _parse_commands(self, xpath):
346365
"""

oz/Windows.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ def _generate_new_iso(self):
7878
self.iso_contents],
7979
printfn=self.log.debug)
8080

81-
def generate_diskimage(self, size=10, force=False):
81+
def generate_diskimage(self, size=10*1024*1024*1024, force=False):
8282
"""
8383
Method to generate a diskimage. By default, a blank diskimage of
84-
10GB will be created; the caller can override this with the size
85-
parameter, specified in GB. If force is False (the default), then
86-
a diskimage will not be created if a cached JEOS is found. If
84+
10 GiB will be created; the caller can override this with the size
85+
parameter, specified in bytes. If force is False (the default),
86+
then a diskimage will not be created if a cached JEOS is found. If
8787
force is True, a diskimage will be created regardless of whether a
8888
cached JEOS exists. See the oz-install man page for more
8989
information about JEOS caching.

oz/ozutil.py

+17
Original file line numberDiff line numberDiff line change
@@ -1178,3 +1178,20 @@ def get_free_port():
11781178
sock.close()
11791179

11801180
return listen_port
1181+
1182+
1183+
def sizeof_fmt(num, suffix="B"):
1184+
"""
1185+
Give a convenient human-readable representation of a large size in
1186+
bytes. Initially by Fred Cirera:
1187+
https://web.archive.org/web/20111010015624/http://blogmag.net/blog/read/38/Print_human_readable_file_size
1188+
edited by multiple contributors at:
1189+
https://stackoverflow.com/questions/1094841
1190+
Per Richard Fontana this is too trivial to be copyrightable, so
1191+
there are no licensing concerns
1192+
"""
1193+
for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"):
1194+
if abs(num) < 1024.0:
1195+
return "%3.1f%s%s" % (num, unit, suffix)
1196+
num /= 1024.0
1197+
return "%.1f%s%s" % (num, 'Yi', suffix)

tests/guest/test_guest.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,8 @@ def test_geteltorito_bogus_bootp(tmpdir):
352352
def test_init_guest():
353353
guest = setup_guest(tdlxml2)
354354

355-
assert guest.disksize == 20
355+
# size without units is taken to be GiB
356+
assert guest.disksize == 20*(2**30)
356357
assert guest.image_name() == 'tester'
357358
assert guest.output_image_path() in (
358359
# user's image storage
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<name>help</name>
3+
<os>
4+
<name>Fedora</name>
5+
<version>12</version>
6+
<arch>i386</arch>
7+
<install type='url'>
8+
<url>http://download.fedoraproject.org/pub/fedora/linux/releases/12/Fedora/x86_64/os/</url>
9+
</install>
10+
</os>
11+
<description>My Fedora 12 JEOS image</description>
12+
<disk>
13+
<size>2EiB</size>
14+
</disk>
15+
</template>
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<name>help</name>
3+
<os>
4+
<name>Fedora</name>
5+
<version>12</version>
6+
<arch>i386</arch>
7+
<install type='url'>
8+
<url>http://download.fedoraproject.org/pub/fedora/linux/releases/12/Fedora/x86_64/os/</url>
9+
</install>
10+
</os>
11+
<description>My Fedora 12 JEOS image</description>
12+
<disk>
13+
<size>10ZB</size>
14+
</disk>
15+
</template>

tests/tdl/test-65-disk-size-byte.tdl

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<template>
2+
<name>help</name>
3+
<os>
4+
<name>Fedora</name>
5+
<version>12</version>
6+
<arch>i386</arch>
7+
<install type='url'>
8+
<url>http://download.fedoraproject.org/pub/fedora/linux/releases/12/Fedora/x86_64/os/</url>
9+
</install>
10+
</os>
11+
<description>My Fedora 12 JEOS image</description>
12+
<disk>
13+
<size>10000000 B</size>
14+
</disk>
15+
</template>

tests/tdl/test_tdl.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,11 @@
9292
"test-55-files-http-url.tdl": True,
9393
"test-56-invalid-disk-size.tdl": False,
9494
"test-57-invalid-disk-size.tdl": False,
95-
"test-58-disk-size-terabyte.tdl": True,
95+
"test-58-disk-size-tebibyte-compat.tdl": True,
9696
"test-59-command-sorting.tdl": True,
97+
"test-63-disk-size-exbibyte.tdl": True,
98+
"test-64-disk-size-zettabyte.tdl": True,
99+
"test-65-disk-size-byte.tdl": True,
97100
}
98101

99102
# Test that iterates over all .tdl files

0 commit comments

Comments
 (0)