Skip to content

Commit

Permalink
add tests for the new rpm extraction logic
Browse files Browse the repository at this point in the history
  • Loading branch information
slayoo committed Mar 1, 2024
1 parent 2c0a1e9 commit 6c44bb9
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 9 deletions.
18 changes: 15 additions & 3 deletions qubesadmin/tests/tools/qvm_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,15 @@ def test_004_verify_rpm_badname(self, mock_proc, mock_call, mock_ts):
mock_ts.assert_called_once()
self.assertAllCalled()

@mock.patch('os.path.exists')
@mock.patch('subprocess.Popen')
def test_010_extract_rpm_success(self, mock_popen):
def test_010_extract_rpm_success(self, mock_popen, mock_path_exists):
mock_popen.return_value.__enter__.return_value = mock_popen.return_value
pipe = mock.Mock()
mock_popen.return_value.stdout = pipe
mock_popen.return_value.wait.return_value = 0
mock_popen.return_value.returncode = 0
mock_path_exists.return_value = True
with tempfile.NamedTemporaryFile() as fd, \
tempfile.TemporaryDirectory() as dir:
path = fd.name
Expand All @@ -220,11 +222,19 @@ def test_010_extract_rpm_success(self, mock_popen):
'xz',
'-C',
dirpath,
'./var/lib/qubes/vm-templates/test-vm/'
'./var/lib/qubes/vm-templates/test-vm/',
'--exclude=root.img.part.?[!0]',
'--exclude=root.img.part.[!0]0',
], stdin=pipe, stdout=subprocess.DEVNULL),
mock.call().__enter__(),
mock.call().__exit__(None, None, None),
mock.call().__exit__(None, None, None),
mock.call(['truncate', '--size=512', dirpath + '//var/lib/qubes/vm-templates/test-vm/root.img.part.00']),
mock.call().__enter__(),
mock.call().__exit__(None, None, None),
mock.call(['ln', '-s', path, dirpath + '//var/lib/qubes/vm-templates/test-vm/template.rpm']),
mock.call().__enter__(),
mock.call().__exit__(None, None, None),
])
self.assertAllCalled()

Expand All @@ -251,7 +261,9 @@ def test_011_extract_rpm_fail(self, mock_popen):
'xz',
'-C',
dirpath,
'./var/lib/qubes/vm-templates/test-vm/'
'./var/lib/qubes/vm-templates/test-vm/',
'--exclude=root.img.part.?[!0]',
'--exclude=root.img.part.[!0]0',
], stdin=pipe, stdout=subprocess.DEVNULL),
mock.call().__enter__(),
mock.call().__exit__(None, None, None),
Expand Down
62 changes: 62 additions & 0 deletions qubesadmin/tests/tools/qvm_template_postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from unittest import mock
import qubesadmin.tests
import qubesadmin.tools.qvm_template_postprocess
from qubesadmin.exc import QubesException


class QubesLocalMock(qubesadmin.tests.QubesTest):
Expand Down Expand Up @@ -67,6 +68,36 @@ def test_000_import_root_img_raw(self):
vm, self.source_dir.name)
self.assertAllCalled()

def test_001_import_root_img_tar_pre_mar_2024(self):
root_img = os.path.join(self.source_dir.name, 'root.img')
volume_data = b'volume data' * 1000
with open(root_img, 'wb') as f:
f.write(volume_data)

subprocess.check_call(['tar', 'cf', 'root.img.tar', 'root.img'],
cwd=self.source_dir.name)
subprocess.check_call(['split', '-d', '-b', '1024', 'root.img.tar',
'root.img.part.'], cwd=self.source_dir.name)
os.unlink(root_img)

self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\0test-vm class=TemplateVM state=Halted\n'
self.app.expected_calls[('test-vm', 'admin.vm.volume.List', None,
None)] = \
b'0\0root\nprivate\nvolatile\nkernel\n'

self.app.expected_calls[(
'test-vm', 'admin.vm.volume.ImportWithSize', 'root',
str(len(volume_data)).encode() + b'\n' + volume_data)] = b'0\0'
vm = self.app.domains['test-vm']
try:
qubesadmin.tools.qvm_template_postprocess.import_root_img(
vm, self.source_dir.name)
except QubesException as e:
assert str(e).startswith('template.rpm symlink not found for multi-part image')
else:
assert False

def test_001_import_root_img_tar(self):
root_img = os.path.join(self.source_dir.name, 'root.img')
volume_data = b'volume data' * 1000
Expand All @@ -79,6 +110,37 @@ def test_001_import_root_img_tar(self):
'root.img.part.'], cwd=self.source_dir.name)
os.unlink(root_img)

spec = os.path.join(self.source_dir.name, 'template.spec')
with open(spec, 'w') as f:
f.writelines((
'%define _rpmdir %{expand:%%(pwd)}/build\n',
'Name: test\n',
'Summary: testing\n',
'License: none\n',
'Version: 6.6.6\n',
'Release: 44\n',

'%description\n',
'test\n',

'%prep\n',
'mkdir -p $RPM_BUILD_ROOT\n',
'mv %{expand:%%(pwd)}/root.img.part.* $RPM_BUILD_ROOT\n',
'dd if=$RPM_BUILD_ROOT/root.img.part.00 count=1 of=%{expand:%%(pwd)}/root.img.part.00\n',
'ln -s %{expand:%%(pwd)}/build/i386/test-6.6.6-44.i386.rpm %{expand:%%(pwd)}/template.rpm\n',

'%files\n',
'/root.img.part.*\n',
))
subprocess.check_call(['rpmbuild',
'-bb',
'--rmspec',
'--target', 'i386-redhat-linux',
'--clean',
'-D', f'_topdir {self.source_dir.name}',
spec
], cwd=self.source_dir.name)

self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\0test-vm class=TemplateVM state=Halted\n'
self.app.expected_calls[('test-vm', 'admin.vm.volume.List', None,
Expand Down
15 changes: 9 additions & 6 deletions qubesadmin/tools/qvm_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,12 +758,15 @@ def extract_rpm(name: str, path: str, target: str) -> bool:

part_00_path = f'{target}/{PATH_PREFIX}/{name}/root.img.part.00'
if os.path.exists(part_00_path):
try:
# retain minimal data needed to interrogate root.img size
os.truncate(path=part_00_path, length=TAR_HEADER_BYTES)
# and create rpm file symlink
os.symlink(src=path, dst=f'{target}/{PATH_PREFIX}/{name}/template.rpm')
except OSError:
# retain minimal data needed to interrogate root.img size
with subprocess.Popen(['truncate', f'--size={TAR_HEADER_BYTES}', part_00_path]) as truncate:
pass
if truncate.returncode != 0:
return False
# and create rpm file symlink
with subprocess.Popen(['ln', '-s', path, f'{target}/{PATH_PREFIX}/{name}/template.rpm']) as symlink:
pass
if symlink.returncode != 0:
return False
return True

Expand Down

0 comments on commit 6c44bb9

Please sign in to comment.