Skip to content

Commit

Permalink
Merge pull request #15473 from umohnani8/empty-dir
Browse files Browse the repository at this point in the history
Add emptyDir volume support to kube play
  • Loading branch information
openshift-merge-robot authored Aug 31, 2022
2 parents 60e4b76 + 98169c2 commit 8266dbe
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 8266dbe

Please sign in to comment.