mirror of https://github.com/k3s-io/k3s
ConfigMap volume source
parent
44d12a1389
commit
d1dc259ef2
|
@ -32,6 +32,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/volume/azure_file"
|
||||
"k8s.io/kubernetes/pkg/volume/cephfs"
|
||||
"k8s.io/kubernetes/pkg/volume/cinder"
|
||||
"k8s.io/kubernetes/pkg/volume/configmap"
|
||||
"k8s.io/kubernetes/pkg/volume/downwardapi"
|
||||
"k8s.io/kubernetes/pkg/volume/empty_dir"
|
||||
"k8s.io/kubernetes/pkg/volume/fc"
|
||||
|
@ -80,6 +81,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
|
|||
allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, flexvolume.ProbeVolumePlugins(pluginDir)...)
|
||||
allPlugins = append(allPlugins, azure_file.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...)
|
||||
return allPlugins
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ cluster/aws/templates/configure-vm-aws.sh: # We set the hostname_override to th
|
|||
cluster/aws/templates/configure-vm-aws.sh: api_servers: '${API_SERVERS}'
|
||||
cluster/aws/templates/configure-vm-aws.sh: env-to-grains "hostname_override"
|
||||
cluster/aws/templates/configure-vm-aws.sh: env-to-grains "runtime_config"
|
||||
cluster/aws/templates/salt-minion.sh:# We set the hostname_override to the full EC2 private dns name
|
||||
cluster/centos/util.sh: local node_ip=${node#*@}
|
||||
cluster/gce/configure-vm.sh: advertise_address: '${EXTERNAL_IP}'
|
||||
cluster/gce/configure-vm.sh: api_servers: '${KUBERNETES_MASTER_NAME}'
|
||||
|
@ -95,12 +94,11 @@ hack/local-up-cluster.sh: runtime_config=""
|
|||
pkg/kubelet/qos/memory_policy_test.go: t.Errorf("oom_score_adj should be between %d and %d, but was %d", test.lowOOMScoreAdj, test.highOOMScoreAdj, oomScoreAdj)
|
||||
pkg/kubelet/qos/memory_policy_test.go: highOOMScoreAdj int // The min oom_score_adj score the container should be assigned.
|
||||
pkg/kubelet/qos/memory_policy_test.go: lowOOMScoreAdj int // The max oom_score_adj score the container should be assigned.
|
||||
pkg/util/oom/oom_linux.go: err = fmt.Errorf("failed to read oom_score_adj: %v", readErr)
|
||||
pkg/util/oom/oom_linux.go: err = fmt.Errorf("failed to set oom_score_adj to %d: %v", oomScoreAdj, writeErr)
|
||||
pkg/util/oom/oom_linux.go: return fmt.Errorf("invalid PID %d specified for oom_score_adj", pid)
|
||||
pkg/util/oom/oom_linux.go: oomScoreAdjPath := path.Join("/proc", pidStr, "oom_score_adj")
|
||||
pkg/util/oom/oom_linux.go:// Writes 'value' to /proc/<pid>/oom_score_adj for all processes in cgroup cgroupName.
|
||||
pkg/util/oom/oom_linux.go:// Writes 'value' to /proc/<pid>/oom_score_adj. PID = 0 means self
|
||||
test/e2e/configmap.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/configmap-volume/data-1"},
|
||||
test/e2e/downwardapi_volume.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=" + filePath},
|
||||
test/e2e/es_cluster_logging.go: Failf("No cluster_name field in Elasticsearch response: %v", esResponse)
|
||||
test/e2e/es_cluster_logging.go: // Check to see if have a cluster_name field.
|
||||
|
|
|
@ -217,6 +217,8 @@ type VolumeSource struct {
|
|||
FC *FCVolumeSource `json:"fc,omitempty"`
|
||||
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
|
||||
AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty"`
|
||||
// ConfigMap represents a configMap that should populate this volume
|
||||
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
|
||||
}
|
||||
|
||||
// Similar to VolumeSource but meant for the administrator who creates PVs.
|
||||
|
@ -688,6 +690,36 @@ type AzureFileVolumeSource struct {
|
|||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
// Adapts a ConfigMap into a volume.
|
||||
//
|
||||
// The contents of the target ConfigMap's Data field will be presented in a
|
||||
// volume as files using the keys in the Data field as the file names, unless
|
||||
// the items element is populated with specific mappings of keys to paths.
|
||||
// ConfigMap volumes support ownership management and SELinux relabeling.
|
||||
type ConfigMapVolumeSource struct {
|
||||
LocalObjectReference `json:",inline"`
|
||||
// If unspecified, each key-value pair in the Data field of the referenced
|
||||
// ConfigMap will be projected into the volume as a file whose name is the
|
||||
// key and content is the value. If specified, the listed keys will be
|
||||
// projected into the specified paths, and unlisted keys will not be
|
||||
// present. If a key is specified which is not present in the ConfigMap,
|
||||
// the volume setup will error. Paths must be relative and may not contain
|
||||
// the '..' path or start with '..'.
|
||||
Items []KeyToPath `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// Maps a string key to a path within a volume.
|
||||
type KeyToPath struct {
|
||||
// The key to project.
|
||||
Key string `json:"key"`
|
||||
|
||||
// The relative path of the file to map the key to.
|
||||
// May not be an absolute path.
|
||||
// May not contain the path element '..'.
|
||||
// May not start with the string '..'.
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// ContainerPort represents a network port in a single container
|
||||
type ContainerPort struct {
|
||||
// Optional: If specified, this must be an IANA_SVC_NAME Each named port
|
||||
|
|
|
@ -263,6 +263,8 @@ type VolumeSource struct {
|
|||
FC *FCVolumeSource `json:"fc,omitempty"`
|
||||
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
|
||||
AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty"`
|
||||
// ConfigMap represents a configMap that should populate this volume
|
||||
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
|
||||
|
@ -808,6 +810,36 @@ type AzureFileVolumeSource struct {
|
|||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
// Adapts a ConfigMap into a volume.
|
||||
//
|
||||
// The contents of the target ConfigMap's Data field will be presented in a
|
||||
// volume as files using the keys in the Data field as the file names, unless
|
||||
// the items element is populated with specific mappings of keys to paths.
|
||||
// ConfigMap volumes support ownership management and SELinux relabeling.
|
||||
type ConfigMapVolumeSource struct {
|
||||
LocalObjectReference `json:",inline"`
|
||||
// If unspecified, each key-value pair in the Data field of the referenced
|
||||
// ConfigMap will be projected into the volume as a file whose name is the
|
||||
// key and content is the value. If specified, the listed keys will be
|
||||
// projected into the specified paths, and unlisted keys will not be
|
||||
// present. If a key is specified which is not present in the ConfigMap,
|
||||
// the volume setup will error. Paths must be relative and may not contain
|
||||
// the '..' path or start with '..'.
|
||||
Items []KeyToPath `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// Maps a string key to a path within a volume.
|
||||
type KeyToPath struct {
|
||||
// The key to project.
|
||||
Key string `json:"key"`
|
||||
|
||||
// The relative path of the file to map the key to.
|
||||
// May not be an absolute path.
|
||||
// May not contain the path element '..'.
|
||||
// May not start with the string '..'.
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// ContainerPort represents a network port in a single container.
|
||||
type ContainerPort struct {
|
||||
// If specified, this must be an IANA_SVC_NAME and unique within the pod. Each
|
||||
|
|
|
@ -491,8 +491,20 @@ func validateVolumeSource(source *api.VolumeSource, fldPath *field.Path) field.E
|
|||
}
|
||||
}
|
||||
if source.FlexVolume != nil {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateFlexVolumeSource(source.FlexVolume, fldPath.Child("flexVolume"))...)
|
||||
if numVolumes > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("flexVolume"), "may not specifiy more than 1 volume type"))
|
||||
} else {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateFlexVolumeSource(source.FlexVolume, fldPath.Child("flexVolume"))...)
|
||||
}
|
||||
}
|
||||
if source.ConfigMap != nil {
|
||||
if numVolumes > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specifiy more than 1 volume type"))
|
||||
} else {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateConfigMapVolumeSource(source.ConfigMap, fldPath.Child("configMap"))...)
|
||||
}
|
||||
}
|
||||
if source.AzureFile != nil {
|
||||
numVolumes++
|
||||
|
@ -584,6 +596,14 @@ func validateSecretVolumeSource(secretSource *api.SecretVolumeSource, fldPath *f
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateConfigMapVolumeSource(configMapSource *api.ConfigMapVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(configMapSource.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validatePersistentClaimVolumeSource(claim *api.PersistentVolumeClaimVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(claim.ClaimName) == 0 {
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
ioutil "k8s.io/kubernetes/pkg/util/io"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/util/strings"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
// ProbeVolumePlugin is the entry point for plugin detection in a package.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&configMapPlugin{}}
|
||||
}
|
||||
|
||||
const (
|
||||
configMapPluginName = "kubernetes.io/configmap"
|
||||
)
|
||||
|
||||
// configMapPlugin implements the VolumePlugin interface.
|
||||
type configMapPlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &configMapPlugin{}
|
||||
|
||||
func (plugin *configMapPlugin) Init(host volume.VolumeHost) error {
|
||||
plugin.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *configMapPlugin) Name() string {
|
||||
return configMapPluginName
|
||||
}
|
||||
|
||||
func (plugin *configMapPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
return spec.Volume != nil && spec.Volume.ConfigMap != nil
|
||||
}
|
||||
|
||||
func (plugin *configMapPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Builder, error) {
|
||||
return &configMapVolumeBuilder{
|
||||
configMapVolume: &configMapVolume{spec.Name(), pod.UID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}},
|
||||
source: *spec.Volume.ConfigMap,
|
||||
pod: *pod,
|
||||
opts: &opts}, nil
|
||||
}
|
||||
|
||||
func (plugin *configMapPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) {
|
||||
return &configMapVolumeCleaner{&configMapVolume{volName, podUID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}}}, nil
|
||||
}
|
||||
|
||||
type configMapVolume struct {
|
||||
volName string
|
||||
podUID types.UID
|
||||
plugin *configMapPlugin
|
||||
mounter mount.Interface
|
||||
writer ioutil.Writer
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
var _ volume.Volume = &configMapVolume{}
|
||||
|
||||
func (sv *configMapVolume) GetPath() string {
|
||||
return sv.plugin.host.GetPodVolumeDir(sv.podUID, strings.EscapeQualifiedNameForDisk(configMapPluginName), sv.volName)
|
||||
}
|
||||
|
||||
// configMapVolumeBuilder handles retrieving secrets from the API server
|
||||
// and placing them into the volume on the host.
|
||||
type configMapVolumeBuilder struct {
|
||||
*configMapVolume
|
||||
|
||||
source api.ConfigMapVolumeSource
|
||||
pod api.Pod
|
||||
opts *volume.VolumeOptions
|
||||
}
|
||||
|
||||
var _ volume.Builder = &configMapVolumeBuilder{}
|
||||
|
||||
func (sv *configMapVolume) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: true,
|
||||
Managed: true,
|
||||
SupportsSELinux: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *configMapVolumeBuilder) getMetaDir() string {
|
||||
return path.Join(b.plugin.host.GetPodPluginDir(b.podUID, strings.EscapeQualifiedNameForDisk(configMapPluginName)), b.volName)
|
||||
}
|
||||
|
||||
// This is the spec for the volume that this plugin wraps.
|
||||
var wrappedVolumeSpec = volume.Spec{
|
||||
Volume: &api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageMediumMemory}}},
|
||||
}
|
||||
|
||||
func (b *configMapVolumeBuilder) SetUp(fsGroup *int64) error {
|
||||
return b.SetUpAt(b.GetPath(), fsGroup)
|
||||
}
|
||||
|
||||
func (b *configMapVolumeBuilder) SetUpAt(dir string, fsGroup *int64) error {
|
||||
glog.V(3).Infof("Setting up volume %v for pod %v at %v", b.volName, b.pod.UID, dir)
|
||||
|
||||
// Wrap EmptyDir, let it do the setup.
|
||||
wrapped, err := b.plugin.host.NewWrapperBuilder(b.volName, wrappedVolumeSpec, &b.pod, *b.opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := wrapped.SetUpAt(dir, fsGroup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeClient := b.plugin.host.GetKubeClient()
|
||||
if kubeClient == nil {
|
||||
return fmt.Errorf("Cannot setup configMap volume %v because kube client is not configured", b.volName)
|
||||
}
|
||||
|
||||
configMap, err := kubeClient.Core().ConfigMaps(b.pod.Namespace).Get(b.source.Name)
|
||||
if err != nil {
|
||||
glog.Errorf("Couldn't get configMap %v/%v: %v", b.pod.Namespace, b.source.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
totalBytes := totalBytes(configMap)
|
||||
glog.V(3).Infof("Received configMap %v/%v containing (%v) pieces of data, %v total bytes",
|
||||
b.pod.Namespace,
|
||||
b.source.Name,
|
||||
len(configMap.Data),
|
||||
totalBytes)
|
||||
|
||||
payload, err := makePayload(b.source.Items, configMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
|
||||
writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
|
||||
if err != nil {
|
||||
glog.Errorf("Error creating atomic writer: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = writer.Write(payload)
|
||||
if err != nil {
|
||||
glog.Errorf("Error writing payload to dir: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = volume.SetVolumeOwnership(b, fsGroup)
|
||||
if err != nil {
|
||||
glog.Errorf("Error applying volume ownership settings for group: %v", fsGroup)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makePayload(mappings []api.KeyToPath, configMap *api.ConfigMap) (map[string][]byte, error) {
|
||||
payload := make(map[string][]byte, len(configMap.Data))
|
||||
|
||||
if len(mappings) == 0 {
|
||||
for name, data := range configMap.Data {
|
||||
payload[name] = []byte(data)
|
||||
}
|
||||
} else {
|
||||
for _, ktp := range mappings {
|
||||
content, ok := configMap.Data[ktp.Key]
|
||||
if !ok {
|
||||
glog.Errorf("references non-existent config key")
|
||||
return nil, fmt.Errorf("references non-existent config key")
|
||||
}
|
||||
|
||||
payload[ktp.Path] = []byte(content)
|
||||
}
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func totalBytes(configMap *api.ConfigMap) int {
|
||||
totalSize := 0
|
||||
for _, value := range configMap.Data {
|
||||
totalSize += len(value)
|
||||
}
|
||||
|
||||
return totalSize
|
||||
}
|
||||
|
||||
// configMapVolumeCleaner handles cleaning up configMap volumes.
|
||||
type configMapVolumeCleaner struct {
|
||||
*configMapVolume
|
||||
}
|
||||
|
||||
var _ volume.Cleaner = &configMapVolumeCleaner{}
|
||||
|
||||
func (c *configMapVolumeCleaner) TearDown() error {
|
||||
return c.TearDownAt(c.GetPath())
|
||||
}
|
||||
|
||||
func (c *configMapVolumeCleaner) TearDownAt(dir string) error {
|
||||
glog.V(3).Infof("Tearing down volume %v for pod %v at %v", c.volName, c.podUID, dir)
|
||||
|
||||
// Wrap EmptyDir, let it do the teardown.
|
||||
wrapped, err := c.plugin.host.NewWrapperCleaner(c.volName, wrappedVolumeSpec, c.podUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wrapped.TearDownAt(dir)
|
||||
}
|
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package configmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/testing/fake"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/empty_dir"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
func TestMakePayload(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
mappings []api.KeyToPath
|
||||
configMap *api.ConfigMap
|
||||
payload map[string][]byte
|
||||
success bool
|
||||
}{
|
||||
{
|
||||
name: "no overrides",
|
||||
configMap: &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
},
|
||||
payload: map[string][]byte{
|
||||
"foo": []byte("foo"),
|
||||
"bar": []byte("bar"),
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
name: "basic 1",
|
||||
mappings: []api.KeyToPath{
|
||||
{
|
||||
Key: "foo",
|
||||
Path: "path/to/foo.txt",
|
||||
},
|
||||
},
|
||||
configMap: &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
},
|
||||
payload: map[string][]byte{
|
||||
"path/to/foo.txt": []byte("foo"),
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
name: "subdirs",
|
||||
mappings: []api.KeyToPath{
|
||||
{
|
||||
Key: "foo",
|
||||
Path: "path/to/1/2/3/foo.txt",
|
||||
},
|
||||
},
|
||||
configMap: &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
},
|
||||
payload: map[string][]byte{
|
||||
"path/to/1/2/3/foo.txt": []byte("foo"),
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
name: "subdirs 2",
|
||||
mappings: []api.KeyToPath{
|
||||
{
|
||||
Key: "foo",
|
||||
Path: "path/to/1/2/3/foo.txt",
|
||||
},
|
||||
},
|
||||
configMap: &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
},
|
||||
payload: map[string][]byte{
|
||||
"path/to/1/2/3/foo.txt": []byte("foo"),
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
name: "subdirs 3",
|
||||
mappings: []api.KeyToPath{
|
||||
{
|
||||
Key: "foo",
|
||||
Path: "path/to/1/2/3/foo.txt",
|
||||
},
|
||||
{
|
||||
Key: "bar",
|
||||
Path: "another/path/to/the/esteemed/bar.bin",
|
||||
},
|
||||
},
|
||||
configMap: &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
},
|
||||
payload: map[string][]byte{
|
||||
"path/to/1/2/3/foo.txt": []byte("foo"),
|
||||
"another/path/to/the/esteemed/bar.bin": []byte("bar"),
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
{
|
||||
name: "non existent key",
|
||||
mappings: []api.KeyToPath{
|
||||
{
|
||||
Key: "zab",
|
||||
Path: "path/to/foo.txt",
|
||||
},
|
||||
},
|
||||
configMap: &api.ConfigMap{
|
||||
Data: map[string]string{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
},
|
||||
success: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
actualPayload, err := makePayload(tc.mappings, tc.configMap)
|
||||
if err != nil && tc.success {
|
||||
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err == nil && !tc.success {
|
||||
t.Errorf("%v: unexpected success making payload", tc.name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !tc.success {
|
||||
continue
|
||||
}
|
||||
|
||||
if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%v: expected and actual payload do not match", tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) {
|
||||
tempDir, err := ioutil.TempDir("/tmp", "configmap_volume_test.")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp rootdir: %v", err)
|
||||
}
|
||||
|
||||
return tempDir, volume.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins())
|
||||
}
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
pluginMgr := volume.VolumePluginMgr{}
|
||||
_, host := newTestHost(t, nil)
|
||||
pluginMgr.InitPlugins(ProbeVolumePlugins(), host)
|
||||
|
||||
plugin, err := pluginMgr.FindPluginByName(configMapPluginName)
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if plugin.Name() != configMapPluginName {
|
||||
t.Errorf("Wrong name: %s", plugin.Name())
|
||||
}
|
||||
if !plugin.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{""}}}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
if plugin.CanSupport(&volume.Spec{}) {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
var (
|
||||
testPodUID = types.UID("test_pod_uid")
|
||||
testVolumeName = "test_volume_name"
|
||||
testNamespace = "test_configmap_namespace"
|
||||
testName = "test_configmap_name"
|
||||
|
||||
volumeSpec = volumeSpec(testVolumeName, testName)
|
||||
configMap = configMap(testNamespace, testName)
|
||||
client = fake.NewSimpleClientset(&configMap)
|
||||
pluginMgr = volume.VolumePluginMgr{}
|
||||
_, host = newTestHost(t, client)
|
||||
)
|
||||
|
||||
pluginMgr.InitPlugins(ProbeVolumePlugins(), host)
|
||||
|
||||
plugin, err := pluginMgr.FindPluginByName(configMapPluginName)
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
|
||||
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}}
|
||||
builder, err := plugin.NewBuilder(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Builder: %v", err)
|
||||
}
|
||||
if builder == nil {
|
||||
t.Errorf("Got a nil Builder")
|
||||
}
|
||||
|
||||
volumePath := builder.GetPath()
|
||||
if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid/volumes/kubernetes.io~configmap/test_volume_name")) {
|
||||
t.Errorf("Got unexpected path: %s", volumePath)
|
||||
}
|
||||
|
||||
fsGroup := int64(1001)
|
||||
err = builder.SetUp(&fsGroup)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup volume: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volumePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
|
||||
} else {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
doTestConfigMapDataInVolume(volumePath, configMap, t)
|
||||
doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t)
|
||||
}
|
||||
|
||||
// Test the case where the plugin's ready file exists, but the volume dir is not a
|
||||
// mountpoint, which is the state the system will be in after reboot. The dir
|
||||
// should be mounter and the configMap data written to it.
|
||||
func TestPluginReboot(t *testing.T) {
|
||||
var (
|
||||
testPodUID = types.UID("test_pod_uid3")
|
||||
testVolumeName = "test_volume_name"
|
||||
testNamespace = "test_configmap_namespace"
|
||||
testName = "test_configmap_name"
|
||||
|
||||
volumeSpec = volumeSpec(testVolumeName, testName)
|
||||
configMap = configMap(testNamespace, testName)
|
||||
client = fake.NewSimpleClientset(&configMap)
|
||||
pluginMgr = volume.VolumePluginMgr{}
|
||||
rootDir, host = newTestHost(t, client)
|
||||
)
|
||||
|
||||
pluginMgr.InitPlugins(ProbeVolumePlugins(), host)
|
||||
|
||||
plugin, err := pluginMgr.FindPluginByName(configMapPluginName)
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
|
||||
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}}
|
||||
builder, err := plugin.NewBuilder(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Builder: %v", err)
|
||||
}
|
||||
if builder == nil {
|
||||
t.Errorf("Got a nil Builder")
|
||||
}
|
||||
|
||||
podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid3/plugins/kubernetes.io~configmap/test_volume_name", rootDir)
|
||||
util.SetReady(podMetadataDir)
|
||||
volumePath := builder.GetPath()
|
||||
if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid3/volumes/kubernetes.io~configmap/test_volume_name")) {
|
||||
t.Errorf("Got unexpected path: %s", volumePath)
|
||||
}
|
||||
|
||||
fsGroup := int64(1001)
|
||||
err = builder.SetUp(&fsGroup)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup volume: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volumePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
|
||||
} else {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
doTestConfigMapDataInVolume(volumePath, configMap, t)
|
||||
doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t)
|
||||
}
|
||||
|
||||
func volumeSpec(volumeName, configMapName string) *api.Volume {
|
||||
return &api.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
ConfigMap: &api.ConfigMapVolumeSource{
|
||||
LocalObjectReference: api.LocalObjectReference{
|
||||
Name: configMapName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func configMap(namespace, name string) api.ConfigMap {
|
||||
return api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"data-1": "value-1",
|
||||
"data-2": "value-2",
|
||||
"data-3": "value-3",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func doTestConfigMapDataInVolume(volumePath string, configMap api.ConfigMap, t *testing.T) {
|
||||
for key, value := range configMap.Data {
|
||||
configMapDataHostPath := path.Join(volumePath, key)
|
||||
if _, err := os.Stat(configMapDataHostPath); err != nil {
|
||||
t.Fatalf("SetUp() failed, couldn't find configMap data on disk: %v", configMapDataHostPath)
|
||||
} else {
|
||||
actualValue, err := ioutil.ReadFile(configMapDataHostPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't read configMap data from: %v", configMapDataHostPath)
|
||||
}
|
||||
|
||||
if value != string(actualValue) {
|
||||
t.Errorf("Unexpected value; expected %q, got %q", value, actualValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func doTestCleanAndTeardown(plugin volume.VolumePlugin, podUID types.UID, testVolumeName, volumePath string, t *testing.T) {
|
||||
cleaner, err := plugin.NewCleaner(testVolumeName, podUID)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Cleaner: %v", err)
|
||||
}
|
||||
if cleaner == nil {
|
||||
t.Errorf("Got a nil Cleaner")
|
||||
}
|
||||
|
||||
if err := cleaner.TearDown(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volumePath); err == nil {
|
||||
t.Errorf("TearDown() failed, volume path still exists: %s", volumePath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package configmap contains the internal representation of configMap volumes.
|
||||
package configmap
|
|
@ -18,16 +18,258 @@ package e2e
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ConfigMap", func() {
|
||||
|
||||
f := NewFramework("configmap")
|
||||
|
||||
It("should be consumable from pods in volume [Conformance]", func() {
|
||||
name := "configmap-test-volume-" + string(util.NewUUID())
|
||||
volumeName := "configmap-volume"
|
||||
volumeMountPath := "/etc/configmap-volume"
|
||||
|
||||
configMap := &api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: f.Namespace.Name,
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"data-1": "value-1",
|
||||
"data-2": "value-2",
|
||||
"data-3": "value-3",
|
||||
},
|
||||
}
|
||||
|
||||
By(fmt.Sprintf("Creating configMap with name %s", configMap.Name))
|
||||
defer func() {
|
||||
By("Cleaning up the configMap")
|
||||
if err := f.Client.ConfigMaps(f.Namespace.Name).Delete(configMap.Name); err != nil {
|
||||
Failf("unable to delete configMap %v: %v", configMap.Name, err)
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
if configMap, err = f.Client.ConfigMaps(f.Namespace.Name).Create(configMap); err != nil {
|
||||
Failf("unable to create test configMap %s: %v", configMap.Name, err)
|
||||
}
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "pod-configmaps-" + string(util.NewUUID()),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
Name: volumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
ConfigMap: &api.ConfigMapVolumeSource{
|
||||
LocalObjectReference: api.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "configmap-volume-test",
|
||||
Image: "gcr.io/google_containers/mounttest:0.6",
|
||||
Args: []string{"--file_content=/etc/configmap-volume/data-1"},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{
|
||||
Name: volumeName,
|
||||
MountPath: volumeMountPath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
|
||||
testContainerOutput("consume configMaps", f.Client, pod, 0, []string{
|
||||
"content of file \"/etc/configmap-volume/data-1\": value-1",
|
||||
}, f.Namespace.Name)
|
||||
})
|
||||
|
||||
It("should be consumable from pods in volume with mappings [Conformance]", func() {
|
||||
name := "configmap-test-volume-map-" + string(util.NewUUID())
|
||||
volumeName := "configmap-volume"
|
||||
volumeMountPath := "/etc/configmap-volume"
|
||||
|
||||
configMap := &api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: f.Namespace.Name,
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"data-1": "value-1",
|
||||
"data-2": "value-2",
|
||||
"data-3": "value-3",
|
||||
},
|
||||
}
|
||||
|
||||
By(fmt.Sprintf("Creating configMap with name %s", configMap.Name))
|
||||
defer func() {
|
||||
By("Cleaning up the configMap")
|
||||
if err := f.Client.ConfigMaps(f.Namespace.Name).Delete(configMap.Name); err != nil {
|
||||
Failf("unable to delete configMap %v: %v", configMap.Name, err)
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
if configMap, err = f.Client.ConfigMaps(f.Namespace.Name).Create(configMap); err != nil {
|
||||
Failf("unable to create test configMap %s: %v", configMap.Name, err)
|
||||
}
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "pod-configmaps-" + string(util.NewUUID()),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
Name: volumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
ConfigMap: &api.ConfigMapVolumeSource{
|
||||
LocalObjectReference: api.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Items: []api.KeyToPath{
|
||||
{
|
||||
Key: "data-2",
|
||||
Path: "path/to/data-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "configmap-volume-test",
|
||||
Image: "gcr.io/google_containers/mounttest:0.6",
|
||||
Args: []string{"--file_content=/etc/configmap-volume/path/to/data-2"},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{
|
||||
Name: volumeName,
|
||||
MountPath: volumeMountPath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
|
||||
testContainerOutput("consume configMaps", f.Client, pod, 0, []string{
|
||||
"content of file \"/etc/configmap-volume/path/to/data-2\": value-2",
|
||||
}, f.Namespace.Name)
|
||||
})
|
||||
|
||||
It("updates should be reflected in volume [Conformance]", func() {
|
||||
|
||||
// We may have to wait or a full sync period to elapse before the
|
||||
// Kubelet projects the update into the volume and the container picks
|
||||
// it up. This timeout is based on the default Kubelet sync period (1
|
||||
// minute) plus additional time for fudge factor.
|
||||
const podLogTimeout = 90 * time.Second
|
||||
|
||||
name := "configmap-test-upd-" + string(util.NewUUID())
|
||||
volumeName := "configmap-volume"
|
||||
volumeMountPath := "/etc/configmap-volume"
|
||||
containerName := "configmap-volume-test"
|
||||
|
||||
configMap := &api.ConfigMap{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: f.Namespace.Name,
|
||||
Name: name,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"data-1": "value-1",
|
||||
},
|
||||
}
|
||||
|
||||
By(fmt.Sprintf("Creating configMap with name %s", configMap.Name))
|
||||
defer func() {
|
||||
By("Cleaning up the configMap")
|
||||
if err := f.Client.ConfigMaps(f.Namespace.Name).Delete(configMap.Name); err != nil {
|
||||
Failf("unable to delete configMap %v: %v", configMap.Name, err)
|
||||
}
|
||||
}()
|
||||
var err error
|
||||
if configMap, err = f.Client.ConfigMaps(f.Namespace.Name).Create(configMap); err != nil {
|
||||
Failf("unable to create test configMap %s: %v", configMap.Name, err)
|
||||
}
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "pod-configmaps-" + string(util.NewUUID()),
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
Name: volumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
ConfigMap: &api.ConfigMapVolumeSource{
|
||||
LocalObjectReference: api.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Image: "gcr.io/google_containers/mounttest:0.6",
|
||||
Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/configmap-volume/data-1"},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{
|
||||
Name: volumeName,
|
||||
MountPath: volumeMountPath,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: api.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
By("Deleting the pod")
|
||||
f.Client.Pods(f.Namespace.Name).Delete(pod.Name, api.NewDeleteOptions(0))
|
||||
}()
|
||||
By("Creating the pod")
|
||||
_, err = f.Client.Pods(f.Namespace.Name).Create(pod)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
expectNoError(waitForPodRunningInNamespace(f.Client, pod.Name, f.Namespace.Name))
|
||||
|
||||
pollLogs := func() (string, error) {
|
||||
return getPodLogs(f.Client, f.Namespace.Name, pod.Name, containerName)
|
||||
}
|
||||
|
||||
Eventually(pollLogs, podLogTimeout, poll).Should(ContainSubstring("value-1"))
|
||||
|
||||
By(fmt.Sprintf("Updating configmap %v", configMap.Name))
|
||||
configMap.ResourceVersion = "" // to force update
|
||||
configMap.Data["data-1"] = "value-2"
|
||||
_, err = f.Client.ConfigMaps(f.Namespace.Name).Update(configMap)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Eventually(pollLogs, podLogTimeout, poll).Should(ContainSubstring("value-2"))
|
||||
})
|
||||
|
||||
It("should be consumable via environment variable [Conformance]", func() {
|
||||
name := "configmap-test-" + string(util.NewUUID())
|
||||
configMap := &api.ConfigMap{
|
||||
|
|
|
@ -27,10 +27,10 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// How long to wait for a log pod to be displayed
|
||||
const podLogTimeout = 45 * time.Second
|
||||
|
||||
var _ = Describe("Downward API volume", func() {
|
||||
// How long to wait for a log pod to be displayed
|
||||
const podLogTimeout = 45 * time.Second
|
||||
|
||||
f := NewFramework("downward-api")
|
||||
It("should provide podname only [Conformance]", func() {
|
||||
podName := "downwardapi-volume-" + string(util.NewUUID())
|
||||
|
|
Loading…
Reference in New Issue