Skip to content

Commit 9215ac5

Browse files
authored
Merge pull request #2657 from OSInside/volume_quotas
Add quota attribute to volume section
2 parents fb32551 + 8a087f8 commit 9215ac5

File tree

10 files changed

+132
-5
lines changed

10 files changed

+132
-5
lines changed

build-tests/x86/tumbleweed/test-image-disk/appliance.kiwi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<oem-multipath-scan>false</oem-multipath-scan>
2525
</oemconfig>
2626
<systemdisk>
27-
<volume name="home"/>
27+
<volume name="home" quota="5G"/>
2828
</systemdisk>
2929
</type>
3030
</preferences>

doc/source/working_with_images/custom_volumes.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ elements of the `systemdisk` element:
2525
<volume name="usr/lib" size="1G" label="library"/>
2626
<volume name="@root" freespace="500M"/>
2727
<volume name="etc_volume" mountpoint="etc" copy_on_write="false"/>
28-
<volume name="bin_volume" size="all" mountpoint="/usr/bin"/>
28+
<volume name="bin_volume" size="all" mountpoint="/usr/bin" quota="2G"/>
2929
</systemdisk>
3030
</type>
3131
</image>
@@ -73,6 +73,9 @@ attributes:
7373
- `copy_on_write`: Optional attribute to set the filesystem copy-on-write
7474
attribute for this volume.
7575

76+
- `quota`: Optional attribute for the `btrfs` filesystem only. Allows
77+
to specify a quota size for the generated volume.
78+
7679
- `filesystem_check`: Optional attribute to indicate that this
7780
filesystem should perform the validation to become filesystem checked.
7881
The actual constraints if the check is performed or not depends on

