vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

72
vendor/k8s.io/kubernetes/pkg/volume/scaleio/BUILD generated vendored Normal file
View File

@ -0,0 +1,72 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"sio_mgr_test.go",
"sio_util_test.go",
"sio_volume_test.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/scaleio",
library = ":go_default_library",
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//vendor/github.com/codedellemc/goscaleio/types/v1:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"sio_client.go",
"sio_mgr.go",
"sio_plugin.go",
"sio_util.go",
"sio_volume.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/scaleio",
deps = [
"//pkg/util/keymutex:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//pkg/volume/util/volumehelper:go_default_library",
"//vendor/github.com/codedellemc/goscaleio:go_default_library",
"//vendor/github.com/codedellemc/goscaleio/types/v1:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,535 @@
/*
Copyright 2017 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 scaleio
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
"k8s.io/kubernetes/pkg/util/mount"
sio "github.com/codedellemc/goscaleio"
siotypes "github.com/codedellemc/goscaleio/types/v1"
"github.com/golang/glog"
)
var (
sioDiskIDPath = "/dev/disk/by-id"
)
type sioVolumeID string
type sioInterface interface {
FindVolume(name string) (*siotypes.Volume, error)
Volume(sioVolumeID) (*siotypes.Volume, error)
CreateVolume(name string, sizeGB int64) (*siotypes.Volume, error)
AttachVolume(sioVolumeID, bool) error
DetachVolume(sioVolumeID) error
DeleteVolume(sioVolumeID) error
IID() (string, error)
Devs() (map[string]string, error)
WaitForAttachedDevice(token string) (string, error)
WaitForDetachedDevice(token string) error
GetVolumeRefs(sioVolumeID) (int, error)
}
type sioClient struct {
client *sio.Client
gateway string
username string
password string
insecure bool
certsEnabled bool
system *siotypes.System
sysName string
sysClient *sio.System
protectionDomain *siotypes.ProtectionDomain
pdName string
pdClient *sio.ProtectionDomain
storagePool *siotypes.StoragePool
spName string
spClient *sio.StoragePool
provisionMode string
sdcPath string
sdcGuid string
instanceID string
inited bool
diskRegex *regexp.Regexp
mtx sync.Mutex
exec mount.Exec
}
func newSioClient(gateway, username, password string, sslEnabled bool, exec mount.Exec) (*sioClient, error) {
client := new(sioClient)
client.gateway = gateway
client.username = username
client.password = password
client.exec = exec
if sslEnabled {
client.insecure = false
client.certsEnabled = true
} else {
client.insecure = true
client.certsEnabled = false
}
r, err := regexp.Compile(`^emc-vol-\w*-\w*$`)
if err != nil {
glog.Error(log("failed to compile regex: %v", err))
return nil, err
}
client.diskRegex = r
// delay client setup/login until init()
return client, nil
}
// init setups client and authenticate
func (c *sioClient) init() error {
c.mtx.Lock()
defer c.mtx.Unlock()
if c.inited {
return nil
}
glog.V(4).Infoln(log("initializing scaleio client"))
client, err := sio.NewClientWithArgs(c.gateway, "", c.insecure, c.certsEnabled)
if err != nil {
glog.Error(log("failed to create client: %v", err))
return err
}
c.client = client
if _, err = c.client.Authenticate(
&sio.ConfigConnect{
Endpoint: c.gateway,
Version: "",
Username: c.username,
Password: c.password},
); err != nil {
glog.Error(log("client authentication failed: %v", err))
return err
}
// retrieve system
if c.system, err = c.findSystem(c.sysName); err != nil {
glog.Error(log("unable to find system %s: %v", c.sysName, err))
return err
}
// retrieve protection domain
if c.protectionDomain, err = c.findProtectionDomain(c.pdName); err != nil {
glog.Error(log("unable to find protection domain %s: %v", c.protectionDomain, err))
return err
}
// retrieve storage pool
if c.storagePool, err = c.findStoragePool(c.spName); err != nil {
glog.Error(log("unable to find storage pool %s: %v", c.storagePool, err))
return err
}
c.inited = true
return nil
}
func (c *sioClient) Volumes() ([]*siotypes.Volume, error) {
if err := c.init(); err != nil {
return nil, err
}
vols, err := c.getVolumes()
if err != nil {
glog.Error(log("failed to retrieve volumes: %v", err))
return nil, err
}
return vols, nil
}
func (c *sioClient) Volume(id sioVolumeID) (*siotypes.Volume, error) {
if err := c.init(); err != nil {
return nil, err
}
vols, err := c.getVolumesByID(id)
if err != nil {
glog.Error(log("failed to retrieve volume by id: %v", err))
return nil, err
}
vol := vols[0]
if vol == nil {
glog.V(4).Info(log("volume not found, id %s", id))
return nil, errors.New("volume not found")
}
return vol, nil
}
func (c *sioClient) FindVolume(name string) (*siotypes.Volume, error) {
if err := c.init(); err != nil {
return nil, err
}
glog.V(4).Info(log("searching for volume %s", name))
volumes, err := c.getVolumesByName(name)
if err != nil {
glog.Error(log("failed to find volume by name %v", err))
return nil, err
}
for _, volume := range volumes {
if volume.Name == name {
glog.V(4).Info(log("found volume %s", name))
return volume, nil
}
}
glog.V(4).Info(log("volume not found, name %s", name))
return nil, errors.New("volume not found")
}
func (c *sioClient) CreateVolume(name string, sizeGB int64) (*siotypes.Volume, error) {
if err := c.init(); err != nil {
return nil, err
}
params := &siotypes.VolumeParam{
Name: name,
VolumeSizeInKb: strconv.Itoa(int(sizeGB) * 1024 * 1024),
VolumeType: c.provisionMode,
}
createResponse, err := c.client.CreateVolume(params, c.storagePool.Name)
if err != nil {
glog.Error(log("failed to create volume %s: %v", name, err))
return nil, err
}
return c.Volume(sioVolumeID(createResponse.ID))
}
// AttachVolume maps the scaleio volume to an sdc node. If the multipleMappings flag
// is true, ScaleIO will allow other SDC to map to that volume.
func (c *sioClient) AttachVolume(id sioVolumeID, multipleMappings bool) error {
if err := c.init(); err != nil {
glog.Error(log("failed to init'd client in attach volume: %v", err))
return err
}
iid, err := c.IID()
if err != nil {
glog.Error(log("failed to get instanceIID for attach volume: %v", err))
return err
}
params := &siotypes.MapVolumeSdcParam{
SdcID: iid,
AllowMultipleMappings: strconv.FormatBool(multipleMappings),
AllSdcs: "",
}
volClient := sio.NewVolume(c.client)
volClient.Volume = &siotypes.Volume{ID: string(id)}
if err := volClient.MapVolumeSdc(params); err != nil {
glog.Error(log("failed to attach volume id %s: %v", id, err))
return err
}
glog.V(4).Info(log("volume %s attached successfully", id))
return nil
}
// DetachVolume detaches the volume with specified id.
func (c *sioClient) DetachVolume(id sioVolumeID) error {
if err := c.init(); err != nil {
return err
}
iid, err := c.IID()
if err != nil {
return err
}
params := &siotypes.UnmapVolumeSdcParam{
SdcID: "",
IgnoreScsiInitiators: "true",
AllSdcs: iid,
}
volClient := sio.NewVolume(c.client)
volClient.Volume = &siotypes.Volume{ID: string(id)}
if err := volClient.UnmapVolumeSdc(params); err != nil {
return err
}
return nil
}
// DeleteVolume deletes the volume with the specified id
func (c *sioClient) DeleteVolume(id sioVolumeID) error {
if err := c.init(); err != nil {
return err
}
vol, err := c.Volume(id)
if err != nil {
return err
}
volClient := sio.NewVolume(c.client)
volClient.Volume = vol
if err := volClient.RemoveVolume("ONLY_ME"); err != nil {
return err
}
return nil
}
// IID returns the scaleio instance id for node
func (c *sioClient) IID() (string, error) {
if err := c.init(); err != nil {
return "", err
}
// if instanceID not set, retrieve it
if c.instanceID == "" {
guid, err := c.getGuid()
if err != nil {
return "", err
}
sdc, err := c.sysClient.FindSdc("SdcGuid", guid)
if err != nil {
glog.Error(log("failed to retrieve sdc info %s", err))
return "", err
}
c.instanceID = sdc.Sdc.ID
glog.V(4).Info(log("retrieved instanceID %s", c.instanceID))
}
return c.instanceID, nil
}
// getGuid returns instance GUID, if not set using resource labels
// it attemps to fallback to using drv_cfg binary
func (c *sioClient) getGuid() (string, error) {
if c.sdcGuid == "" {
glog.V(4).Info(log("sdc guid label not set, falling back to using drv_cfg"))
cmd := c.getSdcCmd()
output, err := c.exec.Run(cmd, "--query_guid")
if err != nil {
glog.Error(log("drv_cfg --query_guid failed: %v", err))
return "", err
}
c.sdcGuid = strings.TrimSpace(string(output))
}
return c.sdcGuid, nil
}
// getSioDiskPaths traverse local disk devices to retrieve device path
// The path is extracted from /dev/disk/by-id; each sio device path has format:
// emc-vol-<mdmID-volID> e.g.:
// emc-vol-788d9efb0a8f20cb-a2b8419300000000
func (c *sioClient) getSioDiskPaths() ([]os.FileInfo, error) {
files, err := ioutil.ReadDir(sioDiskIDPath)
if err != nil {
glog.Error(log("failed to ReadDir %s: %v", sioDiskIDPath, err))
return nil, err
}
result := []os.FileInfo{}
for _, file := range files {
if c.diskRegex.MatchString(file.Name()) {
result = append(result, file)
}
}
return result, nil
}
// GetVolumeRefs counts the number of references an SIO volume has a disk device.
// This is useful in preventing premature detach.
func (c *sioClient) GetVolumeRefs(volId sioVolumeID) (refs int, err error) {
files, err := c.getSioDiskPaths()
if err != nil {
return 0, err
}
for _, file := range files {
if strings.Contains(file.Name(), string(volId)) {
refs++
}
}
return
}
// Devs returns a map of local devices as map[<volume.id>]<deviceName>
func (c *sioClient) Devs() (map[string]string, error) {
volumeMap := make(map[string]string)
files, err := c.getSioDiskPaths()
if err != nil {
return nil, err
}
for _, f := range files {
// split emc-vol-<mdmID>-<volumeID> to pull out volumeID
parts := strings.Split(f.Name(), "-")
if len(parts) != 4 {
return nil, errors.New("unexpected ScaleIO device name format")
}
volumeID := parts[3]
devPath, err := filepath.EvalSymlinks(fmt.Sprintf("%s/%s", sioDiskIDPath, f.Name()))
if err != nil {
glog.Error(log("devicepath-to-volID mapping error: %v", err))
return nil, err
}
// map volumeID to devicePath
volumeMap[volumeID] = devPath
}
return volumeMap, nil
}
// WaitForAttachedDevice sets up a timer to wait for an attached device to appear in the instance's list.
func (c *sioClient) WaitForAttachedDevice(token string) (string, error) {
if token == "" {
return "", fmt.Errorf("invalid attach token")
}
// wait for device to show up in local device list
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
timer := time.NewTimer(30 * time.Second)
defer timer.Stop()
for {
select {
case <-ticker.C:
devMap, err := c.Devs()
if err != nil {
glog.Error(log("failed while waiting for volume to attach: %v", err))
return "", err
}
go func() {
glog.V(4).Infof(log("waiting for volume %s to be mapped/attached", token))
}()
if path, ok := devMap[token]; ok {
glog.V(4).Info(log("device %s mapped to vol %s", path, token))
return path, nil
}
case <-timer.C:
glog.Error(log("timed out while waiting for volume to be mapped to a device"))
return "", fmt.Errorf("volume attach timeout")
}
}
}
// waitForDetachedDevice waits for device to be detached
func (c *sioClient) WaitForDetachedDevice(token string) error {
if token == "" {
return fmt.Errorf("invalid detach token")
}
// wait for attach.Token to show up in local device list
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
timer := time.NewTimer(30 * time.Second)
defer timer.Stop()
for {
select {
case <-ticker.C:
devMap, err := c.Devs()
if err != nil {
glog.Error(log("failed while waiting for volume to unmap/detach: %v", err))
return err
}
go func() {
glog.V(4).Infof(log("waiting for volume %s to be unmapped/detached", token))
}()
// cant find vol id, then ok.
if _, ok := devMap[token]; !ok {
return nil
}
case <-timer.C:
glog.Error(log("timed out while waiting for volume %s to be unmapped/detached", token))
return fmt.Errorf("volume detach timeout")
}
}
}
// ***********************************************************************
// Little Helpers!
// ***********************************************************************
func (c *sioClient) findSystem(sysname string) (sys *siotypes.System, err error) {
if c.sysClient, err = c.client.FindSystem("", sysname, ""); err != nil {
return nil, err
}
systems, err := c.client.GetInstance("")
if err != nil {
glog.Error(log("failed to retrieve instances: %v", err))
return nil, err
}
for _, sys = range systems {
if sys.Name == sysname {
return sys, nil
}
}
glog.Error(log("system %s not found", sysname))
return nil, errors.New("system not found")
}
func (c *sioClient) findProtectionDomain(pdname string) (*siotypes.ProtectionDomain, error) {
c.pdClient = sio.NewProtectionDomain(c.client)
if c.sysClient != nil {
protectionDomain, err := c.sysClient.FindProtectionDomain("", pdname, "")
if err != nil {
glog.Error(log("failed to retrieve protection domains: %v", err))
return nil, err
}
c.pdClient.ProtectionDomain = protectionDomain
return protectionDomain, nil
}
glog.Error(log("protection domain %s not set", pdname))
return nil, errors.New("protection domain not set")
}
func (c *sioClient) findStoragePool(spname string) (*siotypes.StoragePool, error) {
c.spClient = sio.NewStoragePool(c.client)
if c.pdClient != nil {
sp, err := c.pdClient.FindStoragePool("", spname, "")
if err != nil {
glog.Error(log("failed to retrieve storage pool: %v", err))
return nil, err
}
c.spClient.StoragePool = sp
return sp, nil
}
glog.Error(log("storage pool %s not set", spname))
return nil, errors.New("storage pool not set")
}
func (c *sioClient) getVolumes() ([]*siotypes.Volume, error) {
return c.client.GetVolume("", "", "", "", true)
}
func (c *sioClient) getVolumesByID(id sioVolumeID) ([]*siotypes.Volume, error) {
return c.client.GetVolume("", string(id), "", "", true)
}
func (c *sioClient) getVolumesByName(name string) ([]*siotypes.Volume, error) {
return c.client.GetVolume("", "", "", name, true)
}
func (c *sioClient) getSdcPath() string {
return sdcRootPath
}
func (c *sioClient) getSdcCmd() string {
return path.Join(c.getSdcPath(), "drv_cfg")
}

248
vendor/k8s.io/kubernetes/pkg/volume/scaleio/sio_mgr.go generated vendored Normal file
View File

@ -0,0 +1,248 @@
/*
Copyright 2017 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 scaleio
import (
"errors"
"strconv"
"k8s.io/kubernetes/pkg/util/mount"
"github.com/golang/glog"
siotypes "github.com/codedellemc/goscaleio/types/v1"
)
type storageInterface interface {
CreateVolume(string, int64) (*siotypes.Volume, error)
AttachVolume(string, bool) (string, error)
IsAttached(string) (bool, error)
DetachVolume(string) error
DeleteVolume(string) error
}
type sioMgr struct {
client sioInterface
configData map[string]string
exec mount.Exec
}
func newSioMgr(configs map[string]string, exec mount.Exec) (*sioMgr, error) {
if configs == nil {
return nil, errors.New("missing configuration data")
}
configs[confKey.protectionDomain] = defaultString(configs[confKey.protectionDomain], "default")
configs[confKey.storagePool] = defaultString(configs[confKey.storagePool], "default")
configs[confKey.sdcRootPath] = defaultString(configs[confKey.sdcRootPath], sdcRootPath)
configs[confKey.storageMode] = defaultString(configs[confKey.storageMode], "ThinProvisioned")
mgr := &sioMgr{configData: configs, exec: exec}
return mgr, nil
}
// getClient safely returns an sioInterface
func (m *sioMgr) getClient() (sioInterface, error) {
if m.client == nil {
glog.V(4).Info(log("creating scaleio client"))
configs := m.configData
username := configs[confKey.username]
password := configs[confKey.password]
gateway := configs[confKey.gateway]
b, err := strconv.ParseBool(configs[confKey.sslEnabled])
if err != nil {
glog.Error(log("failed to parse sslEnabled, must be either \"true\" or \"false\""))
return nil, err
}
certsEnabled := b
glog.V(4).Info(log("creating new client for gateway %s", gateway))
client, err := newSioClient(gateway, username, password, certsEnabled, m.exec)
if err != nil {
glog.Error(log("failed to create scaleio client: %v", err))
return nil, err
}
client.sysName = configs[confKey.system]
client.pdName = configs[confKey.protectionDomain]
client.spName = configs[confKey.storagePool]
client.sdcPath = configs[confKey.sdcRootPath]
client.provisionMode = configs[confKey.storageMode]
client.sdcGuid = configs[confKey.sdcGuid]
m.client = client
glog.V(4).Info(log("client created successfully [gateway=%s]", gateway))
}
return m.client, nil
}
// CreateVolume creates a new ScaleIO volume
func (m *sioMgr) CreateVolume(volName string, sizeGB int64) (*siotypes.Volume, error) {
client, err := m.getClient()
if err != nil {
return nil, err
}
glog.V(4).Infof("scaleio: creating volume %s", volName)
vol, err := client.CreateVolume(volName, sizeGB)
if err != nil {
glog.V(4).Infof("scaleio: failed creating volume %s: %v", volName, err)
return nil, err
}
glog.V(4).Infof("scaleio: created volume %s successfully", volName)
return vol, nil
}
// AttachVolume maps a ScaleIO volume to the running node. If flag multiMaps,
// ScaleIO will allow other SDC to map to volume.
func (m *sioMgr) AttachVolume(volName string, multipleMappings bool) (string, error) {
client, err := m.getClient()
if err != nil {
glog.Error(log("attach volume failed: %v", err))
return "", err
}
glog.V(4).Infoln(log("attaching volume %s", volName))
iid, err := client.IID()
if err != nil {
glog.Error(log("failed to get instanceID"))
return "", err
}
glog.V(4).Info(log("attaching volume %s to host instance %s", volName, iid))
devs, err := client.Devs()
if err != nil {
return "", err
}
vol, err := client.FindVolume(volName)
if err != nil {
glog.Error(log("failed to find volume %s: %v", volName, err))
return "", err
}
// handle vol if already attached
if len(vol.MappedSdcInfo) > 0 {
if m.isSdcMappedToVol(iid, vol) {
glog.V(4).Info(log("skippping attachment, volume %s already attached to sdc %s", volName, iid))
return devs[vol.ID], nil
}
}
// attach volume, get deviceName
if err := client.AttachVolume(sioVolumeID(vol.ID), multipleMappings); err != nil {
glog.Error(log("attachment for volume %s failed :%v", volName, err))
return "", err
}
device, err := client.WaitForAttachedDevice(vol.ID)
if err != nil {
glog.Error(log("failed while waiting for device to attach: %v", err))
return "", err
}
glog.V(4).Info(log("volume %s attached successfully as %s to instance %s", volName, device, iid))
return device, nil
}
// IsAttached verifies that the named ScaleIO volume is still attached
func (m *sioMgr) IsAttached(volName string) (bool, error) {
client, err := m.getClient()
if err != nil {
return false, err
}
iid, err := client.IID()
if err != nil {
glog.Error("scaleio: failed to get instanceID")
return false, err
}
vol, err := client.FindVolume(volName)
if err != nil {
return false, err
}
return m.isSdcMappedToVol(iid, vol), nil
}
// DetachVolume detaches the name ScaleIO volume from an instance
func (m *sioMgr) DetachVolume(volName string) error {
client, err := m.getClient()
if err != nil {
return err
}
iid, err := client.IID()
if err != nil {
glog.Error(log("failed to get instanceID: %v", err))
return err
}
vol, err := client.FindVolume(volName)
if err != nil {
return err
}
if !m.isSdcMappedToVol(iid, vol) {
glog.Warning(log(
"skipping detached, vol %s not attached to instance %s",
volName, iid,
))
return nil
}
if err := client.DetachVolume(sioVolumeID(vol.ID)); err != nil {
glog.Error(log("failed to detach vol %s: %v", volName, err))
return err
}
glog.V(4).Info(log("volume %s detached successfully", volName))
return nil
}
// DeleteVolumes removes the ScaleIO volume
func (m *sioMgr) DeleteVolume(volName string) error {
client, err := m.getClient()
if err != nil {
return err
}
vol, err := client.FindVolume(volName)
if err != nil {
return err
}
if err := client.DeleteVolume(sioVolumeID(vol.ID)); err != nil {
glog.Error(log("failed to delete volume %s: %v", volName, err))
return err
}
glog.V(4).Info(log("deleted volume %s successfully", volName))
return nil
}
// isSdcMappedToVol returns true if the sdc is mapped to the volume
func (m *sioMgr) isSdcMappedToVol(sdcID string, vol *siotypes.Volume) bool {
if len(vol.MappedSdcInfo) == 0 {
glog.V(4).Info(log("no attachment found"))
return false
}
for _, sdcInfo := range vol.MappedSdcInfo {
if sdcInfo.SdcID == sdcID {
return true
}
}
return false
}

View File

@ -0,0 +1,338 @@
/*
Copyright 2017 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 scaleio
import (
"errors"
"testing"
"time"
"k8s.io/kubernetes/pkg/util/mount"
siotypes "github.com/codedellemc/goscaleio/types/v1"
)
var (
fakeSdcID = "test-sdc-123456789"
fakeVolumeName = "test-vol-0001"
fakeVolumeID = "1234567890"
fakeDev = "/dev/testABC"
fakeConfig = map[string]string{
confKey.gateway: "http://sio.gateway:1234",
confKey.sslEnabled: "false",
confKey.system: "scaleio",
confKey.volumeName: "sio-0001",
confKey.secretName: "sio-secret",
confKey.username: "c2lvdXNlcgo=", // siouser
confKey.password: "c2lvcGFzc3dvcmQK", // siopassword
}
)
func newTestMgr(t *testing.T) *sioMgr {
mgr, err := newSioMgr(fakeConfig, mount.NewFakeExec(nil))
if err != nil {
t.Error(err)
}
mgr.client = newFakeSio()
return mgr
}
func TestMgrNew(t *testing.T) {
mgr, err := newSioMgr(fakeConfig, mount.NewFakeExec(nil))
if err != nil {
t.Fatal(err)
}
if mgr.configData == nil {
t.Fatal("configuration data not set")
}
if mgr.configData[confKey.volumeName] != "sio-0001" {
t.Errorf("expecting %s, got %s", "sio-0001", mgr.configData[confKey.volumeName])
}
// check defaults
if mgr.configData[confKey.protectionDomain] != "default" {
t.Errorf("unexpected value for confData[protectionDomain] %s", mgr.configData[confKey.protectionDomain])
}
if mgr.configData[confKey.storagePool] != "default" {
t.Errorf("unexpected value for confData[storagePool] %s", mgr.configData[confKey.storagePool])
}
if mgr.configData[confKey.storageMode] != "ThinProvisioned" {
t.Errorf("unexpected value for confData[storageMode] %s", mgr.configData[confKey.storageMode])
}
}
func TestMgrGetClient(t *testing.T) {
mgr := newTestMgr(t)
_, err := mgr.getClient()
if err != nil {
t.Fatal(err)
}
if mgr.client == nil {
t.Fatal("mgr.client not set")
}
}
func TestMgrCreateVolume(t *testing.T) {
mgr := newTestMgr(t)
vol, err := mgr.CreateVolume("test-vol-0001", 8*1024*1024)
if err != nil {
t.Fatal(err)
}
if vol.Name != "test-vol-0001" {
t.Errorf("unexpected vol.Name %s", vol.Name)
}
}
func TestMgrAttachVolume(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
device, err := mgr.AttachVolume("test-vol-0001", false)
if err != nil {
t.Fatal(err)
}
if device != "/dev/testABC" {
t.Errorf("unexpected value for mapped device %s", device)
}
}
func TestMgrAttachVolume_AlreadyAttached(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
dev, err := mgr.AttachVolume("test-vol-0001", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if dev != "/dev/testABC" {
t.Errorf("unexpected value for mapped device %s", dev)
}
}
func TestMgrAttachVolume_VolumeNotFoundError(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
_, err := mgr.AttachVolume("test-vol-0002", false)
if err == nil {
t.Error("attachVolume should fail with volume not found error")
}
}
func TestMgrAttachVolume_WaitForAttachError(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
go func() {
c := mgr.client.(*fakeSio)
close(c.waitAttachCtrl)
}()
_, err := mgr.AttachVolume("test-vol-0001", false)
if err == nil {
t.Error("attachVolume should fail with attach timeout error")
}
}
func TestMgrDetachVolume(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
if err := mgr.DetachVolume("test-vol-0001"); err != nil {
t.Fatal(err)
}
fakeSio := mgr.client.(*fakeSio)
if len(fakeSio.volume.MappedSdcInfo) != 0 {
t.Errorf("expecting attached sdc to 0, got %d", len(fakeSio.volume.MappedSdcInfo))
}
if len(fakeSio.devs) != 0 {
t.Errorf("expecting local devs to be 0, got %d", len(fakeSio.devs))
}
}
func TestMgrDetachVolume_VolumeNotFound(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
err := mgr.DetachVolume("test-vol-0002")
if err == nil {
t.Fatal("expected a volume not found failure")
}
}
func TestMgrDetachVolume_VolumeNotAttached(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
err := mgr.DetachVolume("test-vol-0001")
if err != nil {
t.Fatal(err)
}
}
func TestMgrDetachVolume_VolumeAlreadyDetached(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
mgr.DetachVolume("test-vol-0001")
err := mgr.DetachVolume("test-vol-0001")
if err != nil {
t.Fatal("failed detaching a volume already detached")
}
}
func TestMgrDetachVolume_WaitForDetachError(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
err := mgr.DetachVolume("test-vol-0001")
if err != nil {
t.Error("detachVolume failed")
}
}
func TestMgrDeleteVolume(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
err := mgr.DeleteVolume("test-vol-0001")
if err != nil {
t.Fatal(err)
}
sio := mgr.client.(*fakeSio)
if sio.volume != nil {
t.Errorf("volume not nil after delete operation")
}
}
func TestMgrDeleteVolume_VolumeNotFound(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
err := mgr.DeleteVolume("test-vol-0002")
if err == nil {
t.Fatal("expected volume not found error")
}
}
// ************************************************************
// Helper Test Types
// ************************************************************
type fakeSio struct {
volume *siotypes.Volume
waitAttachCtrl chan struct{}
waitDetachCtrl chan struct{}
devs map[string]string
isMultiMap bool
}
func newFakeSio() *fakeSio {
return &fakeSio{
waitAttachCtrl: make(chan struct{}),
waitDetachCtrl: make(chan struct{}),
}
}
func (f *fakeSio) FindVolume(volumeName string) (*siotypes.Volume, error) {
if f.volume == nil || f.volume.Name != volumeName {
return nil, errors.New("volume not found")
}
return f.volume, nil
}
func (f *fakeSio) Volume(id sioVolumeID) (*siotypes.Volume, error) {
if f.volume == nil || f.volume.ID != string(id) {
return nil, errors.New("volume not found")
}
return f.volume, nil
}
func (f *fakeSio) CreateVolume(volName string, sizeGB int64) (*siotypes.Volume, error) {
f.volume = &siotypes.Volume{
ID: fakeVolumeID,
Name: volName,
SizeInKb: int(sizeGB),
VolumeType: "test",
}
return f.volume, nil
}
func (f *fakeSio) AttachVolume(id sioVolumeID, multiMaps bool) error {
f.isMultiMap = multiMaps
_, err := f.Volume(id)
if err != nil {
return err
}
f.volume.MappedSdcInfo = []*siotypes.MappedSdcInfo{
{SdcID: fakeSdcID},
}
return nil
}
func (f *fakeSio) DetachVolume(id sioVolumeID) error {
if _, err := f.Volume(id); err != nil {
return err
}
f.volume.MappedSdcInfo = nil
delete(f.devs, f.volume.ID)
return nil
}
func (f *fakeSio) DeleteVolume(id sioVolumeID) error {
if _, err := f.Volume(id); err != nil {
return err
}
f.volume = nil
return nil
}
func (f *fakeSio) IID() (string, error) {
return fakeSdcID, nil
}
func (f *fakeSio) Devs() (map[string]string, error) {
if f.volume == nil {
return nil, errors.New("volume not found")
}
f.devs = map[string]string{
f.volume.ID: fakeDev,
}
return f.devs, nil
}
func (f *fakeSio) GetVolumeRefs(volId sioVolumeID) (int, error) {
if f.volume == nil {
return 0, nil
}
return 1, nil
}
func (f *fakeSio) WaitForAttachedDevice(token string) (string, error) {
select {
case <-time.After(500 * time.Millisecond):
return fakeDev, nil
case <-f.waitAttachCtrl:
return "", errors.New("attached device timeout")
}
}
func (f *fakeSio) WaitForDetachedDevice(token string) error {
select {
case <-time.After(500 * time.Millisecond):
delete(f.devs, f.volume.ID)
return nil
case <-f.waitDetachCtrl:
return errors.New("detach device timeout")
}
}

View File

@ -0,0 +1,220 @@
/*
Copyright 2017 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 scaleio
import (
"errors"
"github.com/golang/glog"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/util/keymutex"
"k8s.io/kubernetes/pkg/volume"
)
const (
sioName = "scaleio"
sioPluginName = "kubernetes.io/scaleio"
sioConfigFileName = "sioconf.dat"
)
type sioPlugin struct {
host volume.VolumeHost
volumeMtx keymutex.KeyMutex
}
func ProbeVolumePlugins() []volume.VolumePlugin {
p := &sioPlugin{
host: nil,
}
return []volume.VolumePlugin{p}
}
// *******************
// VolumePlugin Impl
// *******************
var _ volume.VolumePlugin = &sioPlugin{}
func (p *sioPlugin) Init(host volume.VolumeHost) error {
p.host = host
p.volumeMtx = keymutex.NewKeyMutex()
return nil
}
func (p *sioPlugin) GetPluginName() string {
return sioPluginName
}
func (p *sioPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
attribs, err := getVolumeSourceAttribs(spec)
if err != nil {
return "", err
}
return attribs.volName, nil
}
func (p *sioPlugin) CanSupport(spec *volume.Spec) bool {
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.ScaleIO != nil) ||
(spec.Volume != nil && spec.Volume.ScaleIO != nil)
}
func (p *sioPlugin) RequiresRemount() bool {
return false
}
func (p *sioPlugin) NewMounter(
spec *volume.Spec,
pod *api.Pod,
_ volume.VolumeOptions) (volume.Mounter, error) {
// extract source info from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource type
attribs, err := getVolumeSourceAttribs(spec)
if err != nil {
return nil, errors.New(log("mounter failed to extract volume attributes from spec: %v", err))
}
secretName, secretNS, err := getSecretAndNamespaceFromSpec(spec, pod)
if err != nil {
return nil, errors.New(log("failed to get secret name or secretNamespace: %v", err))
}
return &sioVolume{
pod: pod,
spec: spec,
secretName: secretName,
secretNamespace: secretNS,
volSpecName: spec.Name(),
volName: attribs.volName,
podUID: pod.UID,
readOnly: attribs.readOnly,
fsType: attribs.fsType,
plugin: p,
}, nil
}
// NewUnmounter creates a representation of the volume to unmount
func (p *sioPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) {
glog.V(4).Info(log("Unmounter for %s", specName))
return &sioVolume{
podUID: podUID,
volSpecName: specName,
plugin: p,
}, nil
}
func (p *sioPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
sioVol := &api.Volume{
Name: volumeName,
VolumeSource: api.VolumeSource{
ScaleIO: &api.ScaleIOVolumeSource{},
},
}
return volume.NewSpecFromVolume(sioVol), nil
}
// SupportsMountOption returns true if volume plugins supports Mount options
// Specifying mount options in a volume plugin that doesn't support
// user specified mount options will result in error creating persistent volumes
func (p *sioPlugin) SupportsMountOption() bool {
return false
}
// SupportsBulkVolumeVerification checks if volume plugin type is capable
// of enabling bulk polling of all nodes. This can speed up verification of
// attached volumes by quite a bit, but underlying pluging must support it.
func (p *sioPlugin) SupportsBulkVolumeVerification() bool {
return false
}
//******************************
// PersistentVolumePlugin Impl
// *****************************
var _ volume.PersistentVolumePlugin = &sioPlugin{}
func (p *sioPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
}
}
// ***************************
// DeletableVolumePlugin Impl
//****************************
var _ volume.DeletableVolumePlugin = &sioPlugin{}
func (p *sioPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
attribs, err := getVolumeSourceAttribs(spec)
if err != nil {
glog.Error(log("deleter failed to extract volume attributes from spec: %v", err))
return nil, err
}
secretName, secretNS, err := getSecretAndNamespaceFromSpec(spec, nil)
if err != nil {
return nil, errors.New(log("failed to get secret name or secretNamespace: %v", err))
}
return &sioVolume{
spec: spec,
secretName: secretName,
secretNamespace: secretNS,
volSpecName: spec.Name(),
volName: attribs.volName,
plugin: p,
readOnly: attribs.readOnly,
}, nil
}
// *********************************
// ProvisionableVolumePlugin Impl
// *********************************
var _ volume.ProvisionableVolumePlugin = &sioPlugin{}
func (p *sioPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
glog.V(4).Info(log("creating Provisioner"))
configData := options.Parameters
if configData == nil {
glog.Error(log("provisioner missing parameters, unable to continue"))
return nil, errors.New("option parameters missing")
}
// Supports ref of name of secret a couple of ways:
// options.Parameters["secretRef"] for backward compat, or
// options.Parameters["secretName"]
secretName := configData[confKey.secretName]
if secretName == "" {
secretName = configData["secretName"]
configData[confKey.secretName] = secretName
}
secretNS := configData[confKey.secretNamespace]
if secretNS == "" {
secretNS = options.PVC.Namespace
}
return &sioVolume{
configData: configData,
plugin: p,
options: options,
secretName: secretName,
secretNamespace: secretNS,
volSpecName: options.PVName,
}, nil
}

337
vendor/k8s.io/kubernetes/pkg/volume/scaleio/sio_util.go generated vendored Normal file
View File

@ -0,0 +1,337 @@
/*
Copyright 2017 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 scaleio
import (
"encoding/gob"
"errors"
"fmt"
"os"
"path"
"strconv"
"github.com/golang/glog"
api "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/volume"
volutil "k8s.io/kubernetes/pkg/volume/util"
)
type volSourceAttribs struct {
volName,
fsType string
readOnly bool
}
var (
confKey = struct {
gateway,
sslEnabled,
secretName,
system,
protectionDomain,
storagePool,
storageMode,
sdcRootPath,
volumeName,
volSpecName,
fsType,
readOnly,
username,
password,
secretNamespace,
sdcGuid string
}{
gateway: "gateway",
sslEnabled: "sslEnabled",
secretName: "secretRef",
secretNamespace: "secretNamespace",
system: "system",
protectionDomain: "protectionDomain",
storagePool: "storagePool",
storageMode: "storageMode",
sdcRootPath: "sdcRootPath",
volumeName: "volumeName",
volSpecName: "volSpecName",
fsType: "fsType",
readOnly: "readOnly",
username: "username",
password: "password",
sdcGuid: "sdcGuid",
}
sdcGuidLabelName = "scaleio.sdcGuid"
sdcRootPath = "/opt/emc/scaleio/sdc/bin"
secretNotFoundErr = errors.New("secret not found")
configMapNotFoundErr = errors.New("configMap not found")
gatewayNotProvidedErr = errors.New("ScaleIO gateway not provided")
secretRefNotProvidedErr = errors.New("secret ref not provided")
systemNotProvidedErr = errors.New("ScaleIO system not provided")
storagePoolNotProvidedErr = errors.New("ScaleIO storage pool not provided")
protectionDomainNotProvidedErr = errors.New("ScaleIO protection domain not provided")
)
// mapVolumeSpec maps attributes from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource to config
func mapVolumeSpec(config map[string]string, spec *volume.Spec) {
if source, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil {
config[confKey.gateway] = source.Gateway
config[confKey.system] = source.System
config[confKey.volumeName] = source.VolumeName
config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled)
config[confKey.protectionDomain] = source.ProtectionDomain
config[confKey.storagePool] = source.StoragePool
config[confKey.storageMode] = source.StorageMode
config[confKey.fsType] = source.FSType
config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly)
}
if source, err := getScaleIOVolumeSourceFromSpec(spec); err == nil {
config[confKey.gateway] = source.Gateway
config[confKey.system] = source.System
config[confKey.volumeName] = source.VolumeName
config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled)
config[confKey.protectionDomain] = source.ProtectionDomain
config[confKey.storagePool] = source.StoragePool
config[confKey.storageMode] = source.StorageMode
config[confKey.fsType] = source.FSType
config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly)
}
//optionals
applyConfigDefaults(config)
}
func validateConfigs(config map[string]string) error {
if config[confKey.gateway] == "" {
return gatewayNotProvidedErr
}
if config[confKey.secretName] == "" {
return secretRefNotProvidedErr
}
if config[confKey.system] == "" {
return systemNotProvidedErr
}
if config[confKey.storagePool] == "" {
return storagePoolNotProvidedErr
}
if config[confKey.protectionDomain] == "" {
return protectionDomainNotProvidedErr
}
return nil
}
// applyConfigDefaults apply known defaults to incoming spec for dynamic PVCs.
func applyConfigDefaults(config map[string]string) {
b, err := strconv.ParseBool(config[confKey.sslEnabled])
if err != nil {
glog.Warning(log("failed to parse param sslEnabled, setting it to false"))
b = false
}
config[confKey.sslEnabled] = strconv.FormatBool(b)
config[confKey.storageMode] = defaultString(config[confKey.storageMode], "ThinProvisioned")
config[confKey.fsType] = defaultString(config[confKey.fsType], "xfs")
b, err = strconv.ParseBool(config[confKey.readOnly])
if err != nil {
glog.Warning(log("failed to parse param readOnly, setting it to false"))
b = false
}
config[confKey.readOnly] = strconv.FormatBool(b)
}
func defaultString(val, defVal string) string {
if val == "" {
return defVal
}
return val
}
// loadConfig loads configuration data from a file on disk
func loadConfig(configName string) (map[string]string, error) {
glog.V(4).Info(log("loading config file %s", configName))
file, err := os.Open(configName)
if err != nil {
glog.Error(log("failed to open config file %s: %v", configName, err))
return nil, err
}
defer file.Close()
data := map[string]string{}
if err := gob.NewDecoder(file).Decode(&data); err != nil {
glog.Error(log("failed to parse config data %s: %v", configName, err))
return nil, err
}
applyConfigDefaults(data)
if err := validateConfigs(data); err != nil {
glog.Error(log("failed to load ConfigMap %s: %v", err))
return nil, err
}
return data, nil
}
// saveConfig saves the configuration data to local disk
func saveConfig(configName string, data map[string]string) error {
glog.V(4).Info(log("saving config file %s", configName))
dir := path.Dir(configName)
if _, err := os.Stat(dir); err != nil {
if !os.IsNotExist(err) {
return err
}
glog.V(4).Info(log("creating config dir for config data: %s", dir))
if err := os.MkdirAll(dir, 0750); err != nil {
glog.Error(log("failed to create config data dir %v", err))
return err
}
}
file, err := os.Create(configName)
if err != nil {
glog.V(4).Info(log("failed to save config data file %s: %v", configName, err))
return err
}
defer file.Close()
if err := gob.NewEncoder(file).Encode(data); err != nil {
glog.Error(log("failed to save config %s: %v", configName, err))
return err
}
glog.V(4).Info(log("config data file saved successfully as %s", configName))
return nil
}
// attachSecret loads secret object and attaches to configData
func attachSecret(plug *sioPlugin, namespace string, configData map[string]string) error {
// load secret
secretRefName := configData[confKey.secretName]
kubeClient := plug.host.GetKubeClient()
secretMap, err := volutil.GetSecretForPV(namespace, secretRefName, sioPluginName, kubeClient)
if err != nil {
glog.Error(log("failed to get secret: %v", err))
return secretNotFoundErr
}
// merge secret data
for key, val := range secretMap {
configData[key] = val
}
return nil
}
// attachSdcGuid injects the sdc guid node label value into config
func attachSdcGuid(plug *sioPlugin, conf map[string]string) error {
guid, err := getSdcGuidLabel(plug)
if err != nil {
return err
}
conf[confKey.sdcGuid] = guid
return nil
}
// getSdcGuidLabel fetches the scaleio.sdcGuid node label
// associated with the node executing this code.
func getSdcGuidLabel(plug *sioPlugin) (string, error) {
nodeLabels, err := plug.host.GetNodeLabels()
if err != nil {
return "", err
}
label, ok := nodeLabels[sdcGuidLabelName]
if !ok {
glog.V(4).Info(log("node label %s not found", sdcGuidLabelName))
return "", nil
}
glog.V(4).Info(log("found node label %s=%s", sdcGuidLabelName, label))
return label, nil
}
// getVolumeSourceFromSpec safely extracts ScaleIOVolumeSource or ScaleIOPersistentVolumeSource from spec
func getVolumeSourceFromSpec(spec *volume.Spec) (interface{}, error) {
if spec.Volume != nil && spec.Volume.ScaleIO != nil {
return spec.Volume.ScaleIO, nil
}
if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.ScaleIO != nil {
return spec.PersistentVolume.Spec.ScaleIO, nil
}
return nil, fmt.Errorf("ScaleIO not defined in spec")
}
func getVolumeSourceAttribs(spec *volume.Spec) (*volSourceAttribs, error) {
attribs := new(volSourceAttribs)
if pvSource, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil {
attribs.volName = pvSource.VolumeName
attribs.fsType = pvSource.FSType
attribs.readOnly = pvSource.ReadOnly
} else if pSource, err := getScaleIOVolumeSourceFromSpec(spec); err == nil {
attribs.volName = pSource.VolumeName
attribs.fsType = pSource.FSType
attribs.readOnly = pSource.ReadOnly
} else {
msg := log("failed to get ScaleIOVolumeSource or ScaleIOPersistentVolumeSource from spec")
glog.Error(msg)
return nil, errors.New(msg)
}
return attribs, nil
}
func getScaleIOPersistentVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOPersistentVolumeSource, error) {
source, err := getVolumeSourceFromSpec(spec)
if err != nil {
return nil, err
}
if val, ok := source.(*api.ScaleIOPersistentVolumeSource); ok {
return val, nil
}
return nil, fmt.Errorf("spec is not a valid ScaleIOPersistentVolume type")
}
func getScaleIOVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error) {
source, err := getVolumeSourceFromSpec(spec)
if err != nil {
return nil, err
}
if val, ok := source.(*api.ScaleIOVolumeSource); ok {
return val, nil
}
return nil, fmt.Errorf("spec is not a valid ScaleIOVolume type")
}
func getSecretAndNamespaceFromSpec(spec *volume.Spec, pod *api.Pod) (secretName string, secretNS string, err error) {
if source, err := getScaleIOVolumeSourceFromSpec(spec); err == nil {
secretName = source.SecretRef.Name
if pod != nil {
secretNS = pod.Namespace
}
} else if source, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil {
if source.SecretRef != nil {
secretName = source.SecretRef.Name
secretNS = source.SecretRef.Namespace
if secretNS == "" && pod != nil {
secretNS = pod.Namespace
}
}
} else {
return "", "", errors.New("failed to get ScaleIOVolumeSource or ScaleIOPersistentVolumeSource")
}
return secretName, secretNS, nil
}
func log(msg string, parts ...interface{}) string {
return fmt.Sprintf(fmt.Sprintf("scaleio: %s", msg), parts...)
}

View File

@ -0,0 +1,224 @@
/*
Copyright 2017 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 scaleio
import (
"encoding/gob"
"os"
"path"
"reflect"
"testing"
api "k8s.io/api/core/v1"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/volume"
)
var (
vol = &api.Volume{
Name: testSioVolName,
VolumeSource: api.VolumeSource{
ScaleIO: &api.ScaleIOVolumeSource{
Gateway: "http://test.scaleio:1111",
System: "sio",
ProtectionDomain: "defaultPD",
StoragePool: "defaultSP",
VolumeName: "test-vol",
FSType: "ext4",
SecretRef: &api.LocalObjectReference{Name: "test-secret"},
},
},
}
config = map[string]string{
confKey.system: "sio",
confKey.gateway: "http://sio/",
confKey.volSpecName: testSioVolName,
confKey.volumeName: "sio-vol",
confKey.secretName: "sio-secret",
confKey.protectionDomain: "defaultPD",
confKey.storagePool: "deraultSP",
confKey.fsType: "xfs",
confKey.readOnly: "true",
}
testConfigFile = "conf.dat"
)
func TestUtilMapVolumeSource(t *testing.T) {
data := make(map[string]string)
mapVolumeSpec(data, volume.NewSpecFromVolume(vol))
if data[confKey.gateway] != "http://test.scaleio:1111" {
t.Error("Unexpected gateway value")
}
if data[confKey.system] != "sio" {
t.Error("Unexpected system value")
}
if data[confKey.protectionDomain] != "defaultPD" {
t.Error("Unexpected protection domain value")
}
if data[confKey.storagePool] != "defaultSP" {
t.Error("Unexpected storage pool value")
}
if data[confKey.volumeName] != "test-vol" {
t.Error("Unexpected volume name value")
}
if data[confKey.fsType] != "ext4" {
t.Error("Unexpected fstype value")
}
if data[confKey.sslEnabled] != "false" {
t.Error("Unexpected sslEnabled value")
}
if data[confKey.readOnly] != "false" {
t.Error("Unexpected readOnly value: ", data[confKey.readOnly])
}
}
func TestUtilValidateConfigs(t *testing.T) {
data := map[string]string{
confKey.secretName: "sio-secret",
confKey.system: "sio",
}
if err := validateConfigs(data); err != gatewayNotProvidedErr {
t.Error("Expecting error for missing gateway, but did not get it")
}
}
func TestUtilApplyConfigDefaults(t *testing.T) {
data := map[string]string{
confKey.system: "sio",
confKey.gateway: "http://sio/",
confKey.volumeName: "sio-vol",
confKey.secretName: "test-secret",
}
applyConfigDefaults(data)
if data[confKey.gateway] != "http://sio/" {
t.Error("Unexpected gateway value")
}
if data[confKey.system] != "sio" {
t.Error("Unexpected system value")
}
if data[confKey.protectionDomain] != "" {
t.Error("Unexpected protection domain value")
}
if data[confKey.storagePool] != "" {
t.Error("Unexpected storage pool value")
}
if data[confKey.volumeName] != "sio-vol" {
t.Error("Unexpected volume name value")
}
if data[confKey.fsType] != "xfs" {
t.Error("Unexpected fstype value")
}
if data[confKey.storageMode] != "ThinProvisioned" {
t.Error("Unexpected storage mode value")
}
if data[confKey.secretName] != "test-secret" {
t.Error("Unexpected secret ref value")
}
if data[confKey.sslEnabled] != "false" {
t.Error("Unexpected sslEnabled value")
}
if data[confKey.readOnly] != "false" {
t.Error("Unexpected readOnly value: ", data[confKey.readOnly])
}
}
func TestUtilDefaultString(t *testing.T) {
if defaultString("", "foo") != "foo" {
t.Error("Unexpected value for default value")
}
}
func TestUtilSaveConfig(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("scaleio-test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
config := path.Join(tmpDir, testConfigFile)
data := map[string]string{
confKey.gateway: "https://test-gateway/",
confKey.secretName: "sio-secret",
confKey.sslEnabled: "false",
}
if err := saveConfig(config, data); err != nil {
t.Fatal("failed while saving data", err)
}
file, err := os.Open(config)
if err != nil {
t.Fatal("failed to open conf file: ", file)
}
defer file.Close()
dataRcvd := map[string]string{}
if err := gob.NewDecoder(file).Decode(&dataRcvd); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(data, dataRcvd) {
t.Error("we got problem, config data not the same")
}
}
func TestUtilAttachSecret(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Errorf("Can't find the plugin %v", sioPluginName)
}
sioPlug, ok := plug.(*sioPlugin)
if !ok {
t.Errorf("Cannot assert plugin to be type sioPlugin")
}
data := make(map[string]string)
for k, v := range config {
data[k] = v
}
if err := attachSecret(sioPlug, "default", data); err != nil {
t.Errorf("failed to setupConfigData %v", err)
}
if data[confKey.username] == "" {
t.Errorf("failed to merge secret")
}
}
func TestUtilLoadConfig(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("scaleio-test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
configFile := path.Join(tmpDir, sioConfigFileName)
if err := saveConfig(configFile, config); err != nil {
t.Fatalf("failed to save configFile %s error:%v", configFile, err)
}
dataRcvd, err := loadConfig(configFile)
if err != nil {
t.Fatalf("failed to load configFile %s error:%v", configFile, err)
}
if dataRcvd[confKey.gateway] != config[confKey.gateway] ||
dataRcvd[confKey.system] != config[confKey.system] {
t.Fatal("loaded config data not matching saved config data")
}
}

View File

@ -0,0 +1,522 @@
/*
Copyright 2017 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 scaleio
import (
"fmt"
"os"
"path"
"strconv"
"strings"
"github.com/golang/glog"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/pkg/util/mount"
kstrings "k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)
type sioVolume struct {
sioMgr *sioMgr
plugin *sioPlugin
pod *api.Pod
podUID types.UID
spec *volume.Spec
secretName string
secretNamespace string
volSpecName string
volName string
readOnly bool
fsType string
options volume.VolumeOptions
configData map[string]string
volume.MetricsNil
}
// *******************
// volume.Volume Impl
var _ volume.Volume = &sioVolume{}
// GetPath returns the path where the volume will be mounted.
func (v *sioVolume) GetPath() string {
return v.plugin.host.GetPodVolumeDir(
v.podUID,
kstrings.EscapeQualifiedNameForDisk(sioPluginName),
v.volSpecName)
}
// *************
// Mounter Impl
// *************
var _ volume.Mounter = &sioVolume{}
// CanMount checks to verify that the volume can be mounted prior to Setup.
// A nil error indicates that the volume is ready for mounitnig.
func (v *sioVolume) CanMount() error {
return nil
}
func (v *sioVolume) SetUp(fsGroup *int64) error {
return v.SetUpAt(v.GetPath(), fsGroup)
}
// SetUp bind mounts the disk global mount to the volume path.
func (v *sioVolume) SetUpAt(dir string, fsGroup *int64) error {
v.plugin.volumeMtx.LockKey(v.volSpecName)
defer v.plugin.volumeMtx.UnlockKey(v.volSpecName)
glog.V(4).Info(log("setting up volume for PV.spec %s", v.volSpecName))
if err := v.setSioMgr(); err != nil {
glog.Error(log("setup failed to create scalio manager: %v", err))
return err
}
mounter := v.plugin.host.GetMounter(v.plugin.GetPluginName())
notDevMnt, err := mounter.IsLikelyNotMountPoint(dir)
if err != nil && !os.IsNotExist(err) {
glog.Error(log("IsLikelyNotMountPoint test failed for dir %v", dir))
return err
}
if !notDevMnt {
glog.V(4).Info(log("skipping setup, dir %s already a mount point", v.volName))
return nil
}
// should multiple-mapping be enabled
enableMultiMaps := false
isROM := false
if v.spec.PersistentVolume != nil {
ams := v.spec.PersistentVolume.Spec.AccessModes
for _, am := range ams {
if am == api.ReadOnlyMany {
enableMultiMaps = true
isROM = true
}
}
}
glog.V(4).Info(log("multiple mapping enabled = %v", enableMultiMaps))
volName := v.volName
devicePath, err := v.sioMgr.AttachVolume(volName, enableMultiMaps)
if err != nil {
glog.Error(log("setup of volume %v: %v", v.volSpecName, err))
return err
}
options := []string{}
switch {
default:
options = append(options, "rw")
case isROM && !v.readOnly:
options = append(options, "rw")
case isROM:
options = append(options, "ro")
case v.readOnly:
options = append(options, "ro")
}
glog.V(4).Info(log("mounting device %s -> %s", devicePath, dir))
if err := os.MkdirAll(dir, 0750); err != nil {
glog.Error(log("failed to create dir %#v: %v", dir, err))
return err
}
glog.V(4).Info(log("setup created mount point directory %s", dir))
diskMounter := volumehelper.NewSafeFormatAndMountFromHost(v.plugin.GetPluginName(), v.plugin.host)
err = diskMounter.FormatAndMount(devicePath, dir, v.fsType, options)
if err != nil {
glog.Error(log("mount operation failed during setup: %v", err))
if err := os.Remove(dir); err != nil && !os.IsNotExist(err) {
glog.Error(log("failed to remove dir %s during a failed mount at setup: %v", dir, err))
return err
}
return err
}
if !v.readOnly && fsGroup != nil {
glog.V(4).Info(log("applying value FSGroup ownership"))
volume.SetVolumeOwnership(v, fsGroup)
}
glog.V(4).Info(log("successfully setup PV %s: volume %s mapped as %s mounted at %s", v.volSpecName, v.volName, devicePath, dir))
return nil
}
func (v *sioVolume) GetAttributes() volume.Attributes {
return volume.Attributes{
ReadOnly: v.readOnly,
Managed: !v.readOnly,
SupportsSELinux: true,
}
}
// **********************
// volume.Unmounter Impl
// *********************
var _ volume.Unmounter = &sioVolume{}
// TearDownAt unmounts the bind mount
func (v *sioVolume) TearDown() error {
return v.TearDownAt(v.GetPath())
}
// TearDown unmounts and remove the volume
func (v *sioVolume) TearDownAt(dir string) error {
v.plugin.volumeMtx.LockKey(v.volSpecName)
defer v.plugin.volumeMtx.UnlockKey(v.volSpecName)
mounter := v.plugin.host.GetMounter(v.plugin.GetPluginName())
dev, _, err := mount.GetDeviceNameFromMount(mounter, dir)
if err != nil {
glog.Errorf(log("failed to get reference count for volume: %s", dir))
return err
}
glog.V(4).Info(log("attempting to unmount %s", dir))
if err := util.UnmountPath(dir, mounter); err != nil {
glog.Error(log("teardown failed while unmounting dir %s: %v ", dir, err))
return err
}
glog.V(4).Info(log("dir %s unmounted successfully", dir))
// detach/unmap
deviceBusy, err := mounter.DeviceOpened(dev)
if err != nil {
glog.Error(log("teardown unable to get status for device %s: %v", dev, err))
return err
}
// Detach volume from node:
// use "last attempt wins" strategy to detach volume from node
// only allow volume to detach when it is not busy (not being used by other pods)
if !deviceBusy {
glog.V(4).Info(log("teardown is attempting to detach/unmap volume for PV %s", v.volSpecName))
if err := v.resetSioMgr(); err != nil {
glog.Error(log("teardown failed, unable to reset scalio mgr: %v", err))
}
volName := v.volName
if err := v.sioMgr.DetachVolume(volName); err != nil {
glog.Warning(log("warning: detaching failed for volume %s: %v", volName, err))
return nil
}
glog.V(4).Infof(log("teardown of volume %v detached successfully", volName))
}
return nil
}
// ********************
// volume.Deleter Impl
// ********************
var _ volume.Deleter = &sioVolume{}
func (v *sioVolume) Delete() error {
glog.V(4).Info(log("deleting pvc %s", v.volSpecName))
if err := v.setSioMgrFromSpec(); err != nil {
glog.Error(log("delete failed while setting sio manager: %v", err))
return err
}
err := v.sioMgr.DeleteVolume(v.volName)
if err != nil {
glog.Error(log("failed to delete volume %s: %v", v.volName, err))
return err
}
glog.V(4).Info(log("successfully deleted PV %s with volume %s", v.volSpecName, v.volName))
return nil
}
// ************************
// volume.Provisioner Impl
// ************************
var _ volume.Provisioner = &sioVolume{}
func (v *sioVolume) Provision() (*api.PersistentVolume, error) {
glog.V(4).Info(log("attempting to dynamically provision pvc %v", v.options.PVC.Name))
if !volume.AccessModesContainedInAll(v.plugin.GetAccessModes(), v.options.PVC.Spec.AccessModes) {
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", v.options.PVC.Spec.AccessModes, v.plugin.GetAccessModes())
}
// setup volume attrributes
genName := v.generateName("k8svol", 11)
var oneGig int64 = 1024 * 1024 * 1024
var eightGig int64 = 8 * oneGig
capacity := v.options.PVC.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)]
volSizeBytes := capacity.Value()
volSizeGB := int64(volume.RoundUpSize(volSizeBytes, oneGig))
if volSizeBytes == 0 {
return nil, fmt.Errorf("invalid volume size of 0 specified")
}
if volSizeBytes < eightGig {
volSizeGB = int64(volume.RoundUpSize(eightGig, oneGig))
glog.V(4).Info(log("capacity less than 8Gi found, adjusted to %dGi", volSizeGB))
}
// create sio manager
if err := v.setSioMgrFromConfig(); err != nil {
glog.Error(log("provision failed while setting up sio mgr: %v", err))
return nil, err
}
// create volume
volName := genName
vol, err := v.sioMgr.CreateVolume(volName, volSizeGB)
if err != nil {
glog.Error(log("provision failed while creating volume: %v", err))
return nil, err
}
// prepare data for pv
v.configData[confKey.volumeName] = volName
sslEnabled, err := strconv.ParseBool(v.configData[confKey.sslEnabled])
if err != nil {
glog.Warning(log("failed to parse parameter sslEnabled, setting to false"))
sslEnabled = false
}
readOnly, err := strconv.ParseBool(v.configData[confKey.readOnly])
if err != nil {
glog.Warning(log("failed to parse parameter readOnly, setting it to false"))
readOnly = false
}
// describe created pv
pvName := genName
pv := &api.PersistentVolume{
ObjectMeta: meta.ObjectMeta{
Name: pvName,
Namespace: v.options.PVC.Namespace,
Labels: map[string]string{},
Annotations: map[string]string{
volumehelper.VolumeDynamicallyCreatedByKey: "scaleio-dynamic-provisioner",
},
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: v.options.PersistentVolumeReclaimPolicy,
AccessModes: v.options.PVC.Spec.AccessModes,
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse(
fmt.Sprintf("%dGi", volSizeGB),
),
},
PersistentVolumeSource: api.PersistentVolumeSource{
ScaleIO: &api.ScaleIOPersistentVolumeSource{
Gateway: v.configData[confKey.gateway],
SSLEnabled: sslEnabled,
SecretRef: &api.SecretReference{Name: v.secretName, Namespace: v.secretNamespace},
System: v.configData[confKey.system],
ProtectionDomain: v.configData[confKey.protectionDomain],
StoragePool: v.configData[confKey.storagePool],
StorageMode: v.configData[confKey.storageMode],
VolumeName: volName,
FSType: v.configData[confKey.fsType],
ReadOnly: readOnly,
},
},
},
}
if len(v.options.PVC.Spec.AccessModes) == 0 {
pv.Spec.AccessModes = v.plugin.GetAccessModes()
}
glog.V(4).Info(log("provisioner created pv %v and volume %s successfully", pvName, vol.Name))
return pv, nil
}
// setSioMgr creates scaleio mgr from cached config data if found
// otherwise, setups new config data and create mgr
func (v *sioVolume) setSioMgr() error {
glog.V(4).Info(log("setting up sio mgr for spec %s", v.volSpecName))
podDir := v.plugin.host.GetPodPluginDir(v.podUID, sioPluginName)
configName := path.Join(podDir, sioConfigFileName)
if v.sioMgr == nil {
configData, err := loadConfig(configName) // try to load config if exist
if err != nil {
if !os.IsNotExist(err) {
glog.Error(log("failed to load config %s : %v", configName, err))
return err
}
glog.V(4).Info(log("previous config file not found, creating new one"))
// prepare config data
configData = make(map[string]string)
mapVolumeSpec(configData, v.spec)
// additional config data
configData[confKey.secretNamespace] = v.secretNamespace
configData[confKey.secretName] = v.secretName
configData[confKey.volSpecName] = v.volSpecName
if err := validateConfigs(configData); err != nil {
glog.Error(log("config setup failed: %s", err))
return err
}
// persist config
if err := saveConfig(configName, configData); err != nil {
glog.Error(log("failed to save config data: %v", err))
return err
}
}
// merge in secret
if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil {
glog.Error(log("failed to load secret: %v", err))
return err
}
// merge in Sdc Guid label value
if err := attachSdcGuid(v.plugin, configData); err != nil {
glog.Error(log("failed to retrieve sdc guid: %v", err))
return err
}
mgr, err := newSioMgr(configData, v.plugin.host.GetExec(v.plugin.GetPluginName()))
if err != nil {
glog.Error(log("failed to reset sio manager: %v", err))
return err
}
v.sioMgr = mgr
}
return nil
}
// resetSioMgr creates scaleio manager from existing (cached) config data
func (v *sioVolume) resetSioMgr() error {
podDir := v.plugin.host.GetPodPluginDir(v.podUID, sioPluginName)
configName := path.Join(podDir, sioConfigFileName)
if v.sioMgr == nil {
// load config data from disk
configData, err := loadConfig(configName)
if err != nil {
glog.Error(log("failed to load config data: %v", err))
return err
}
v.secretName = configData[confKey.secretName]
v.secretNamespace = configData[confKey.secretNamespace]
v.volName = configData[confKey.volumeName]
v.volSpecName = configData[confKey.volSpecName]
// attach secret
if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil {
glog.Error(log("failed to load secret: %v", err))
return err
}
// merge in Sdc Guid label value
if err := attachSdcGuid(v.plugin, configData); err != nil {
glog.Error(log("failed to retrieve sdc guid: %v", err))
return err
}
mgr, err := newSioMgr(configData, v.plugin.host.GetExec(v.plugin.GetPluginName()))
if err != nil {
glog.Error(log("failed to reset scaleio mgr: %v", err))
return err
}
v.sioMgr = mgr
}
return nil
}
// setSioFromConfig sets up scaleio mgr from an available config data map
// designed to be called from dynamic provisioner
func (v *sioVolume) setSioMgrFromConfig() error {
glog.V(4).Info(log("setting scaleio mgr from available config"))
if v.sioMgr == nil {
applyConfigDefaults(v.configData)
v.configData[confKey.volSpecName] = v.volSpecName
if err := validateConfigs(v.configData); err != nil {
glog.Error(log("config data setup failed: %s", err))
return err
}
// copy config and attach secret
data := map[string]string{}
for k, v := range v.configData {
data[k] = v
}
if err := attachSecret(v.plugin, v.secretNamespace, data); err != nil {
glog.Error(log("failed to load secret: %v", err))
return err
}
mgr, err := newSioMgr(data, v.plugin.host.GetExec(v.plugin.GetPluginName()))
if err != nil {
glog.Error(log("failed while setting scaleio mgr from config: %v", err))
return err
}
v.sioMgr = mgr
}
return nil
}
// setSioMgrFromSpec sets the scaleio manager from a spec object.
// The spec may be complete or incomplete depending on lifecycle phase.
func (v *sioVolume) setSioMgrFromSpec() error {
glog.V(4).Info(log("setting sio manager from spec"))
if v.sioMgr == nil {
// get config data form spec volume source
configData := map[string]string{}
mapVolumeSpec(configData, v.spec)
// additional config
configData[confKey.secretNamespace] = v.secretNamespace
configData[confKey.secretName] = v.secretName
configData[confKey.volSpecName] = v.volSpecName
if err := validateConfigs(configData); err != nil {
glog.Error(log("config setup failed: %s", err))
return err
}
// attach secret object to config data
if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil {
glog.Error(log("failed to load secret: %v", err))
return err
}
mgr, err := newSioMgr(configData, v.plugin.host.GetExec(v.plugin.GetPluginName()))
if err != nil {
glog.Error(log("failed to reset sio manager: %v", err))
return err
}
v.sioMgr = mgr
}
return nil
}
func (v *sioVolume) generateName(prefix string, size int) string {
return fmt.Sprintf("%s-%s", prefix, strings.Replace(string(uuid.NewUUID()), "-", "", -1)[0:size])
}

View File

@ -0,0 +1,535 @@
/*
Copyright 2017 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 scaleio
import (
"fmt"
"os"
"path"
"strings"
"testing"
"github.com/golang/glog"
api "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
fakeclient "k8s.io/client-go/kubernetes/fake"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
)
var (
testSioSystem = "sio"
testSioPD = "default"
testSioVol = "vol-0001"
testns = "default"
testSecret = "sio-secret"
testSioVolName = fmt.Sprintf("%s%s%s", testns, "-", testSioVol)
podUID = types.UID("sio-pod")
)
func newPluginMgr(t *testing.T, apiObject runtime.Object) (*volume.VolumePluginMgr, string) {
tmpDir, err := utiltesting.MkTmpdir("scaleio-test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
fakeClient := fakeclient.NewSimpleClientset(apiObject)
host := volumetest.NewFakeVolumeHostWithNodeLabels(
tmpDir,
fakeClient,
nil,
map[string]string{sdcGuidLabelName: "abc-123"},
)
plugMgr := &volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host)
return plugMgr, tmpDir
}
func makeScaleIOSecret(name, namespace string) *api.Secret {
return &api.Secret{
ObjectMeta: meta.ObjectMeta{
Name: name,
Namespace: namespace,
UID: "1234567890",
},
Type: api.SecretType("kubernetes.io/scaleio"),
Data: map[string][]byte{
"username": []byte("username"),
"password": []byte("password"),
},
}
}
func TestVolumeCanSupport(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Errorf("Can't find the plugin %s by name", sioPluginName)
}
if plug.GetPluginName() != "kubernetes.io/scaleio" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(
&volume.Spec{
Volume: &api.Volume{
VolumeSource: api.VolumeSource{
ScaleIO: &api.ScaleIOVolumeSource{},
},
},
},
) {
t.Errorf("Expected true for CanSupport LibStorage VolumeSource")
}
if !plug.CanSupport(
&volume.Spec{
PersistentVolume: &api.PersistentVolume{
Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{
ScaleIO: &api.ScaleIOPersistentVolumeSource{},
},
},
},
},
) {
t.Errorf("Expected true for CanSupport LibStorage PersistentVolumeSource")
}
}
func TestVolumeGetAccessModes(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPersistentPluginByName(sioPluginName)
if err != nil {
t.Errorf("Can't find the plugin %v", sioPluginName)
}
if !containsMode(plug.GetAccessModes(), api.ReadWriteOnce) {
t.Errorf("Expected two AccessModeTypes: %s or %s", api.ReadWriteOnce, api.ReadOnlyMany)
}
}
func containsMode(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool {
for _, m := range modes {
if m == mode {
return true
}
}
return false
}
func TestVolumeMounterUnmounter(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Errorf("Can't find the plugin %v", sioPluginName)
}
sioPlug, ok := plug.(*sioPlugin)
if !ok {
t.Errorf("Cannot assert plugin to be type sioPlugin")
}
vol := &api.Volume{
Name: testSioVolName,
VolumeSource: api.VolumeSource{
ScaleIO: &api.ScaleIOVolumeSource{
Gateway: "http://test.scaleio:1111",
System: testSioSystem,
ProtectionDomain: testSioPD,
StoragePool: "default",
VolumeName: testSioVol,
FSType: "ext4",
SecretRef: &api.LocalObjectReference{Name: testSecret},
ReadOnly: false,
},
},
}
sioMounter, err := sioPlug.NewMounter(
volume.NewSpecFromVolume(vol),
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}},
volume.VolumeOptions{},
)
if err != nil {
t.Fatalf("Failed to make a new Mounter: %v", err)
}
if sioMounter == nil {
t.Fatal("Got a nil Mounter")
}
sio := newFakeSio()
sioVol := sioMounter.(*sioVolume)
if err := sioVol.setSioMgr(); err != nil {
t.Fatalf("failed to create sio mgr: %v", err)
}
sioVol.sioMgr.client = sio
sioVol.sioMgr.CreateVolume(testSioVol, 8) //create vol ahead of time
volPath := path.Join(tmpDir, fmt.Sprintf("pods/%s/volumes/kubernetes.io~scaleio/%s", podUID, testSioVolName))
path := sioMounter.GetPath()
if path != volPath {
t.Errorf("Got unexpected path: %s", path)
}
if err := sioMounter.SetUp(nil); 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 sio.isMultiMap {
t.Errorf("SetUp() - expecting multiple volume disabled by default")
}
// did we read sdcGuid label
if _, ok := sioVol.sioMgr.configData[confKey.sdcGuid]; !ok {
t.Errorf("Expected to find node label scaleio.sdcGuid, but did not find it")
}
// rebuild spec
builtSpec, err := sioPlug.ConstructVolumeSpec(volume.NewSpecFromVolume(vol).Name(), path)
if err != nil {
t.Errorf("ConstructVolumeSpec failed %v", err)
}
if builtSpec.Name() != vol.Name {
t.Errorf("Unexpected spec name %s", builtSpec.Name())
}
// unmount
sioUnmounter, err := sioPlug.NewUnmounter(volume.NewSpecFromVolume(vol).Name(), podUID)
if err != nil {
t.Fatalf("Failed to make a new Unmounter: %v", err)
}
if sioUnmounter == nil {
t.Fatal("Got a nil Unmounter")
}
sioVol = sioUnmounter.(*sioVolume)
if err := sioVol.resetSioMgr(); err != nil {
t.Fatalf("failed to reset sio mgr: %v", err)
}
sioVol.sioMgr.client = sio
if err := sioUnmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
// is mount point gone ?
if _, err := os.Stat(path); err == nil {
t.Errorf("TearDown() failed, volume path still exists: %s", path)
} else if !os.IsNotExist(err) {
t.Errorf("TearDown() failed: %v", err)
}
// are we still mapped
if sio.volume.MappedSdcInfo != nil {
t.Errorf("expected SdcMappedInfo to be nil, volume may still be mapped")
}
}
func TestVolumeProvisioner(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Fatalf("Can't find the plugin %v", sioPluginName)
}
sioPlug, ok := plug.(*sioPlugin)
if !ok {
t.Fatal("Cannot assert plugin to be type sioPlugin")
}
options := volume.VolumeOptions{
ClusterName: "testcluster",
PVC: volumetest.CreateTestPVC("100Mi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
options.PVC.Name = "testpvc"
options.PVC.Namespace = testns
options.PVC.Spec.AccessModes = []api.PersistentVolumeAccessMode{
api.ReadOnlyMany,
}
options.Parameters = map[string]string{
confKey.gateway: "http://test.scaleio:11111",
confKey.system: "sio",
confKey.protectionDomain: testSioPD,
confKey.storagePool: "default",
confKey.secretName: testSecret,
}
provisioner, err := sioPlug.NewProvisioner(options)
if err != nil {
t.Fatalf("failed to create new provisioner: %v", err)
}
if provisioner == nil {
t.Fatal("got a nil provisioner")
}
sio := newFakeSio()
sioVol := provisioner.(*sioVolume)
if err := sioVol.setSioMgrFromConfig(); err != nil {
t.Fatalf("failed to create scaleio mgr from config: %v", err)
}
sioVol.sioMgr.client = sio
spec, err := provisioner.Provision()
if err != nil {
t.Fatalf("call to Provision() failed: %v", err)
}
if spec.Namespace != testns {
t.Fatalf("unexpected namespace %v", spec.Namespace)
}
if spec.Spec.ScaleIO.SecretRef == nil {
t.Fatalf("unexpected nil value for spec.SecretRef")
}
if spec.Spec.ScaleIO.SecretRef.Name != testSecret ||
spec.Spec.ScaleIO.SecretRef.Namespace != testns {
t.Fatalf("spec.SecretRef is not being set properly")
}
spec.Spec.ClaimRef = &api.ObjectReference{Namespace: testns}
// validate provision
actualSpecName := spec.Name
actualVolName := spec.Spec.PersistentVolumeSource.ScaleIO.VolumeName
if !strings.HasPrefix(actualSpecName, "k8svol-") {
t.Errorf("expecting volume name to start with k8svol-, got %s", actualSpecName)
}
vol, err := sio.FindVolume(actualVolName)
if err != nil {
t.Fatalf("failed getting volume %v: %v", actualVolName, err)
}
if vol.Name != actualVolName {
t.Errorf("expected volume name to be %s, got %s", actualVolName, vol.Name)
}
if vol.SizeInKb != 8*1024*1024 {
glog.V(4).Info(log("unexpected volume size"))
}
// mount dynamic vol
sioMounter, err := sioPlug.NewMounter(
volume.NewSpecFromPersistentVolume(spec, false),
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: podUID, Namespace: testns}},
volume.VolumeOptions{},
)
if err != nil {
t.Fatalf("Failed to make a new Mounter: %v", err)
}
sioVol = sioMounter.(*sioVolume)
if err := sioVol.setSioMgr(); err != nil {
t.Fatalf("failed to create sio mgr: %v", err)
}
sioVol.sioMgr.client = sio
if err := sioMounter.SetUp(nil); err != nil {
t.Fatalf("Expected success, got: %v", err)
}
// did we read sdcGuid label
if _, ok := sioVol.sioMgr.configData[confKey.sdcGuid]; !ok {
t.Errorf("Expected to find node label scaleio.sdcGuid, but did not find it")
}
// isMultiMap applied
if !sio.isMultiMap {
t.Errorf("SetUp() expecting attached volume with multi-mapping")
}
// teardown dynamic vol
sioUnmounter, err := sioPlug.NewUnmounter(spec.Name, podUID)
if err != nil {
t.Fatalf("Failed to make a new Unmounter: %v", err)
}
sioVol = sioUnmounter.(*sioVolume)
if err := sioVol.resetSioMgr(); err != nil {
t.Fatalf("failed to reset sio mgr: %v", err)
}
sioVol.sioMgr.client = sio
if err := sioUnmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
// test deleter
deleter, err := sioPlug.NewDeleter(volume.NewSpecFromPersistentVolume(spec, false))
if err != nil {
t.Fatalf("failed to create a deleter %v", err)
}
sioVol = deleter.(*sioVolume)
if err := sioVol.setSioMgrFromSpec(); err != nil {
t.Fatalf("failed to set sio mgr: %v", err)
}
sioVol.sioMgr.client = sio
if err := deleter.Delete(); err != nil {
t.Fatalf("failed while deleteing vol: %v", err)
}
path := deleter.GetPath()
if _, err := os.Stat(path); err == nil {
t.Errorf("TearDown() failed, volume path still exists: %s", path)
} else if !os.IsNotExist(err) {
t.Errorf("Deleter did not delete path %v: %v", path, err)
}
}
func TestVolumeProvisionerWithIncompleteConfig(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Fatalf("Can't find the plugin %v", sioPluginName)
}
sioPlug, ok := plug.(*sioPlugin)
if !ok {
t.Fatal("Cannot assert plugin to be type sioPlugin")
}
options := volume.VolumeOptions{
ClusterName: "testcluster",
PVName: "pvc-sio-dynamic-vol",
PVC: volumetest.CreateTestPVC("100Mi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
options.PVC.Namespace = testns
options.PVC.Spec.AccessModes = []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
}
// incomplete options, test should fail
_, err = sioPlug.NewProvisioner(options)
if err == nil {
t.Fatal("expected failure due to incomplete options")
}
}
func TestVolumeProvisionerWithZeroCapacity(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Fatalf("Can't find the plugin %v", sioPluginName)
}
sioPlug, ok := plug.(*sioPlugin)
if !ok {
t.Fatal("Cannot assert plugin to be type sioPlugin")
}
options := volume.VolumeOptions{
ClusterName: "testcluster",
PVName: "pvc-sio-dynamic-vol",
PVC: volumetest.CreateTestPVC("0Mi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
options.PVC.Namespace = testns
options.PVC.Spec.AccessModes = []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
}
options.Parameters = map[string]string{
confKey.gateway: "http://test.scaleio:11111",
confKey.system: "sio",
confKey.protectionDomain: testSioPD,
confKey.storagePool: "default",
confKey.secretName: "sio-secret",
}
provisioner, _ := sioPlug.NewProvisioner(options)
sio := newFakeSio()
sioVol := provisioner.(*sioVolume)
if err := sioVol.setSioMgrFromConfig(); err != nil {
t.Fatalf("failed to create scaleio mgr from config: %v", err)
}
sioVol.sioMgr.client = sio
_, err = provisioner.Provision()
if err == nil {
t.Fatalf("call to Provision() should fail with invalid capacity")
}
}
func TestVolumeProvisionerWithSecretNamespace(t *testing.T) {
plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret("sio-sec", "sio-ns"))
defer os.RemoveAll(tmpDir)
plug, err := plugMgr.FindPluginByName(sioPluginName)
if err != nil {
t.Fatalf("Can't find the plugin %v", sioPluginName)
}
sioPlug, ok := plug.(*sioPlugin)
if !ok {
t.Fatal("Cannot assert plugin to be type sioPlugin")
}
options := volume.VolumeOptions{
ClusterName: "testcluster",
PVName: "pvc-sio-dynamic-vol",
PVC: volumetest.CreateTestPVC("100Mi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
options.PVC.Spec.AccessModes = []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
}
options.PVC.Namespace = "pvc-ns"
options.Parameters = map[string]string{
confKey.gateway: "http://test.scaleio:11111",
confKey.system: "sio",
confKey.protectionDomain: testSioPD,
confKey.storagePool: "default",
confKey.secretName: "sio-sec",
confKey.secretNamespace: "sio-ns",
}
provisioner, _ := sioPlug.NewProvisioner(options)
sio := newFakeSio()
sioVol := provisioner.(*sioVolume)
if err := sioVol.setSioMgrFromConfig(); err != nil {
t.Fatalf("failed to create scaleio mgr from config: %v", err)
}
sioVol.sioMgr.client = sio
spec, err := sioVol.Provision()
if err != nil {
t.Fatalf("call to Provision() failed: %v", err)
}
if spec.GetObjectMeta().GetNamespace() != "pvc-ns" {
t.Fatalf("unexpected spec.namespace %s", spec.GetObjectMeta().GetNamespace())
}
if spec.Spec.ScaleIO.SecretRef.Name != "sio-sec" {
t.Fatalf("unexpected spec.ScaleIOPersistentVolume.SecretRef.Name %v", spec.Spec.ScaleIO.SecretRef.Name)
}
if spec.Spec.ScaleIO.SecretRef.Namespace != "sio-ns" {
t.Fatalf("unexpected spec.ScaleIOPersistentVolume.SecretRef.Namespace %v", spec.Spec.ScaleIO.SecretRef.Namespace)
}
}