summaryrefslogtreecommitdiffstats
path: root/tests/docker_source.py
diff options
context:
space:
mode:
authorRadostin Stoyanov <rstoyanov1@gmail.com>2017-08-26 21:41:52 +0100
committerRadostin Stoyanov <rstoyanov1@gmail.com>2017-08-28 15:57:16 +0100
commit186bf4860e963d58a1779b1898985d4d13211286 (patch)
tree51c04c8b7e378245de6b39a82c364aea8ba52ebf /tests/docker_source.py
parenta48353f83374c37480c98c463363873b95058e5f (diff)
downloadvirt-bootstrap.git-186bf4860e963d58a1779b1898985d4d13211286.tar.gz
virt-bootstrap.git-186bf4860e963d58a1779b1898985d4d13211286.tar.xz
virt-bootstrap.git-186bf4860e963d58a1779b1898985d4d13211286.zip
Add regression tests
These tests aim to verify the output of virt-bootstrap by creating tar archives which contain dummy root file system and call the function bootstrap(). The check the extracted root file system.
Diffstat (limited to 'tests/docker_source.py')
-rw-r--r--tests/docker_source.py243
1 files changed, 240 insertions, 3 deletions
diff --git a/tests/docker_source.py b/tests/docker_source.py
index 60404e6..5f9e6fe 100644
--- a/tests/docker_source.py
+++ b/tests/docker_source.py
@@ -18,15 +18,252 @@
"""
Tests which aim is to exercise creation of root file system with DockerSource.
+
+To avoid fetching network resources we mock out the functions:
+- utils.get_image_details(): Returns manifest content
+- utils.get_image_dir(): Returns the directory which contains the tar files
+
+Brief description of this tests:
+1. Create dummy tar files named <checksum>.tar used as image layers.
+2. Generate manifest content.
+3. Mock out get_image_details() and get_image_dir().
+4. Call bootstrap().
+5. Check the result.
"""
+import copy
+import os
+import subprocess
import unittest
+import guestfs
from . import mock
from . import sources
+from . import virt_bootstrap
+from . import BuildTarFiles
+from . import ImageAccessor
+from . import Qcow2ImageAccessor
+from . import NOT_ROOT
# pylint: disable=invalid-name
+class CreateLayers(object):
+ """
+ Create tar files to mimic image layers and generate manifest content.
+ """
+ def __init__(self, initial_tar_file, initial_rootfs_tree, dest_dir):
+ """
+ Create dummy tar files used as image layers.
+
+ The variables:
+ - layers: Store a lists of paths to created archives.
+ - layers_rootfs: Store self.rootfs_tree value used to generate tarball.
+ """
+ self.layers = [initial_tar_file]
+ self.layers_rootfs = [copy.deepcopy(initial_rootfs_tree)]
+
+ tar_builder = BuildTarFiles(dest_dir)
+ tar_builder.rootfs_tree['root']['dirs'] = []
+ tar_builder.rootfs_tree['root']['files'] = [
+ ('etc/foo/bar', 0o644, "This should be overwritten")
+ ]
+
+ self.layers.append(tar_builder.create_tar_file())
+ self.layers_rootfs.append(copy.deepcopy(tar_builder.rootfs_tree))
+
+ tar_builder.rootfs_tree['root']['files'] = [
+ ('etc/foo/bar', 0o644, "Content of etc/foo/bar"),
+ ('bin/foobar', 0o755, "My executable script")
+ ]
+ self.layers.append(tar_builder.create_tar_file())
+ self.layers_rootfs.append(copy.deepcopy(tar_builder.rootfs_tree))
+
+ def get_layers_rootfs(self):
+ """
+ Return root file systems used to create layers.
+ """
+ return self.layers_rootfs
+
+ def generate_manifest(self):
+ """
+ Generate Manifest content for layers.
+ """
+ return {
+ "schemaVersion": 2,
+ "layers": [
+ {
+ "digest":
+ "sha256:" + os.path.basename(layer).split('.')[0]
+ }
+ for layer in self.layers
+ ]
+ }
+
+
+class TestDirDockerSource(ImageAccessor):
+ """
+ Ensures that all layers extracted correctly in destination folder.
+ """
+ def check_result(self, layers_rootfs, dest):
+ """
+ Iterates trough values of layers_rootfs in reverse order (from the last
+ layer to first) and calls check_extracted_files().
+ """
+
+ def call_bootstrap(self, manifest):
+ """
+ Mock get_image_details() and get_image_dir() and call the function
+ virt_bootstrap.bootstrap() with root_password value.
+ """
+ with mock.patch.multiple('virtBootstrap.utils',
+ get_image_details=mock.DEFAULT,
+ get_image_dir=mock.DEFAULT) as mocked:
+
+ mocked['get_image_details'].return_value = manifest
+ mocked['get_image_dir'].return_value = self.tar_dir
+
+ virt_bootstrap.bootstrap(
+ progress_cb=mock.Mock(),
+ uri='docker://foo',
+ fmt='dir',
+ uid_map=self.uid_map,
+ gid_map=self.gid_map,
+ dest=self.dest_dir,
+ root_password=self.root_password
+ )
+
+ def test_dir_extract_rootfs(self):
+ """
+ Ensures that all layers were extracted correctly.
+ """
+ layers = CreateLayers(self.tar_file, self.rootfs_tree, self.tar_dir)
+ self.call_bootstrap(layers.generate_manifest())
+ layers_rootfs = layers.get_layers_rootfs()
+ for rootfs_tree in layers_rootfs[::-1]:
+ self.rootfs_tree = rootfs_tree
+ self.check_rootfs(skip_ownership=(os.geteuid != 0))
+
+ @unittest.skipIf(NOT_ROOT, "Root privileges required")
+ def test_dir_ownership_mapping(self):
+ """
+ Ensures that the UID/GID mapping was applied correctly to extracted
+ root file system of all layers.
+ """
+ self.uid_map = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]]
+ self.gid_map = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]]
+ layers = CreateLayers(self.tar_file, self.rootfs_tree, self.tar_dir)
+ self.call_bootstrap(layers.generate_manifest())
+ layers_rootfs = layers.get_layers_rootfs()
+ for rootfs_tree in layers_rootfs[::-1]:
+ self.rootfs_tree = rootfs_tree
+ self.apply_mapping()
+ self.check_rootfs()
+
+ def test_dir_setting_root_password(self):
+ """
+ Ensures that the root password is set correctly.
+ """
+ layers = CreateLayers(self.tar_file, self.rootfs_tree, self.tar_dir)
+ self.root_password = "My secret root password"
+ self.call_bootstrap(layers.generate_manifest())
+ self.validate_shadow_file()
+
+
+class TestQcow2DockerSource(Qcow2ImageAccessor):
+ """
+ Ensures that the conversion of tar files to qcow2 image with backing chains
+ works as expected.
+ """
+ def get_image_info(self, image_path):
+ """
+ Wrapper around "qemu-img info" used to information about disk image.
+ """
+ cmd = ['qemu-img', 'info', image_path]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ output, _ignore = proc.communicate()
+ return output.decode('utf-8').split('\n')
+
+ def call_bootstrap(self):
+ """
+ Generate tar files which mimic container layers and manifest content.
+ Mock get_image_details() and get_image_dir() and call the function
+ virt_bootstrap.bootstrap() for qcow2 format.
+ Return the root file systems used to generate the tar archives.
+ """
+ layers = CreateLayers(self.tar_file, self.rootfs_tree, self.tar_dir)
+ manifest = layers.generate_manifest()
+
+ with mock.patch.multiple('virtBootstrap.utils',
+ get_image_details=mock.DEFAULT,
+ get_image_dir=mock.DEFAULT) as mocked:
+
+ mocked['get_image_details'].return_value = manifest
+ mocked['get_image_dir'].return_value = self.tar_dir
+
+ virt_bootstrap.bootstrap(
+ progress_cb=mock.Mock(),
+ uri='docker://foobar',
+ dest=self.dest_dir,
+ fmt='qcow2',
+ uid_map=self.uid_map,
+ gid_map=self.gid_map,
+ root_password=self.root_password
+ )
+
+ return layers.get_layers_rootfs()
+
+ def test_qcow2_build_image(self):
+ """
+ Ensures that the root file system is copied correctly to single
+ partition qcow2 image and layers are converted correctly to qcow2
+ images.
+ """
+ layers_rootfs = self.call_bootstrap()
+
+ ###################
+ # Check base layer
+ ###################
+ base_layer_path = self.get_image_path()
+ img_format = self.get_image_info(base_layer_path)[1]
+ self.assertEqual(img_format, 'file format: qcow2')
+ images = [base_layer_path]
+ ###########################
+ # Check backing chains
+ ###########################
+ for i in range(1, len(layers_rootfs)):
+ img_path = self.get_image_path(i)
+ # img_info contains the output of "qemu-img info"
+ img_info = self.get_image_info(img_path)
+ self.assertEqual(
+ img_info[1],
+ 'file format: qcow2',
+ 'Invalid qcow2 disk image: %s' % img_path
+ )
+ backing_file = self.get_image_path(i - 1)
+ self.assertEqual(
+ img_info[5],
+ 'backing file: %s' % backing_file,
+ "Incorrect backing file for: %s\n"
+ "Expected: %s\n"
+ "Found: %s" % (img_info, backing_file, img_info[5])
+ )
+ images.append(img_path)
+ ###############################
+ # Check extracted files/folders
+ ###############################
+ g = guestfs.GuestFS(python_return_dict=True)
+ for path in images:
+ g.add_drive_opts(path, readonly=True)
+ g.launch()
+ devices = g.list_filesystems()
+ for dev, rootfs in zip(sorted(devices), layers_rootfs):
+ self.rootfs_tree = rootfs
+ g.mount(dev, '/')
+ self.check_image(g)
+ g.umount('/')
+ g.shutdown()
+
+
class TestDockerSource(unittest.TestCase):
"""
Unit tests for DockerSource
@@ -36,9 +273,9 @@ class TestDockerSource(unittest.TestCase):
###################################
def _mock_retrieve_layers_info(self, manifest, kwargs):
"""
- This method is gather common test pattern used in the following
- two test cases which aim to return an instance of the class
- DockerSource with some util functions being mocked.
+ This method is gather common test pattern used in the following cases
+ which aim is to return an instance of the class DockerSource with
+ get_image_details() and get_image_dir() being mocked.
"""
with mock.patch.multiple('virtBootstrap.utils',
get_image_details=mock.DEFAULT,