Cinder Volume Plugin
This commit is contained in:
281
pkg/volume/cinder/cinder.go
Normal file
281
pkg/volume/cinder/cinder.go
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
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 cinder
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
// This is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&cinderPlugin{nil}}
|
||||
}
|
||||
|
||||
type cinderPlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &cinderPlugin{}
|
||||
|
||||
const (
|
||||
cinderVolumePluginName = "kubernetes.io/cinder"
|
||||
)
|
||||
|
||||
func (plugin *cinderPlugin) Init(host volume.VolumeHost) {
|
||||
plugin.host = host
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) Name() string {
|
||||
return cinderVolumePluginName
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
return spec.PersistentVolumeSource.Cinder != nil || spec.VolumeSource.Cinder != nil
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
|
||||
return []api.PersistentVolumeAccessMode{
|
||||
api.ReadWriteOnce,
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions, mounter mount.Interface) (volume.Builder, error) {
|
||||
return plugin.newBuilderInternal(spec, pod.UID, &CinderDiskUtil{}, mounter)
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) newBuilderInternal(spec *volume.Spec, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Builder, error) {
|
||||
var cinder *api.CinderVolumeSource
|
||||
if spec.VolumeSource.Cinder != nil {
|
||||
cinder = spec.VolumeSource.Cinder
|
||||
} else {
|
||||
cinder = spec.PersistentVolumeSource.Cinder
|
||||
}
|
||||
|
||||
pdName := cinder.VolumeID
|
||||
fsType := cinder.FSType
|
||||
readOnly := cinder.ReadOnly
|
||||
|
||||
return &cinderVolumeBuilder{
|
||||
cinderVolume: &cinderVolume{
|
||||
podUID: podUID,
|
||||
volName: spec.Name,
|
||||
pdName: pdName,
|
||||
mounter: mounter,
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
},
|
||||
fsType: fsType,
|
||||
readOnly: readOnly,
|
||||
blockDeviceMounter: &cinderSafeFormatAndMount{mounter, exec.New()}}, nil
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) NewCleaner(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
|
||||
return plugin.newCleanerInternal(volName, podUID, &CinderDiskUtil{}, mounter)
|
||||
}
|
||||
|
||||
func (plugin *cinderPlugin) newCleanerInternal(volName string, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Cleaner, error) {
|
||||
return &cinderVolumeCleaner{
|
||||
&cinderVolume{
|
||||
podUID: podUID,
|
||||
volName: volName,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
plugin: plugin,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// Abstract interface to PD operations.
|
||||
type cdManager interface {
|
||||
// Attaches the disk to the kubelet's host machine.
|
||||
AttachDisk(builder *cinderVolumeBuilder, globalPDPath string) error
|
||||
// Detaches the disk from the kubelet's host machine.
|
||||
DetachDisk(cleaner *cinderVolumeCleaner) error
|
||||
}
|
||||
|
||||
var _ volume.Builder = &cinderVolumeBuilder{}
|
||||
|
||||
type cinderVolumeBuilder struct {
|
||||
*cinderVolume
|
||||
fsType string
|
||||
readOnly bool
|
||||
blockDeviceMounter mount.Interface
|
||||
}
|
||||
|
||||
// cinderPersistentDisk volumes are disk resources provided by C3
|
||||
// that are attached to the kubelet's host machine and exposed to the pod.
|
||||
type cinderVolume struct {
|
||||
volName string
|
||||
podUID types.UID
|
||||
// Unique identifier of the volume, used to find the disk resource in the provider.
|
||||
pdName string
|
||||
// Filesystem type, optional.
|
||||
fsType string
|
||||
// Specifies the partition to mount
|
||||
//partition string
|
||||
// Specifies whether the disk will be attached as read-only.
|
||||
readOnly bool
|
||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||
manager cdManager
|
||||
// Mounter interface that provides system calls to mount the global path to the pod local path.
|
||||
mounter mount.Interface
|
||||
// diskMounter provides the interface that is used to mount the actual block device.
|
||||
blockDeviceMounter mount.Interface
|
||||
plugin *cinderPlugin
|
||||
}
|
||||
|
||||
func detachDiskLogError(cd *cinderVolume) {
|
||||
err := cd.manager.DetachDisk(&cinderVolumeCleaner{cd})
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to detach disk: %v (%v)", cd, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *cinderVolumeBuilder) SetUp() error {
|
||||
return b.SetUpAt(b.GetPath())
|
||||
}
|
||||
|
||||
// SetUp attaches the disk and bind mounts to the volume path.
|
||||
func (b *cinderVolumeBuilder) SetUpAt(dir string) error {
|
||||
// TODO: handle failed mounts here.
|
||||
notmnt, err := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
glog.V(4).Infof("PersistentDisk set up: %s %v %v", dir, !notmnt, err)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if !notmnt {
|
||||
return nil
|
||||
}
|
||||
globalPDPath := makeGlobalPDName(b.plugin.host, b.pdName)
|
||||
if err := b.manager.AttachDisk(b, globalPDPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := []string{"bind"}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||
// TODO: we should really eject the attach/detach out into its own control loop.
|
||||
detachDiskLogError(b.cinderVolume)
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
|
||||
err = b.mounter.Mount(globalPDPath, dir, "", options)
|
||||
if err != nil {
|
||||
notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !notmnt {
|
||||
if mntErr = b.mounter.Unmount(dir); mntErr != nil {
|
||||
glog.Errorf("Failed to unmount: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !notmnt {
|
||||
// This is very odd, we don't expect it. We'll try again next sync loop.
|
||||
glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", b.GetPath())
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(dir)
|
||||
// TODO: we should really eject the attach/detach out into its own control loop.
|
||||
detachDiskLogError(b.cinderVolume)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *cinderVolumeBuilder) IsReadOnly() bool {
|
||||
return b.readOnly
|
||||
}
|
||||
|
||||
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
|
||||
return path.Join(host.GetPluginDir(cinderVolumePluginName), "mounts", devName)
|
||||
}
|
||||
|
||||
func (cd *cinderVolume) GetPath() string {
|
||||
name := cinderVolumePluginName
|
||||
return cd.plugin.host.GetPodVolumeDir(cd.podUID, util.EscapeQualifiedNameForDisk(name), cd.volName)
|
||||
}
|
||||
|
||||
type cinderVolumeCleaner struct {
|
||||
*cinderVolume
|
||||
}
|
||||
|
||||
var _ volume.Cleaner = &cinderVolumeCleaner{}
|
||||
|
||||
func (c *cinderVolumeCleaner) TearDown() error {
|
||||
return c.TearDownAt(c.GetPath())
|
||||
}
|
||||
|
||||
// Unmounts the bind mount, and detaches the disk only if the PD
|
||||
// resource was the last reference to that disk on the kubelet.
|
||||
func (c *cinderVolumeCleaner) TearDownAt(dir string) error {
|
||||
notmnt, err := c.mounter.IsLikelyNotMountPoint(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if notmnt {
|
||||
return os.Remove(dir)
|
||||
}
|
||||
refs, err := mount.GetMountRefs(c.mounter, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.mounter.Unmount(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infof("successfully unmounted: %s\n", dir)
|
||||
|
||||
// If refCount is 1, then all bind mounts have been removed, and the
|
||||
// remaining reference is the global mount. It is safe to detach.
|
||||
if len(refs) == 1 {
|
||||
c.pdName = path.Base(refs[0])
|
||||
if err := c.manager.DetachDisk(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
notmnt, mntErr := c.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !notmnt {
|
||||
if err := os.Remove(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
135
pkg/volume/cinder/cinder_test.go
Normal file
135
pkg/volume/cinder/cinder_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
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 cinder
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if plug.Name() != "kubernetes.io/cinder" {
|
||||
t.Errorf("Wrong name: %s", plug.Name())
|
||||
}
|
||||
if !plug.CanSupport(&volume.Spec{
|
||||
Name: "foo",
|
||||
VolumeSource: api.VolumeSource{Cinder: &api.CinderVolumeSource{}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
|
||||
if !plug.CanSupport(&volume.Spec{Name: "foo", PersistentVolumeSource: api.PersistentVolumeSource{Cinder: &api.CinderVolumeSource{}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
}
|
||||
|
||||
type fakePDManager struct{}
|
||||
|
||||
func (fake *fakePDManager) AttachDisk(b *cinderVolumeBuilder, globalPDPath string) error {
|
||||
globalPath := makeGlobalPDName(b.plugin.host, b.pdName)
|
||||
err := os.MkdirAll(globalPath, 0750)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) DetachDisk(c *cinderVolumeCleaner) error {
|
||||
globalPath := makeGlobalPDName(c.plugin.host, c.pdName)
|
||||
err := os.RemoveAll(globalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volume.NewFakeVolumeHost("/tmp/fake", nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
spec := &api.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: api.VolumeSource{
|
||||
Cinder: &api.CinderVolumeSource{
|
||||
VolumeID: "pd",
|
||||
FSType: "ext4",
|
||||
},
|
||||
},
|
||||
}
|
||||
builder, err := plug.(*cinderPlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Builder: %v", err)
|
||||
}
|
||||
if builder == nil {
|
||||
t.Errorf("Got a nil Builder: %v")
|
||||
}
|
||||
|
||||
path := builder.GetPath()
|
||||
if path != "/tmp/fake/pods/poduid/volumes/kubernetes.io~cinder/vol1" {
|
||||
t.Errorf("Got unexpected path: %s", path)
|
||||
}
|
||||
|
||||
if err := builder.SetUp(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed, volume path not created: %s", path)
|
||||
} else {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed, volume path not created: %s", path)
|
||||
} else {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cleaner, err := plug.(*cinderPlugin).newCleanerInternal("vol1", types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Cleaner: %v", err)
|
||||
}
|
||||
if cleaner == nil {
|
||||
t.Errorf("Got a nil Cleaner: %v")
|
||||
}
|
||||
|
||||
if err := cleaner.TearDown(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Errorf("TearDown() failed, volume path still exists: %s", path)
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
212
pkg/volume/cinder/cinder_util.go
Normal file
212
pkg/volume/cinder/cinder_util.go
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
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 cinder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
)
|
||||
|
||||
type CinderDiskUtil struct{}
|
||||
|
||||
// Attaches a disk specified by a volume.CinderPersistenDisk to the current kubelet.
|
||||
// Mounts the disk to it's global path.
|
||||
func (util *CinderDiskUtil) AttachDisk(b *cinderVolumeBuilder, globalPDPath string) error {
|
||||
options := []string{}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
cloud := b.plugin.host.GetCloudProvider()
|
||||
if cloud == nil {
|
||||
glog.Errorf("Cloud provider not initialized properly")
|
||||
return errors.New("Cloud provider not initialized properly")
|
||||
}
|
||||
diskid, err := cloud.(*openstack.OpenStack).AttachDisk(b.pdName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var devicePath string
|
||||
numTries := 0
|
||||
for {
|
||||
devicePath = makeDevicePath(diskid)
|
||||
// probe the attached vol so that symlink in /dev/disk/by-id is created
|
||||
probeAttachedVolume()
|
||||
|
||||
_, err := os.Stat(devicePath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
numTries++
|
||||
if numTries == 10 {
|
||||
return errors.New("Could not attach disk: Timeout after 60s")
|
||||
}
|
||||
time.Sleep(time.Second * 6)
|
||||
}
|
||||
|
||||
notmnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(globalPDPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
notmnt = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if notmnt {
|
||||
err = b.blockDeviceMounter.Mount(devicePath, globalPDPath, b.fsType, options)
|
||||
if err != nil {
|
||||
os.Remove(globalPDPath)
|
||||
return err
|
||||
}
|
||||
glog.V(2).Infof("Safe mount successful: %q\n", devicePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeDevicePath(diskid string) string {
|
||||
files, _ := ioutil.ReadDir("/dev/disk/by-id/")
|
||||
for _, f := range files {
|
||||
if strings.Contains(f.Name(), "virtio-") {
|
||||
devid_prefix := f.Name()[len("virtio-"):len(f.Name())]
|
||||
if strings.Contains(diskid, devid_prefix) {
|
||||
glog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name()))
|
||||
return path.Join("/dev/disk/by-id/", f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
glog.Warningf("Failed to find device for the diskid: %q\n", diskid)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Unmounts the device and detaches the disk from the kubelet's host machine.
|
||||
func (util *CinderDiskUtil) DetachDisk(cd *cinderVolumeCleaner) error {
|
||||
globalPDPath := makeGlobalPDName(cd.plugin.host, cd.pdName)
|
||||
if err := cd.mounter.Unmount(globalPDPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(globalPDPath); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(2).Infof("Successfully unmounted main device: %s\n", globalPDPath)
|
||||
|
||||
cloud := cd.plugin.host.GetCloudProvider()
|
||||
if cloud == nil {
|
||||
glog.Errorf("Cloud provider not initialized properly")
|
||||
return errors.New("Cloud provider not initialized properly")
|
||||
}
|
||||
|
||||
if err := cloud.(*openstack.OpenStack).DetachDisk(cd.pdName); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(2).Infof("Successfully detached cinder volume %s", cd.pdName)
|
||||
return nil
|
||||
}
|
||||
|
||||
type cinderSafeFormatAndMount struct {
|
||||
mount.Interface
|
||||
runner exec.Interface
|
||||
}
|
||||
|
||||
/*
|
||||
The functions below depend on the following executables; This will have to be ported to more generic implementations
|
||||
/bin/lsblk
|
||||
/sbin/mkfs.ext3 or /sbin/mkfs.ext4
|
||||
/usr/bin/udevadm
|
||||
*/
|
||||
func (diskmounter *cinderSafeFormatAndMount) Mount(device string, target string, fstype string, options []string) error {
|
||||
fmtRequired, err := isFormatRequired(device, fstype, diskmounter)
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to determine if formating is required: %v\n", err)
|
||||
//return err
|
||||
}
|
||||
if fmtRequired {
|
||||
glog.V(2).Infof("Formatting of the vol required")
|
||||
if _, err := formatVolume(device, fstype, diskmounter); err != nil {
|
||||
glog.Warningf("Failed to format volume: %v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return diskmounter.Interface.Mount(device, target, fstype, options)
|
||||
}
|
||||
|
||||
func isFormatRequired(devicePath string, fstype string, exec *cinderSafeFormatAndMount) (bool, error) {
|
||||
args := []string{"-f", devicePath}
|
||||
glog.V(4).Infof("exec-ing: /bin/lsblk %v\n", args)
|
||||
cmd := exec.runner.Command("/bin/lsblk", args...)
|
||||
dataOut, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Warningf("error running /bin/lsblk\n%s", string(dataOut))
|
||||
return false, err
|
||||
}
|
||||
if len(string(dataOut)) > 0 {
|
||||
if strings.Contains(string(dataOut), fstype) {
|
||||
return false, nil
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
} else {
|
||||
glog.Warningf("Failed to get any response from /bin/lsblk")
|
||||
return false, errors.New("Failed to get reponse from /bin/lsblk")
|
||||
}
|
||||
glog.Warningf("Unknown error occured executing /bin/lsblk")
|
||||
return false, errors.New("Unknown error occured executing /bin/lsblk")
|
||||
}
|
||||
|
||||
func formatVolume(devicePath string, fstype string, exec *cinderSafeFormatAndMount) (bool, error) {
|
||||
if "ext4" != fstype && "ext3" != fstype {
|
||||
glog.Warningf("Unsupported format type: %q\n", fstype)
|
||||
return false, errors.New(fmt.Sprint("Unsupported format type: %q\n", fstype))
|
||||
}
|
||||
args := []string{devicePath}
|
||||
cmd := exec.runner.Command(fmt.Sprintf("/sbin/mkfs.%s", fstype), args...)
|
||||
dataOut, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Warningf("error running /sbin/mkfs for fstype: %q \n%s", fstype, string(dataOut))
|
||||
return false, err
|
||||
}
|
||||
glog.V(2).Infof("Successfully formated device: %q with fstype %q; output:\n %q\n,", devicePath, fstype, string(dataOut))
|
||||
return true, err
|
||||
}
|
||||
|
||||
func probeAttachedVolume() error {
|
||||
executor := exec.New()
|
||||
args := []string{"trigger"}
|
||||
cmd := executor.Command("/usr/bin/udevadm", args...)
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Errorf("error running udevadm trigger %v\n", err)
|
||||
return err
|
||||
}
|
||||
glog.V(4).Infof("Successfully probed all attachments")
|
||||
return nil
|
||||
}
|
82
pkg/volume/cinder/cinder_util_test.go
Normal file
82
pkg/volume/cinder/cinder_util_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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 cinder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
)
|
||||
|
||||
func TestSafeFormatAndMount(t *testing.T) {
|
||||
tests := []struct {
|
||||
fstype string
|
||||
expectedArgs []string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
fstype: "ext4",
|
||||
expectedArgs: []string{"/dev/foo", "/mnt/bar"},
|
||||
},
|
||||
{
|
||||
fstype: "ext3",
|
||||
expectedArgs: []string{"/dev/foo/blah", "/mnt/bar/blah"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
||||
var cmdOut string
|
||||
var argsOut []string
|
||||
fake := exec.FakeExec{
|
||||
CommandScript: []exec.FakeCommandAction{
|
||||
func(cmd string, args ...string) exec.Cmd {
|
||||
cmdOut = cmd
|
||||
argsOut = args
|
||||
fake := exec.FakeCmd{
|
||||
CombinedOutputScript: []exec.FakeCombinedOutputAction{
|
||||
func() ([]byte, error) { return []byte{}, test.err },
|
||||
},
|
||||
}
|
||||
return exec.InitFakeCmd(&fake, cmd, args...)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mounter := cinderSafeFormatAndMount{
|
||||
&mount.FakeMounter{},
|
||||
&fake,
|
||||
}
|
||||
|
||||
err := mounter.Mount("/dev/foo", "/mnt/bar", test.fstype, nil)
|
||||
if test.err == nil && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if test.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if cmdOut != "/bin/lsblk" {
|
||||
t.Errorf("unexpected command: %s", cmdOut)
|
||||
}
|
||||
if len(argsOut) != len(test.expectedArgs) {
|
||||
t.Errorf("unexpected args: %v, expected: %v", argsOut, test.expectedArgs)
|
||||
}
|
||||
}
|
||||
}
|
18
pkg/volume/cinder/doc.go
Normal file
18
pkg/volume/cinder/doc.go
Normal file
@@ -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 cinder contains the internal representation of cinder volumes.
|
||||
package cinder
|
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
"k8s.io/kubernetes/pkg/util/errors"
|
||||
@@ -121,6 +122,9 @@ type VolumeHost interface {
|
||||
// the provided spec. See comments on NewWrapperBuilder for more
|
||||
// context.
|
||||
NewWrapperCleaner(spec *Spec, podUID types.UID, mounter mount.Interface) (Cleaner, error)
|
||||
|
||||
//Get cloud provider from kubelet
|
||||
GetCloudProvider() cloudprovider.Interface
|
||||
}
|
||||
|
||||
// VolumePluginMgr tracks registered plugins.
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
@@ -32,10 +33,11 @@ type fakeVolumeHost struct {
|
||||
rootDir string
|
||||
kubeClient client.Interface
|
||||
pluginMgr VolumePluginMgr
|
||||
cloud cloudprovider.Interface
|
||||
}
|
||||
|
||||
func NewFakeVolumeHost(rootDir string, kubeClient client.Interface, plugins []VolumePlugin) *fakeVolumeHost {
|
||||
host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient}
|
||||
host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient, cloud: nil}
|
||||
host.pluginMgr.InitPlugins(plugins, host)
|
||||
return host
|
||||
}
|
||||
@@ -56,6 +58,10 @@ func (f *fakeVolumeHost) GetKubeClient() client.Interface {
|
||||
return f.kubeClient
|
||||
}
|
||||
|
||||
func (f *fakeVolumeHost) GetCloudProvider() cloudprovider.Interface {
|
||||
return f.cloud
|
||||
}
|
||||
|
||||
func (f *fakeVolumeHost) NewWrapperBuilder(spec *Spec, pod *api.Pod, opts VolumeOptions, mounter mount.Interface) (Builder, error) {
|
||||
plug, err := f.pluginMgr.FindPluginBySpec(spec)
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user