Quadlet - Add support for .pod units

Add support for .pod unit files with only PodmanArgs, GlobalArgs, ContainersConfModule and PodName
Add support for linking .container units with .pod ones
Add e2e and system tests
Add to man page

Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
This commit is contained in:
Ygal Blum
2023-11-23 18:26:17 +02:00
parent b7ca114078
commit 6b2f48129e
12 changed files with 444 additions and 7 deletions

View File

@ -54,6 +54,7 @@ var (
".kube": 3,
".network": 2,
".image": 1,
".pod": 4,
}
)
@ -389,6 +390,23 @@ func warnIfAmbiguousName(unit *parser.UnitFile, group string) {
}
}
func generatePodsInfoMap(units []*parser.UnitFile) map[string]*quadlet.PodInfo {
podsInfoMap := make(map[string]*quadlet.PodInfo)
for _, unit := range units {
if !strings.HasSuffix(unit.Filename, ".pod") {
continue
}
serviceName := quadlet.GetPodServiceName(unit)
podsInfoMap[unit.Filename] = &quadlet.PodInfo{
ServiceName: serviceName,
Containers: make([]string, 0),
}
}
return podsInfoMap
}
func main() {
if err := process(); err != nil {
Logf("%s", err.Error())
@ -478,6 +496,9 @@ func process() error {
return getOrder(i) < getOrder(j)
})
// Generate the PodsInfoMap to allow containers to link to their pods and add themselves to the pod's containers list
podsInfoMap := generatePodsInfoMap(units)
// A map of network/volume unit file-names, against their calculated names, as needed by Podman.
var resourceNames = make(map[string]string)
@ -489,7 +510,7 @@ func process() error {
switch {
case strings.HasSuffix(unit.Filename, ".container"):
warnIfAmbiguousName(unit, quadlet.ContainerGroup)
service, err = quadlet.ConvertContainer(unit, resourceNames, isUserFlag)
service, err = quadlet.ConvertContainer(unit, resourceNames, isUserFlag, podsInfoMap)
case strings.HasSuffix(unit.Filename, ".volume"):
warnIfAmbiguousName(unit, quadlet.VolumeGroup)
service, name, err = quadlet.ConvertVolume(unit, unit.Filename, resourceNames)
@ -500,6 +521,8 @@ func process() error {
case strings.HasSuffix(unit.Filename, ".image"):
warnIfAmbiguousName(unit, quadlet.ImageGroup)
service, name, err = quadlet.ConvertImage(unit)
case strings.HasSuffix(unit.Filename, ".pod"):
service, err = quadlet.ConvertPod(unit, unit.Filename, podsInfoMap)
default:
Logf("Unsupported file type %q", unit.Filename)
continue

View File

@ -6,7 +6,7 @@ podman\-systemd.unit - systemd units using Podman Quadlet
## SYNOPSIS
*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image
*name*.container, *name*.volume, *name*.network, *name*.kube *name*.image, *name*.pod
### Podman unit search path
@ -35,13 +35,11 @@ the [Service] table and [Install] tables pass directly to systemd and are handle
See systemd.unit(5) man page for more information.
The Podman generator reads the search paths above and reads files with the extensions `.container`
`.volume` and `*.kube`, and for each file generates a similarly named `.service` file. Be aware that
`.volume`, `.network`, `.pod` and `.kube`, and for each file generates a similarly named `.service` file. Be aware that
existing vendor services (i.e., in `/usr/`) are replaced if they have the same name. The generated unit files can
be started and managed with `systemctl` like any other systemd service. `systemctl {--user} list-unit-files`
lists existing unit files on the system.
Files with the `.network` extension are only read if they are mentioned in a `.container` file. See the `Network=` key.
The Podman files use the same format as [regular systemd unit files](https://www.freedesktop.org/software/systemd/man/systemd.syntax.html).
Each file type has a custom section (for example, `[Container]`) that is handled by Podman, and all
other sections are passed on untouched, allowing the use of any normal systemd configuration options
@ -72,7 +70,8 @@ Quadlet requires the use of cgroup v2, use `podman info --format {{.Host.Cgroups
### Service Type
By default, the `Type` field of the `Service` section of the Quadlet file does not need to be set.
Quadlet will set it to `notify` for `.container` and `.kube` files and to `oneshot` for `.volume`, `.network` and `.image` files.
Quadlet will set it to `notify` for `.container` and `.kube` files,
`forking` for `.pod` files, and `oneshot` for `.volume`, `.network` and `.image` files.
However, `Type` may be explicitly set to `oneshot` for `.container` and `.kube` files when no containers are expected
to run once `podman` exits.
@ -190,6 +189,7 @@ Valid options for `[Container]` are listed below:
| Rootfs=/var/lib/rootfs | --rootfs /var/lib/rootfs |
| Notify=true | --sdnotify container |
| PidsLimit=10000 | --pids-limit 10000 |
| Pod=pod-name | --pod=pod-name |
| PodmanArgs=--add-host foobar | --add-host foobar |
| PublishPort=50-59 | --publish 50-59 |
| Pull=never | --pull=never |
@ -501,6 +501,14 @@ of startup on its own.
Tune the container's pids limit.
This is equivalent to the Podman `--pids-limit` option.
### `Pod=`
Specify a Quadlet `.pod` unit to link the container to.
The value must take the form of `<name>.pod` and the `.pod` unit must exist.
Quadlet will add all the necessary parameters to link between the container and the pod and between their corresponding services.
### `PodmanArgs=`
This key contains a list of arguments passed directly to the end of the `podman run` command
@ -658,6 +666,69 @@ Working directory inside the container.
The default working directory for running binaries within a container is the root directory (/). The image developer can set a different default with the WORKDIR instruction. This option overrides the working directory by using the -w option.
## Pod units [Pod]
Pod units are named with a `.pod` extension and contain a `[Pod]` section describing
the pod that is created and run as a service. The resulting service file contains a line like
`ExecStartPre=podman pod create …`, and most of the keys in this section control the command-line
options passed to Podman.
By default, the Podman pod has the same name as the unit, but with a `systemd-` prefix, i.e.
a `$name.pod` file creates a `$name-pod.service` unit and a `systemd-$name` Podman pod. The
`PodName` option allows for overriding this default name with a user-provided one.
Valid options for `[Container]` are listed below:
| **[Pod] options** | **podman container create equivalent** |
|-------------------------------------|----------------------------------------|
| ContainersConfModule=/etc/nvd\.conf | --module=/etc/nvd\.conf |
| GlobalArgs=--log-level=debug | --log-level=debug |
| PodmanArgs=\-\-cpus=2 | --cpus=2 |
| PodName=name | --name=name |
Supported keys in the `[Pod]` section are:
### `ContainersConfModule=`
Load the specified containers.conf(5) module. Equivalent to the Podman `--module` option.
This key can be listed multiple times.
### `GlobalArgs=`
This key contains a list of arguments passed directly between `podman` and `kube`
in the generated file (right before the image name in the command line). It can be used to
access Podman features otherwise unsupported by the generator. Since the generator is unaware
of what unexpected interactions can be caused by these arguments, it is not recommended to use
this option.
The format of this is a space separated list of arguments, which can optionally be individually
escaped to allow inclusion of whitespace and other control characters.
This key can be listed multiple times.
### `PodmanArgs=`
This key contains a list of arguments passed directly to the end of the `podman kube play` command
in the generated file (right before the path to the yaml file in the command line). It can be used to
access Podman features otherwise unsupported by the generator. Since the generator is unaware
of what unexpected interactions can be caused by these arguments, is not recommended to use
this option.
The format of this is a space separated list of arguments, which can optionally be individually
escaped to allow inclusion of whitespace and other control characters.
This key can be listed multiple times.
### `PodName=`
The (optional) name of the Podman pod. If this is not specified, the default value
of `systemd-%N` is used, which is the same as the service name but with a `systemd-`
prefix to avoid conflicts with user-managed containers.
Please note that pods and containers cannot have the same name.
So, if PodName is set, it must not conflict with any container.
## Kube units [Kube]
Kube units are named with a `.kube` extension and contain a `[Kube]` section describing
@ -1295,6 +1366,22 @@ IPRange=172.16.0.0/28
Label=org.test.Key=value
```
Example for Container in a Pod:
`test.pod`
```
[Pod]
PodName=test
```
`centos.container`
```
[Container]
Image=quay.io/centos/centos:latest
Exec=sh -c "sleep inf"
Pod=test.pod
```
## SEE ALSO
**[systemd.unit(5)](https://www.freedesktop.org/software/systemd/man/systemd.unit.html)**,
**[systemd.service(5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html)**,

View File

@ -29,6 +29,7 @@ const (
InstallGroup = "Install"
KubeGroup = "Kube"
NetworkGroup = "Network"
PodGroup = "Pod"
ServiceGroup = "Service"
UnitGroup = "Unit"
VolumeGroup = "Volume"
@ -36,6 +37,7 @@ const (
XContainerGroup = "X-Container"
XKubeGroup = "X-Kube"
XNetworkGroup = "X-Network"
XPodGroup = "X-Pod"
XVolumeGroup = "X-Volume"
XImageGroup = "X-Image"
)
@ -114,6 +116,8 @@ const (
KeyOS = "OS"
KeyPidsLimit = "PidsLimit"
KeyPodmanArgs = "PodmanArgs"
KeyPodName = "PodName"
KeyPod = "Pod"
KeyPublishPort = "PublishPort"
KeyPull = "Pull"
KeyReadOnly = "ReadOnly"
@ -153,6 +157,11 @@ const (
KeyYaml = "Yaml"
)
type PodInfo struct {
ServiceName string
Containers []string
}
var (
validPortRange = regexp.Delayed(`\d+(-\d+)?(/udp|/tcp)?$`)
@ -199,6 +208,7 @@ var (
KeyNoNewPrivileges: true,
KeyNotify: true,
KeyPidsLimit: true,
KeyPod: true,
KeyPodmanArgs: true,
KeyPublishPort: true,
KeyPull: true,
@ -307,6 +317,13 @@ var (
KeyTLSVerify: true,
KeyVariant: true,
}
supportedPodKeys = map[string]bool{
KeyContainersConfModule: true,
KeyGlobalArgs: true,
KeyPodmanArgs: true,
KeyPodName: true,
}
)
func replaceExtension(name string, extension string, extraPrefix string, extraSuffix string) string {
@ -382,7 +399,7 @@ func usernsOpts(kind string, opts []string) string {
// service file (unit file with Service group) based on the options in the
// Container group.
// The original Container group is kept around as X-Container.
func ConvertContainer(container *parser.UnitFile, names map[string]string, isUser bool) (*parser.UnitFile, error) {
func ConvertContainer(container *parser.UnitFile, names map[string]string, isUser bool, podsInfoMap map[string]*PodInfo) (*parser.UnitFile, error) {
service := container.Dup()
service.Filename = replaceExtension(container.Filename, ".service", "", "")
@ -767,6 +784,10 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse
podman.add("--pull", pull)
}
if err := handlePod(container, service, ContainerGroup, podsInfoMap, podman); err != nil {
return nil, err
}
handlePodmanArgs(container, ContainerGroup, podman)
if len(image) > 0 {
@ -1225,6 +1246,95 @@ func ConvertImage(image *parser.UnitFile) (*parser.UnitFile, string, error) {
return service, imageName, nil
}
func GetPodServiceName(podUnit *parser.UnitFile) string {
return replaceExtension(podUnit.Filename, "", "", "-pod")
}
func ConvertPod(podUnit *parser.UnitFile, name string, podsInfoMap map[string]*PodInfo) (*parser.UnitFile, error) {
podInfo, ok := podsInfoMap[podUnit.Filename]
if !ok {
return nil, fmt.Errorf("internal error while processing pod %s", podUnit.Filename)
}
service := podUnit.Dup()
service.Filename = replaceExtension(podInfo.ServiceName, ".service", "", "")
if podUnit.Path != "" {
service.Add(UnitGroup, "SourcePath", podUnit.Path)
}
if err := checkForUnknownKeys(podUnit, PodGroup, supportedPodKeys); err != nil {
return nil, err
}
// Derive pod name from unit name (with added prefix), or use user-provided name.
podName, ok := podUnit.Lookup(PodGroup, KeyPodName)
if !ok || len(podName) == 0 {
podName = replaceExtension(name, "", "systemd-", "")
}
/* Rename old Pod group to x-Pod so that systemd ignores it */
service.RenameGroup(PodGroup, XPodGroup)
// Need the containers filesystem mounted to start podman
service.Add(UnitGroup, "RequiresMountsFor", "%t/containers")
for _, containerService := range podInfo.Containers {
service.Add(UnitGroup, "Wants", containerService)
service.Add(UnitGroup, "Before", containerService)
}
if !podUnit.HasKey(ServiceGroup, "SyslogIdentifier") {
service.Set(ServiceGroup, "SyslogIdentifier", "%N")
}
execStart := createBasePodmanCommand(podUnit, PodGroup)
execStart.add("pod", "start", "--pod-id-file=%t/%N.pod-id")
service.AddCmdline(ServiceGroup, "ExecStart", execStart.Args)
execStop := createBasePodmanCommand(podUnit, PodGroup)
execStop.add("pod", "stop")
execStop.add(
"--pod-id-file=%t/%N.pod-id",
"--ignore",
"--time=10",
)
service.AddCmdline(ServiceGroup, "ExecStop", execStop.Args)
execStopPost := createBasePodmanCommand(podUnit, PodGroup)
execStopPost.add("pod", "rm")
execStopPost.add(
"--pod-id-file=%t/%N.pod-id",
"--ignore",
"--force",
)
service.AddCmdline(ServiceGroup, "ExecStopPost", execStopPost.Args)
execStartPre := createBasePodmanCommand(podUnit, PodGroup)
execStartPre.add("pod", "create")
execStartPre.add(
"--infra-conmon-pidfile=%t/%N.pid",
"--pod-id-file=%t/%N.pod-id",
"--exit-policy=stop",
"--replace",
)
execStartPre.addf("--name=%s", podName)
handlePodmanArgs(podUnit, PodGroup, execStartPre)
service.AddCmdline(ServiceGroup, "ExecStartPre", execStartPre.Args)
service.Setv(ServiceGroup,
"Environment", "PODMAN_SYSTEMD_UNIT=%n",
"Type", "forking",
"Restart", "on-failure",
"PIDFile", "%t/%N.pid",
)
return service, nil
}
func handleUser(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline) error {
user, hasUser := unitFile.Lookup(groupName, KeyUser)
okUser := hasUser && len(user) > 0
@ -1685,3 +1795,26 @@ func createBasePodmanCommand(unitFile *parser.UnitFile, groupName string) *Podma
return podman
}
func handlePod(quadletUnitFile, serviceUnitFile *parser.UnitFile, groupName string, podsInfoMap map[string]*PodInfo, podman *PodmanCmdline) error {
pod, ok := quadletUnitFile.Lookup(groupName, KeyPod)
if ok && len(pod) > 0 {
if !strings.HasSuffix(pod, ".pod") {
return fmt.Errorf("pod %s is not Quadlet based", pod)
}
podInfo, ok := podsInfoMap[pod]
if !ok {
return fmt.Errorf("quadlet pod unit %s does not exist", pod)
}
podman.add("--pod-id-file", fmt.Sprintf("%%t/%s.pod-id", podInfo.ServiceName))
podServiceName := fmt.Sprintf("%s.service", podInfo.ServiceName)
serviceUnitFile.Add(UnitGroup, "BindsTo", podServiceName)
serviceUnitFile.Add(UnitGroup, "After", podServiceName)
podInfo.Containers = append(podInfo.Containers, serviceUnitFile.Filename)
}
return nil
}

View File

@ -0,0 +1,9 @@
## assert-key-is Unit RequiresMountsFor "%t/containers"
## assert-key-is Service Type forking
## assert-key-is Service SyslogIdentifier "%N"
## assert-key-is-regex Service ExecStartPre ".*/podman pod create --infra-conmon-pidfile=%t/%N.pid --pod-id-file=%t/%N.pod-id --exit-policy=stop --replace --name=systemd-basic"
## assert-key-is-regex Service ExecStart ".*/podman pod start --pod-id-file=%t/%N.pod-id"
## assert-key-is-regex Service ExecStop ".*/podman pod stop --pod-id-file=%t/%N.pod-id --ignore --time=10"
## assert-key-is-regex Service ExecStopPost ".*/podman pod rm --pod-id-file=%t/%N.pod-id --ignore --force"
[Pod]

View File

@ -0,0 +1,4 @@
## assert-podman-pre-args "--name=test-pod"
[Pod]
PodName=test-pod

View File

@ -0,0 +1,6 @@
## assert-failed
## assert-stderr-contains "pod test-pod is not Quadlet based"
[Container]
Image=localhost/imagename
Pod=test-pod

View File

@ -0,0 +1,6 @@
## assert-failed
## assert-stderr-contains "quadlet pod unit not-found.pod does not exist"
[Container]
Image=localhost/imagename
Pod=not-found.pod

View File

@ -0,0 +1,13 @@
## assert-podman-pre-args "--foo"
## assert-podman-pre-args "--bar"
## assert-podman-pre-args "--also"
## assert-podman-pre-args "--with-key=value"
## assert-podman-pre-args "--with-space" "yes"
[Pod]
PodmanArgs="--foo" \
--bar
PodmanArgs=--also
PodmanArgs=--with-key=value
PodmanArgs=--with-space yes

View File

@ -39,6 +39,8 @@ func loadQuadletTestcase(path string) *quadletTestcase {
service += "-network"
case ".image":
service += "-image"
case ".pod":
service += "-pod"
}
service += ".service"
@ -331,6 +333,46 @@ func (t *quadletTestcase) assertStartPodmanFinalArgsRegex(args []string, unit *p
return t.assertPodmanFinalArgsRegex(args, unit, "ExecStart")
}
func (t *quadletTestcase) assertStartPrePodmanArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStartPre", false, false)
}
func (t *quadletTestcase) assertStartPrePodmanArgsRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStartPre", true, false)
}
func (t *quadletTestcase) assertStartPrePodmanGlobalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStartPre", false, true)
}
func (t *quadletTestcase) assertStartPrePodmanGlobalArgsRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStartPre", true, true)
}
func (t *quadletTestcase) assertStartPrePodmanArgsKeyVal(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", false, false)
}
func (t *quadletTestcase) assertStartPrePodmanArgsKeyValRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", true, false)
}
func (t *quadletTestcase) assertStartPrePodmanGlobalArgsKeyVal(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", false, true)
}
func (t *quadletTestcase) assertStartPrePodmanGlobalArgsKeyValRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgsKeyVal(args, unit, "ExecStartPre", true, true)
}
func (t *quadletTestcase) assertStartPrePodmanFinalArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgs(args, unit, "ExecStartPre")
}
func (t *quadletTestcase) assertStartPrePodmanFinalArgsRegex(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanFinalArgsRegex(args, unit, "ExecStartPre")
}
func (t *quadletTestcase) assertStopPodmanArgs(args []string, unit *parser.UnitFile) bool {
return t.assertPodmanArgs(args, unit, "ExecStop", false, false)
}
@ -440,6 +482,26 @@ func (t *quadletTestcase) doAssert(check []string, unit *parser.UnitFile, sessio
ok = t.assertStartPodmanFinalArgs(args, unit)
case "assert-podman-final-args-regex":
ok = t.assertStartPodmanFinalArgsRegex(args, unit)
case "assert-podman-pre-args":
ok = t.assertStartPrePodmanArgs(args, unit)
case "assert-podman-pre-args-regex":
ok = t.assertStartPrePodmanArgsRegex(args, unit)
case "assert-podman-pre-args-key-val":
ok = t.assertStartPrePodmanArgsKeyVal(args, unit)
case "assert-podman-pre-args-key-val-regex":
ok = t.assertStartPrePodmanArgsKeyValRegex(args, unit)
case "assert-podman-pre-global-args":
ok = t.assertStartPrePodmanGlobalArgs(args, unit)
case "assert-podman-pre-global-args-regex":
ok = t.assertStartPrePodmanGlobalArgsRegex(args, unit)
case "assert-podman-pre-global-args-key-val":
ok = t.assertStartPrePodmanGlobalArgsKeyVal(args, unit)
case "assert-podman-pre-global-args-key-val-regex":
ok = t.assertStartPrePodmanGlobalArgsKeyValRegex(args, unit)
case "assert-podman-pre-final-args":
ok = t.assertStartPrePodmanFinalArgs(args, unit)
case "assert-podman-pre-final-args-regex":
ok = t.assertStartPrePodmanFinalArgsRegex(args, unit)
case "assert-symlink":
ok = t.assertSymlink(args, unit)
case "assert-podman-stop-args":
@ -714,6 +776,8 @@ BOGUS=foo
Entry("notify.container", "notify.container", 0, ""),
Entry("oneshot.container", "oneshot.container", 0, ""),
Entry("other-sections.container", "other-sections.container", 0, ""),
Entry("pod.non-quadlet.container", "pod.non-quadlet.container", 1, "converting \"pod.non-quadlet.container\": pod test-pod is not Quadlet based"),
Entry("pod.not-found.container", "pod.not-found.container", 1, "converting \"pod.not-found.container\": quadlet pod unit not-found.pod does not exist"),
Entry("podmanargs.container", "podmanargs.container", 0, ""),
Entry("ports.container", "ports.container", 0, ""),
Entry("ports_ipv6.container", "ports_ipv6.container", 0, ""),
@ -821,6 +885,10 @@ BOGUS=foo
Entry("Image - Arch and OS", "arch-os.image", 0, ""),
Entry("Image - global args", "globalargs.image", 0, ""),
Entry("Image - Containers Conf Modules", "containersconfmodule.image", 0, ""),
Entry("basic.pod", "basic.pod", 0, ""),
Entry("name.pod", "name.pod", 0, ""),
Entry("podmanargs.pod", "podmanargs.pod", 0, ""),
)
})

View File

@ -1412,4 +1412,62 @@ EOF
run_podman rmi --ignore $(pause_image)
}
@test "quadlet - pod simple" {
local quadlet_tmpdir=$PODMAN_TMPDIR/quadlets
local test_pod_name=pod_test_$(random_string)
local quadlet_pod_unit=$test_pod_name.pod
local quadlet_pod_file=$PODMAN_TMPDIR/$quadlet_pod_unit
cat > $quadlet_pod_file <<EOF
[Pod]
PodName=$test_pod_name
EOF
local quadlet_container_unit=pod_test_$(random_string).container
local quadlet_container_file=$PODMAN_TMPDIR/$quadlet_container_unit
cat > $quadlet_container_file <<EOF
[Container]
Image=$IMAGE
Exec=sh -c "echo STARTED CONTAINER; echo "READY=1" | socat -u STDIN unix-sendto:\$NOTIFY_SOCKET; sleep inf"
Pod=$quadlet_pod_unit
EOF
# Use the same directory for all quadlet files to make sure later steps access previous ones
mkdir $quadlet_tmpdir
# Have quadlet create the systemd unit file for the pod unit
run_quadlet "$quadlet_pod_file" "$quadlet_tmpdir"
# Save the pod service name since the variable will be overwritten
local pod_service=$QUADLET_SERVICE_NAME
# Have quadlet create the systemd unit file for the container unit
run_quadlet "$quadlet_container_file" "$quadlet_tmpdir"
local container_service=$QUADLET_SERVICE_NAME
local container_name=$QUADLET_CONTAINER_NAME
# Start the pod service
service_setup $pod_service
# Pod should exist
run_podman pod exists ${test_pod_name}
# Wait for systemd to activate the container service
wait_for_command_output "systemctl show --property=ActiveState $container_service" "ActiveState=active"
# Container should exist
run_podman container exists ${container_name}
# Shutdown the service
service_cleanup $pod_service inactive
# The service of the container should be active
run systemctl show --property=ActiveState "$container_service"
assert "ActiveState=failed" \
"quadlet - pod base: container service ActiveState"
# Container should not exist
run_podman 1 container exists ${container_name}
run_podman rmi $(pause_image)
}
# vim: filetype=sh

View File

@ -1139,5 +1139,33 @@ function sleep_to_next_second() {
sleep 0.$(printf '%04d' $((10000 - 10#$(date +%4N))))
}
function wait_for_command_output() {
local cmd="$1"
local want="$2"
local tries=20
local sleep_delay=0.5
case "${#*}" in
2) ;;
4) tries="$3"
sleep_delay="$4"
;;
*) die "Internal error: 'wait_for_command_output' requires two or four arguments" ;;
esac
while [[ $tries -gt 0 ]]; do
echo "$_LOG_PROMPT $cmd"
run $cmd
echo "$output"
if [[ "$output" = "$want" ]]; then
return
fi
sleep $sleep_delay
tries=$((tries - 1))
done
die "Timed out waiting for '$cmd' to return '$want'"
}
# END miscellaneous tools
###############################################################################

View File

@ -59,6 +59,8 @@ quadlet_to_service_name() {
suffix="-network"
elif [ "$extension" == "image" ]; then
suffix="-image"
elif [ "$extension" == "pod" ]; then
suffix="-pod"
fi
echo "$filename$suffix.service"