Skip to content

Commit

Permalink
Add emptyDir volume support to kube play
Browse files Browse the repository at this point in the history
When a kube yaml has a volume set as empty dir, podman
will create an anonymous volume with the empty dir name and
attach it to the containers running in the pod. When the pod
is removed, the empy dir volume created is also removed.

Add tests and docs for this as well.

Signed-off-by: Urvashi Mohnani <[email protected]>
  • Loading branch information
umohnani8 committed Aug 30, 2022
1 parent 57441b4 commit 98169c2
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/source/markdown/podman-kube-play.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Currently, the supported Kubernetes kinds are:

`Kubernetes Pods or Deployments`

Only two volume types are supported by kube play, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
Only three volume types are supported by kube play, the *hostPath*, *emptyDir*, and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. When using an *emptyDir* volume, podman creates an anonymous volume that is attached the containers running inside the pod and is deleted once the pod is removed.

Note: When playing a kube YAML with init containers, the init container will be created with init type value `once`. To change the default type, use the `io.podman.annotations.init.container.type` annotation to set the type to `always`.

Expand Down
3 changes: 3 additions & 0 deletions libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ type ContainerNamedVolume struct {
Dest string `json:"dest"`
// Options are fstab style mount options
Options []string `json:"options,omitempty"`
// IsAnonymous sets the named volume as anonymous even if it has a name
// This is used for emptyDir volumes from a kube yaml
IsAnonymous bool `json:"setAnonymous,omitempty"`
}

// ContainerOverlayVolume is a overlay volume that will be mounted into the
Expand Down
7 changes: 4 additions & 3 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -1413,9 +1413,10 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
}

ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{
Name: vol.Name,
Dest: vol.Dest,
Options: mountOpts,
Name: vol.Name,
Dest: vol.Dest,
Options: mountOpts,
IsAnonymous: vol.IsAnonymous,
})
}

Expand Down
11 changes: 8 additions & 3 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,11 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
return nil, fmt.Errorf("error retrieving named volume %s for new container: %w", vol.Name, err)
}
}
if vol.IsAnonymous {
// If SetAnonymous is true, make this an anonymous volume
// this is needed for emptyDir volumes from kube yamls
isAnonymous = true
}

logrus.Debugf("Creating new volume %s for container", vol.Name)

Expand Down Expand Up @@ -814,11 +819,11 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo
// Ignore error, since podman will report original error
volumesFrom, _ := c.volumesFrom()
if len(volumesFrom) > 0 {
logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v)
logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v.Name)
continue
}
}
logrus.Errorf("Cleaning up volume (%s): %v", v, err)
logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err)
}
}
}
Expand Down Expand Up @@ -968,7 +973,7 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol
continue
}
if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
logrus.Errorf("Cleaning up volume (%s): %v", v, err)
logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/abi/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
}

// Go through the volumes and create a podman volume for all volumes that have been
// defined by a configmap
// defined by a configmap or secret
for _, v := range volumes {
if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional {
vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source))
Expand Down
4 changes: 4 additions & 0 deletions pkg/k8s.io/api/core/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ type VolumeSource struct {
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
// Secret represents a secret that should be mounted as a volume
Secret *SecretVolumeSource `json:"secret,omitempty"`
// emptyDir represents a temporary directory that shares a pod's lifetime.
// More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
// +optional
EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty"`
}

// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
Expand Down
7 changes: 4 additions & 3 deletions pkg/specgen/generate/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
var vols []*libpod.ContainerNamedVolume
for _, v := range volumes {
vols = append(vols, &libpod.ContainerNamedVolume{
Name: v.Name,
Dest: v.Dest,
Options: v.Options,
Name: v.Name,
Dest: v.Dest,
Options: v.Options,
IsAnonymous: v.IsAnonymous,
})
}
options = append(options, libpod.WithNamedVolumes(vols))
Expand Down
9 changes: 8 additions & 1 deletion pkg/specgen/generate/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,15 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Name: volumeSource.Source,
Options: options,
}

s.Volumes = append(s.Volumes, &secretVolume)
case KubeVolumeTypeEmptyDir:
emptyDirVolume := specgen.NamedVolume{
Dest: volume.MountPath,
Name: volumeSource.Source,
Options: options,
IsAnonymous: true,
}
s.Volumes = append(s.Volumes, &emptyDirVolume)
default:
return nil, errors.New("unsupported volume source type")
}
Expand Down
14 changes: 11 additions & 3 deletions pkg/specgen/generate/kube/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
KubeVolumeTypeBlockDevice
KubeVolumeTypeCharDevice
KubeVolumeTypeSecret
KubeVolumeTypeEmptyDir
)