kiwi/schema/kiwi.rnc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,18 @@ div {
26052605
# common element <volume>
26062606
#
26072607
div {
2608+
sch:pattern [
2609+
abstract = "true"
2610+
id = "btrfs_quota"
2611+
sch:rule [
2612+
context = "volume"
2613+
sch:assert [
2614+
test = "not(@quota) or ../../@filesystem='btrfs'"
2615+
"quota attribute is only available for the following "
2616+
"image filesystem: btrfs"
2617+
]
2618+
]
2619+
]
26082620
k.volume.freespace.attribute =
26092621
## free space to be added to this volume. The value is
26102622
## used as MB by default but you can add "M" and/or "G" as
@@ -2635,6 +2647,10 @@ div {
26352647
k.volume.copy_on_write.attribute =
26362648
## Apply the filesystem copy-on-write attribute for this volume
26372649
attribute copy_on_write { xsd:boolean }
2650+
k.volume.quota.attribute =
2651+
## Apply quota value to filesystem volume if supported
2652+
attribute quota { partition-size-type }
2653+
>> sch:pattern [ id = "quota" is-a = "btrfs_quota" ]
26382654
k.volume.filesystem_check =
26392655
## Indicate that this filesystem should perform the validation
26402656
## to become filesystem checked. The actual constraints if the
@@ -2650,6 +2666,7 @@ div {
26502666
k.volume.arch.attribute = k.arch.attribute
26512667
k.volume.attlist =
26522668
k.volume.copy_on_write.attribute? &
2669+
k.volume.quota.attribute? &
26532670
k.volume.filesystem_check? &
26542671
k.volume.freespace.attribute? &
26552672
k.volume.mountpoint.attribute? &

kiwi/schema/kiwi.rng

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3933,6 +3933,11 @@ Allowed values are: t.linux</a:documentation>
39333933
39343934
-->
39353935
<div>
3936+
<sch:pattern abstract="true" id="btrfs_quota">
3937+
<sch:rule context="volume">
3938+
<sch:assert test="not(@quota) or ../../@filesystem='btrfs'">quota attribute is only available for the following image filesystem: btrfs</sch:assert>
3939+
</sch:rule>
3940+
</sch:pattern>
39363941
<define name="k.volume.freespace.attribute">
39373942
<attribute name="freespace">
39383943
<a:documentation>free space to be added to this volume. The value is
@@ -3978,6 +3983,13 @@ add "M" and/or "G" as postfix</a:documentation>
39783983
<data type="boolean"/>
39793984
</attribute>
39803985
</define>
3986+
<define name="k.volume.quota.attribute">
3987+
<attribute name="quota">
3988+
<a:documentation>Apply quota value to filesystem volume if supported</a:documentation>
3989+
<ref name="partition-size-type"/>
3990+
</attribute>
3991+
<sch:pattern id="quota" is-a="btrfs_quota"/>
3992+
</define>
39813993
<define name="k.volume.filesystem_check">
39823994
<attribute name="filesystem_check">
39833995
<a:documentation>Indicate that this filesystem should perform the validation
@@ -4003,6 +4015,9 @@ The latter is the default.</a:documentation>
40034015
<optional>
40044016
<ref name="k.volume.copy_on_write.attribute"/>
40054017
</optional>
4018+
<optional>
4019+
<ref name="k.volume.quota.attribute"/>
4020+
</optional>
40064021
<optional>
40074022
<ref name="k.volume.filesystem_check"/>
40084023
</optional>

kiwi/volume_manager/btrfs.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,10 @@ def create_volumes(self, filesystem_name):
224224
os.path.normpath(toplevel + os.sep + volume.realpath)
225225
]
226226
)
227+
self._apply_quota(
228+
os.path.normpath(toplevel + os.sep + volume.realpath),
229+
volume.attributes
230+
)
227231
self.apply_attributes_on_volume(
228232
toplevel, volume
229233
)
@@ -439,6 +443,17 @@ def set_property_readonly_root(self):
439443
['btrfs', 'property', 'set', sync_target, 'ro', 'true']
440444
)
441445

446+
def _apply_quota(self, volume_path: str, attributes: List[str]):
447+
for attribute in attributes:
448+
if attribute.startswith('quota='):
449+
quota = attribute.split('=')[1]
450+
Command.run(
451+
['btrfs', 'quota', 'enable', '--simple', volume_path]
452+
)
453+
Command.run(
454+
['btrfs', 'qgroup', 'limit', quota, volume_path]
455+
)
456+
442457
def _has_root_volume(self) -> bool:
443458
has_root_volume = bool(self.custom_args['root_is_subvolume'])
444459
if self.custom_args['root_is_subvolume'] is None:

kiwi/xml_parse.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5100,9 +5100,10 @@ class volume(GeneratedsSuper):
51005100
"""Specify which parts of the filesystem should be on an extra volume."""
51015101
subclass = None
51025102
superclass = None
5103-
def __init__(self, copy_on_write=None, filesystem_check=None, freespace=None, mountpoint=None, label=None, name=None, parent=None, size=None, arch=None):
5103+
def __init__(self, copy_on_write=None, quota=None, filesystem_check=None, freespace=None, mountpoint=None, label=None, name=None, parent=None, size=None, arch=None):
51045104
self.original_tagname_ = None
51055105
self.copy_on_write = _cast(bool, copy_on_write)
5106+
self.quota = _cast(None, quota)
51065107
self.filesystem_check = _cast(bool, filesystem_check)
51075108
self.freespace = _cast(None, freespace)
51085109
self.mountpoint = _cast(None, mountpoint)
@@ -5124,6 +5125,8 @@ def factory(*args_, **kwargs_):
51245125
factory = staticmethod(factory)
51255126
def get_copy_on_write(self): return self.copy_on_write
51265127
def set_copy_on_write(self, copy_on_write): self.copy_on_write = copy_on_write
5128+
def get_quota(self): return self.quota
5129+
def set_quota(self, quota): self.quota = quota
51275130
def get_filesystem_check(self): return self.filesystem_check
51285131
def set_filesystem_check(self, filesystem_check): self.filesystem_check = filesystem_check
51295132
def get_freespace(self): return self.freespace
@@ -5140,6 +5143,13 @@ def get_size(self): return self.size
51405143
def set_size(self, size): self.size = size
51415144
def get_arch(self): return self.arch
51425145
def set_arch(self, arch): self.arch = arch
5146+
def validate_partition_size_type(self, value):
5147+
# Validate type partition-size-type, a restriction on xs:token.
5148+
if value is not None and Validate_simpletypes_:
5149+
if not self.gds_validate_simple_patterns(
5150+
self.validate_partition_size_type_patterns_, value):
5151+
warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_partition_size_type_patterns_, ))
5152+
validate_partition_size_type_patterns_ = [['^(\\d+|\\d+M|\\d+G)$']]
51435153
def validate_volume_size_type(self, value):
51445154
# Validate type volume-size-type, a restriction on xs:token.
51455155
if value is not None and Validate_simpletypes_:
@@ -5185,6 +5195,9 @@ def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='
51855195
if self.copy_on_write is not None and 'copy_on_write' not in already_processed:
51865196
already_processed.add('copy_on_write')
51875197
outfile.write(' copy_on_write="%s"' % self.gds_format_boolean(self.copy_on_write, input_name='copy_on_write'))
5198+
if self.quota is not None and 'quota' not in already_processed:
5199+
already_processed.add('quota')
5200+
outfile.write(' quota=%s' % (quote_attrib(self.quota), ))
51885201
if self.filesystem_check is not None and 'filesystem_check' not in already_processed:
51895202
already_processed.add('filesystem_check')
51905203
outfile.write(' filesystem_check="%s"' % self.gds_format_boolean(self.filesystem_check, input_name='filesystem_check'))
@@ -5228,6 +5241,12 @@ def buildAttributes(self, node, attrs, already_processed):
52285241
self.copy_on_write = False
52295242
else:
52305243
raise_parse_error(node, 'Bad boolean attribute')
5244+
value = find_attr_value_('quota', node)
5245+
if value is not None and 'quota' not in already_processed:
5246+
already_processed.add('quota')
5247+
self.quota = value
5248+
self.quota = ' '.join(self.quota.split())
5249+
self.validate_partition_size_type(self.quota) # validate type partition-size-type
52315250
value = find_attr_value_('filesystem_check', node)
52325251
if value is not None and 'filesystem_check' not in already_processed:
52335252
already_processed.add('filesystem_check')

