mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 10:53:34 +00:00
vendor files
This commit is contained in:
62
vendor/k8s.io/kubernetes/pkg/volume/iscsi/BUILD
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/volume/iscsi/BUILD
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attacher.go",
|
||||
"disk_manager.go",
|
||||
"doc.go",
|
||||
"iscsi.go",
|
||||
"iscsi_util.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/volume/iscsi",
|
||||
deps = [
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/util/strings:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util: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/types:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"iscsi_test.go",
|
||||
"iscsi_util_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/volume/iscsi",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/testing: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/types:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
12
vendor/k8s.io/kubernetes/pkg/volume/iscsi/OWNERS
generated
vendored
Normal file
12
vendor/k8s.io/kubernetes/pkg/volume/iscsi/OWNERS
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
approvers:
|
||||
- pmorie
|
||||
- saad-ali
|
||||
- rootfs
|
||||
reviewers:
|
||||
- saad-ali
|
||||
- jsafrane
|
||||
- rootfs
|
||||
- humblec
|
||||
- jingxu97
|
||||
- msau42
|
||||
- mtanino
|
265
vendor/k8s.io/kubernetes/pkg/volume/iscsi/attacher.go
generated
vendored
Normal file
265
vendor/k8s.io/kubernetes/pkg/volume/iscsi/attacher.go
generated
vendored
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
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 iscsi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
type iscsiAttacher struct {
|
||||
host volume.VolumeHost
|
||||
manager diskManager
|
||||
}
|
||||
|
||||
var _ volume.Attacher = &iscsiAttacher{}
|
||||
|
||||
var _ volume.AttachableVolumePlugin = &iscsiPlugin{}
|
||||
|
||||
func (plugin *iscsiPlugin) NewAttacher() (volume.Attacher, error) {
|
||||
return &iscsiAttacher{
|
||||
host: plugin.host,
|
||||
manager: &ISCSIUtil{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
|
||||
mounter := plugin.host.GetMounter(iscsiPluginName)
|
||||
return mount.GetMountRefs(mounter, deviceMountPath)
|
||||
}
|
||||
|
||||
func (attacher *iscsiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (attacher *iscsiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
|
||||
volumesAttachedCheck := make(map[*volume.Spec]bool)
|
||||
for _, spec := range specs {
|
||||
volumesAttachedCheck[spec] = true
|
||||
}
|
||||
|
||||
return volumesAttachedCheck, nil
|
||||
}
|
||||
|
||||
func (attacher *iscsiAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) {
|
||||
mounter, err := attacher.volumeSpecToMounter(spec, attacher.host, pod)
|
||||
if err != nil {
|
||||
glog.Warningf("failed to get iscsi mounter: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return attacher.manager.AttachDisk(*mounter)
|
||||
}
|
||||
|
||||
func (attacher *iscsiAttacher) GetDeviceMountPath(
|
||||
spec *volume.Spec) (string, error) {
|
||||
mounter, err := attacher.volumeSpecToMounter(spec, attacher.host, nil)
|
||||
if err != nil {
|
||||
glog.Warningf("failed to get iscsi mounter: %v", err)
|
||||
return "", err
|
||||
}
|
||||
if mounter.InitiatorName != "" {
|
||||
// new iface name is <target portal>:<volume name>
|
||||
mounter.Iface = mounter.Portals[0] + ":" + mounter.VolName
|
||||
}
|
||||
return attacher.manager.MakeGlobalPDName(*mounter.iscsiDisk), nil
|
||||
}
|
||||
|
||||
func (attacher *iscsiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
|
||||
mounter := attacher.host.GetMounter(iscsiPluginName)
|
||||
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
notMnt = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
readOnly, fsType, err := getISCSIVolumeInfo(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := []string{}
|
||||
if readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
if notMnt {
|
||||
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Exec: attacher.host.GetExec(iscsiPluginName)}
|
||||
mountOptions := volume.MountOptionFromSpec(spec, options...)
|
||||
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, fsType, mountOptions)
|
||||
if err != nil {
|
||||
os.Remove(deviceMountPath)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type iscsiDetacher struct {
|
||||
host volume.VolumeHost
|
||||
mounter mount.Interface
|
||||
manager diskManager
|
||||
}
|
||||
|
||||
var _ volume.Detacher = &iscsiDetacher{}
|
||||
|
||||
func (plugin *iscsiPlugin) NewDetacher() (volume.Detacher, error) {
|
||||
return &iscsiDetacher{
|
||||
host: plugin.host,
|
||||
mounter: plugin.host.GetMounter(iscsiPluginName),
|
||||
manager: &ISCSIUtil{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (detacher *iscsiDetacher) Detach(volumeName string, nodeName types.NodeName) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (detacher *iscsiDetacher) UnmountDevice(deviceMountPath string) error {
|
||||
unMounter := detacher.volumeSpecToUnmounter(detacher.mounter)
|
||||
err := detacher.manager.DetachDisk(*unMounter, deviceMountPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: failed to detach disk: %s\nError: %v", deviceMountPath, err)
|
||||
}
|
||||
glog.V(4).Infof("iscsi: %q is unmounted, deleting the directory", deviceMountPath)
|
||||
err = os.RemoveAll(deviceMountPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: failed to delete the directory: %s\nError: %v", deviceMountPath, err)
|
||||
}
|
||||
glog.V(4).Infof("iscsi: successfully detached disk: %s", deviceMountPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (attacher *iscsiAttacher) volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost, pod *v1.Pod) (*iscsiDiskMounter, error) {
|
||||
var secret map[string]string
|
||||
var bkportal []string
|
||||
readOnly, fsType, err := getISCSIVolumeInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pod != nil {
|
||||
chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chapSession, err := getISCSISessionCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if chapDiscovery || chapSession {
|
||||
secretName, secretNamespace, err := getISCSISecretNameAndNamespace(spec, pod.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(secretNamespace) == 0 || len(secretName) == 0 {
|
||||
return nil, fmt.Errorf("CHAP enabled but secret name or namespace is empty")
|
||||
}
|
||||
// if secret is provided, retrieve it
|
||||
kubeClient := host.GetKubeClient()
|
||||
if kubeClient == nil {
|
||||
return nil, fmt.Errorf("Cannot get kube client")
|
||||
}
|
||||
secretObj, err := kubeClient.Core().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Couldn't get secret %v/%v error: %v", secretNamespace, secretName, err)
|
||||
return nil, err
|
||||
}
|
||||
secret = make(map[string]string)
|
||||
for name, data := range secretObj.Data {
|
||||
glog.V(6).Infof("retrieving CHAP secret name: %s", name)
|
||||
secret[name] = string(data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
tp, portals, iqn, lunStr, err := getISCSITargetInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lun := strconv.Itoa(int(lunStr))
|
||||
portal := portalMounter(tp)
|
||||
bkportal = append(bkportal, portal)
|
||||
for _, p := range portals {
|
||||
bkportal = append(bkportal, portalMounter(string(p)))
|
||||
}
|
||||
|
||||
iface, initiatorNamePtr, err := getISCSIInitiatorInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var initiatorName string
|
||||
if initiatorNamePtr != nil {
|
||||
initiatorName = *initiatorNamePtr
|
||||
}
|
||||
chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chapSession, err := getISCSISessionCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exec := attacher.host.GetExec(iscsiPluginName)
|
||||
|
||||
return &iscsiDiskMounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
plugin: &iscsiPlugin{
|
||||
host: host,
|
||||
},
|
||||
VolName: spec.Name(),
|
||||
Portals: bkportal,
|
||||
Iqn: iqn,
|
||||
lun: lun,
|
||||
Iface: iface,
|
||||
chap_discovery: chapDiscovery,
|
||||
chap_session: chapSession,
|
||||
secret: secret,
|
||||
InitiatorName: initiatorName,
|
||||
manager: &ISCSIUtil{}},
|
||||
fsType: fsType,
|
||||
readOnly: readOnly,
|
||||
mounter: &mount.SafeFormatAndMount{Interface: host.GetMounter(iscsiPluginName), Exec: exec},
|
||||
exec: exec,
|
||||
deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (detacher *iscsiDetacher) volumeSpecToUnmounter(mounter mount.Interface) *iscsiDiskUnmounter {
|
||||
exec := detacher.host.GetExec(iscsiPluginName)
|
||||
return &iscsiDiskUnmounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
plugin: &iscsiPlugin{},
|
||||
},
|
||||
mounter: mounter,
|
||||
exec: exec,
|
||||
}
|
||||
}
|
96
vendor/k8s.io/kubernetes/pkg/volume/iscsi/disk_manager.go
generated
vendored
Normal file
96
vendor/k8s.io/kubernetes/pkg/volume/iscsi/disk_manager.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2015 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 iscsi
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
// Abstract interface to disk operations.
|
||||
type diskManager interface {
|
||||
MakeGlobalPDName(disk iscsiDisk) string
|
||||
// Attaches the disk to the kubelet's host machine.
|
||||
AttachDisk(b iscsiDiskMounter) (string, error)
|
||||
// Detaches the disk from the kubelet's host machine.
|
||||
DetachDisk(disk iscsiDiskUnmounter, mntPath string) error
|
||||
}
|
||||
|
||||
// utility to mount a disk based filesystem
|
||||
func diskSetUp(manager diskManager, b iscsiDiskMounter, volPath string, mounter mount.Interface, fsGroup *int64) error {
|
||||
// TODO: handle failed mounts here.
|
||||
notMnt, err := mounter.IsLikelyNotMountPoint(volPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
glog.Errorf("cannot validate mountpoint: %s", volPath)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(volPath, 0750); err != nil {
|
||||
glog.Errorf("failed to mkdir:%s", volPath)
|
||||
return err
|
||||
}
|
||||
// Perform a bind mount to the full path to allow duplicate mounts of the same disk.
|
||||
options := []string{"bind"}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
if b.iscsiDisk.InitiatorName != "" {
|
||||
// new iface name is <target portal>:<volume name>
|
||||
b.iscsiDisk.Iface = b.iscsiDisk.Portals[0] + ":" + b.iscsiDisk.VolName
|
||||
}
|
||||
globalPDPath := manager.MakeGlobalPDName(*b.iscsiDisk)
|
||||
mountOptions := volume.JoinMountOptions(b.mountOptions, options)
|
||||
err = mounter.Mount(globalPDPath, volPath, "", mountOptions)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to bind mount: source:%s, target:%s, err:%v", globalPDPath, volPath, err)
|
||||
noMnt, mntErr := b.mounter.IsLikelyNotMountPoint(volPath)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !noMnt {
|
||||
if mntErr = b.mounter.Unmount(volPath); mntErr != nil {
|
||||
glog.Errorf("Failed to unmount: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
noMnt, mntErr = b.mounter.IsLikelyNotMountPoint(volPath)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !noMnt {
|
||||
// will most likely retry on next sync loop.
|
||||
glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", volPath)
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(volPath)
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.readOnly {
|
||||
volume.SetVolumeOwnership(&b, fsGroup)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
vendor/k8s.io/kubernetes/pkg/volume/iscsi/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/volume/iscsi/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2015 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 iscsi contains the internal representation of Internet Small
|
||||
// Computer System Interface (iSCSI) volumes.
|
||||
package iscsi // import "k8s.io/kubernetes/pkg/volume/iscsi"
|
399
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi.go
generated
vendored
Normal file
399
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi.go
generated
vendored
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
Copyright 2015 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 iscsi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
ioutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
// This is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&iscsiPlugin{nil}}
|
||||
}
|
||||
|
||||
type iscsiPlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &iscsiPlugin{}
|
||||
var _ volume.PersistentVolumePlugin = &iscsiPlugin{}
|
||||
|
||||
const (
|
||||
iscsiPluginName = "kubernetes.io/iscsi"
|
||||
)
|
||||
|
||||
func (plugin *iscsiPlugin) Init(host volume.VolumeHost) error {
|
||||
plugin.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) GetPluginName() string {
|
||||
return iscsiPluginName
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||
tp, _, iqn, lun, err := getISCSITargetInfo(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v:%v:%v", tp, iqn, lun), nil
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
if (spec.Volume != nil && spec.Volume.ISCSI == nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.ISCSI == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) RequiresRemount() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) SupportsMountOption() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) SupportsBulkVolumeVerification() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
|
||||
return []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
||||
// Inject real implementations here, test through the internal function.
|
||||
var secret map[string]string
|
||||
if pod == nil {
|
||||
return nil, fmt.Errorf("nil pod")
|
||||
}
|
||||
chapDiscover, err := getISCSIDiscoveryCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chapSession, err := getISCSISessionCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if chapDiscover || chapSession {
|
||||
secretName, secretNamespace, err := getISCSISecretNameAndNamespace(spec, pod.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(secretName) > 0 && len(secretNamespace) > 0 {
|
||||
// if secret is provideded, retrieve it
|
||||
kubeClient := plugin.host.GetKubeClient()
|
||||
if kubeClient == nil {
|
||||
return nil, fmt.Errorf("Cannot get kube client")
|
||||
}
|
||||
secretObj, err := kubeClient.Core().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Couldn't get secret %v/%v error: %v", secretNamespace, secretName, err)
|
||||
return nil, err
|
||||
}
|
||||
secret = make(map[string]string)
|
||||
for name, data := range secretObj.Data {
|
||||
glog.V(4).Infof("retrieving CHAP secret name: %s", name)
|
||||
secret[name] = string(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
return plugin.newMounterInternal(spec, pod.UID, &ISCSIUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()), secret)
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec mount.Exec, secret map[string]string) (volume.Mounter, error) {
|
||||
// iscsi volumes used directly in a pod have a ReadOnly flag set by the pod author.
|
||||
// iscsi volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
|
||||
readOnly, fsType, err := getISCSIVolumeInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp, portals, iqn, lunStr, err := getISCSITargetInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lun := strconv.Itoa(int(lunStr))
|
||||
portal := portalMounter(tp)
|
||||
var bkportal []string
|
||||
bkportal = append(bkportal, portal)
|
||||
for _, p := range portals {
|
||||
bkportal = append(bkportal, portalMounter(string(p)))
|
||||
}
|
||||
|
||||
iface, initiatorNamePtr, err := getISCSIInitiatorInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var initiatorName string
|
||||
if initiatorNamePtr != nil {
|
||||
initiatorName = *initiatorNamePtr
|
||||
}
|
||||
chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chapSession, err := getISCSISessionCHAPInfo(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &iscsiDiskMounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
podUID: podUID,
|
||||
VolName: spec.Name(),
|
||||
Portals: bkportal,
|
||||
Iqn: iqn,
|
||||
lun: lun,
|
||||
Iface: iface,
|
||||
chap_discovery: chapDiscovery,
|
||||
chap_session: chapSession,
|
||||
secret: secret,
|
||||
InitiatorName: initiatorName,
|
||||
manager: manager,
|
||||
plugin: plugin},
|
||||
fsType: fsType,
|
||||
readOnly: readOnly,
|
||||
mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec},
|
||||
exec: exec,
|
||||
deviceUtil: ioutil.NewDeviceHandler(ioutil.NewIOHandler()),
|
||||
mountOptions: volume.MountOptionFromSpec(spec),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
// Inject real implementations here, test through the internal function.
|
||||
return plugin.newUnmounterInternal(volName, podUID, &ISCSIUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager, mounter mount.Interface, exec mount.Exec) (volume.Unmounter, error) {
|
||||
return &iscsiDiskUnmounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
podUID: podUID,
|
||||
VolName: volName,
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
},
|
||||
mounter: mounter,
|
||||
exec: exec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *iscsiPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||
iscsiVolume := &v1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ISCSI: &v1.ISCSIVolumeSource{
|
||||
TargetPortal: volumeName,
|
||||
IQN: volumeName,
|
||||
},
|
||||
},
|
||||
}
|
||||
return volume.NewSpecFromVolume(iscsiVolume), nil
|
||||
}
|
||||
|
||||
type iscsiDisk struct {
|
||||
VolName string
|
||||
podUID types.UID
|
||||
Portals []string
|
||||
Iqn string
|
||||
lun string
|
||||
Iface string
|
||||
chap_discovery bool
|
||||
chap_session bool
|
||||
secret map[string]string
|
||||
InitiatorName string
|
||||
plugin *iscsiPlugin
|
||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||
manager diskManager
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (iscsi *iscsiDisk) GetPath() string {
|
||||
name := iscsiPluginName
|
||||
// safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up
|
||||
return iscsi.plugin.host.GetPodVolumeDir(iscsi.podUID, utilstrings.EscapeQualifiedNameForDisk(name), iscsi.VolName)
|
||||
}
|
||||
|
||||
type iscsiDiskMounter struct {
|
||||
*iscsiDisk
|
||||
readOnly bool
|
||||
fsType string
|
||||
mounter *mount.SafeFormatAndMount
|
||||
exec mount.Exec
|
||||
deviceUtil ioutil.DeviceUtil
|
||||
mountOptions []string
|
||||
}
|
||||
|
||||
var _ volume.Mounter = &iscsiDiskMounter{}
|
||||
|
||||
func (b *iscsiDiskMounter) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: b.readOnly,
|
||||
Managed: !b.readOnly,
|
||||
SupportsSELinux: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Checks prior to mount operations to verify that the required components (binaries, etc.)
|
||||
// to mount the volume are available on the underlying node.
|
||||
// If not, it returns an error
|
||||
func (b *iscsiDiskMounter) CanMount() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *iscsiDiskMounter) SetUp(fsGroup *int64) error {
|
||||
return b.SetUpAt(b.GetPath(), fsGroup)
|
||||
}
|
||||
|
||||
func (b *iscsiDiskMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||
// diskSetUp checks mountpoints and prevent repeated calls
|
||||
err := diskSetUp(b.manager, *b, dir, b.mounter, fsGroup)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to setup")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type iscsiDiskUnmounter struct {
|
||||
*iscsiDisk
|
||||
mounter mount.Interface
|
||||
exec mount.Exec
|
||||
}
|
||||
|
||||
var _ volume.Unmounter = &iscsiDiskUnmounter{}
|
||||
|
||||
// Unmounts the bind mount, and detaches the disk only if the disk
|
||||
// resource was the last reference to that disk on the kubelet.
|
||||
func (c *iscsiDiskUnmounter) TearDown() error {
|
||||
return c.TearDownAt(c.GetPath())
|
||||
}
|
||||
|
||||
func (c *iscsiDiskUnmounter) TearDownAt(dir string) error {
|
||||
return ioutil.UnmountPath(dir, c.mounter)
|
||||
}
|
||||
|
||||
func portalMounter(portal string) string {
|
||||
if !strings.Contains(portal, ":") {
|
||||
portal = portal + ":3260"
|
||||
}
|
||||
return portal
|
||||
}
|
||||
|
||||
// get iSCSI volume info: readOnly and fstype
|
||||
func getISCSIVolumeInfo(spec *volume.Spec) (bool, string, error) {
|
||||
// for volume source, readonly is in volume spec
|
||||
// for PV, readonly is in PV spec
|
||||
if spec.Volume != nil && spec.Volume.ISCSI != nil {
|
||||
return spec.Volume.ISCSI.ReadOnly, spec.Volume.ISCSI.FSType, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.ISCSI != nil {
|
||||
return spec.ReadOnly, spec.PersistentVolume.Spec.ISCSI.FSType, nil
|
||||
}
|
||||
|
||||
return false, "", fmt.Errorf("Spec does not reference an ISCSI volume type")
|
||||
}
|
||||
|
||||
// get iSCSI target info: target portal, portals, iqn, and lun
|
||||
func getISCSITargetInfo(spec *volume.Spec) (string, []string, string, int32, error) {
|
||||
if spec.Volume != nil && spec.Volume.ISCSI != nil {
|
||||
return spec.Volume.ISCSI.TargetPortal, spec.Volume.ISCSI.Portals, spec.Volume.ISCSI.IQN, spec.Volume.ISCSI.Lun, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.ISCSI != nil {
|
||||
return spec.PersistentVolume.Spec.ISCSI.TargetPortal, spec.PersistentVolume.Spec.ISCSI.Portals, spec.PersistentVolume.Spec.ISCSI.IQN, spec.PersistentVolume.Spec.ISCSI.Lun, nil
|
||||
}
|
||||
|
||||
return "", nil, "", 0, fmt.Errorf("Spec does not reference an ISCSI volume type")
|
||||
}
|
||||
|
||||
// get iSCSI initiator info: iface and initiator name
|
||||
func getISCSIInitiatorInfo(spec *volume.Spec) (string, *string, error) {
|
||||
if spec.Volume != nil && spec.Volume.ISCSI != nil {
|
||||
return spec.Volume.ISCSI.ISCSIInterface, spec.Volume.ISCSI.InitiatorName, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.ISCSI != nil {
|
||||
return spec.PersistentVolume.Spec.ISCSI.ISCSIInterface, spec.PersistentVolume.Spec.ISCSI.InitiatorName, nil
|
||||
}
|
||||
|
||||
return "", nil, fmt.Errorf("Spec does not reference an ISCSI volume type")
|
||||
}
|
||||
|
||||
// get iSCSI Discovery CHAP boolean
|
||||
func getISCSIDiscoveryCHAPInfo(spec *volume.Spec) (bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.ISCSI != nil {
|
||||
return spec.Volume.ISCSI.DiscoveryCHAPAuth, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.ISCSI != nil {
|
||||
return spec.PersistentVolume.Spec.ISCSI.DiscoveryCHAPAuth, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Spec does not reference an ISCSI volume type")
|
||||
}
|
||||
|
||||
// get iSCSI Session CHAP boolean
|
||||
func getISCSISessionCHAPInfo(spec *volume.Spec) (bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.ISCSI != nil {
|
||||
return spec.Volume.ISCSI.SessionCHAPAuth, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.ISCSI != nil {
|
||||
return spec.PersistentVolume.Spec.ISCSI.SessionCHAPAuth, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("Spec does not reference an ISCSI volume type")
|
||||
}
|
||||
|
||||
// get iSCSI CHAP Secret info: secret name and namespace
|
||||
func getISCSISecretNameAndNamespace(spec *volume.Spec, defaultSecretNamespace string) (string, string, error) {
|
||||
if spec.Volume != nil && spec.Volume.ISCSI != nil {
|
||||
if spec.Volume.ISCSI.SecretRef != nil {
|
||||
return spec.Volume.ISCSI.SecretRef.Name, defaultSecretNamespace, nil
|
||||
}
|
||||
return "", "", nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.ISCSI != nil {
|
||||
secretRef := spec.PersistentVolume.Spec.ISCSI.SecretRef
|
||||
secretNs := defaultSecretNamespace
|
||||
if secretRef != nil {
|
||||
if len(secretRef.Namespace) != 0 {
|
||||
secretNs = secretRef.Namespace
|
||||
}
|
||||
return secretRef.Name, secretNs, nil
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("Spec does not reference an ISCSI volume type")
|
||||
}
|
428
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi_test.go
generated
vendored
Normal file
428
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi_test.go
generated
vendored
Normal file
@ -0,0 +1,428 @@
|
||||
/*
|
||||
Copyright 2015 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 iscsi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("iscsi_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/iscsi")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if plug.GetPluginName() != "kubernetes.io/iscsi" {
|
||||
t.Errorf("Wrong name: %s", plug.GetPluginName())
|
||||
}
|
||||
if plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccessModes(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("iscsi_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/iscsi")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) {
|
||||
t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDiskManager struct {
|
||||
tmpDir string
|
||||
attachCalled bool
|
||||
detachCalled bool
|
||||
}
|
||||
|
||||
func NewFakeDiskManager() *fakeDiskManager {
|
||||
return &fakeDiskManager{
|
||||
tmpDir: utiltesting.MkTmpdirOrDie("fc_test"),
|
||||
}
|
||||
}
|
||||
|
||||
func (fake *fakeDiskManager) Cleanup() {
|
||||
os.RemoveAll(fake.tmpDir)
|
||||
}
|
||||
|
||||
func (fake *fakeDiskManager) MakeGlobalPDName(disk iscsiDisk) string {
|
||||
return fake.tmpDir
|
||||
}
|
||||
func (fake *fakeDiskManager) AttachDisk(b iscsiDiskMounter) (string, error) {
|
||||
globalPath := b.manager.MakeGlobalPDName(*b.iscsiDisk)
|
||||
err := os.MkdirAll(globalPath, 0750)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Simulate the global mount so that the fakeMounter returns the
|
||||
// expected number of mounts for the attached disk.
|
||||
b.mounter.Mount(globalPath, globalPath, b.fsType, nil)
|
||||
|
||||
return "/dev/sdb", nil
|
||||
}
|
||||
|
||||
func (fake *fakeDiskManager) DetachDisk(c iscsiDiskUnmounter, mntPath string) error {
|
||||
globalPath := c.manager.MakeGlobalPDName(*c.iscsiDisk)
|
||||
err := os.RemoveAll(globalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doTestPlugin(t *testing.T, spec *volume.Spec) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("iscsi_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/iscsi")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
fakeManager := NewFakeDiskManager()
|
||||
defer fakeManager.Cleanup()
|
||||
fakeMounter := &mount.FakeMounter{}
|
||||
fakeExec := mount.NewFakeExec(nil)
|
||||
mounter, err := plug.(*iscsiPlugin).newMounterInternal(spec, types.UID("poduid"), fakeManager, fakeMounter, fakeExec, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Mounter: %v", err)
|
||||
}
|
||||
if mounter == nil {
|
||||
t.Error("Got a nil Mounter")
|
||||
}
|
||||
|
||||
path := mounter.GetPath()
|
||||
expectedPath := fmt.Sprintf("%s/pods/poduid/volumes/kubernetes.io~iscsi/vol1", tmpDir)
|
||||
if path != expectedPath {
|
||||
t.Errorf("Unexpected path, expected %q, got: %q", expectedPath, path)
|
||||
}
|
||||
|
||||
if err := mounter.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 _, 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)
|
||||
}
|
||||
}
|
||||
|
||||
fakeManager2 := NewFakeDiskManager()
|
||||
defer fakeManager2.Cleanup()
|
||||
unmounter, err := plug.(*iscsiPlugin).newUnmounterInternal("vol1", types.UID("poduid"), fakeManager2, fakeMounter, fakeExec)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Unmounter: %v", err)
|
||||
}
|
||||
if unmounter == nil {
|
||||
t.Error("Got a nil Unmounter")
|
||||
}
|
||||
|
||||
if err := unmounter.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("TearDown() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginVolume(t *testing.T) {
|
||||
vol := &v1.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ISCSI: &v1.ISCSIVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
doTestPlugin(t, volume.NewSpecFromVolume(vol))
|
||||
}
|
||||
|
||||
func TestPluginPersistentVolume(t *testing.T) {
|
||||
vol := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vol1",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
ISCSI: &v1.ISCSIPersistentVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false))
|
||||
}
|
||||
|
||||
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("iscsi_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
ISCSI: &v1.ISCSIPersistentVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
},
|
||||
},
|
||||
ClaimRef: &v1.ObjectReference{
|
||||
Name: "claimA",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "claimA",
|
||||
Namespace: "nsA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
VolumeName: "pvA",
|
||||
},
|
||||
Status: v1.PersistentVolumeClaimStatus{
|
||||
Phase: v1.ClaimBound,
|
||||
},
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset(pv, claim)
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, client, nil))
|
||||
plug, _ := plugMgr.FindPluginByName(iscsiPluginName)
|
||||
|
||||
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
|
||||
spec := volume.NewSpecFromPersistentVolume(pv, true)
|
||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
|
||||
mounter, _ := plug.NewMounter(spec, pod, volume.VolumeOptions{})
|
||||
if mounter == nil {
|
||||
t.Fatalf("Got a nil Mounter")
|
||||
}
|
||||
|
||||
if !mounter.GetAttributes().ReadOnly {
|
||||
t.Errorf("Expected true for mounter.IsReadOnly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortalMounter(t *testing.T) {
|
||||
if portal := portalMounter("127.0.0.1"); portal != "127.0.0.1:3260" {
|
||||
t.Errorf("wrong portal: %s", portal)
|
||||
}
|
||||
if portal := portalMounter("127.0.0.1:3260"); portal != "127.0.0.1:3260" {
|
||||
t.Errorf("wrong portal: %s", portal)
|
||||
}
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
name string
|
||||
defaultNs string
|
||||
spec *volume.Spec
|
||||
// Expected return of the test
|
||||
expectedName string
|
||||
expectedNs string
|
||||
expectedIface string
|
||||
expectedError error
|
||||
}
|
||||
|
||||
func TestGetSecretNameAndNamespaceForPV(t *testing.T) {
|
||||
tests := []testcase{
|
||||
{
|
||||
name: "persistent volume source",
|
||||
defaultNs: "default",
|
||||
spec: &volume.Spec{
|
||||
PersistentVolume: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
ISCSI: &v1.ISCSIPersistentVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
SecretRef: &v1.SecretReference{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedName: "name",
|
||||
expectedNs: "ns",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "persistent volume source without namespace",
|
||||
defaultNs: "default",
|
||||
spec: &volume.Spec{
|
||||
PersistentVolume: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
ISCSI: &v1.ISCSIPersistentVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
SecretRef: &v1.SecretReference{
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedName: "name",
|
||||
expectedNs: "default",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "pod volume source",
|
||||
defaultNs: "default",
|
||||
spec: &volume.Spec{
|
||||
Volume: &v1.Volume{
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ISCSI: &v1.ISCSIVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedName: "",
|
||||
expectedNs: "",
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, testcase := range tests {
|
||||
resultName, resultNs, err := getISCSISecretNameAndNamespace(testcase.spec, testcase.defaultNs)
|
||||
if err != testcase.expectedError || resultName != testcase.expectedName || resultNs != testcase.expectedNs {
|
||||
t.Errorf("%s failed: expected err=%v ns=%q name=%q, got %v/%q/%q", testcase.name, testcase.expectedError, testcase.expectedNs, testcase.expectedName,
|
||||
err, resultNs, resultName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetISCSIInitiatorInfo(t *testing.T) {
|
||||
tests := []testcase{
|
||||
{
|
||||
name: "persistent volume source",
|
||||
spec: &volume.Spec{
|
||||
PersistentVolume: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
ISCSI: &v1.ISCSIPersistentVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
ISCSIInterface: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedIface: "tcp",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "pod volume source",
|
||||
spec: &volume.Spec{
|
||||
Volume: &v1.Volume{
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ISCSI: &v1.ISCSIVolumeSource{
|
||||
TargetPortal: "127.0.0.1:3260",
|
||||
IQN: "iqn.2014-12.server:storage.target01",
|
||||
FSType: "ext4",
|
||||
Lun: 0,
|
||||
ISCSIInterface: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedIface: "tcp",
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
for _, testcase := range tests {
|
||||
resultIface, _, err := getISCSIInitiatorInfo(testcase.spec)
|
||||
if err != testcase.expectedError || resultIface != testcase.expectedIface {
|
||||
t.Errorf("%s failed: expected err=%v iface=%s, got %v/%s", testcase.name, testcase.expectedError, testcase.expectedIface,
|
||||
err, resultIface)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
554
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi_util.go
generated
vendored
Normal file
554
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi_util.go
generated
vendored
Normal file
@ -0,0 +1,554 @@
|
||||
/*
|
||||
Copyright 2015 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 iscsi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
var (
|
||||
chap_st = []string{
|
||||
"discovery.sendtargets.auth.username",
|
||||
"discovery.sendtargets.auth.password",
|
||||
"discovery.sendtargets.auth.username_in",
|
||||
"discovery.sendtargets.auth.password_in"}
|
||||
chap_sess = []string{
|
||||
"node.session.auth.username",
|
||||
"node.session.auth.password",
|
||||
"node.session.auth.username_in",
|
||||
"node.session.auth.password_in"}
|
||||
ifaceTransportNameRe = regexp.MustCompile(`iface.transport_name = (.*)\n`)
|
||||
ifaceRe = regexp.MustCompile(`.+/iface-([^/]+)/.+`)
|
||||
)
|
||||
|
||||
func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error {
|
||||
if !b.chap_discovery {
|
||||
return nil
|
||||
}
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP")
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: failed to update discoverydb with CHAP, output: %v", string(out))
|
||||
}
|
||||
|
||||
for _, k := range chap_st {
|
||||
v := b.secret[k]
|
||||
if len(v) > 0 {
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", k, "-v", v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: failed to update discoverydb key %q with value %q error: %v", k, v, string(out))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateISCSINode(b iscsiDiskMounter, tp string) error {
|
||||
if !b.chap_session {
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", "node.session.auth.authmethod", "-v", "CHAP")
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: failed to update node with CHAP, output: %v", string(out))
|
||||
}
|
||||
|
||||
for _, k := range chap_sess {
|
||||
v := b.secret[k]
|
||||
if len(v) > 0 {
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", k, "-v", v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: failed to update node session key %q with value %q error: %v", k, v, string(out))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// stat a path, if not exists, retry maxRetries times
|
||||
// when iscsi transports other than default are used, use glob instead as pci id of device is unknown
|
||||
type StatFunc func(string) (os.FileInfo, error)
|
||||
type GlobFunc func(string) ([]string, error)
|
||||
|
||||
func waitForPathToExist(devicePath *string, maxRetries int, deviceTransport string) bool {
|
||||
// This makes unit testing a lot easier
|
||||
return waitForPathToExistInternal(devicePath, maxRetries, deviceTransport, os.Stat, filepath.Glob)
|
||||
}
|
||||
|
||||
func waitForPathToExistInternal(devicePath *string, maxRetries int, deviceTransport string, osStat StatFunc, filepathGlob GlobFunc) bool {
|
||||
if devicePath == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
var err error
|
||||
if deviceTransport == "tcp" {
|
||||
_, err = osStat(*devicePath)
|
||||
} else {
|
||||
fpath, _ := filepathGlob(*devicePath)
|
||||
if fpath == nil {
|
||||
err = os.ErrNotExist
|
||||
} else {
|
||||
// There might be a case that fpath contains multiple device paths if
|
||||
// multiple PCI devices connect to same iscsi target. We handle this
|
||||
// case at subsequent logic. Pick up only first path here.
|
||||
*devicePath = fpath[0]
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
if i == maxRetries-1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getDevicePrefixRefCount: given a prefix of device path, find its reference count from /proc/mounts
|
||||
// returns the reference count to the device and error code
|
||||
// for services like iscsi construct multiple device paths with the same prefix pattern.
|
||||
// this function aggregates all references to a service based on the prefix pattern
|
||||
// More specifically, this prefix semantics is to aggregate disk paths that belong to the same iSCSI target/iqn pair.
|
||||
// an iSCSI target could expose multiple LUNs through the same IQN, and Linux iSCSI initiator creates disk paths that start the same prefix but end with different LUN number
|
||||
// When we decide whether it is time to logout a target, we have to see if none of the LUNs are used any more.
|
||||
// That's where the prefix based ref count kicks in. If we only count the disks using exact match, we could log other disks out.
|
||||
func getDevicePrefixRefCount(mounter mount.Interface, deviceNamePrefix string) (int, error) {
|
||||
mps, err := mounter.List()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Find the number of references to the device.
|
||||
refCount := 0
|
||||
for i := range mps {
|
||||
if strings.HasPrefix(mps[i].Path, deviceNamePrefix) {
|
||||
refCount++
|
||||
}
|
||||
}
|
||||
return refCount, nil
|
||||
}
|
||||
|
||||
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/iscsi/iface_name/portal-some_iqn-lun-lun_id
|
||||
func makePDNameInternal(host volume.VolumeHost, portal string, iqn string, lun string, iface string) string {
|
||||
return path.Join(host.GetPluginDir(iscsiPluginName), "iface-"+iface, portal+"-"+iqn+"-lun-"+lun)
|
||||
}
|
||||
|
||||
type ISCSIUtil struct{}
|
||||
|
||||
func (util *ISCSIUtil) MakeGlobalPDName(iscsi iscsiDisk) string {
|
||||
return makePDNameInternal(iscsi.plugin.host, iscsi.Portals[0], iscsi.Iqn, iscsi.lun, iscsi.Iface)
|
||||
}
|
||||
|
||||
func (util *ISCSIUtil) persistISCSI(conf iscsiDisk, mnt string) error {
|
||||
file := path.Join(mnt, "iscsi.json")
|
||||
fp, err := os.Create(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: create %s err %s", file, err)
|
||||
}
|
||||
defer fp.Close()
|
||||
encoder := json.NewEncoder(fp)
|
||||
if err = encoder.Encode(conf); err != nil {
|
||||
return fmt.Errorf("iscsi: encode err: %v.", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (util *ISCSIUtil) loadISCSI(conf *iscsiDisk, mnt string) error {
|
||||
// NOTE: The iscsi config json is not deleted after logging out from target portals.
|
||||
file := path.Join(mnt, "iscsi.json")
|
||||
fp, err := os.Open(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iscsi: open %s err %s", file, err)
|
||||
}
|
||||
defer fp.Close()
|
||||
decoder := json.NewDecoder(fp)
|
||||
if err = decoder.Decode(conf); err != nil {
|
||||
return fmt.Errorf("iscsi: decode err: %v.", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) {
|
||||
var devicePath string
|
||||
var devicePaths []string
|
||||
var iscsiTransport string
|
||||
var lastErr error
|
||||
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show")
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: could not read iface %s error: %s", b.Iface, string(out))
|
||||
return "", err
|
||||
}
|
||||
|
||||
iscsiTransport = extractTransportname(string(out))
|
||||
|
||||
bkpPortal := b.Portals
|
||||
|
||||
// create new iface and copy parameters from pre-configured iface to the created iface
|
||||
if b.InitiatorName != "" {
|
||||
// new iface name is <target portal>:<volume name>
|
||||
newIface := bkpPortal[0] + ":" + b.VolName
|
||||
err = cloneIface(b, newIface)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to clone iface: %s error: %v", b.Iface, err)
|
||||
return "", err
|
||||
}
|
||||
// update iface name
|
||||
b.Iface = newIface
|
||||
}
|
||||
|
||||
for _, tp := range bkpPortal {
|
||||
// Rescan sessions to discover newly mapped LUNs. Do not specify the interface when rescanning
|
||||
// to avoid establishing additional sessions to the same target.
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-R")
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to rescan session with error: %s (%v)", string(out), err)
|
||||
}
|
||||
|
||||
if iscsiTransport == "" {
|
||||
glog.Errorf("iscsi: could not find transport name in iface %s", b.Iface)
|
||||
return "", fmt.Errorf("Could not parse iface file for %s", b.Iface)
|
||||
}
|
||||
if iscsiTransport == "tcp" {
|
||||
devicePath = strings.Join([]string{"/dev/disk/by-path/ip", tp, "iscsi", b.Iqn, "lun", b.lun}, "-")
|
||||
} else {
|
||||
devicePath = strings.Join([]string{"/dev/disk/by-path/pci", "*", "ip", tp, "iscsi", b.Iqn, "lun", b.lun}, "-")
|
||||
}
|
||||
|
||||
if exist := waitForPathToExist(&devicePath, 1, iscsiTransport); exist {
|
||||
glog.V(4).Infof("iscsi: devicepath (%s) exists", devicePath)
|
||||
devicePaths = append(devicePaths, devicePath)
|
||||
continue
|
||||
}
|
||||
// build discoverydb and discover iscsi target
|
||||
b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "new")
|
||||
// update discoverydb with CHAP secret
|
||||
err = updateISCSIDiscoverydb(b, tp)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("iscsi: failed to update discoverydb to portal %s error: %v", tp, err)
|
||||
continue
|
||||
}
|
||||
out, err = b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "--discover")
|
||||
if err != nil {
|
||||
// delete discoverydb record
|
||||
b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "delete")
|
||||
lastErr = fmt.Errorf("iscsi: failed to sendtargets to portal %s output: %s, err %v", tp, string(out), err)
|
||||
continue
|
||||
}
|
||||
err = updateISCSINode(b, tp)
|
||||
if err != nil {
|
||||
// failure to update node db is rare. But deleting record will likely impact those who already start using it.
|
||||
lastErr = fmt.Errorf("iscsi: failed to update iscsi node to portal %s error: %v", tp, err)
|
||||
continue
|
||||
}
|
||||
// login to iscsi target
|
||||
out, err = b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "--login")
|
||||
if err != nil {
|
||||
// delete the node record from database
|
||||
b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-I", b.Iface, "-T", b.Iqn, "-o", "delete")
|
||||
lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", string(out), err)
|
||||
continue
|
||||
}
|
||||
if exist := waitForPathToExist(&devicePath, 10, iscsiTransport); !exist {
|
||||
glog.Errorf("Could not attach disk: Timeout after 10s")
|
||||
// update last error
|
||||
lastErr = fmt.Errorf("Could not attach disk: Timeout after 10s")
|
||||
continue
|
||||
} else {
|
||||
devicePaths = append(devicePaths, devicePath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(devicePaths) == 0 {
|
||||
// delete cloned iface
|
||||
b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "delete")
|
||||
glog.Errorf("iscsi: failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
|
||||
return "", fmt.Errorf("failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
|
||||
}
|
||||
if lastErr != nil {
|
||||
glog.Errorf("iscsi: last error occurred during iscsi init:\n%v", lastErr)
|
||||
}
|
||||
|
||||
//Make sure we use a valid devicepath to find mpio device.
|
||||
devicePath = devicePaths[0]
|
||||
|
||||
// mount it
|
||||
globalPDPath := b.manager.MakeGlobalPDName(*b.iscsiDisk)
|
||||
notMnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("Heuristic determination of mount point failed:%v", err)
|
||||
}
|
||||
if !notMnt {
|
||||
glog.Infof("iscsi: %s already mounted", globalPDPath)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(globalPDPath, 0750); err != nil {
|
||||
glog.Errorf("iscsi: failed to mkdir %s, error", globalPDPath)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Persist iscsi disk config to json file for DetachDisk path
|
||||
util.persistISCSI(*(b.iscsiDisk), globalPDPath)
|
||||
|
||||
for _, path := range devicePaths {
|
||||
// There shouldnt be any empty device paths. However adding this check
|
||||
// for safer side to avoid the possibility of an empty entry.
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
// check if the dev is using mpio and if so mount it via the dm-XX device
|
||||
if mappedDevicePath := b.deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" {
|
||||
devicePath = mappedDevicePath
|
||||
break
|
||||
}
|
||||
}
|
||||
err = b.mounter.FormatAndMount(devicePath, globalPDPath, b.fsType, nil)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to mount iscsi volume %s [%s] to %s, error %v", devicePath, b.fsType, globalPDPath, err)
|
||||
}
|
||||
|
||||
return devicePath, err
|
||||
}
|
||||
|
||||
func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, mntPath string) error {
|
||||
_, cnt, err := mount.GetDeviceNameFromMount(c.mounter, mntPath)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi detach disk: failed to get device from mnt: %s\nError: %v", mntPath, err)
|
||||
return err
|
||||
}
|
||||
if pathExists, pathErr := volumeutil.PathExists(mntPath); pathErr != nil {
|
||||
return fmt.Errorf("Error checking if path exists: %v", pathErr)
|
||||
} else if !pathExists {
|
||||
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mntPath)
|
||||
return nil
|
||||
}
|
||||
if err = c.mounter.Unmount(mntPath); err != nil {
|
||||
glog.Errorf("iscsi detach disk: failed to unmount: %s\nError: %v", mntPath, err)
|
||||
return err
|
||||
}
|
||||
cnt--
|
||||
if cnt != 0 {
|
||||
return nil
|
||||
}
|
||||
// if device is no longer used, see if need to logout the target
|
||||
device, prefix, err := extractDeviceAndPrefix(mntPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refCount, err := getDevicePrefixRefCount(c.mounter, prefix)
|
||||
if err != nil || refCount != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bkpPortal []string
|
||||
var volName, iqn, iface, initiatorName string
|
||||
found := true
|
||||
|
||||
// load iscsi disk config from json file
|
||||
if err := util.loadISCSI(c.iscsiDisk, mntPath); err == nil {
|
||||
bkpPortal, iqn, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Iface, c.iscsiDisk.VolName
|
||||
initiatorName = c.iscsiDisk.InitiatorName
|
||||
} else {
|
||||
// If the iscsi disk config is not found, fall back to the original behavior.
|
||||
// This portal/iqn/iface is no longer referenced, log out.
|
||||
// Extract the portal and iqn from device path.
|
||||
bkpPortal = make([]string, 1)
|
||||
bkpPortal[0], iqn, err = extractPortalAndIqn(device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Extract the iface from the mountPath and use it to log out. If the iface
|
||||
// is not found, maintain the previous behavior to facilitate kubelet upgrade.
|
||||
// Logout may fail as no session may exist for the portal/IQN on the specified interface.
|
||||
iface, found = extractIface(mntPath)
|
||||
}
|
||||
portals := removeDuplicate(bkpPortal)
|
||||
if len(portals) == 0 {
|
||||
return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations.")
|
||||
}
|
||||
|
||||
for _, portal := range portals {
|
||||
logoutArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "--logout"}
|
||||
deleteArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "-o", "delete"}
|
||||
if found {
|
||||
logoutArgs = append(logoutArgs, []string{"-I", iface}...)
|
||||
deleteArgs = append(deleteArgs, []string{"-I", iface}...)
|
||||
}
|
||||
glog.Infof("iscsi: log out target %s iqn %s iface %s", portal, iqn, iface)
|
||||
out, err := c.exec.Run("iscsiadm", logoutArgs...)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to detach disk Error: %s", string(out))
|
||||
}
|
||||
// Delete the node record
|
||||
glog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn)
|
||||
out, err = c.exec.Run("iscsiadm", deleteArgs...)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to delete node record Error: %s", string(out))
|
||||
}
|
||||
}
|
||||
// Delete the iface after all sessions have logged out
|
||||
// If the iface is not created via iscsi plugin, skip to delete
|
||||
if initiatorName != "" && found && iface == (portals[0]+":"+volName) {
|
||||
deleteArgs := []string{"-m", "iface", "-I", iface, "-o", "delete"}
|
||||
out, err := c.exec.Run("iscsiadm", deleteArgs...)
|
||||
if err != nil {
|
||||
glog.Errorf("iscsi: failed to delete iface Error: %s", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractTransportname(ifaceOutput string) (iscsiTransport string) {
|
||||
rexOutput := ifaceTransportNameRe.FindStringSubmatch(ifaceOutput)
|
||||
if rexOutput == nil {
|
||||
return ""
|
||||
}
|
||||
iscsiTransport = rexOutput[1]
|
||||
|
||||
// While iface.transport_name is a required parameter, handle it being unspecified anyways
|
||||
if iscsiTransport == "<empty>" {
|
||||
iscsiTransport = "tcp"
|
||||
}
|
||||
return iscsiTransport
|
||||
}
|
||||
|
||||
func extractDeviceAndPrefix(mntPath string) (string, string, error) {
|
||||
ind := strings.LastIndex(mntPath, "/")
|
||||
if ind < 0 {
|
||||
return "", "", fmt.Errorf("iscsi detach disk: malformatted mnt path: %s", mntPath)
|
||||
}
|
||||
device := mntPath[(ind + 1):]
|
||||
// strip -lun- from mount path
|
||||
ind = strings.LastIndex(mntPath, "-lun-")
|
||||
if ind < 0 {
|
||||
return "", "", fmt.Errorf("iscsi detach disk: malformatted mnt path: %s", mntPath)
|
||||
}
|
||||
prefix := mntPath[:ind]
|
||||
return device, prefix, nil
|
||||
}
|
||||
|
||||
func extractIface(mntPath string) (string, bool) {
|
||||
reOutput := ifaceRe.FindStringSubmatch(mntPath)
|
||||
if reOutput != nil {
|
||||
return reOutput[1], true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func extractPortalAndIqn(device string) (string, string, error) {
|
||||
ind1 := strings.Index(device, "-")
|
||||
if ind1 < 0 {
|
||||
return "", "", fmt.Errorf("iscsi detach disk: no portal in %s", device)
|
||||
}
|
||||
portal := device[0:ind1]
|
||||
ind2 := strings.Index(device, "iqn.")
|
||||
if ind2 < 0 {
|
||||
ind2 = strings.Index(device, "eui.")
|
||||
}
|
||||
if ind2 < 0 {
|
||||
return "", "", fmt.Errorf("iscsi detach disk: no iqn in %s", device)
|
||||
}
|
||||
ind := strings.LastIndex(device, "-lun-")
|
||||
iqn := device[ind2:ind]
|
||||
return portal, iqn, nil
|
||||
}
|
||||
|
||||
// Remove duplicates or string
|
||||
func removeDuplicate(s []string) []string {
|
||||
m := map[string]bool{}
|
||||
for _, v := range s {
|
||||
if v != "" && !m[v] {
|
||||
s[len(m)] = v
|
||||
m[v] = true
|
||||
}
|
||||
}
|
||||
s = s[:len(m)]
|
||||
return s
|
||||
}
|
||||
|
||||
func parseIscsiadmShow(output string) (map[string]string, error) {
|
||||
params := make(map[string]string)
|
||||
slice := strings.Split(output, "\n")
|
||||
for _, line := range slice {
|
||||
if !strings.HasPrefix(line, "iface.") || strings.Contains(line, "<empty>") {
|
||||
continue
|
||||
}
|
||||
iface := strings.Fields(line)
|
||||
if len(iface) != 3 || iface[1] != "=" {
|
||||
return nil, fmt.Errorf("Error: invalid iface setting: %v", iface)
|
||||
}
|
||||
// iscsi_ifacename is immutable once the iface is created
|
||||
if iface[0] == "iface.iscsi_ifacename" {
|
||||
continue
|
||||
}
|
||||
params[iface[0]] = iface[2]
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func cloneIface(b iscsiDiskMounter, newIface string) error {
|
||||
var lastErr error
|
||||
// get pre-configured iface records
|
||||
out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show")
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("iscsi: failed to show iface records: %s (%v)", string(out), err)
|
||||
return lastErr
|
||||
}
|
||||
// parse obtained records
|
||||
params, err := parseIscsiadmShow(string(out))
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("iscsi: failed to parse iface records: %s (%v)", string(out), err)
|
||||
return lastErr
|
||||
}
|
||||
// update initiatorname
|
||||
params["iface.initiatorname"] = b.InitiatorName
|
||||
// create new iface
|
||||
out, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "new")
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("iscsi: failed to create new iface: %s (%v)", string(out), err)
|
||||
return lastErr
|
||||
}
|
||||
// update new iface records
|
||||
for key, val := range params {
|
||||
_, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "update", "-n", key, "-v", val)
|
||||
if err != nil {
|
||||
b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "delete")
|
||||
lastErr = fmt.Errorf("iscsi: failed to update iface records: %s (%v). iface(%s) will be used", string(out), err, b.Iface)
|
||||
break
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
362
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi_util_test.go
generated
vendored
Normal file
362
vendor/k8s.io/kubernetes/pkg/volume/iscsi/iscsi_util_test.go
generated
vendored
Normal file
@ -0,0 +1,362 @@
|
||||
/*
|
||||
Copyright 2015 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 iscsi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
func TestGetDevicePrefixRefCount(t *testing.T) {
|
||||
fm := &mount.FakeMounter{
|
||||
MountPoints: []mount.MountPoint{
|
||||
{Device: "/dev/sdb",
|
||||
Path: "/127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0"},
|
||||
{Device: "/dev/sdb",
|
||||
Path: "/127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-1"},
|
||||
{Device: "/dev/sdb",
|
||||
Path: "/127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-2"},
|
||||
{Device: "/dev/sdb",
|
||||
Path: "/127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-3"},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
devicePrefix string
|
||||
expectedRefs int
|
||||
}{
|
||||
{
|
||||
"/127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00",
|
||||
4,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if refs, err := getDevicePrefixRefCount(fm, test.devicePrefix); err != nil || test.expectedRefs != refs {
|
||||
t.Errorf("%d. GetDevicePrefixRefCount(%s) = %d, %v; expected %d, nil", i, test.devicePrefix, refs, err, test.expectedRefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDeviceAndPrefix(t *testing.T) {
|
||||
devicePath := "127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00"
|
||||
mountPrefix := "/var/lib/kubelet/plugins/kubernetes.io/iscsi/iface-default/" + devicePath
|
||||
lun := "-lun-0"
|
||||
device, prefix, err := extractDeviceAndPrefix(mountPrefix + lun)
|
||||
if err != nil || device != (devicePath+lun) || prefix != mountPrefix {
|
||||
t.Errorf("extractDeviceAndPrefix: expected %s and %s, got %v %s and %s", devicePath+lun, mountPrefix, err, device, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractIface(t *testing.T) {
|
||||
ifaceName := "default"
|
||||
devicePath := "127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0"
|
||||
iface, found := extractIface("/var/lib/kubelet/plugins/kubernetes.io/iscsi/iface-" + ifaceName + "/" + devicePath)
|
||||
if !found || iface != ifaceName {
|
||||
t.Errorf("extractIface: expected %s and %t, got %s and %t", ifaceName, true, iface, found)
|
||||
}
|
||||
iface, found = extractIface("/var/lib/kubelet/plugins/kubernetes.io/iscsi/" + devicePath)
|
||||
if found || iface != "" {
|
||||
t.Errorf("extractIface: expected %s and %t, got %s and %t", "", false, iface, found)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPortalAndIqn(t *testing.T) {
|
||||
devicePath := "127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0"
|
||||
portal, iqn, err := extractPortalAndIqn(devicePath)
|
||||
if err != nil || portal != "127.0.0.1:3260" || iqn != "iqn.2014-12.com.example:test.tgt00" {
|
||||
t.Errorf("extractPortalAndIqn: got %v %s %s", err, portal, iqn)
|
||||
}
|
||||
devicePath = "127.0.0.1:3260-eui.02004567A425678D-lun-0"
|
||||
portal, iqn, err = extractPortalAndIqn(devicePath)
|
||||
if err != nil || portal != "127.0.0.1:3260" || iqn != "eui.02004567A425678D" {
|
||||
t.Errorf("extractPortalAndIqn: got %v %s %s", err, portal, iqn)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveDuplicate(t *testing.T) {
|
||||
dupPortals := []string{"127.0.0.1:3260", "127.0.0.1:3260", "127.0.0.100:3260"}
|
||||
portals := removeDuplicate(dupPortals)
|
||||
want := []string{"127.0.0.1:3260", "127.0.0.100:3260"}
|
||||
if reflect.DeepEqual(portals, want) == false {
|
||||
t.Errorf("removeDuplicate: want: %s, got: %s", want, portals)
|
||||
}
|
||||
}
|
||||
|
||||
func fakeOsStat(devicePath string) (fi os.FileInfo, err error) {
|
||||
var cmd os.FileInfo
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func fakeFilepathGlob(devicePath string) (globs []string, err error) {
|
||||
return []string{devicePath}, nil
|
||||
}
|
||||
|
||||
func fakeFilepathGlob2(devicePath string) (globs []string, err error) {
|
||||
return []string{
|
||||
"/dev/disk/by-path/pci-0000:00:00.0-ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestExtractTransportname(t *testing.T) {
|
||||
fakeIscsiadmOutput := []string{
|
||||
"# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = default\n" +
|
||||
"iface.transport_name = tcp\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"# END RECORD",
|
||||
"# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = default\n" +
|
||||
"iface.transport_name = cxgb4i\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"# END RECORD",
|
||||
"# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = default\n" +
|
||||
"iface.transport_name = <empty>\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"# END RECORD",
|
||||
"# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = default\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"# END RECORD"}
|
||||
transportName := extractTransportname(fakeIscsiadmOutput[0])
|
||||
if transportName != "tcp" {
|
||||
t.Errorf("extractTransportname: Could not extract correct iface.transport_name 'tcp', got %s", transportName)
|
||||
}
|
||||
transportName = extractTransportname(fakeIscsiadmOutput[1])
|
||||
if transportName != "cxgb4i" {
|
||||
t.Errorf("extractTransportname: Could not extract correct iface.transport_name 'cxgb4i', got %s", transportName)
|
||||
}
|
||||
transportName = extractTransportname(fakeIscsiadmOutput[2])
|
||||
if transportName != "tcp" {
|
||||
t.Errorf("extractTransportname: Could not extract correct iface.transport_name 'tcp', got %s", transportName)
|
||||
}
|
||||
transportName = extractTransportname(fakeIscsiadmOutput[3])
|
||||
if transportName != "" {
|
||||
t.Errorf("extractTransportname: Could not extract correct iface.transport_name '', got %s", transportName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitForPathToExist(t *testing.T) {
|
||||
devicePath := []string{"/dev/disk/by-path/ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0",
|
||||
"/dev/disk/by-path/pci-*-ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0"}
|
||||
fpath := "/dev/disk/by-path/pci-0000:00:00.0-ip-127.0.0.1:3260-iqn.2014-12.com.example:test.tgt00-lun-0"
|
||||
|
||||
exist := waitForPathToExistInternal(&devicePath[0], 1, "tcp", fakeOsStat, filepath.Glob)
|
||||
if exist == false {
|
||||
t.Errorf("waitForPathToExist: could not find path %s", devicePath[0])
|
||||
}
|
||||
exist = waitForPathToExistInternal(&devicePath[0], 1, "fake_iface", fakeOsStat, filepath.Glob)
|
||||
if exist != false {
|
||||
t.Errorf("waitForPathToExist: wrong code path called for %s", devicePath[0])
|
||||
}
|
||||
|
||||
exist = waitForPathToExistInternal(&devicePath[1], 1, "fake_iface", os.Stat, fakeFilepathGlob)
|
||||
if exist == false {
|
||||
t.Errorf("waitForPathToExist: could not find path %s", devicePath[1])
|
||||
}
|
||||
exist = waitForPathToExistInternal(&devicePath[1], 1, "tcp", os.Stat, fakeFilepathGlob)
|
||||
if exist != false {
|
||||
t.Errorf("waitForPathToExist: wrong code path called for %s", devicePath[1])
|
||||
}
|
||||
|
||||
exist = waitForPathToExistInternal(&devicePath[1], 1, "fake_iface", os.Stat, fakeFilepathGlob2)
|
||||
if devicePath[1] != fpath {
|
||||
t.Errorf("waitForPathToExist: wrong code path called for %s", devicePath[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseIscsiadmShow(t *testing.T) {
|
||||
fakeIscsiadmOutput1 := "# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = default\n" +
|
||||
"iface.transport_name = tcp\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"iface.mtu = 0\n" +
|
||||
"# END RECORD"
|
||||
|
||||
fakeIscsiadmOutput2 := "# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = default\n" +
|
||||
"iface.transport_name = cxgb4i\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"iface.mtu = 0\n" +
|
||||
"# END RECORD"
|
||||
|
||||
fakeIscsiadmOutput3 := "# BEGIN RECORD 2.0-873\n" +
|
||||
"iface.iscsi_ifacename = custom\n" +
|
||||
"iface.transport_name = <empty>\n" +
|
||||
"iface.initiatorname = <empty>\n" +
|
||||
"iface.mtu = 0\n" +
|
||||
"# END RECORD"
|
||||
|
||||
fakeIscsiadmOutput4 := "iface.iscsi_ifacename=error"
|
||||
fakeIscsiadmOutput5 := "iface.iscsi_ifacename + error"
|
||||
|
||||
expectedIscsiadmOutput1 := map[string]string{
|
||||
"iface.transport_name": "tcp",
|
||||
"iface.mtu": "0"}
|
||||
|
||||
expectedIscsiadmOutput2 := map[string]string{
|
||||
"iface.transport_name": "cxgb4i",
|
||||
"iface.mtu": "0"}
|
||||
|
||||
expectedIscsiadmOutput3 := map[string]string{
|
||||
"iface.mtu": "0"}
|
||||
|
||||
params, _ := parseIscsiadmShow(fakeIscsiadmOutput1)
|
||||
if !reflect.DeepEqual(params, expectedIscsiadmOutput1) {
|
||||
t.Errorf("parseIscsiadmShow: Fail to parse iface record: %s", params)
|
||||
}
|
||||
params, _ = parseIscsiadmShow(fakeIscsiadmOutput2)
|
||||
if !reflect.DeepEqual(params, expectedIscsiadmOutput2) {
|
||||
t.Errorf("parseIscsiadmShow: Fail to parse iface record: %s", params)
|
||||
}
|
||||
params, _ = parseIscsiadmShow(fakeIscsiadmOutput3)
|
||||
if !reflect.DeepEqual(params, expectedIscsiadmOutput3) {
|
||||
t.Errorf("parseIscsiadmShow: Fail to parse iface record: %s", params)
|
||||
}
|
||||
_, err := parseIscsiadmShow(fakeIscsiadmOutput4)
|
||||
if err == nil {
|
||||
t.Errorf("parseIscsiadmShow: Fail to handle invalid record: iface %s", fakeIscsiadmOutput4)
|
||||
}
|
||||
_, err = parseIscsiadmShow(fakeIscsiadmOutput5)
|
||||
if err == nil {
|
||||
t.Errorf("parseIscsiadmShow: Fail to handle invalid record: iface %s", fakeIscsiadmOutput5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClonedIface(t *testing.T) {
|
||||
cmdCount := 0
|
||||
fakeExec := mount.NewFakeExec(func(cmd string, args ...string) ([]byte, error) {
|
||||
cmdCount++
|
||||
if cmd != "iscsiadm" {
|
||||
t.Errorf("iscsiadm command expected, got %q", cmd)
|
||||
}
|
||||
switch cmdCount {
|
||||
case 1:
|
||||
// iscsiadm -m iface -I <iface> -o show
|
||||
return []byte("iface.ipaddress = <empty>\niface.transport_name = tcp\niface.initiatorname = <empty>\n"), nil
|
||||
|
||||
case 2:
|
||||
// iscsiadm -m iface -I <newIface> -o new
|
||||
return []byte("New interface 192.168.1.10:pv0001 added"), nil
|
||||
case 3:
|
||||
// iscsiadm -m iface -I <newIface> -o update -n <key> -v <val>
|
||||
return []byte(""), nil
|
||||
case 4:
|
||||
return []byte(""), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Unexpected exec call nr %d: %s", cmdCount, cmd)
|
||||
})
|
||||
plugins := []volume.VolumePlugin{
|
||||
&iscsiPlugin{
|
||||
host: nil,
|
||||
},
|
||||
}
|
||||
plugin := plugins[0]
|
||||
fakeMounter := iscsiDiskMounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
plugin: plugin.(*iscsiPlugin)},
|
||||
exec: fakeExec,
|
||||
}
|
||||
newIface := "192.168.1.10:pv0001"
|
||||
cloneIface(fakeMounter, newIface)
|
||||
if cmdCount != 4 {
|
||||
t.Errorf("expected 4 CombinedOutput() calls, got %d", cmdCount)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClonedIfaceShowError(t *testing.T) {
|
||||
cmdCount := 0
|
||||
fakeExec := mount.NewFakeExec(func(cmd string, args ...string) ([]byte, error) {
|
||||
cmdCount++
|
||||
if cmd != "iscsiadm" {
|
||||
t.Errorf("iscsiadm command expected, got %q", cmd)
|
||||
}
|
||||
// iscsiadm -m iface -I <iface> -o show, return test error
|
||||
return []byte(""), errors.New("test error")
|
||||
})
|
||||
plugins := []volume.VolumePlugin{
|
||||
&iscsiPlugin{
|
||||
host: nil,
|
||||
},
|
||||
}
|
||||
plugin := plugins[0]
|
||||
fakeMounter := iscsiDiskMounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
plugin: plugin.(*iscsiPlugin)},
|
||||
exec: fakeExec,
|
||||
}
|
||||
newIface := "192.168.1.10:pv0001"
|
||||
cloneIface(fakeMounter, newIface)
|
||||
if cmdCount != 1 {
|
||||
t.Errorf("expected 1 CombinedOutput() calls, got %d", cmdCount)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClonedIfaceUpdateError(t *testing.T) {
|
||||
cmdCount := 0
|
||||
fakeExec := mount.NewFakeExec(func(cmd string, args ...string) ([]byte, error) {
|
||||
cmdCount++
|
||||
if cmd != "iscsiadm" {
|
||||
t.Errorf("iscsiadm command expected, got %q", cmd)
|
||||
}
|
||||
switch cmdCount {
|
||||
case 1:
|
||||
// iscsiadm -m iface -I <iface> -o show
|
||||
return []byte("iface.ipaddress = <empty>\niface.transport_name = tcp\niface.initiatorname = <empty>\n"), nil
|
||||
|
||||
case 2:
|
||||
// iscsiadm -m iface -I <newIface> -o new
|
||||
return []byte("New interface 192.168.1.10:pv0001 added"), nil
|
||||
case 3:
|
||||
// iscsiadm -m iface -I <newIface> -o update -n <key> -v <val>
|
||||
return []byte(""), nil
|
||||
case 4:
|
||||
return []byte(""), errors.New("test error")
|
||||
case 5:
|
||||
// iscsiadm -m iface -I <newIface> -o delete
|
||||
return []byte(""), nil
|
||||
}
|
||||
return nil, fmt.Errorf("Unexpected exec call nr %d: %s", cmdCount, cmd)
|
||||
})
|
||||
plugins := []volume.VolumePlugin{
|
||||
&iscsiPlugin{
|
||||
host: nil,
|
||||
},
|
||||
}
|
||||
plugin := plugins[0]
|
||||
fakeMounter := iscsiDiskMounter{
|
||||
iscsiDisk: &iscsiDisk{
|
||||
plugin: plugin.(*iscsiPlugin)},
|
||||
exec: fakeExec,
|
||||
}
|
||||
newIface := "192.168.1.10:pv0001"
|
||||
cloneIface(fakeMounter, newIface)
|
||||
if cmdCount != 5 {
|
||||
t.Errorf("expected 5 CombinedOutput() calls, got %d", cmdCount)
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user