From 53c6ea6d487aab0c3aabd648a8f2df3b9e8f5c3b Mon Sep 17 00:00:00 2001 From: Scott Creeley Date: Wed, 17 Jan 2018 12:21:44 -0500 Subject: [PATCH] update aws plugin for block support --- pkg/volume/aws_ebs/BUILD | 2 + pkg/volume/aws_ebs/aws_ebs_block.go | 175 +++++++++++++++++++++++ pkg/volume/aws_ebs/aws_ebs_block_test.go | 145 +++++++++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 pkg/volume/aws_ebs/aws_ebs_block.go create mode 100644 pkg/volume/aws_ebs/aws_ebs_block_test.go diff --git a/pkg/volume/aws_ebs/BUILD b/pkg/volume/aws_ebs/BUILD index 9fd852900bb..3fba242acdc 100644 --- a/pkg/volume/aws_ebs/BUILD +++ b/pkg/volume/aws_ebs/BUILD @@ -11,6 +11,7 @@ go_library( srcs = [ "attacher.go", "aws_ebs.go", + "aws_ebs_block.go", "aws_util.go", "doc.go", ], @@ -35,6 +36,7 @@ go_test( name = "go_default_test", srcs = [ "attacher_test.go", + "aws_ebs_block_test.go", "aws_ebs_test.go", ], embed = [":go_default_library"], diff --git a/pkg/volume/aws_ebs/aws_ebs_block.go b/pkg/volume/aws_ebs/aws_ebs_block.go new file mode 100644 index 00000000000..5f55358dc64 --- /dev/null +++ b/pkg/volume/aws_ebs/aws_ebs_block.go @@ -0,0 +1,175 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 aws_ebs + +import ( + "fmt" + "path" + "path/filepath" + "strconv" + "strings" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" + "k8s.io/kubernetes/pkg/util/mount" + kstrings "k8s.io/kubernetes/pkg/util/strings" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" +) + +var _ volume.VolumePlugin = &awsElasticBlockStorePlugin{} +var _ volume.PersistentVolumePlugin = &awsElasticBlockStorePlugin{} +var _ volume.BlockVolumePlugin = &awsElasticBlockStorePlugin{} +var _ volume.DeletableVolumePlugin = &awsElasticBlockStorePlugin{} +var _ volume.ProvisionableVolumePlugin = &awsElasticBlockStorePlugin{} + +func (plugin *awsElasticBlockStorePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) { + pluginDir := plugin.host.GetVolumeDevicePluginDir(awsElasticBlockStorePluginName) + blkutil := util.NewBlockVolumePathHandler() + globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID) + if err != nil { + return nil, err + } + glog.V(5).Infof("globalMapPathUUID: %s", globalMapPathUUID) + + globalMapPath := filepath.Dir(globalMapPathUUID) + if len(globalMapPath) <= 1 { + return nil, fmt.Errorf("failed to get volume plugin information from globalMapPathUUID: %v", globalMapPathUUID) + } + + return getVolumeSpecFromGlobalMapPath(globalMapPath) +} + +func getVolumeSpecFromGlobalMapPath(globalMapPath string) (*volume.Spec, error) { + // Get volume spec information from globalMapPath + // globalMapPath example: + // plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumeID} + // plugins/kubernetes.io/aws-ebs/volumeDevices/vol-XXXXXX + vID := filepath.Base(globalMapPath) + if len(vID) <= 1 { + return nil, fmt.Errorf("failed to get volumeID from global path=%s", globalMapPath) + } + if !strings.Contains(vID, "vol-") { + return nil, fmt.Errorf("failed to get volumeID from global path=%s, invalid volumeID format = %s", globalMapPath, vID) + } + block := v1.PersistentVolumeBlock + awsVolume := &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{ + VolumeID: vID, + }, + }, + VolumeMode: &block, + }, + } + + return volume.NewSpecFromPersistentVolume(awsVolume, true), nil +} + +// NewBlockVolumeMapper creates a new volume.BlockVolumeMapper from an API specification. +func (plugin *awsElasticBlockStorePlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) { + // If this is called via GenerateUnmapDeviceFunc(), pod is nil. + // Pass empty string as dummy uid since uid isn't used in the case. + var uid types.UID + if pod != nil { + uid = pod.UID + } + + return plugin.newBlockVolumeMapperInternal(spec, uid, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *awsElasticBlockStorePlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.BlockVolumeMapper, error) { + ebs, readOnly, err := getVolumeSource(spec) + if err != nil { + return nil, err + } + + volumeID := aws.KubernetesVolumeID(ebs.VolumeID) + partition := "" + if ebs.Partition != 0 { + partition = strconv.Itoa(int(ebs.Partition)) + } + + return &awsElasticBlockStoreMapper{ + awsElasticBlockStore: &awsElasticBlockStore{ + podUID: podUID, + volName: spec.Name(), + volumeID: volumeID, + partition: partition, + manager: manager, + mounter: mounter, + plugin: plugin, + }, + readOnly: readOnly}, nil +} + +func (plugin *awsElasticBlockStorePlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) { + return plugin.newUnmapperInternal(volName, podUID, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *awsElasticBlockStorePlugin) newUnmapperInternal(volName string, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.BlockVolumeUnmapper, error) { + return &awsElasticBlockStoreUnmapper{ + awsElasticBlockStore: &awsElasticBlockStore{ + podUID: podUID, + volName: volName, + manager: manager, + mounter: mounter, + plugin: plugin, + }}, nil +} + +func (c *awsElasticBlockStoreUnmapper) TearDownDevice(mapPath, devicePath string) error { + return nil +} + +type awsElasticBlockStoreUnmapper struct { + *awsElasticBlockStore +} + +var _ volume.BlockVolumeUnmapper = &awsElasticBlockStoreUnmapper{} + +type awsElasticBlockStoreMapper struct { + *awsElasticBlockStore + readOnly bool +} + +var _ volume.BlockVolumeMapper = &awsElasticBlockStoreMapper{} + +func (b *awsElasticBlockStoreMapper) SetUpDevice() (string, error) { + return "", nil +} + +// GetGlobalMapPath returns global map path and error +// path: plugins/kubernetes.io/{PluginName}/volumeDevices/volumeID +// plugins/kubernetes.io/aws-ebs/volumeDevices/vol-XXXXXX +func (ebs *awsElasticBlockStore) GetGlobalMapPath(spec *volume.Spec) (string, error) { + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + return "", err + } + return path.Join(ebs.plugin.host.GetVolumeDevicePluginDir(awsElasticBlockStorePluginName), string(volumeSource.VolumeID)), nil +} + +// GetPodDeviceMapPath returns pod device map path and volume name +// path: pods/{podUid}/volumeDevices/kubernetes.io~aws +func (ebs *awsElasticBlockStore) GetPodDeviceMapPath() (string, string) { + name := awsElasticBlockStorePluginName + return ebs.plugin.host.GetPodVolumeDeviceDir(ebs.podUID, kstrings.EscapeQualifiedNameForDisk(name)), ebs.volName +} diff --git a/pkg/volume/aws_ebs/aws_ebs_block_test.go b/pkg/volume/aws_ebs/aws_ebs_block_test.go new file mode 100644 index 00000000000..a1a4ba37d8c --- /dev/null +++ b/pkg/volume/aws_ebs/aws_ebs_block_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2018 The Kubernetes Authors. + +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 aws_ebs + +import ( + "os" + "path" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utiltesting "k8s.io/client-go/util/testing" + "k8s.io/kubernetes/pkg/volume" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +const ( + testVolName = "vol-1234" + testPVName = "pv1" + testGlobalPath = "plugins/kubernetes.io/aws-ebs/volumeDevices/vol-1234" + testPodPath = "pods/poduid/volumeDevices/kubernetes.io~aws-ebs" +) + +func TestGetVolumeSpecFromGlobalMapPath(t *testing.T) { + // make our test path for fake GlobalMapPath + // /tmp symbolized our pluginDir + // /tmp/testGlobalPathXXXXX/plugins/kubernetes.io/gce-pd/volumeDevices/pdVol1 + tmpVDir, err := utiltesting.MkTmpdir("awsBlockTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + //deferred clean up + defer os.RemoveAll(tmpVDir) + + expectedGlobalPath := path.Join(tmpVDir, testGlobalPath) + + //Bad Path + badspec, err := getVolumeSpecFromGlobalMapPath("") + if badspec != nil || err == nil { + t.Fatalf("Expected not to get spec from GlobalMapPath but did") + } + + // Good Path + spec, err := getVolumeSpecFromGlobalMapPath(expectedGlobalPath) + if spec == nil || err != nil { + t.Fatalf("Failed to get spec from GlobalMapPath: %v", err) + } + if spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID != testVolName { + t.Errorf("Invalid volumeID from GlobalMapPath spec: %s", spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID) + } + block := v1.PersistentVolumeBlock + specMode := spec.PersistentVolume.Spec.VolumeMode + if &specMode == nil { + t.Errorf("Invalid volumeMode from GlobalMapPath spec: %v - %v", &specMode, block) + } + if *specMode != block { + t.Errorf("Invalid volumeMode from GlobalMapPath spec: %v - %v", *specMode, block) + } +} + +func getTestVolume(readOnly bool, isBlock bool) *volume.Spec { + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: testPVName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{ + VolumeID: testVolName, + }, + }, + }, + } + + if isBlock { + blockMode := v1.PersistentVolumeBlock + pv.Spec.VolumeMode = &blockMode + } + return volume.NewSpecFromPersistentVolume(pv, readOnly) +} + +func TestGetPodAndPluginMapPaths(t *testing.T) { + tmpVDir, err := utiltesting.MkTmpdir("awsBlockTest") + if err != nil { + t.Fatalf("can't make a temp dir: %v", err) + } + //deferred clean up + defer os.RemoveAll(tmpVDir) + + expectedGlobalPath := path.Join(tmpVDir, testGlobalPath) + expectedPodPath := path.Join(tmpVDir, testPodPath) + + spec := getTestVolume(false, true /*isBlock*/) + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpVDir, nil, nil)) + plug, err := plugMgr.FindMapperPluginByName(awsElasticBlockStorePluginName) + if err != nil { + os.RemoveAll(tmpVDir) + t.Fatalf("Can't find the plugin by name: %q", awsElasticBlockStorePluginName) + } + if plug.GetPluginName() != awsElasticBlockStorePluginName { + t.Fatalf("Wrong name: %s", plug.GetPluginName()) + } + pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}} + mapper, err := plug.NewBlockVolumeMapper(spec, pod, volume.VolumeOptions{}) + if err != nil { + t.Fatalf("Failed to make a new Mounter: %v", err) + } + if mapper == nil { + t.Fatalf("Got a nil Mounter") + } + + //GetGlobalMapPath + gMapPath, err := mapper.GetGlobalMapPath(spec) + if err != nil || len(gMapPath) == 0 { + t.Fatalf("Invalid path from GlobalMapPath spec: %s", spec.PersistentVolume.Spec.GCEPersistentDisk.PDName) + } + if gMapPath != expectedGlobalPath { + t.Fatalf("Failed to get GlobalMapPath: %s %s", gMapPath, expectedGlobalPath) + } + + //GetPodDeviceMapPath + gDevicePath, gVolName := mapper.GetPodDeviceMapPath() + if gDevicePath != expectedPodPath { + t.Errorf("Got unexpected pod path: %s, expected %s", gDevicePath, expectedPodPath) + } + if gVolName != testPVName { + t.Errorf("Got unexpected volNamne: %s, expected %s", gVolName, testPVName) + } +}