summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-19 16:08:45 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-26 01:05:01 +0000
commitc734621380aed127ee515839aeeb8126f2dcf9ad (patch)
tree42b9ad081b4a9c627dc06d65262c9b2176f72d52
parent035e0daebeb53880ea2a6bd0f0e31785f3ec9e55 (diff)
downloadmeta-virtualization-c734621380aed127ee515839aeeb8126f2dcf9ad.tar.gz
vxn: add host-side OCI image cache and fix Docker iptables conflict
Add a host-side OCI image cache at ~/.vxn/images/ for the vdkr/vpdmn standalone Xen path. Images pulled via skopeo are stored in a content-addressed layout (refs/ symlinks + store/ OCI dirs) so subsequent runs hit the cache without network access. New commands on Xen: pull, images, rmi, tag, inspect, image <subcmd>. The run path is unchanged — cache integration into hv_prepare_container is deferred to a follow-up. Also fix Docker iptables conflict: when docker-moby and vxn-docker-config coexist on Dom0, Docker's default FORWARD DROP policy blocks DHCP for Xen DomU vifs on xenbr0. Adding "iptables": false to daemon.json prevents Docker from modifying iptables since VM-based containers manage their own network stack. Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
-rwxr-xr-xrecipes-containers/vcontainer/files/vcontainer-common.sh274
-rw-r--r--recipes-core/vxn/vxn_1.0.bb12
2 files changed, 275 insertions, 11 deletions
diff --git a/recipes-containers/vcontainer/files/vcontainer-common.sh b/recipes-containers/vcontainer/files/vcontainer-common.sh
index ea78b265..c7160860 100755
--- a/recipes-containers/vcontainer/files/vcontainer-common.sh
+++ b/recipes-containers/vcontainer/files/vcontainer-common.sh
@@ -486,6 +486,105 @@ EOF
486 return 0 486 return 0
487} 487}
488 488
489# ============================================================================
490# Host-side OCI Image Cache (Xen standalone path)
491# ============================================================================
492# Cache layout:
493# ~/.vxn/images/refs/ - Symlinks from normalized image names to store dirs
494# ~/.vxn/images/store/ - Content-addressed OCI layout dirs by manifest digest
495
496VXN_IMAGE_CACHE="${VXN_IMAGE_CACHE:-$HOME/.vxn/images}"
497
498vxn_normalize_image_name() {
499 # alpine → docker.io/library/alpine:latest
500 # nginx:1.25 → docker.io/library/nginx:1.25
501 # ghcr.io/foo/bar → ghcr.io/foo/bar:latest
502 local name="$1"
503 # Add default registry
504 case "$name" in
505 *.*/*) ;; # has registry (contains . before /)
506 */*) name="docker.io/$name" ;; # has namespace, no registry
507 *) name="docker.io/library/$name" ;; # bare name
508 esac
509 # Add default tag
510 case "$name" in
511 *:*) ;; # already has tag/digest
512 *) name="$name:latest" ;;
513 esac
514 echo "$name"
515}
516
517vxn_image_ref_key() {
518 # docker.io/library/alpine:latest → docker.io_library_alpine:latest
519 local name="$1"
520 echo "$name" | tr '/' '_'
521}
522
523vxn_image_cache_lookup() {
524 # Returns OCI dir path if cached, empty if not
525 local image="$1"
526 local normalized ref_key ref_link
527 normalized=$(vxn_normalize_image_name "$image")
528 ref_key=$(vxn_image_ref_key "$normalized")
529 ref_link="$VXN_IMAGE_CACHE/refs/$ref_key"
530 if [ -L "$ref_link" ] && [ -d "$ref_link" ]; then
531 readlink -f "$ref_link"
532 fi
533}
534
535vxn_image_cache_store() {
536 # Store OCI dir in cache, create ref symlink
537 # $1 = image name, $2 = source OCI dir
538 local image="$1" oci_dir="$2"
539 local normalized ref_key manifest_digest store_dir
540 normalized=$(vxn_normalize_image_name "$image")
541 ref_key=$(vxn_image_ref_key "$normalized")
542
543 mkdir -p "$VXN_IMAGE_CACHE/refs" "$VXN_IMAGE_CACHE/store/sha256"
544
545 # Get manifest digest for content-addressed storage
546 manifest_digest=$(grep -o '"sha256:[a-f0-9]*"' "$oci_dir/index.json" 2>/dev/null | head -1 | tr -d '"')
547 manifest_digest="${manifest_digest#sha256:}"
548 if [ -z "$manifest_digest" ]; then
549 # Fallback: hash the index.json itself
550 manifest_digest=$(sha256sum "$oci_dir/index.json" | cut -d' ' -f1)
551 fi
552
553 store_dir="$VXN_IMAGE_CACHE/store/sha256/$manifest_digest"
554 if [ ! -d "$store_dir" ]; then
555 cp -a "$oci_dir" "$store_dir"
556 fi
557
558 # Create/update ref symlink (relative path)
559 ln -sfn "../store/sha256/$manifest_digest" "$VXN_IMAGE_CACHE/refs/$ref_key"
560}
561
562vxn_image_cache_inspect() {
563 # Print OCI config info (Entrypoint, Cmd, Env, WorkingDir)
564 local oci_dir="$1"
565 if ! command -v jq >/dev/null 2>&1; then
566 echo "jq not found, cannot inspect image" >&2
567 return 1
568 fi
569 local manifest_digest config_digest manifest_file config_file
570 manifest_digest=$(jq -r '.manifests[0].digest' "$oci_dir/index.json" 2>/dev/null)
571 manifest_file="$oci_dir/blobs/${manifest_digest/://}"
572 [ -f "$manifest_file" ] || { echo "Manifest not found" >&2; return 1; }
573 config_digest=$(jq -r '.config.digest' "$manifest_file" 2>/dev/null)
574 config_file="$oci_dir/blobs/${config_digest/://}"
575 [ -f "$config_file" ] || { echo "Config not found" >&2; return 1; }
576 jq '{
577 Entrypoint: .config.Entrypoint,
578 Cmd: .config.Cmd,
579 Env: .config.Env,
580 WorkingDir: .config.WorkingDir,
581 ExposedPorts: .config.ExposedPorts,
582 Labels: .config.Labels,
583 Architecture: .architecture,
584 Os: .os
585 }' "$config_file"
586}
587
489show_usage() { 588show_usage() {
490 local PROG_NAME=$(basename "$0") 589 local PROG_NAME=$(basename "$0")
491 local RUNTIME_UPPER=$(echo "$VCONTAINER_RUNTIME_CMD" | sed 's/./\U&/') 590 local RUNTIME_UPPER=$(echo "$VCONTAINER_RUNTIME_CMD" | sed 's/./\U&/')
@@ -1470,13 +1569,102 @@ case "$COMMAND" in
1470 # docker image rm → docker rmi 1569 # docker image rm → docker rmi
1471 # docker image pull → docker pull 1570 # docker image pull → docker pull
1472 # etc. 1571 # etc.
1473 [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ] && vxn_not_yet "image"
1474 if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then 1572 if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
1475 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} image requires a subcommand (ls, rm, pull, inspect, tag, push, prune)" >&2 1573 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} image requires a subcommand (ls, rm, pull, inspect, tag, push, prune)" >&2
1476 exit 1 1574 exit 1
1477 fi 1575 fi
1478 SUBCMD="${COMMAND_ARGS[0]}" 1576 SUBCMD="${COMMAND_ARGS[0]}"
1479 SUBCMD_ARGS=("${COMMAND_ARGS[@]:1}") 1577 SUBCMD_ARGS=("${COMMAND_ARGS[@]:1}")
1578 if [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ]; then
1579 # Xen: delegate to cache-based image subcommands
1580 case "$SUBCMD" in
1581 ls|list)
1582 printf "%-40s %-15s %-12s\n" "REPOSITORY:TAG" "SIZE" "CACHED"
1583 if [ -d "$VXN_IMAGE_CACHE/refs" ]; then
1584 for ref in "$VXN_IMAGE_CACHE/refs"/*; do
1585 [ -L "$ref" ] || continue
1586 _vxn_ref_name=$(basename "$ref" | tr '_' '/')
1587 _vxn_store_dir=$(readlink -f "$ref")
1588 _vxn_size="unknown"
1589 [ -d "$_vxn_store_dir" ] && _vxn_size=$(du -sh "$_vxn_store_dir" 2>/dev/null | cut -f1)
1590 printf "%-40s %-15s %-12s\n" "$_vxn_ref_name" "$_vxn_size" "yes"
1591 done
1592 fi
1593 exit 0
1594 ;;
1595 rm|remove)
1596 [ ${#SUBCMD_ARGS[@]} -lt 1 ] && { echo "rmi requires <image>" >&2; exit 1; }
1597 _vxn_normalized=$(vxn_normalize_image_name "${SUBCMD_ARGS[0]}")
1598 _vxn_ref_key=$(vxn_image_ref_key "$_vxn_normalized")
1599 _vxn_ref_link="$VXN_IMAGE_CACHE/refs/$_vxn_ref_key"
1600 if [ -L "$_vxn_ref_link" ]; then
1601 _vxn_store_dir=$(readlink -f "$_vxn_ref_link")
1602 rm -f "$_vxn_ref_link"
1603 _vxn_other_refs=$(find "$VXN_IMAGE_CACHE/refs" -lname "*/$(basename "$_vxn_store_dir")" 2>/dev/null | wc -l)
1604 [ "$_vxn_other_refs" -eq 0 ] && rm -rf "$_vxn_store_dir"
1605 echo "Removed: $_vxn_normalized"
1606 else
1607 echo "Image not found: $_vxn_normalized" >&2; exit 1
1608 fi
1609 exit 0
1610 ;;
1611 pull)
1612 [ ${#SUBCMD_ARGS[@]} -lt 1 ] && { echo "pull requires <image>" >&2; exit 1; }
1613 IMAGE_NAME="${SUBCMD_ARGS[0]}"
1614 command -v skopeo >/dev/null 2>&1 || { echo "skopeo not found" >&2; exit 1; }
1615 _vxn_normalized=$(vxn_normalize_image_name "$IMAGE_NAME")
1616 echo "Pulling $_vxn_normalized..."
1617 _vxn_tmpoci="$(mktemp -d)/oci-image"
1618 if skopeo copy "docker://$_vxn_normalized" "oci:$_vxn_tmpoci:latest" 2>&1; then
1619 vxn_image_cache_store "$IMAGE_NAME" "$_vxn_tmpoci"
1620 rm -rf "$(dirname "$_vxn_tmpoci")"
1621 echo "Pulled: $_vxn_normalized"
1622 else
1623 rm -rf "$(dirname "$_vxn_tmpoci")"
1624 echo "Failed to pull $_vxn_normalized" >&2; exit 1
1625 fi
1626 exit 0
1627 ;;
1628 inspect)
1629 [ ${#SUBCMD_ARGS[@]} -lt 1 ] && { echo "inspect requires <image>" >&2; exit 1; }
1630 _vxn_cached_oci=$(vxn_image_cache_lookup "${SUBCMD_ARGS[0]}")
1631 if [ -n "$_vxn_cached_oci" ]; then
1632 vxn_image_cache_inspect "$_vxn_cached_oci"
1633 else
1634 echo "Image not found: ${SUBCMD_ARGS[0]}" >&2; exit 1
1635 fi
1636 exit 0
1637 ;;
1638 tag)
1639 [ ${#SUBCMD_ARGS[@]} -lt 2 ] && { echo "tag requires <source> <target>" >&2; exit 1; }
1640 _vxn_src_oci=$(vxn_image_cache_lookup "${SUBCMD_ARGS[0]}")
1641 if [ -z "$_vxn_src_oci" ]; then
1642 echo "Image not found: ${SUBCMD_ARGS[0]}" >&2; exit 1
1643 fi
1644 _vxn_target_normalized=$(vxn_normalize_image_name "${SUBCMD_ARGS[1]}")
1645 _vxn_target_ref_key=$(vxn_image_ref_key "$_vxn_target_normalized")
1646 mkdir -p "$VXN_IMAGE_CACHE/refs"
1647 # Point new ref to same store dir
1648 _vxn_store_base=$(basename "$_vxn_src_oci")
1649 ln -sfn "../store/sha256/$_vxn_store_base" "$VXN_IMAGE_CACHE/refs/$_vxn_target_ref_key"
1650 echo "Tagged: $_vxn_target_normalized"
1651 exit 0
1652 ;;
1653 push)
1654 vxn_not_yet "image push"
1655 ;;
1656 prune)
1657 vxn_not_yet "image prune"
1658 ;;
1659 history)
1660 vxn_not_yet "image history"
1661 ;;
1662 *)
1663 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} Unknown image subcommand: $SUBCMD" >&2
1664 exit 1
1665 ;;
1666 esac
1667 fi
1480 case "$SUBCMD" in 1668 case "$SUBCMD" in
1481 ls|list) 1669 ls|list)
1482 run_runtime_command "$VCONTAINER_RUNTIME_CMD images ${SUBCMD_ARGS[*]}" 1670 run_runtime_command "$VCONTAINER_RUNTIME_CMD images ${SUBCMD_ARGS[*]}"
@@ -1525,15 +1713,26 @@ case "$COMMAND" in
1525 ;; 1713 ;;
1526 1714
1527 images) 1715 images)
1528 [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ] && vxn_not_yet "images" 1716 if [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ]; then
1717 printf "%-40s %-15s %-12s\n" "REPOSITORY:TAG" "SIZE" "CACHED"
1718 if [ -d "$VXN_IMAGE_CACHE/refs" ]; then
1719 for ref in "$VXN_IMAGE_CACHE/refs"/*; do
1720 [ -L "$ref" ] || continue
1721 _vxn_ref_name=$(basename "$ref" | tr '_' '/')
1722 _vxn_store_dir=$(readlink -f "$ref")
1723 _vxn_size="unknown"
1724 [ -d "$_vxn_store_dir" ] && _vxn_size=$(du -sh "$_vxn_store_dir" 2>/dev/null | cut -f1)
1725 printf "%-40s %-15s %-12s\n" "$_vxn_ref_name" "$_vxn_size" "yes"
1726 done
1727 fi
1728 exit 0
1729 fi
1529 # runtime images 1730 # runtime images
1530 run_runtime_command "$VCONTAINER_RUNTIME_CMD images ${COMMAND_ARGS[*]}" 1731 run_runtime_command "$VCONTAINER_RUNTIME_CMD images ${COMMAND_ARGS[*]}"
1531 ;; 1732 ;;
1532 1733
1533 pull) 1734 pull)
1534 [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ] && vxn_not_yet "pull"
1535 # runtime pull <image> 1735 # runtime pull <image>
1536 # Daemon mode already has networking enabled, so this works via daemon
1537 if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then 1736 if [ ${#COMMAND_ARGS[@]} -lt 1 ]; then
1538 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} pull requires <image>" >&2 1737 echo -e "${RED}[$VCONTAINER_RUNTIME_NAME]${NC} pull requires <image>" >&2
1539 exit 1 1738 exit 1
@@ -1541,6 +1740,24 @@ case "$COMMAND" in
1541 1740
1542 IMAGE_NAME="${COMMAND_ARGS[0]}" 1741 IMAGE_NAME="${COMMAND_ARGS[0]}"
1543 1742
1743 if [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ]; then
1744 # Host-side pull via skopeo → cache
1745 command -v skopeo >/dev/null 2>&1 || { echo "skopeo not found" >&2; exit 1; }
1746 _vxn_normalized=$(vxn_normalize_image_name "$IMAGE_NAME")
1747 echo "Pulling $_vxn_normalized..."
1748 _vxn_tmpoci="$(mktemp -d)/oci-image"
1749 if skopeo copy "docker://$_vxn_normalized" "oci:$_vxn_tmpoci:latest" 2>&1; then
1750 vxn_image_cache_store "$IMAGE_NAME" "$_vxn_tmpoci"
1751 rm -rf "$(dirname "$_vxn_tmpoci")"
1752 echo "Pulled: $_vxn_normalized"
1753 else
1754 rm -rf "$(dirname "$_vxn_tmpoci")"
1755 echo "Failed to pull $_vxn_normalized" >&2; exit 1
1756 fi
1757 exit 0
1758 fi
1759
1760 # Daemon mode already has networking enabled, so this works via daemon
1544 if daemon_is_running; then 1761 if daemon_is_running; then
1545 # Use daemon mode (already has networking) 1762 # Use daemon mode (already has networking)
1546 run_runtime_command "$VCONTAINER_RUNTIME_CMD pull $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images" 1763 run_runtime_command "$VCONTAINER_RUNTIME_CMD pull $IMAGE_NAME && $VCONTAINER_RUNTIME_CMD images"
@@ -1768,7 +1985,42 @@ case "$COMMAND" in
1768 ;; 1985 ;;
1769 1986
1770 tag|rmi) 1987 tag|rmi)
1771 [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ] && vxn_not_yet "$COMMAND" 1988 if [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ]; then
1989 case "$COMMAND" in
1990 rmi)
1991 [ ${#COMMAND_ARGS[@]} -lt 1 ] && { echo "rmi requires <image>" >&2; exit 1; }
1992 IMAGE_NAME="${COMMAND_ARGS[0]}"
1993 _vxn_normalized=$(vxn_normalize_image_name "$IMAGE_NAME")
1994 _vxn_ref_key=$(vxn_image_ref_key "$_vxn_normalized")
1995 _vxn_ref_link="$VXN_IMAGE_CACHE/refs/$_vxn_ref_key"
1996 if [ -L "$_vxn_ref_link" ]; then
1997 _vxn_store_dir=$(readlink -f "$_vxn_ref_link")
1998 rm -f "$_vxn_ref_link"
1999 # Remove store dir if no other refs point to it
2000 _vxn_other_refs=$(find "$VXN_IMAGE_CACHE/refs" -lname "*/$(basename "$_vxn_store_dir")" 2>/dev/null | wc -l)
2001 [ "$_vxn_other_refs" -eq 0 ] && rm -rf "$_vxn_store_dir"
2002 echo "Removed: $_vxn_normalized"
2003 else
2004 echo "Image not found: $_vxn_normalized" >&2; exit 1
2005 fi
2006 exit 0
2007 ;;
2008 tag)
2009 [ ${#COMMAND_ARGS[@]} -lt 2 ] && { echo "tag requires <source> <target>" >&2; exit 1; }
2010 _vxn_src_oci=$(vxn_image_cache_lookup "${COMMAND_ARGS[0]}")
2011 if [ -z "$_vxn_src_oci" ]; then
2012 echo "Image not found: ${COMMAND_ARGS[0]}" >&2; exit 1
2013 fi
2014 _vxn_target_normalized=$(vxn_normalize_image_name "${COMMAND_ARGS[1]}")
2015 _vxn_target_ref_key=$(vxn_image_ref_key "$_vxn_target_normalized")
2016 mkdir -p "$VXN_IMAGE_CACHE/refs"
2017 _vxn_store_base=$(basename "$_vxn_src_oci")
2018 ln -sfn "../store/sha256/$_vxn_store_base" "$VXN_IMAGE_CACHE/refs/$_vxn_target_ref_key"
2019 echo "Tagged: $_vxn_target_normalized"
2020 exit 0
2021 ;;
2022 esac
2023 fi
1772 # Commands that work with existing images 2024 # Commands that work with existing images
1773 run_runtime_command "$VCONTAINER_RUNTIME_CMD $COMMAND ${COMMAND_ARGS[*]}" 2025 run_runtime_command "$VCONTAINER_RUNTIME_CMD $COMMAND ${COMMAND_ARGS[*]}"
1774 ;; 2026 ;;
@@ -1872,7 +2124,17 @@ case "$COMMAND" in
1872 ;; 2124 ;;
1873 2125
1874 inspect) 2126 inspect)
1875 [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ] && vxn_not_yet "inspect" 2127 if [ "${VCONTAINER_HYPERVISOR:-}" = "xen" ]; then
2128 [ ${#COMMAND_ARGS[@]} -lt 1 ] && { echo "inspect requires <image>" >&2; exit 1; }
2129 _vxn_cached_oci=$(vxn_image_cache_lookup "${COMMAND_ARGS[0]}")
2130 if [ -n "$_vxn_cached_oci" ]; then
2131 vxn_image_cache_inspect "$_vxn_cached_oci"
2132 exit 0
2133 fi
2134 # Not in image cache — could be a container name on Xen
2135 echo "Not found: ${COMMAND_ARGS[0]}" >&2
2136 exit 1
2137 fi
1876 # Inspect container or image 2138 # Inspect container or image
1877 run_runtime_command "$VCONTAINER_RUNTIME_CMD inspect ${COMMAND_ARGS[*]}" 2139 run_runtime_command "$VCONTAINER_RUNTIME_CMD inspect ${COMMAND_ARGS[*]}"
1878 ;; 2140 ;;
diff --git a/recipes-core/vxn/vxn_1.0.bb b/recipes-core/vxn/vxn_1.0.bb
index a08dac09..278911d5 100644
--- a/recipes-core/vxn/vxn_1.0.bb
+++ b/recipes-core/vxn/vxn_1.0.bb
@@ -228,12 +228,14 @@ do_install() {
228 install -m 0755 ${S}/vpdmn.sh ${D}${bindir}/vpdmn 228 install -m 0755 ${S}/vpdmn.sh ${D}${bindir}/vpdmn
229 229
230 # Docker daemon config: register vxn-oci-runtime (vxn-docker-config sub-package) 230 # Docker daemon config: register vxn-oci-runtime (vxn-docker-config sub-package)
231 # no-new-privileges=false is needed because vxn ignores Linux security features. 231 # iptables=false: Docker's default FORWARD DROP policy blocks DHCP and
232 # Users must use --network=none or --network=host with vxn containers since 232 # bridged traffic for Xen DomU vifs on xenbr0. Since vxn containers are
233 # Xen DomUs have their own kernel network stack and Docker's veth/namespace 233 # full VMs with their own network stack, Docker's iptables rules are
234 # setup is incompatible with VM-based runtimes. 234 # unnecessary and harmful. Note: bridge networking is left enabled so
235 # that 'docker pull' works (needs bridge for DNS). Users must pass
236 # --network=none for 'docker run' (veth/netns incompatible with VMs).
235 install -d ${D}${sysconfdir}/docker 237 install -d ${D}${sysconfdir}/docker
236 printf '{\n "runtimes": {\n "vxn": {\n "path": "/usr/bin/vxn-oci-runtime"\n }\n },\n "default-runtime": "vxn"\n}\n' \ 238 printf '{\n "runtimes": {\n "vxn": {\n "path": "/usr/bin/vxn-oci-runtime"\n }\n },\n "default-runtime": "vxn",\n "iptables": false\n}\n' \
237 > ${D}${sysconfdir}/docker/daemon.json 239 > ${D}${sysconfdir}/docker/daemon.json
238 240
239 # Podman config: register vxn-oci-runtime (vxn-podman-config sub-package) 241 # Podman config: register vxn-oci-runtime (vxn-podman-config sub-package)