kiwi/xml_state.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,9 @@ def get_volumes(self) -> List[volume_type]:
17731773
attributes = []
17741774
is_root_volume = False
17751775

1776+
if volume.get_quota():
1777+
attributes.append(f'quota={volume.get_quota()}')
1778+
17761779
if volume.get_copy_on_write() is False:
17771780
# by default copy-on-write is switched on for any
17781781
# filesystem. Thus only if no copy on write is requested
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<image schemaversion="8.2" name="custom-btrfs-volume-setup">
4+
<description type="system">
5+
<author>Marcus Schäfer</author>
6+
<contact>[email protected]</contact>
7+
<specification>Some</specification>
8+
</description>
9+
<preferences>
10+
<version>1.15.5</version>
11+
<packagemanager>zypper</packagemanager>
12+
<type image="oem" filesystem="btrfs">
13+
<systemdisk>
14+
<volume name="some" quota="500M"/>
15+
</systemdisk>
16+
</type>
17+
</preferences>
18+
<repository>
19+
<source path="obs://some/repo/oss"/>
20+
</repository>
21+
<packages type="image">
22+
<package name="patterns-openSUSE-base"/>
23+
</packages>
24+
<packages type="bootstrap">
25+
<package name="filesystem"/>
26+
</packages>
27+
</image>

test/unit/volume_manager/btrfs_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def setup(self, mock_path):
3535
volume_type(
3636
name='etc', parent='', size='freespace:200', realpath='/etc',
3737
mountpoint='/etc', fullsize=False, label=None,
38-
attributes=[], is_root_volume=False
38+
attributes=['quota=2G'], is_root_volume=False
3939
),
4040
volume_type(
4141
name='myvol', parent='', size='size:500', realpath='/data',
@@ -262,7 +262,7 @@ def test_create_volumes(
262262
'tmpdir/@', volume_type(
263263
name='etc', parent='', size='freespace:200', realpath='/etc',
264264
mountpoint='/etc', fullsize=False, label=None,
265-
attributes=[],
265+
attributes=['quota=2G'],
266266
is_root_volume=False
267267
)
268268
),
@@ -286,6 +286,8 @@ def test_create_volumes(
286286
assert mock_command.call_args_list == [
287287
call(['btrfs', 'subvolume', 'create', 'tmpdir/@/data']),
288288
call(['btrfs', 'subvolume', 'create', 'tmpdir/@/etc']),
289+
call(['btrfs', 'quota', 'enable', '--simple', 'tmpdir/@/etc']),
290+
call(['btrfs', 'qgroup', 'limit', '2G', 'tmpdir/@/etc']),
289291
call(['btrfs', 'subvolume', 'create', 'tmpdir/@/home'])
290292
]
291293
assert mock_mount.call_args_list == [

test/unit/xml_state_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,32 @@ def test_get_volumes_custom_root_volume_name(self):
428428
)
429429
]
430430

431+
def test_get_volumes_btrfs_quota(self):
432+
description = XMLDescription(
433+
'../data/example_btrfs_vol_config.xml'
434+
)
435+
xml_data = description.load()
436+
state = XMLState(xml_data)
437+
volume_type = self.volume_type
438+
assert state.get_volumes() == [
439+
volume_type(
440+
name='some', parent='', size='freespace:120',
441+
realpath='some',
442+
mountpoint='some', fullsize=False,
443+
label=None,
444+
attributes=['quota=500M'],
445+
is_root_volume=False
446+
),
447+
volume_type(
448+
name='', parent='', size=None,
449+
realpath='/',
450+
mountpoint=None, fullsize=True,
451+
label=None,
452+
attributes=[],
453+
is_root_volume=True
454+
)
455+
]
456+
431457
def test_get_volumes_for_arch(self):
432458
description = XMLDescription('../data/example_lvm_arch_config.xml')
433459
xml_data = description.load()

0 commit comments

Comments
 (0)