//nolint:revive
Expand Down Expand Up @@ -219,8 +220,13 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config
return kv, nil
}

// Create a kubeVolume for an emptyDir volume
func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name string) (*KubeVolume, error) {
return &KubeVolume{Type: KubeVolumeTypeEmptyDir, Source: name}, nil
}

// Create a KubeVolume from one of the supported VolumeSource
func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (*KubeVolume, error) {
func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName string) (*KubeVolume, error) {
switch {
case volumeSource.HostPath != nil:
return VolumeFromHostPath(volumeSource.HostPath)
Expand All @@ -230,8 +236,10 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, s
return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps)
case volumeSource.Secret != nil:
return VolumeFromSecret(volumeSource.Secret, secretsManager)
case volumeSource.EmptyDir != nil:
return VolumeFromEmptyDir(volumeSource.EmptyDir, volName)
default:
return nil, errors.New("HostPath, ConfigMap, and PersistentVolumeClaim are currently the only supported VolumeSource")
return nil, errors.New("HostPath, ConfigMap, EmptyDir, and PersistentVolumeClaim are currently the only supported VolumeSource")
}
}

Expand All @@ -240,7 +248,7 @@ func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secre
volumes := make(map[string]*KubeVolume)

for _, specVolume := range specVolumes {
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager)
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name)
if err != nil {
return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/specgen/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type NamedVolume struct {
Dest string
// Options are options that the named volume will be mounted with.
Options []string
// IsAnonymous sets the named volume as anonymous even if it has a name
// This is used for emptyDir volumes from a kube yaml
IsAnonymous bool
}

// OverlayVolume holds information about a overlay volume that will be mounted into
Expand Down
51 changes: 51 additions & 0 deletions test/e2e/play_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,9 @@ spec:
volumes:
{{ range . }}
- name: {{ .Name }}
{{- if (eq .VolumeType "EmptyDir") }}
emptyDir: {}
{{- end }}
{{- if (eq .VolumeType "HostPath") }}
hostPath:
path: {{ .HostPath.Path }}
Expand Down Expand Up @@ -1242,12 +1245,15 @@ type ConfigMap struct {
Optional bool
}

type EmptyDir struct{}

type Volume struct {
VolumeType string
Name string
HostPath
PersistentVolumeClaim
ConfigMap
EmptyDir
}

// getHostPathVolume takes a type and a location for a HostPath
Expand Down Expand Up @@ -1289,6 +1295,14 @@ func getConfigMapVolume(vName string, items []map[string]string, optional bool)
}
}

func getEmptyDirVolume() *Volume {
return &Volume{
VolumeType: "EmptyDir",
Name: defaultVolName,
EmptyDir: EmptyDir{},
}
}

type Env struct {
Name string
Value string
Expand Down Expand Up @@ -2762,6 +2776,43 @@ VOLUME %s`, ALPINE, hostPathDir+"/")
Expect(kube).Should(Exit(0))
})

It("podman play kube with emptyDir volume", func() {
podName := "test-pod"
ctrName1 := "vol-test-ctr"
ctrName2 := "vol-test-ctr-2"
ctr1 := getCtr(withVolumeMount("/test-emptydir", false), withImage(BB), withName(ctrName1))
ctr2 := getCtr(withVolumeMount("/test-emptydir-2", false), withImage(BB), withName(ctrName2))
pod := getPod(withPodName(podName), withVolume(getEmptyDirVolume()), withCtr(ctr1), withCtr(ctr2))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

emptyDirCheck1 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName1, "ls", "/test-emptydir"})
emptyDirCheck1.WaitWithDefaultTimeout()
Expect(emptyDirCheck1).Should(Exit(0))

emptyDirCheck2 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName2, "ls", "/test-emptydir-2"})
emptyDirCheck2.WaitWithDefaultTimeout()
Expect(emptyDirCheck2).Should(Exit(0))

volList1 := podmanTest.Podman([]string{"volume", "ls", "-q"})
volList1.WaitWithDefaultTimeout()
Expect(volList1).Should(Exit(0))
Expect(volList1.OutputToString()).To(Equal(defaultVolName))

remove := podmanTest.Podman([]string{"pod", "rm", "-f", podName})
remove.WaitWithDefaultTimeout()
Expect(remove).Should(Exit(0))

volList2 := podmanTest.Podman([]string{"volume", "ls", "-q"})
volList2.WaitWithDefaultTimeout()
Expect(volList2).Should(Exit(0))
Expect(volList2.OutputToString()).To(Equal(""))
})

It("podman play kube applies labels to pods", func() {
var numReplicas int32 = 5
expectedLabelKey := "key1"
Expand Down

0 comments on commit 98169c2

Please sign in to comment.