summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-06 03:54:31 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-06 03:54:31 +0000
commit5aab0f92f1e774305c23802566d75922f65e0862 (patch)
tree11193654c643a39e768c267435d562e8e04e8794
parent8c31b451c6f5a9d0bb526ee77f467e5b48846bb4 (diff)
downloadmeta-virtualization-container-cross-install.tar.gz
container-cross-install: add tests and documentation for custom service filescontainer-cross-install
Add pytest tests to verify CONTAINER_SERVICE_FILE varflag support: TestCustomServiceFileSupport (unit tests, no build required): - test_bbclass_has_service_file_support - test_bundle_class_has_service_file_support - test_service_file_map_syntax - test_install_custom_service_function TestCustomServiceFileBoot (boot tests, require built image): - test_systemd_services_directory_exists - test_container_services_present - test_container_service_enabled - test_custom_service_content - test_podman_quadlet_directory Documentation updates: - docs/container-bundling.md: Add "Custom Service Files" section with variable format, usage examples for both BUNDLED_CONTAINERS and container-bundle packages, and example .service/.container files - tests/README.md: Add test class entries to structure diagram and "What the Tests Check" table Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
-rw-r--r--docs/container-bundling.md95
-rw-r--r--tests/README.md6
-rw-r--r--tests/test_container_cross_install.py194
3 files changed, 294 insertions, 1 deletions
diff --git a/docs/container-bundling.md b/docs/container-bundling.md
index 745622b5..f4587a99 100644
--- a/docs/container-bundling.md
+++ b/docs/container-bundling.md
@@ -360,6 +360,101 @@ Containers can be configured to start automatically on boot:
360- Podman: `/etc/containers/systemd/<name>.container` (Quadlet format) 360- Podman: `/etc/containers/systemd/<name>.container` (Quadlet format)
361 361
362 362
363Custom Service Files
364--------------------
365
366For containers that require specific startup configuration (ports, volumes,
367capabilities, dependencies), you can provide custom service files instead of
368using the auto-generated ones.
369
370### Variable Format
371
372Use the `CONTAINER_SERVICE_FILE` varflag to specify custom service files:
373
374 CONTAINER_SERVICE_FILE[container-name] = "${UNPACKDIR}/myservice.service"
375 CONTAINER_SERVICE_FILE[other-container] = "${UNPACKDIR}/other.container"
376
377### For BUNDLED_CONTAINERS (in image recipe)
378
379 # host-image.bb or local.conf
380 inherit container-cross-install
381
382 SRC_URI += "\
383 file://myapp.service \
384 file://mydb.container \
385 "
386
387 BUNDLED_CONTAINERS = "\
388 myapp-container:docker:autostart \
389 mydb-container:podman:autostart \
390 "
391
392 # Map containers to custom service files
393 CONTAINER_SERVICE_FILE[myapp-container] = "${UNPACKDIR}/myapp.service"
394 CONTAINER_SERVICE_FILE[mydb-container] = "${UNPACKDIR}/mydb.container"
395
396### For container-bundle Packages
397
398 # my-bundle_1.0.bb
399 inherit container-bundle
400
401 SRC_URI = "\
402 file://myapp.service \
403 file://mydb.container \
404 "
405
406 CONTAINER_BUNDLES = "\
407 myapp-container:autostart \
408 mydb-container:autostart \
409 "
410
411 CONTAINER_SERVICE_FILE[myapp-container] = "${UNPACKDIR}/myapp.service"
412 CONTAINER_SERVICE_FILE[mydb-container] = "${UNPACKDIR}/mydb.container"
413
414### Docker .service Example
415
416 # myapp.service
417 [Unit]
418 Description=MyApp Container
419 After=docker.service
420 Requires=docker.service
421
422 [Service]
423 Type=simple
424 Restart=unless-stopped
425 RestartSec=5s
426 ExecStartPre=-/usr/bin/docker rm -f myapp
427 ExecStart=/usr/bin/docker run --rm --name myapp \
428 -p 8080:80 \
429 -v /data/myapp:/var/lib/myapp:rw \
430 --cap-add NET_ADMIN \
431 myapp:latest
432 ExecStop=/usr/bin/docker stop myapp
433
434 [Install]
435 WantedBy=multi-user.target
436
437### Podman .container (Quadlet) Example
438
439 # mydb.container
440 [Unit]
441 Description=MyDB Container
442
443 [Container]
444 Image=mydb:latest
445 ContainerName=mydb
446 PublishPort=5432:5432
447 Volume=/data/db:/var/lib/postgresql/data:Z
448 Environment=POSTGRES_PASSWORD=secret
449
450 [Service]
451 Restart=unless-stopped
452 RestartSec=5s
453
454 [Install]
455 WantedBy=multi-user.target
456
457
363vdkr and vpdmn - Virtual Container Runtimes 458vdkr and vpdmn - Virtual Container Runtimes
364=========================================== 459===========================================
365 460
diff --git a/tests/README.md b/tests/README.md
index 4f5aed28..09bc70ca 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -162,6 +162,8 @@ BBMULTICONFIG = "vruntime-aarch64 vruntime-x86-64"
162| `TestVdkrRecipes` | vdkr recipe builds | `bitbake vcontainer-tarball` | 162| `TestVdkrRecipes` | vdkr recipe builds | `bitbake vcontainer-tarball` |
163| `TestMulticonfig` | Multiconfig setup | `BBMULTICONFIG` configured | 163| `TestMulticonfig` | Multiconfig setup | `BBMULTICONFIG` configured |
164| `TestBundledContainersBoot` | **Boot image and verify containers** | Full image with Docker/Podman | 164| `TestBundledContainersBoot` | **Boot image and verify containers** | Full image with Docker/Podman |
165| `TestCustomServiceFileSupport` | CONTAINER_SERVICE_FILE varflag support | None (file check only) |
166| `TestCustomServiceFileBoot` | Custom service files installed correctly | Full image with autostart containers |
165 167
166### Boot Tests (TestBundledContainersBoot) 168### Boot Tests (TestBundledContainersBoot)
167 169
@@ -440,7 +442,9 @@ tests/
440│ ├── TestBundledContainers # end-to-end bundling 442│ ├── TestBundledContainers # end-to-end bundling
441│ ├── TestVdkrRecipes # vdkr builds 443│ ├── TestVdkrRecipes # vdkr builds
442│ ├── TestMulticonfig # multiconfig setup 444│ ├── TestMulticonfig # multiconfig setup
443│ └── TestBundledContainersBoot # boot and verify containers 445│ ├── TestBundledContainersBoot # boot and verify containers
446│ ├── TestCustomServiceFileSupport # CONTAINER_SERVICE_FILE varflag support
447│ └── TestCustomServiceFileBoot # custom service file boot verification
444├── test_multiarch_oci.py # Multi-architecture OCI tests 448├── test_multiarch_oci.py # Multi-architecture OCI tests
445│ ├── TestOCIImageIndexDetection # multi-arch OCI detection 449│ ├── TestOCIImageIndexDetection # multi-arch OCI detection
446│ ├── TestPlatformSelection # arch selection (aarch64/x86_64) 450│ ├── TestPlatformSelection # arch selection (aarch64/x86_64)
diff --git a/tests/test_container_cross_install.py b/tests/test_container_cross_install.py
index 9a4d23a4..ceb8b874 100644
--- a/tests/test_container_cross_install.py
+++ b/tests/test_container_cross_install.py
@@ -904,3 +904,197 @@ class TestBundledContainersBoot:
904 904
905 assert 'CONTAINER_WORKS' in output, \ 905 assert 'CONTAINER_WORKS' in output, \
906 f"Container {container} failed to run.\nOutput:\n{output}" 906 f"Container {container} failed to run.\nOutput:\n{output}"
907
908
909# ============================================================================
910# Custom Service File Tests
911# ============================================================================
912
913class TestCustomServiceFileSupport:
914 """
915 Test CONTAINER_SERVICE_FILE varflag support.
916
917 This tests the ability to provide custom systemd service files or
918 Podman Quadlet files instead of auto-generated ones.
919 """
920
921 def test_bbclass_has_service_file_support(self, meta_virt_dir):
922 """Test that the bbclass includes CONTAINER_SERVICE_FILE support."""
923 class_file = meta_virt_dir / "classes" / "container-cross-install.bbclass"
924 content = class_file.read_text()
925
926 # Check for the key implementation elements
927 assert "CONTAINER_SERVICE_FILE" in content, \
928 "CONTAINER_SERVICE_FILE variable not found in bbclass"
929 assert "get_container_service_file_map" in content, \
930 "get_container_service_file_map function not found"
931 assert "CONTAINER_SERVICE_FILE_MAP" in content, \
932 "CONTAINER_SERVICE_FILE_MAP variable not found"
933 assert "install_custom_service" in content, \
934 "install_custom_service function not found"
935
936 def test_bundle_class_has_service_file_support(self, meta_virt_dir):
937 """Test that container-bundle.bbclass includes CONTAINER_SERVICE_FILE support."""
938 class_file = meta_virt_dir / "classes" / "container-bundle.bbclass"
939 content = class_file.read_text()
940
941 # Check for the key implementation elements
942 assert "CONTAINER_SERVICE_FILE" in content, \
943 "CONTAINER_SERVICE_FILE variable not found in container-bundle.bbclass"
944 assert "_CONTAINER_SERVICE_FILE_MAP" in content, \
945 "_CONTAINER_SERVICE_FILE_MAP variable not found"
946 assert "services" in content, \
947 "services directory handling not found"
948
949 def test_service_file_map_syntax(self, meta_virt_dir):
950 """Test that the service file map function has correct syntax."""
951 class_file = meta_virt_dir / "classes" / "container-cross-install.bbclass"
952 content = class_file.read_text()
953
954 # Check the function signature and key logic
955 assert "def get_container_service_file_map(d):" in content, \
956 "get_container_service_file_map function signature not found"
957 assert "getVarFlag('CONTAINER_SERVICE_FILE'" in content, \
958 "getVarFlag call for CONTAINER_SERVICE_FILE not found"
959 assert 'mappings.append' in content or 'mappings =' in content, \
960 "Service file mapping logic not found"
961
962 def test_install_custom_service_function(self, meta_virt_dir):
963 """Test that install_custom_service handles both Docker and Podman."""
964 class_file = meta_virt_dir / "classes" / "container-cross-install.bbclass"
965 content = class_file.read_text()
966
967 # Check the function handles both runtimes
968 assert 'install_custom_service()' in content or 'install_custom_service ' in content, \
969 "install_custom_service function not found"
970
971 # Docker service installation
972 assert '/lib/systemd/system' in content, \
973 "Docker service directory path not found"
974 assert 'multi-user.target.wants' in content, \
975 "Systemd enable symlink path not found"
976
977 # Podman Quadlet installation
978 assert '/etc/containers/systemd' in content, \
979 "Podman Quadlet directory path not found"
980
981
982class TestCustomServiceFileBoot:
983 """
984 Boot tests for custom service files.
985
986 These tests verify that custom service files are properly installed
987 and enabled in the booted system.
988 """
989
990 @pytest.mark.slow
991 @pytest.mark.boot
992 def test_systemd_services_directory_exists(self, runqemu_session):
993 """Test that systemd service directories exist."""
994 output = runqemu_session.run_command('ls -la /lib/systemd/system/ | head -5')
995 assert 'systemd' in output or 'total' in output, \
996 "Systemd system directory not accessible"
997
998 @pytest.mark.slow
999 @pytest.mark.boot
1000 def test_container_services_present(self, runqemu_session, bundled_containers_config):
1001 """Test that container service files are present (custom or generated)."""
1002 docker_containers = bundled_containers_config.get('docker', [])
1003
1004 if not docker_containers:
1005 pytest.skip("No Docker containers configured")
1006
1007 # Check if docker is available
1008 output = runqemu_session.run_command('which docker')
1009 if '/docker' not in output:
1010 pytest.skip("docker not installed in image")
1011
1012 # Check for container service files
1013 output = runqemu_session.run_command('ls /lib/systemd/system/container-*.service 2>/dev/null || echo "NONE"')
1014
1015 if 'NONE' in output:
1016 # No autostart services - check if any containers have autostart
1017 pytest.skip("No container autostart services found (containers may not have autostart enabled)")
1018
1019 # Verify at least one service file exists
1020 assert '.service' in output, \
1021 f"No container service files found. Output: {output}"
1022
1023 @pytest.mark.slow
1024 @pytest.mark.boot
1025 def test_container_service_enabled(self, runqemu_session, bundled_containers_config):
1026 """Test that container services are enabled (linked in wants directory)."""
1027 docker_containers = bundled_containers_config.get('docker', [])
1028
1029 if not docker_containers:
1030 pytest.skip("No Docker containers configured")
1031
1032 # Check for enabled services in multi-user.target.wants
1033 output = runqemu_session.run_command(
1034 'ls /etc/systemd/system/multi-user.target.wants/container-*.service 2>/dev/null || echo "NONE"'
1035 )
1036
1037 if 'NONE' in output:
1038 pytest.skip("No container autostart services enabled")
1039
1040 # Verify services are symlinked
1041 assert '.service' in output, \
1042 f"No enabled container services found. Output: {output}"
1043
1044 @pytest.mark.slow
1045 @pytest.mark.boot
1046 def test_custom_service_content(self, runqemu_session, bundled_containers_config):
1047 """Test that custom service files have expected content markers."""
1048 docker_containers = bundled_containers_config.get('docker', [])
1049
1050 if not docker_containers:
1051 pytest.skip("No Docker containers configured")
1052
1053 # Find a container service file
1054 output = runqemu_session.run_command(
1055 'ls /lib/systemd/system/container-*.service 2>/dev/null | head -1'
1056 )
1057
1058 if not output or 'container-' not in output:
1059 pytest.skip("No container service files found")
1060
1061 service_file = output.strip().split('\n')[0]
1062
1063 # Read the service file content
1064 content = runqemu_session.run_command(f'cat {service_file}')
1065
1066 # Verify it has expected systemd service structure
1067 assert '[Unit]' in content, f"Service file missing [Unit] section: {service_file}"
1068 assert '[Service]' in content, f"Service file missing [Service] section: {service_file}"
1069 assert '[Install]' in content, f"Service file missing [Install] section: {service_file}"
1070
1071 # Check for docker-related content
1072 assert 'docker' in content.lower(), \
1073 f"Service file doesn't reference docker: {content}"
1074
1075 @pytest.mark.slow
1076 @pytest.mark.boot
1077 def test_podman_quadlet_directory(self, runqemu_session, bundled_containers_config):
1078 """Test Podman Quadlet directory exists for Podman containers."""
1079 podman_containers = bundled_containers_config.get('podman', [])
1080
1081 if not podman_containers:
1082 pytest.skip("No Podman containers configured")
1083
1084 # Check if podman is available
1085 output = runqemu_session.run_command('which podman')
1086 if '/podman' not in output:
1087 pytest.skip("podman not installed in image")
1088
1089 # Check for Quadlet directory
1090 output = runqemu_session.run_command('ls -la /etc/containers/systemd/ 2>/dev/null || echo "NONE"')
1091
1092 if 'NONE' in output:
1093 pytest.skip("Quadlet directory not found (containers may not have autostart enabled)")
1094
1095 # Check for .container files
1096 output = runqemu_session.run_command('ls /etc/containers/systemd/*.container 2>/dev/null || echo "NONE"')
1097
1098 if 'NONE' not in output:
1099 assert '.container' in output, \
1100 f"No Quadlet container files found. Output: {output}"