Commit 542f2dc7 authored by saadali's avatar saadali

Introduce new kubelet volume manager

This commit adds a new volume manager in kubelet that synchronizes volume mount/unmount (and attach/detach, if attach/detach controller is not enabled). This eliminates the race conditions between the pod creation loop and the orphaned volumes loops. It also removes the unmount/detach from the `syncPod()` path so volume clean up never blocks the `syncPod` loop.
parent 9b6a505f
......@@ -16767,7 +16767,7 @@
"items": {
"$ref": "v1.UniqueVolumeName"
},
"description": "List of attachable volume devices in use (mounted) by the node."
"description": "List of attachable volumes in use (mounted) by the node."
}
}
},
......
......@@ -2768,6 +2768,10 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div>
<div class="sect2">
<h3 id="_v1_uniquevolumename">v1.UniqueVolumeName</h3>
</div>
<div class="sect2">
<h3 id="_unversioned_labelselector">unversioned.LabelSelector</h3>
<div class="paragraph">
<p>A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.</p>
......@@ -2806,9 +2810,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_uniquevolumename">v1.UniqueVolumeName</h3>
</div>
<div class="sect2">
<h3 id="_v1_endpointsubset">v1.EndpointSubset</h3>
......@@ -4755,7 +4757,7 @@ The resulting set of endpoints can be viewed as:<br>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumesInUse</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">List of attachable volume devices in use (mounted) by the node.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">List of attachable volumes in use (mounted) by the node.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_uniquevolumename">v1.UniqueVolumeName</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
......@@ -8103,7 +8105,7 @@ The resulting set of endpoints can be viewed as:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-06-06 17:05:06 UTC
Last updated 2016-06-08 04:10:38 UTC
</div>
</div>
</body>
......
......@@ -1304,7 +1304,7 @@ message NodeStatus {
// List of container images on this node
repeated ContainerImage images = 8;
// List of attachable volume devices in use (mounted) by the node.
// List of attachable volumes in use (mounted) by the node.
repeated string volumesInUse = 9;
}
......
......@@ -2386,7 +2386,7 @@ type NodeStatus struct {
NodeInfo NodeSystemInfo `json:"nodeInfo,omitempty" protobuf:"bytes,7,opt,name=nodeInfo"`
// List of container images on this node
Images []ContainerImage `json:"images,omitempty" protobuf:"bytes,8,rep,name=images"`
// List of attachable volume devices in use (mounted) by the node.
// List of attachable volumes in use (mounted) by the node.
VolumesInUse []UniqueVolumeName `json:"volumesInUse,omitempty" protobuf:"bytes,9,rep,name=volumesInUse"`
}
......
......@@ -880,7 +880,7 @@ var map_NodeStatus = map[string]string{
"daemonEndpoints": "Endpoints of daemons running on the Node.",
"nodeInfo": "Set of ids/uuids to uniquely identify the node. More info: http://releases.k8s.io/HEAD/docs/admin/node.md#node-info",
"images": "List of container images on this node",
"volumesInUse": "List of attachable volume devices in use (mounted) by the node.",
"volumesInUse": "List of attachable volumes in use (mounted) by the node.",
}
func (NodeStatus) SwaggerDoc() map[string]string {
......
......@@ -980,14 +980,22 @@ func (plugin *mockVolumePlugin) Init(host vol.VolumeHost) error {
return nil
}
func (plugin *mockVolumePlugin) Name() string {
func (plugin *mockVolumePlugin) GetPluginName() string {
return mockPluginName
}
func (plugin *mockVolumePlugin) GetVolumeName(spec *vol.Spec) (string, error) {
return spec.Name(), nil
}
func (plugin *mockVolumePlugin) CanSupport(spec *vol.Spec) bool {
return true
}
func (plugin *mockVolumePlugin) RequiresRemount() bool {
return false
}
func (plugin *mockVolumePlugin) NewMounter(spec *vol.Spec, podRef *api.Pod, opts vol.VolumeOptions) (vol.Mounter, error) {
return nil, fmt.Errorf("Mounter is not supported by this plugin")
}
......
......@@ -18,6 +18,7 @@ package persistentvolume
import (
"fmt"
"net"
"k8s.io/kubernetes/pkg/api"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
......@@ -71,3 +72,11 @@ func (ctrl *PersistentVolumeController) GetWriter() io.Writer {
func (ctrl *PersistentVolumeController) GetHostName() string {
return ""
}
func (ctrl *PersistentVolumeController) GetHostIP() (net.IP, error) {
return nil, fmt.Errorf("PersistentVolumeController.GetHostIP() is not implemented")
}
func (ctrl *PersistentVolumeController) GetRootContext() string {
return ""
}
......@@ -20,6 +20,7 @@ package volume
import (
"fmt"
"net"
"time"
"github.com/golang/glog"
......@@ -27,7 +28,6 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller/framework"
"k8s.io/kubernetes/pkg/controller/volume/attacherdetacher"
"k8s.io/kubernetes/pkg/controller/volume/cache"
"k8s.io/kubernetes/pkg/controller/volume/reconciler"
"k8s.io/kubernetes/pkg/types"
......@@ -35,11 +35,12 @@ import (
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)
const (
// loopPeriod is the ammount of time the reconciler loop waits between
// loopPeriod is the amount of time the reconciler loop waits between
// successive executions
reconcilerLoopPeriod time.Duration = 100 * time.Millisecond
......@@ -103,7 +104,8 @@ func NewAttachDetachController(
adc.desiredStateOfWorld = cache.NewDesiredStateOfWorld(&adc.volumePluginMgr)
adc.actualStateOfWorld = cache.NewActualStateOfWorld(&adc.volumePluginMgr)
adc.attacherDetacher = attacherdetacher.NewAttacherDetacher(&adc.volumePluginMgr)
adc.attacherDetacher =
operationexecutor.NewOperationExecutor(&adc.volumePluginMgr)
adc.reconciler = reconciler.NewReconciler(
reconcilerLoopPeriod,
reconcilerMaxWaitForUnmountDuration,
......@@ -152,7 +154,7 @@ type attachDetachController struct {
actualStateOfWorld cache.ActualStateOfWorld
// attacherDetacher is used to start asynchronous attach and operations
attacherDetacher attacherdetacher.AttacherDetacher
attacherDetacher operationexecutor.OperationExecutor
// reconciler is used to run an asynchronous periodic loop to reconcile the
// desiredStateOfWorld with the actualStateOfWorld by triggering attach
......@@ -205,7 +207,7 @@ func (adc *attachDetachController) nodeAdd(obj interface{}) {
}
nodeName := node.Name
if _, exists := node.Annotations[volumehelper.ControllerManagedAnnotation]; exists {
if _, exists := node.Annotations[volumehelper.ControllerManagedAttachAnnotation]; exists {
// Node specifies annotation indicating it should be managed by attach
// detach controller. Add it to desired state of world.
adc.desiredStateOfWorld.AddNode(nodeName)
......@@ -284,7 +286,7 @@ func (adc *attachDetachController) processPodVolumes(
continue
}
uniquePodName := getUniquePodName(pod)
uniquePodName := volumehelper.GetUniquePodName(pod)
if addVolumes {
// Add volume to desired state of world
_, err := adc.desiredStateOfWorld.AddPod(
......@@ -304,7 +306,7 @@ func (adc *attachDetachController) processPodVolumes(
attachableVolumePlugin, volumeSpec)
if err != nil {
glog.V(10).Infof(
"Failed to delete volume %q for pod %q/%q from desiredStateOfWorld. GenerateUniqueVolumeName failed with %v",
"Failed to delete volume %q for pod %q/%q from desiredStateOfWorld. GetUniqueVolumeNameFromSpec failed with %v",
podVolume.Name,
pod.Namespace,
pod.Name,
......@@ -502,11 +504,6 @@ func (adc *attachDetachController) processVolumesInUse(
}
}
// getUniquePodName returns a unique name to reference pod by in memory caches
func getUniquePodName(pod *api.Pod) types.UniquePodName {
return types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}.UniquePodName()
}
// VolumeHost implementation
// This is an unfortunate requirement of the current factoring of volume plugin
// initializing code. It requires kubelet specific methods used by the mounting
......@@ -552,3 +549,11 @@ func (adc *attachDetachController) GetWriter() io.Writer {
func (adc *attachDetachController) GetHostName() string {
return ""
}
func (adc *attachDetachController) GetHostIP() (net.IP, error) {
return nil, fmt.Errorf("GetHostIP() not supported by Attach/Detach controller's VolumeHost implementation")
}
func (adc *attachDetachController) GetRootContext() string {
return ""
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package attacherdetacher implements interfaces that enable triggering attach
// and detach operations on volumes.
package attacherdetacher
import (
"fmt"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/controller/volume/cache"
"k8s.io/kubernetes/pkg/util/goroutinemap"
"k8s.io/kubernetes/pkg/volume"
)
// AttacherDetacher defines a set of operations for attaching or detaching a
// volume from a node.
type AttacherDetacher interface {
// Spawns a new goroutine to execute volume-specific logic to attach the
// volume to the node specified in the volumeToAttach.
// Once attachment completes successfully, the actualStateOfWorld is updated
// to indicate the volume is attached to the node.
// If there is an error indicating the volume is already attached to the
// specified node, attachment is assumed to be successful (plugins are
// responsible for implmenting this behavior).
// All other errors are logged and the goroutine terminates without updating
// actualStateOfWorld (caller is responsible for retrying as needed).
AttachVolume(volumeToAttach cache.VolumeToAttach, actualStateOfWorld cache.ActualStateOfWorld) error
// Spawns a new goroutine to execute volume-specific logic to detach the
// volume from the node specified in volumeToDetach.
// Once detachment completes successfully, the actualStateOfWorld is updated
// to remove the volume/node combo.
// If there is an error indicating the volume is already detached from the
// specified node, detachment is assumed to be successful (plugins are
// responsible for implmenting this behavior).
// All other errors are logged and the goroutine terminates without updating
// actualStateOfWorld (caller is responsible for retrying as needed).
DetachVolume(volumeToDetach cache.AttachedVolume, actualStateOfWorld cache.ActualStateOfWorld) error
}
// NewAttacherDetacher returns a new instance of AttacherDetacher.
func NewAttacherDetacher(volumePluginMgr *volume.VolumePluginMgr) AttacherDetacher {
return &attacherDetacher{
volumePluginMgr: volumePluginMgr,
pendingOperations: goroutinemap.NewGoRoutineMap(),
}
}
type attacherDetacher struct {
// volumePluginMgr is the volume plugin manager used to create volume
// plugin objects.
volumePluginMgr *volume.VolumePluginMgr
// pendingOperations keeps track of pending attach and detach operations so
// multiple operations are not started on the same volume
pendingOperations goroutinemap.GoRoutineMap
}
func (ad *attacherDetacher) AttachVolume(
volumeToAttach cache.VolumeToAttach,
actualStateOfWorld cache.ActualStateOfWorld) error {
attachFunc, err := ad.generateAttachVolumeFunc(volumeToAttach, actualStateOfWorld)
if err != nil {
return err
}
return ad.pendingOperations.Run(string(volumeToAttach.VolumeName), attachFunc)
}
func (ad *attacherDetacher) DetachVolume(
volumeToDetach cache.AttachedVolume,
actualStateOfWorld cache.ActualStateOfWorld) error {
detachFunc, err := ad.generateDetachVolumeFunc(volumeToDetach, actualStateOfWorld)
if err != nil {
return err
}
return ad.pendingOperations.Run(string(volumeToDetach.VolumeName), detachFunc)
}
func (ad *attacherDetacher) generateAttachVolumeFunc(
volumeToAttach cache.VolumeToAttach,
actualStateOfWorld cache.ActualStateOfWorld) (func() error, error) {
// Get attacher plugin
attachableVolumePlugin, err := ad.volumePluginMgr.FindAttachablePluginBySpec(volumeToAttach.VolumeSpec)
if err != nil || attachableVolumePlugin == nil {
return nil, fmt.Errorf(
"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
volumeToAttach.VolumeSpec.Name(),
err)
}
volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher()
if newAttacherErr != nil {
return nil, fmt.Errorf(
"failed to get NewAttacher from volumeSpec for volume %q err=%v",
volumeToAttach.VolumeSpec.Name(),
newAttacherErr)
}
return func() error {
// Execute attach
attachErr := volumeAttacher.Attach(volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
if attachErr != nil {
// On failure, just log and exit. The controller will retry
glog.Errorf(
"Attach operation for device %q to node %q failed with: %v",
volumeToAttach.VolumeName, volumeToAttach.NodeName, attachErr)
return attachErr
}
glog.Infof(
"Successfully attached device %q to node %q. Will update actual state of world.",
volumeToAttach.VolumeName, volumeToAttach.NodeName)
// Update actual state of world
_, addVolumeNodeErr := actualStateOfWorld.AddVolumeNode(volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
if addVolumeNodeErr != nil {
// On failure, just log and exit. The controller will retry
glog.Errorf(
"Attach operation for device %q to node %q succeeded, but updating actualStateOfWorld failed with: %v",
volumeToAttach.VolumeName, volumeToAttach.NodeName, addVolumeNodeErr)
return addVolumeNodeErr
}
return nil
}, nil
}
func (ad *attacherDetacher) generateDetachVolumeFunc(
volumeToDetach cache.AttachedVolume,
actualStateOfWorld cache.ActualStateOfWorld) (func() error, error) {
// Get attacher plugin
attachableVolumePlugin, err := ad.volumePluginMgr.FindAttachablePluginBySpec(volumeToDetach.VolumeSpec)
if err != nil || attachableVolumePlugin == nil {
return nil, fmt.Errorf(
"failed to get AttachablePlugin from volumeSpec for volume %q err=%v",
volumeToDetach.VolumeSpec.Name(),
err)
}
deviceName, err := attachableVolumePlugin.GetVolumeName(volumeToDetach.VolumeSpec)
if err != nil {
return nil, fmt.Errorf(
"failed to GetDeviceName from AttachablePlugin for volumeSpec %q err=%v",
volumeToDetach.VolumeSpec.Name(),
err)
}
volumeDetacher, err := attachableVolumePlugin.NewDetacher()
if err != nil {
return nil, fmt.Errorf(
"failed to get NewDetacher from volumeSpec for volume %q err=%v",
volumeToDetach.VolumeSpec.Name(),
err)
}
return func() error {
// Execute detach
detachErr := volumeDetacher.Detach(deviceName, volumeToDetach.NodeName)
if detachErr != nil {
// On failure, just log and exit. The controller will retry
glog.Errorf(
"Detach operation for device %q from node %q failed with: %v",
volumeToDetach.VolumeName, volumeToDetach.NodeName, detachErr)
return detachErr
}
glog.Infof(
"Successfully detached device %q from node %q. Will update actual state of world.",
volumeToDetach.VolumeName, volumeToDetach.NodeName)
// Update actual state of world
actualStateOfWorld.DeleteVolumeNode(volumeToDetach.VolumeName, volumeToDetach.NodeName)
return nil
}, nil
}
......@@ -28,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)
......@@ -35,10 +36,17 @@ import (
// the attach/detach controller's actual state of the world cache.
// This cache contains volumes->nodes i.e. a set of all volumes and the nodes
// the attach/detach controller believes are successfully attached.
// Note: This is distinct from the ActualStateOfWorld implemented by the kubelet
// volume manager. They both keep track of different objects. This contains
// attach/detach controller specific state.
type ActualStateOfWorld interface {
// ActualStateOfWorld must implement the methods required to allow
// operationexecutor to interact with it.
operationexecutor.ActualStateOfWorldAttacherUpdater
// AddVolumeNode adds the given volume and node to the underlying store
// indicating the specified volume is attached to the specified node.
// A unique volumeName is generated from the volumeSpec and returned on
// A unique volume name is generated from the volumeSpec and returned on
// success.
// If the volume/node combo already exists, the detachRequestedTime is reset
// to zero.
......@@ -93,15 +101,7 @@ type ActualStateOfWorld interface {
// AttachedVolume represents a volume that is attached to a node.
type AttachedVolume struct {
// VolumeName is the unique identifier for the volume that is attached.
VolumeName api.UniqueVolumeName
// VolumeSpec is the volume spec containing the specification for the
// volume that is attached.
VolumeSpec *volume.Spec
// NodeName is the identifier for the node that the volume is attached to.
NodeName string
operationexecutor.AttachedVolume
// MountedByNode indicates that this volume has been been mounted by the
// node and is unsafe to detach.
......@@ -173,6 +173,17 @@ type nodeAttachedTo struct {
detachRequestedTime time.Time
}
func (asw *actualStateOfWorld) MarkVolumeAsAttached(
volumeSpec *volume.Spec, nodeName string) error {
_, err := asw.AddVolumeNode(volumeSpec, nodeName)
return err
}
func (asw *actualStateOfWorld) MarkVolumeAsDetached(
volumeName api.UniqueVolumeName, nodeName string) {
asw.DeleteVolumeNode(volumeName, nodeName)
}
func (asw *actualStateOfWorld) AddVolumeNode(
volumeSpec *volume.Spec, nodeName string) (api.UniqueVolumeName, error) {
asw.Lock()
......@@ -330,16 +341,11 @@ func (asw *actualStateOfWorld) GetAttachedVolumes() []AttachedVolume {
defer asw.RUnlock()
attachedVolumes := make([]AttachedVolume, 0 /* len */, len(asw.attachedVolumes) /* cap */)
for volumeName, volumeObj := range asw.attachedVolumes {
for nodeName, nodeObj := range volumeObj.nodesAttachedTo {
for _, volumeObj := range asw.attachedVolumes {
for _, nodeObj := range volumeObj.nodesAttachedTo {
attachedVolumes = append(
attachedVolumes,
AttachedVolume{
NodeName: nodeName,
VolumeName: volumeName,
VolumeSpec: volumeObj.spec,
MountedByNode: nodeObj.mountedByNode,
DetachRequestedTime: nodeObj.detachRequestedTime})
getAttachedVolume(&volumeObj, &nodeObj))
}
}
......@@ -353,20 +359,29 @@ func (asw *actualStateOfWorld) GetAttachedVolumesForNode(
attachedVolumes := make(
[]AttachedVolume, 0 /* len */, len(asw.attachedVolumes) /* cap */)
for volumeName, volumeObj := range asw.attachedVolumes {
for _, volumeObj := range asw.attachedVolumes {
for actualNodeName, nodeObj := range volumeObj.nodesAttachedTo {
if actualNodeName == nodeName {
attachedVolumes = append(
attachedVolumes,
AttachedVolume{
NodeName: nodeName,
VolumeName: volumeName,
VolumeSpec: volumeObj.spec,
MountedByNode: nodeObj.mountedByNode,
DetachRequestedTime: nodeObj.detachRequestedTime})
getAttachedVolume(&volumeObj, &nodeObj))
}
}
}
return attachedVolumes
}
func getAttachedVolume(
attachedVolume *attachedVolume,
nodeAttachedTo *nodeAttachedTo) AttachedVolume {
return AttachedVolume{
AttachedVolume: operationexecutor.AttachedVolume{
VolumeName: attachedVolume.volumeName,
VolumeSpec: attachedVolume.spec,
NodeName: nodeAttachedTo.nodeName,
PluginIsAttachable: true,
},
MountedByNode: nodeAttachedTo.mountedByNode,
DetachRequestedTime: nodeAttachedTo.detachRequestedTime}
}
......@@ -26,8 +26,9 @@ import (
"sync"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
"k8s.io/kubernetes/pkg/volume/util/types"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)
......@@ -37,6 +38,9 @@ import (
// managed by the attach/detach controller, volumes are all the volumes that
// should be attached to the specified node, and pods are the pods that
// reference the volume and are scheduled to that node.
// Note: This is distinct from the DesiredStateOfWorld implemented by the
// kubelet volume manager. The both keep track of different objects. This
// contains attach/detach controller specific state.
type DesiredStateOfWorld interface {
// AddNode adds the given node to the list of nodes managed by the attach/
// detach controller.
......@@ -90,17 +94,7 @@ type DesiredStateOfWorld interface {
// VolumeToAttach represents a volume that should be attached to a node.
type VolumeToAttach struct {
// VolumeName is the unique identifier for the volume that should be
// attached.
VolumeName api.UniqueVolumeName
// VolumeSpec is a volume spec containing the specification for the volume
// that should be attached.
VolumeSpec *volume.Spec
// NodeName is the identifier for the node that the volume should be
// attached to.
NodeName string
operationexecutor.VolumeToAttach
}
// NewDesiredStateOfWorld returns a new instance of DesiredStateOfWorld.
......@@ -196,7 +190,7 @@ func (dsw *desiredStateOfWorld) AddPod(
attachableVolumePlugin, volumeSpec)
if err != nil {
return "", fmt.Errorf(
"failed to GenerateUniqueVolumeName for volumeSpec %q err=%v",
"failed to GetUniqueVolumeNameFromSpec for volumeSpec %q err=%v",
volumeSpec.Name(),
err)
}
......@@ -304,7 +298,12 @@ func (dsw *desiredStateOfWorld) GetVolumesToAttach() []VolumeToAttach {
volumesToAttach := make([]VolumeToAttach, 0 /* len */, len(dsw.nodesManaged) /* cap */)
for nodeName, nodeObj := range dsw.nodesManaged {
for volumeName, volumeObj := range nodeObj.volumesToAttach {
volumesToAttach = append(volumesToAttach, VolumeToAttach{NodeName: nodeName, VolumeName: volumeName, VolumeSpec: volumeObj.spec})
volumesToAttach = append(volumesToAttach,
VolumeToAttach{
VolumeToAttach: operationexecutor.VolumeToAttach{
VolumeName: volumeName,
VolumeSpec: volumeObj.spec,
NodeName: nodeName}})
}
}
......
......@@ -23,13 +23,16 @@ import (
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/controller/volume/attacherdetacher"
"k8s.io/kubernetes/pkg/controller/volume/cache"
"k8s.io/kubernetes/pkg/util/wait"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
)
// Reconciler runs a periodic loop to reconcile the desired state of the with
// the actual state of the world by triggering attach detach operations.
// Note: This is distinct from the Reconciler implemented by the kubelet volume
// manager. This reconciles state for the attach/detach controller. That
// reconciles state for the kubelet volume manager.
type Reconciler interface {
// Starts running the reconciliation loop which executes periodically, checks
// if volumes that should be attached are attached and volumes that should
......@@ -52,7 +55,7 @@ func NewReconciler(
maxWaitForUnmountDuration time.Duration,
desiredStateOfWorld cache.DesiredStateOfWorld,
actualStateOfWorld cache.ActualStateOfWorld,
attacherDetacher attacherdetacher.AttacherDetacher) Reconciler {
attacherDetacher operationexecutor.OperationExecutor) Reconciler {
return &reconciler{
loopPeriod: loopPeriod,
maxWaitForUnmountDuration: maxWaitForUnmountDuration,
......@@ -67,7 +70,7 @@ type reconciler struct {
maxWaitForUnmountDuration time.Duration
desiredStateOfWorld cache.DesiredStateOfWorld
actualStateOfWorld cache.ActualStateOfWorld
attacherDetacher attacherdetacher.AttacherDetacher
attacherDetacher operationexecutor.OperationExecutor
}
func (rc *reconciler) Run(stopCh <-chan struct{}) {
......@@ -86,7 +89,7 @@ func (rc *reconciler) reconciliationLoopFunc() func() {
// Volume exists in actual state of world but not desired
if !attachedVolume.MountedByNode {
glog.V(5).Infof("Attempting to start DetachVolume for volume %q to node %q", attachedVolume.VolumeName, attachedVolume.NodeName)
err := rc.attacherDetacher.DetachVolume(attachedVolume, rc.actualStateOfWorld)
err := rc.attacherDetacher.DetachVolume(attachedVolume.AttachedVolume, rc.actualStateOfWorld)
if err == nil {
glog.Infof("Started DetachVolume for volume %q to node %q", attachedVolume.VolumeName, attachedVolume.NodeName)
}
......@@ -98,7 +101,7 @@ func (rc *reconciler) reconciliationLoopFunc() func() {
}
if timeElapsed > rc.maxWaitForUnmountDuration {
glog.V(5).Infof("Attempting to start DetachVolume for volume %q to node %q. Volume is not safe to detach, but maxWaitForUnmountDuration expired.", attachedVolume.VolumeName, attachedVolume.NodeName)
err := rc.attacherDetacher.DetachVolume(attachedVolume, rc.actualStateOfWorld)
err := rc.attacherDetacher.DetachVolume(attachedVolume.AttachedVolume, rc.actualStateOfWorld)
if err == nil {
glog.Infof("Started DetachVolume for volume %q to node %q due to maxWaitForUnmountDuration expiry.", attachedVolume.VolumeName, attachedVolume.NodeName)
}
......@@ -121,7 +124,7 @@ func (rc *reconciler) reconciliationLoopFunc() func() {
} else {
// Volume/Node doesn't exist, spawn a goroutine to attach it
glog.V(5).Infof("Attempting to start AttachVolume for volume %q to node %q", volumeToAttach.VolumeName, volumeToAttach.NodeName)
err := rc.attacherDetacher.AttachVolume(volumeToAttach, rc.actualStateOfWorld)
err := rc.attacherDetacher.AttachVolume(volumeToAttach.VolumeToAttach, rc.actualStateOfWorld)
if err == nil {
glog.Infof("Started AttachVolume for volume %q to node %q", volumeToAttach.VolumeName, volumeToAttach.NodeName)
}
......
......@@ -21,12 +21,12 @@ import (
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/controller/volume/attacherdetacher"
"k8s.io/kubernetes/pkg/controller/volume/cache"
controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/testing"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/wait"
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
"k8s.io/kubernetes/pkg/volume/util/types"
)
const (
......@@ -38,10 +38,10 @@ const (
// Verifies there are no calls to attach or detach.
func Test_Run_Positive_DoNothing(t *testing.T) {
// Arrange
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
asw := cache.NewActualStateOfWorld(volumePluginMgr)
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
ad := operationexecutor.NewOperationExecutor(volumePluginMgr)
reconciler := NewReconciler(
reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad)
......@@ -61,13 +61,13 @@ func Test_Run_Positive_DoNothing(t *testing.T) {
// Verifies there is one attach call and no detach calls.
func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) {
// Arrange
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
asw := cache.NewActualStateOfWorld(volumePluginMgr)
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
ad := operationexecutor.NewOperationExecutor(volumePluginMgr)
reconciler := NewReconciler(
reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad)
podName := types.UniquePodName("pod-name")
podName := types.UniquePodName("pod-uid")
volumeName := api.UniqueVolumeName("volume-name")
volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName)
nodeName := "node-name"
......@@ -102,13 +102,13 @@ func Test_Run_Positive_OneDesiredVolumeAttach(t *testing.T) {
// Verifies there is one detach call and no (new) attach calls.
func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *testing.T) {
// Arrange
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
asw := cache.NewActualStateOfWorld(volumePluginMgr)
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
ad := operationexecutor.NewOperationExecutor(volumePluginMgr)
reconciler := NewReconciler(
reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad)
podName := types.UniquePodName("pod-name")
podName := types.UniquePodName("pod-uid")
volumeName := api.UniqueVolumeName("volume-name")
volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName)
nodeName := "node-name"
......@@ -164,13 +164,13 @@ func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithUnmountedVolume(t *te
// Verifies there is one detach call and no (new) attach calls.
func Test_Run_Positive_OneDesiredVolumeAttachThenDetachWithMountedVolume(t *testing.T) {
// Arrange
volumePluginMgr, fakePlugin := controllervolumetesting.GetTestVolumePluginMgr((t))
volumePluginMgr, fakePlugin := volumetesting.GetTestVolumePluginMgr(t)
dsw := cache.NewDesiredStateOfWorld(volumePluginMgr)
asw := cache.NewActualStateOfWorld(volumePluginMgr)
ad := attacherdetacher.NewAttacherDetacher(volumePluginMgr)
ad := operationexecutor.NewOperationExecutor(volumePluginMgr)
reconciler := NewReconciler(
reconcilerLoopPeriod, maxWaitForUnmountDuration, dsw, asw, ad)
podName := types.UniquePodName("pod-name")
podName := types.UniquePodName("pod-uid")
volumeName := api.UniqueVolumeName("volume-name")
volumeSpec := controllervolumetesting.GetTestVolumeSpec(string(volumeName), volumeName)
nodeName := "node-name"
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testing
import (
"fmt"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/io"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
)
// GetTestVolumePluginMgr creates, initializes, and returns a test volume
// plugin manager.
func GetTestVolumePluginMgr(t *testing.T) (*volume.VolumePluginMgr, *volumetesting.FakeVolumePlugin) {
plugins := []volume.VolumePlugin{}
// plugins = append(plugins, aws_ebs.ProbeVolumePlugins()...)
// plugins = append(plugins, gce_pd.ProbeVolumePlugins()...)
// plugins = append(plugins, cinder.ProbeVolumePlugins()...)
volumeTestingPlugins := volumetesting.ProbeVolumePlugins(volume.VolumeConfig{})
plugins = append(plugins, volumeTestingPlugins...)
volumePluginMgr := testVolumePluginMgr{}
if err := volumePluginMgr.InitPlugins(plugins, &volumePluginMgr); err != nil {
t.Fatalf("Could not initialize volume plugins for Attach/Detach Controller: %+v", err)
}
return &volumePluginMgr.VolumePluginMgr, volumeTestingPlugins[0].(*volumetesting.FakeVolumePlugin)
}
type testVolumePluginMgr struct {
volume.VolumePluginMgr
}
// VolumeHost implementation
// This is an unfortunate requirement of the current factoring of volume plugin
// initializing code. It requires kubelet specific methods used by the mounting
// code to be implemented by all initializers even if the initializer does not
// do mounting (like this attach/detach controller).
// Issue kubernetes/kubernetes/issues/14217 to fix this.
func (vpm *testVolumePluginMgr) GetPluginDir(podUID string) string {
return ""
}
func (vpm *testVolumePluginMgr) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
return ""
}
func (vpm *testVolumePluginMgr) GetPodPluginDir(podUID types.UID, pluginName string) string {
return ""
}
func (vpm *testVolumePluginMgr) GetKubeClient() internalclientset.Interface {
return nil
}
func (vpm *testVolumePluginMgr) NewWrapperMounter(volName string, spec volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return nil, fmt.Errorf("NewWrapperMounter not supported by Attach/Detach controller's VolumeHost implementation")
}
func (vpm *testVolumePluginMgr) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) {
return nil, fmt.Errorf("NewWrapperUnmounter not supported by Attach/Detach controller's VolumeHost implementation")
}
func (vpm *testVolumePluginMgr) GetCloudProvider() cloudprovider.Interface {
return &fake.FakeCloud{}
}
func (vpm *testVolumePluginMgr) GetMounter() mount.Interface {
return nil
}
func (vpm *testVolumePluginMgr) GetWriter() io.Writer {
return nil
}
func (vpm *testVolumePluginMgr) GetHostName() string {
return ""
}
......@@ -33,7 +33,7 @@ func TestGetContainerInfo(t *testing.T) {
},
}
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
fakeRuntime := testKubelet.fakeRuntime
kubelet := testKubelet.kubelet
cadvisorReq := &cadvisorapi.ContainerInfoRequest{}
......@@ -69,7 +69,7 @@ func TestGetRawContainerInfoRoot(t *testing.T) {
Name: containerPath,
},
}
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
cadvisorReq := &cadvisorapi.ContainerInfoRequest{}
......@@ -96,7 +96,7 @@ func TestGetRawContainerInfoSubcontainers(t *testing.T) {
},
},
}
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
cadvisorReq := &cadvisorapi.ContainerInfoRequest{}
......@@ -114,7 +114,7 @@ func TestGetRawContainerInfoSubcontainers(t *testing.T) {
func TestGetContainerInfoWhenCadvisorFailed(t *testing.T) {
containerID := "ab2cdf"
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
fakeRuntime := testKubelet.fakeRuntime
......@@ -149,7 +149,7 @@ func TestGetContainerInfoWhenCadvisorFailed(t *testing.T) {
}
func TestGetContainerInfoOnNonExistContainer(t *testing.T) {
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
fakeRuntime := testKubelet.fakeRuntime
......@@ -163,7 +163,7 @@ func TestGetContainerInfoOnNonExistContainer(t *testing.T) {
}
func TestGetContainerInfoWhenContainerRuntimeFailed(t *testing.T) {
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
fakeRuntime := testKubelet.fakeRuntime
......@@ -184,7 +184,7 @@ func TestGetContainerInfoWhenContainerRuntimeFailed(t *testing.T) {
}
func TestGetContainerInfoWithNoContainers(t *testing.T) {
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
......@@ -202,7 +202,7 @@ func TestGetContainerInfoWithNoContainers(t *testing.T) {
}
func TestGetContainerInfoWithNoMatchingContainers(t *testing.T) {
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
fakeRuntime := testKubelet.fakeRuntime
kubelet := testKubelet.kubelet
mockCadvisor := testKubelet.fakeCadvisor
......
......@@ -24,7 +24,7 @@ import (
)
func TestKubeletDirs(t *testing.T) {
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
root := kubelet.rootDirectory
......@@ -86,7 +86,7 @@ func TestKubeletDirs(t *testing.T) {
}
func TestKubeletDirsCompat(t *testing.T) {
testKubelet := newTestKubelet(t)
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
kubelet := testKubelet.kubelet
root := kubelet.rootDirectory
if err := os.MkdirAll(root, 0750); err != nil {
......
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubelet
import (
"fmt"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/volume"
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
)
// ListVolumesForPod returns a map of the mounted volumes for the given pod.
// The key in the map is the OuterVolumeSpecName (i.e. pod.Spec.Volumes[x].Name)
func (kl *Kubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) {
volumesToReturn := make(map[string]volume.Volume)
podVolumes := kl.volumeManager.GetMountedVolumesForPod(
volumetypes.UniquePodName(podUID))
for outerVolumeSpecName, volume := range podVolumes {
volumesToReturn[outerVolumeSpecName] = volume.Mounter
}
return volumesToReturn, len(volumesToReturn) > 0
}
// podVolumesExist checks wiht the volume manager and returns true any of the
// pods for the specified volume are mounted.
func (kl *Kubelet) podVolumesExist(podUID types.UID) bool {
if mountedVolumes :=
kl.volumeManager.GetMountedVolumesForPod(
volumetypes.UniquePodName(podUID)); len(mountedVolumes) > 0 {
return true
}
return false
}
// newVolumeMounterFromPlugins attempts to find a plugin by volume spec, pod
// and volume options and then creates a Mounter.
// Returns a valid Unmounter or an error.
func (kl *Kubelet) newVolumeMounterFromPlugins(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
plugin, err := kl.volumePluginMgr.FindPluginBySpec(spec)
if err != nil {
return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err)
}
physicalMounter, err := plugin.NewMounter(spec, pod, opts)
if err != nil {
return nil, fmt.Errorf("failed to instantiate mounter for volume: %s using plugin: %s with a root cause: %v", spec.Name(), plugin.GetPluginName(), err)
}
glog.V(10).Infof("Using volume plugin %q to mount %s", plugin.GetPluginName(), spec.Name())
return physicalMounter, nil
}
......@@ -38,9 +38,12 @@ import (
podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
"k8s.io/kubernetes/pkg/kubelet/server/stats"
"k8s.io/kubernetes/pkg/kubelet/status"
kubeletvolume "k8s.io/kubernetes/pkg/kubelet/volume"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
)
func TestRunOnce(t *testing.T) {
......@@ -73,7 +76,6 @@ func TestRunOnce(t *testing.T) {
containerRefManager: kubecontainer.NewRefManager(),
podManager: podManager,
os: &containertest.FakeOS{},
volumeManager: newVolumeManager(),
diskSpaceManager: diskSpaceManager,
containerRuntime: fakeRuntime,
reasonCache: NewReasonCache(),
......@@ -84,6 +86,19 @@ func TestRunOnce(t *testing.T) {
}
kb.containerManager = cm.NewStubContainerManager()
plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil}
kb.volumePluginMgr, err =
NewInitializedVolumePluginMgr(kb, []volume.VolumePlugin{plug})
if err != nil {
t.Fatalf("failed to initialize VolumePluginMgr: %v", err)
}
kb.volumeManager, err = kubeletvolume.NewVolumeManager(
true,
kb.hostname,
kb.podManager,
kb.kubeClient,
kb.volumePluginMgr)
kb.networkPlugin, _ = network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil), componentconfig.HairpinNone, kb.nonMasqueradeCIDR)
// TODO: Factor out "StatsProvider" from Kubelet so we don't have a cyclic dependency
volumeStatsAggPeriod := time.Second * 10
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/volume"
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)
// Calls AddPodToVolume() to add new pod to new volume
// Verifies newly added pod/volume exists via
// PodExistsInVolume() VolumeExists() and GetVolumesToMount()
func Test_AddPodToVolume_Positive_NewPodNewVolume(t *testing.T) {
// Arrange
volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t)
dsw := NewDesiredStateOfWorld(volumePluginMgr)
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "pod3",
UID: "pod3uid",
},
Spec: api.PodSpec{
Volumes: []api.Volume{
{
Name: "volume-name",
VolumeSource: api.VolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
PDName: "fake-device1",
},
},
},
},
},
}
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := volumehelper.GetUniquePodName(pod)
// Act
generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
// Assert
if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
}
verifyVolumeExists(t, generatedVolumeName, dsw)
verifyVolumeExistsInVolumesToMount(t, generatedVolumeName, dsw)
verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, dsw)
}
// Calls AddPodToVolume() twice to add the same pod to the same volume
// Verifies newly added pod/volume exists via
// PodExistsInVolume() VolumeExists() and GetVolumesToMount() and no errors.
func Test_AddPodToVolume_Positive_ExistingPodExistingVolume(t *testing.T) {
// Arrange
volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t)
dsw := NewDesiredStateOfWorld(volumePluginMgr)
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "pod3",
UID: "pod3uid",
},
Spec: api.PodSpec{
Volumes: []api.Volume{
{
Name: "volume-name",
VolumeSource: api.VolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
PDName: "fake-device1",
},
},
},
},
},
}
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := volumehelper.GetUniquePodName(pod)
// Act
generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
// Assert
if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
}
verifyVolumeExists(t, generatedVolumeName, dsw)
verifyVolumeExistsInVolumesToMount(t, generatedVolumeName, dsw)
verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, dsw)
}
// Populates data struct with a new volume/pod
// Calls DeletePodFromVolume() to removes the pod
// Verifies newly added pod/volume are deleted
func Test_DeletePodFromVolume_Positive_PodExistsVolumeExists(t *testing.T) {
// Arrange
volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t)
dsw := NewDesiredStateOfWorld(volumePluginMgr)
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "pod3",
UID: "pod3uid",
},
Spec: api.PodSpec{
Volumes: []api.Volume{
{
Name: "volume-name",
VolumeSource: api.VolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
PDName: "fake-device1",
},
},
},
},
},
}
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := volumehelper.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */)
if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
}
verifyVolumeExists(t, generatedVolumeName, dsw)
verifyVolumeExistsInVolumesToMount(t, generatedVolumeName, dsw)
verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, dsw)
// Act
dsw.DeletePodFromVolume(podName, generatedVolumeName)
// Assert
verifyVolumeDoesntExist(t, generatedVolumeName, dsw)
verifyVolumeDoesntExistInVolumesToMount(t, generatedVolumeName, dsw)
verifyPodDoesntExistInVolumeDsw(t, podName, generatedVolumeName, dsw)
}
func verifyVolumeExists(
t *testing.T, expectedVolumeName api.UniqueVolumeName, dsw DesiredStateOfWorld) {
volumeExists := dsw.VolumeExists(expectedVolumeName)
if !volumeExists {
t.Fatalf(
"VolumeExists(%q) failed. Expected: <true> Actual: <%v>",
expectedVolumeName,
volumeExists)
}
}
func verifyVolumeDoesntExist(
t *testing.T, expectedVolumeName api.UniqueVolumeName, dsw DesiredStateOfWorld) {
volumeExists := dsw.VolumeExists(expectedVolumeName)
if volumeExists {
t.Fatalf(
"VolumeExists(%q) returned incorrect value. Expected: <false> Actual: <%v>",
expectedVolumeName,
volumeExists)
}
}
func verifyVolumeExistsInVolumesToMount(
t *testing.T, expectedVolumeName api.UniqueVolumeName, dsw DesiredStateOfWorld) {
volumesToMount := dsw.GetVolumesToMount()
for _, volume := range volumesToMount {
if volume.VolumeName == expectedVolumeName {
return
}
}
t.Fatalf(
"Could not find volume %v in the list of desired state of world volumes to mount %+v",
expectedVolumeName,
volumesToMount)
}
func verifyVolumeDoesntExistInVolumesToMount(
t *testing.T, volumeToCheck api.UniqueVolumeName, dsw DesiredStateOfWorld) {
volumesToMount := dsw.GetVolumesToMount()
for _, volume := range volumesToMount {
if volume.VolumeName == volumeToCheck {
t.Fatalf(
"Found volume %v in the list of desired state of world volumes to mount. Expected it not to exist.",
volumeToCheck)
}
}
}
func verifyPodExistsInVolumeDsw(
t *testing.T,
expectedPodName volumetypes.UniquePodName,
expectedVolumeName api.UniqueVolumeName,
dsw DesiredStateOfWorld) {
if podExistsInVolume := dsw.PodExistsInVolume(
expectedPodName, expectedVolumeName); !podExistsInVolume {
t.Fatalf(
"DSW PodExistsInVolume returned incorrect value. Expected: <true> Actual: <%v>",
podExistsInVolume)
}
}
func verifyPodDoesntExistInVolumeDsw(
t *testing.T,
expectedPodName volumetypes.UniquePodName,
expectedVolumeName api.UniqueVolumeName,
dsw DesiredStateOfWorld) {
if podExistsInVolume := dsw.PodExistsInVolume(
expectedPodName, expectedVolumeName); podExistsInVolume {
t.Fatalf(
"DSW PodExistsInVolume returned incorrect value. Expected: <true> Actual: <%v>",
podExistsInVolume)
}
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubelet
import (
"fmt"
"net"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/io"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
)
// NewInitializedVolumePluginMgr returns a new instance of
// volume.VolumePluginMgr initialized with kubelets implementation of the
// volume.VolumeHost interface.
//
// kubelet - used by VolumeHost methods to expose kubelet specific parameters
// plugins - used to initialize volumePluginMgr
func NewInitializedVolumePluginMgr(
kubelet *Kubelet,
plugins []volume.VolumePlugin) (*volume.VolumePluginMgr, error) {
kvh := &kubeletVolumeHost{
kubelet: kubelet,
volumePluginMgr: volume.VolumePluginMgr{},
}
if err := kvh.volumePluginMgr.InitPlugins(plugins, kvh); err != nil {
return nil, fmt.Errorf(
"Could not initialize volume plugins for KubeletVolumePluginMgr: %v",
err)
}
return &kvh.volumePluginMgr, nil
}
// Compile-time check to ensure kubeletVolumeHost implements the VolumeHost interface
var _ volume.VolumeHost = &kubeletVolumeHost{}
func (kvh *kubeletVolumeHost) GetPluginDir(pluginName string) string {
return kvh.kubelet.getPluginDir(pluginName)
}
type kubeletVolumeHost struct {
kubelet *Kubelet
volumePluginMgr volume.VolumePluginMgr
}
func (kvh *kubeletVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
return kvh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName)
}
func (kvh *kubeletVolumeHost) GetPodPluginDir(podUID types.UID, pluginName string) string {
return kvh.kubelet.getPodPluginDir(podUID, pluginName)
}
func (kvh *kubeletVolumeHost) GetKubeClient() internalclientset.Interface {
return kvh.kubelet.kubeClient
}
func (kvh *kubeletVolumeHost) NewWrapperMounter(
volName string,
spec volume.Spec,
pod *api.Pod,
opts volume.VolumeOptions) (volume.Mounter, error) {
// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
wrapperVolumeName := "wrapped_" + volName
if spec.Volume != nil {
spec.Volume.Name = wrapperVolumeName
}
return kvh.kubelet.newVolumeMounterFromPlugins(&spec, pod, opts)
}
func (kvh *kubeletVolumeHost) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) {
// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
wrapperVolumeName := "wrapped_" + volName
if spec.Volume != nil {
spec.Volume.Name = wrapperVolumeName
}
plugin, err := kvh.kubelet.volumePluginMgr.FindPluginBySpec(&spec)
if err != nil {
return nil, err
}
return plugin.NewUnmounter(spec.Name(), podUID)
}
func (kvh *kubeletVolumeHost) GetCloudProvider() cloudprovider.Interface {
return kvh.kubelet.cloud
}
func (kvh *kubeletVolumeHost) GetMounter() mount.Interface {
return kvh.kubelet.mounter
}
func (kvh *kubeletVolumeHost) GetWriter() io.Writer {
return kvh.kubelet.writer
}
func (kvh *kubeletVolumeHost) GetHostName() string {
return kvh.kubelet.hostname
}
func (kvh *kubeletVolumeHost) GetHostIP() (net.IP, error) {
return kvh.kubelet.GetHostIP()
}
func (kvh *kubeletVolumeHost) GetRootContext() string {
rootContext, err := kvh.kubelet.getRootDirContext()
if err != nil {
return ""
}
return rootContext
}
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubelet
import (
"sync"
"k8s.io/kubernetes/pkg/api"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/types"
)
// volumeManager manages the volumes for the pods running on the kubelet.
// Currently it only does book keeping, but it can be expanded to
// take care of the volumePlugins.
// TODO(saad-ali): note that volumeManager will be completley refactored as part
// of mount/unmount refactor.
type volumeManager struct {
lock sync.RWMutex
volumeMaps map[types.UID]kubecontainer.VolumeMap
volumesInUse []api.UniqueVolumeName
}
func newVolumeManager() *volumeManager {
vm := &volumeManager{
volumeMaps: make(map[types.UID]kubecontainer.VolumeMap),
volumesInUse: []api.UniqueVolumeName{},
}
return vm
}
// SetVolumes sets the volume map for a pod.
// TODO(yifan): Currently we assume the volume is already mounted, so we only do a book keeping here.
func (vm *volumeManager) SetVolumes(podUID types.UID, podVolumes kubecontainer.VolumeMap) {
vm.lock.Lock()
defer vm.lock.Unlock()
vm.volumeMaps[podUID] = podVolumes
}
// GetVolumes returns the volume map which are already mounted on the host machine
// for a pod.
func (vm *volumeManager) GetVolumes(podUID types.UID) (kubecontainer.VolumeMap, bool) {
vm.lock.RLock()
defer vm.lock.RUnlock()
vol, ok := vm.volumeMaps[podUID]
return vol, ok
}
// DeleteVolumes removes the reference to a volume map for a pod.
func (vm *volumeManager) DeleteVolumes(podUID types.UID) {
vm.lock.Lock()
defer vm.lock.Unlock()
delete(vm.volumeMaps, podUID)
}
// AddVolumeInUse adds specified volume to volumesInUse list, if it doesn't
// already exist
func (vm *volumeManager) AddVolumeInUse(uniqueDeviceName api.UniqueVolumeName) {
vm.lock.Lock()
defer vm.lock.Unlock()
for _, volume := range vm.volumesInUse {
if volume == uniqueDeviceName {
// Volume already exists in list
return
}
}
vm.volumesInUse = append(vm.volumesInUse, uniqueDeviceName)
}
// RemoveVolumeInUse removes the specified volume from volumesInUse list, if it
// exists
func (vm *volumeManager) RemoveVolumeInUse(uniqueDeviceName api.UniqueVolumeName) {
vm.lock.Lock()
defer vm.lock.Unlock()
for i := len(vm.volumesInUse) - 1; i >= 0; i-- {
if vm.volumesInUse[i] == uniqueDeviceName {
// Volume exists, remove it
vm.volumesInUse = append(vm.volumesInUse[:i], vm.volumesInUse[i+1:]...)
}
}
}
// GetVolumesInUse returns the volumesInUse list
func (vm *volumeManager) GetVolumesInUse() []api.UniqueVolumeName {
vm.lock.RLock()
defer vm.lock.RUnlock()
return vm.volumesInUse
}
......@@ -16,10 +16,6 @@ limitations under the License.
package types
// UniquePodName is an identifier that can be used to uniquely identify a pod
// within the cluster.
type UniquePodName string
// NamespacedName comprises a resource name, with a mandatory namespace,
// rendered as "<namespace>/<name>". Being a type captures intent and
// helps make sure that UIDs, namespaced names and non-namespaced names
......@@ -37,8 +33,3 @@ type NamespacedName struct {
func (n NamespacedName) String() string {
return n.Namespace + "/" + n.Name
}
// UniquePodName returns the UniquePodName object representation
func (n NamespacedName) UniquePodName() UniquePodName {
return UniquePodName(n.String())
}
......@@ -24,7 +24,6 @@ import (
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
......@@ -42,17 +41,12 @@ func (plugin *awsElasticBlockStorePlugin) NewAttacher() (volume.Attacher, error)
return &awsElasticBlockStoreAttacher{host: plugin.host}, nil
}
func (plugin *awsElasticBlockStorePlugin) GetDeviceName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference an EBS volume type")
func (attacher *awsElasticBlockStoreAttacher) Attach(spec *volume.Spec, hostName string) error {
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return err
}
return volumeSource.VolumeID, nil
}
func (attacher *awsElasticBlockStoreAttacher) Attach(spec *volume.Spec, hostName string) error {
volumeSource, readOnly := getVolumeSource(spec)
volumeID := volumeSource.VolumeID
awsCloud, err := getCloudProvider(attacher.host.GetCloudProvider())
......@@ -86,7 +80,12 @@ func (attacher *awsElasticBlockStoreAttacher) WaitForAttach(spec *volume.Spec, t
if err != nil {
return "", err
}
volumeSource, _ := getVolumeSource(spec)
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
volumeID := volumeSource.VolumeID
partition := ""
if volumeSource.Partition != 0 {
......@@ -136,13 +135,19 @@ func (attacher *awsElasticBlockStoreAttacher) WaitForAttach(spec *volume.Spec, t
}
}
func (attacher *awsElasticBlockStoreAttacher) GetDeviceMountPath(spec *volume.Spec) string {
volumeSource, _ := getVolumeSource(spec)
return makeGlobalPDPath(attacher.host, volumeSource.VolumeID)
func (attacher *awsElasticBlockStoreAttacher) GetDeviceMountPath(
spec *volume.Spec) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return makeGlobalPDPath(attacher.host, volumeSource.VolumeID), nil
}
// FIXME: this method can be further pruned.
func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
mounter := attacher.host.GetMounter()
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil {
if os.IsNotExist(err) {
......@@ -155,7 +160,10 @@ func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, dev
}
}
volumeSource, readOnly := getVolumeSource(spec)
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return err
}
options := []string{}
if readOnly {
......@@ -231,7 +239,8 @@ func (detacher *awsElasticBlockStoreDetacher) WaitForDetach(devicePath string, t
}
}
func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath string, mounter mount.Interface) error {
func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath string) error {
mounter := detacher.host.GetMounter()
volume := path.Base(deviceMountPath)
if err := unmountPDAndRemoveGlobalPath(deviceMountPath, mounter); err != nil {
glog.Errorf("Error unmounting %q: %v", volume, err)
......@@ -239,18 +248,3 @@ func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath stri
return nil
}
func getVolumeSource(spec *volume.Spec) (*api.AWSElasticBlockStoreVolumeSource, bool) {
var readOnly bool
var volumeSource *api.AWSElasticBlockStoreVolumeSource
if spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil {
volumeSource = spec.Volume.AWSElasticBlockStore
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.AWSElasticBlockStore
readOnly = spec.ReadOnly
}
return volumeSource, readOnly
}
......@@ -66,9 +66,9 @@ func (plugin *awsElasticBlockStorePlugin) GetPluginName() string {
}
func (plugin *awsElasticBlockStorePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference an AWS EBS volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.VolumeID, nil
......@@ -79,6 +79,10 @@ func (plugin *awsElasticBlockStorePlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil)
}
func (plugin *awsElasticBlockStorePlugin) RequiresRemount() bool {
return false
}
func (plugin *awsElasticBlockStorePlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -93,14 +97,9 @@ func (plugin *awsElasticBlockStorePlugin) NewMounter(spec *volume.Spec, pod *api
func (plugin *awsElasticBlockStorePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.Mounter, error) {
// EBSs used directly in a pod have a ReadOnly flag set by the pod author.
// EBSs used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
var readOnly bool
var ebs *api.AWSElasticBlockStoreVolumeSource
if spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil {
ebs = spec.Volume.AWSElasticBlockStore
readOnly = ebs.ReadOnly
} else {
ebs = spec.PersistentVolume.Spec.AWSElasticBlockStore
readOnly = spec.ReadOnly
ebs, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
volumeID := ebs.VolumeID
......@@ -176,19 +175,16 @@ func (plugin *awsElasticBlockStorePlugin) newProvisionerInternal(options volume.
}, nil
}
func getVolumeSource(spec *volume.Spec) (*api.AWSElasticBlockStoreVolumeSource, bool) {
var readOnly bool
var volumeSource *api.AWSElasticBlockStoreVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.AWSElasticBlockStoreVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil {
volumeSource = spec.Volume.AWSElasticBlockStore
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.AWSElasticBlockStore
readOnly = spec.ReadOnly
return spec.Volume.AWSElasticBlockStore, spec.Volume.AWSElasticBlockStore.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.AWSElasticBlockStore != nil {
return spec.PersistentVolume.Spec.AWSElasticBlockStore, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference an AWS EBS volume type")
}
// Abstract interface to PD operations.
......
......@@ -39,14 +39,14 @@ func TestCanSupport(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/aws-ebs" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/aws-ebs" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -63,7 +63,7 @@ func TestGetAccessModes(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/aws-ebs")
if err != nil {
......@@ -112,7 +112,7 @@ func TestPlugin(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
if err != nil {
......@@ -254,7 +254,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, clientset, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, clientset, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(awsElasticBlockStorePluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......@@ -274,7 +274,7 @@ func TestMounterAndUnmounterTypeAssert(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
if err != nil {
......
......@@ -59,9 +59,9 @@ func (plugin *azureFilePlugin) GetPluginName() string {
}
func (plugin *azureFilePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference an AzureFile volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.ShareName, nil
......@@ -73,6 +73,10 @@ func (plugin *azureFilePlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.AzureFile != nil)
}
func (plugin *azureFilePlugin) RequiresRemount() bool {
return false
}
func (plugin *azureFilePlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -86,15 +90,11 @@ func (plugin *azureFilePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ vol
}
func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod, util azureUtil, mounter mount.Interface) (volume.Mounter, error) {
var source *api.AzureFileVolumeSource
var readOnly bool
if spec.Volume != nil && spec.Volume.AzureFile != nil {
source = spec.Volume.AzureFile
readOnly = spec.Volume.AzureFile.ReadOnly
} else {
source = spec.PersistentVolume.Spec.AzureFile
readOnly = spec.ReadOnly
source, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
return &azureFileMounter{
azureFile: &azureFile{
volName: spec.Name(),
......@@ -247,17 +247,14 @@ func (c *azureFileUnmounter) TearDownAt(dir string) error {
return nil
}
func getVolumeSource(spec *volume.Spec) (*api.AzureFileVolumeSource, bool) {
var readOnly bool
var volumeSource *api.AzureFileVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.AzureFileVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.AzureFile != nil {
volumeSource = spec.Volume.AzureFile
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.AzureFile
readOnly = spec.ReadOnly
return spec.Volume.AzureFile, spec.Volume.AzureFile.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.AzureFile != nil {
return spec.PersistentVolume.Spec.AzureFile, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference an AzureFile volume type")
}
......@@ -37,14 +37,14 @@ func TestCanSupport(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/azure-file")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/azure-file" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/azure-file" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -61,7 +61,7 @@ func TestGetAccessModes(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/azure-file")
if err != nil {
......@@ -88,7 +88,7 @@ func TestPlugin(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/azure-file")
if err != nil {
......@@ -185,7 +185,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost("/tmp/fake", client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost("/tmp/fake", client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(azureFilePluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......@@ -211,7 +211,7 @@ func TestMounterAndUnmounterTypeAssert(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/azure-file")
if err != nil {
......
......@@ -54,9 +54,9 @@ func (plugin *cephfsPlugin) GetPluginName() string {
}
func (plugin *cephfsPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a CephFS volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return fmt.Sprintf("%v", volumeSource.Monitors), nil
......@@ -66,6 +66,10 @@ func (plugin *cephfsPlugin) CanSupport(spec *volume.Spec) bool {
return (spec.Volume != nil && spec.Volume.CephFS != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CephFS != nil)
}
func (plugin *cephfsPlugin) RequiresRemount() bool {
return false
}
func (plugin *cephfsPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -75,7 +79,10 @@ func (plugin *cephfsPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
}
func (plugin *cephfsPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
cephvs := plugin.getVolumeSource(spec)
cephvs, _, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
secret := ""
if cephvs.SecretRef != nil {
kubeClient := plugin.host.GetKubeClient()
......@@ -97,7 +104,11 @@ func (plugin *cephfsPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume
}
func (plugin *cephfsPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, mounter mount.Interface, secret string) (volume.Mounter, error) {
cephvs := plugin.getVolumeSource(spec)
cephvs, _, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
id := cephvs.User
if id == "" {
id = "admin"
......@@ -143,14 +154,6 @@ func (plugin *cephfsPlugin) newUnmounterInternal(volName string, podUID types.UI
}, nil
}
func (plugin *cephfsPlugin) getVolumeSource(spec *volume.Spec) *api.CephFSVolumeSource {
if spec.Volume != nil && spec.Volume.CephFS != nil {
return spec.Volume.CephFS
} else {
return spec.PersistentVolume.Spec.CephFS
}
}
// CephFS volumes represent a bare host file or directory mount of an CephFS export.
type cephfs struct {
volName string
......@@ -289,17 +292,13 @@ func (cephfsVolume *cephfs) execMount(mountpoint string) error {
return nil
}
func getVolumeSource(spec *volume.Spec) (*api.CephFSVolumeSource, bool) {
var readOnly bool
var volumeSource *api.CephFSVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.CephFSVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.CephFS != nil {
volumeSource = spec.Volume.CephFS
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.CephFS
readOnly = spec.ReadOnly
return spec.Volume.CephFS, spec.Volume.CephFS.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.CephFS != nil {
return spec.PersistentVolume.Spec.CephFS, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a CephFS volume type")
}
......@@ -36,13 +36,13 @@ func TestCanSupport(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/cephfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/cephfs" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/cephfs" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{}}}) {
t.Errorf("Expected false")
......@@ -59,7 +59,7 @@ func TestPlugin(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/cephfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
......
......@@ -24,7 +24,6 @@ import (
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
......@@ -46,17 +45,12 @@ func (plugin *cinderPlugin) NewAttacher() (volume.Attacher, error) {
return &cinderDiskAttacher{host: plugin.host}, nil
}
func (plugin *cinderPlugin) GetDeviceName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a Cinder volume type")
func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return err
}
return volumeSource.VolumeID, nil
}
func (attacher *cinderDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
volumeSource, _ := getVolumeSource(spec)
volumeID := volumeSource.VolumeID
cloud, err := getCloudProvider(attacher.host.GetCloudProvider())
......@@ -101,7 +95,12 @@ func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, timeout tim
if err != nil {
return "", err
}
volumeSource, _ := getVolumeSource(spec)
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
volumeID := volumeSource.VolumeID
instanceid, err := cloud.InstanceID()
if err != nil {
......@@ -150,13 +149,19 @@ func (attacher *cinderDiskAttacher) WaitForAttach(spec *volume.Spec, timeout tim
}
}
func (attacher *cinderDiskAttacher) GetDeviceMountPath(spec *volume.Spec) string {
volumeSource, _ := getVolumeSource(spec)
return makeGlobalPDName(attacher.host, volumeSource.VolumeID)
func (attacher *cinderDiskAttacher) GetDeviceMountPath(
spec *volume.Spec) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return makeGlobalPDName(attacher.host, volumeSource.VolumeID), nil
}
// FIXME: this method can be further pruned.
func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
mounter := attacher.host.GetMounter()
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil {
if os.IsNotExist(err) {
......@@ -169,7 +174,10 @@ func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath st
}
}
volumeSource, readOnly := getVolumeSource(spec)
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return err
}
options := []string{}
if readOnly {
......@@ -254,7 +262,8 @@ func (detacher *cinderDiskDetacher) WaitForDetach(devicePath string, timeout tim
}
}
func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string, mounter mount.Interface) error {
func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string) error {
mounter := detacher.host.GetMounter()
volume := path.Base(deviceMountPath)
if err := unmountPDAndRemoveGlobalPath(deviceMountPath, mounter); err != nil {
glog.Errorf("Error unmounting %q: %v", volume, err)
......@@ -263,21 +272,6 @@ func (detacher *cinderDiskDetacher) UnmountDevice(deviceMountPath string, mounte
return nil
}
func getVolumeSource(spec *volume.Spec) (*api.CinderVolumeSource, bool) {
var readOnly bool
var volumeSource *api.CinderVolumeSource
if spec.Volume != nil && spec.Volume.Cinder != nil {
volumeSource = spec.Volume.Cinder
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.Cinder
readOnly = spec.ReadOnly
}
return volumeSource, readOnly
}
// Checks if the specified path exists
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
......
......@@ -79,9 +79,9 @@ func (plugin *cinderPlugin) GetPluginName() string {
}
func (plugin *cinderPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a Cinder volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.VolumeID, nil
......@@ -91,6 +91,10 @@ func (plugin *cinderPlugin) CanSupport(spec *volume.Spec) bool {
return (spec.Volume != nil && spec.Volume.Cinder != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Cinder != nil)
}
func (plugin *cinderPlugin) RequiresRemount() bool {
return false
}
func (plugin *cinderPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -102,16 +106,13 @@ func (plugin *cinderPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume
}
func (plugin *cinderPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager cdManager, mounter mount.Interface) (volume.Mounter, error) {
var cinder *api.CinderVolumeSource
if spec.Volume != nil && spec.Volume.Cinder != nil {
cinder = spec.Volume.Cinder
} else {
cinder = spec.PersistentVolume.Spec.Cinder
cinder, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
pdName := cinder.VolumeID
fsType := cinder.FSType
readOnly := cinder.ReadOnly
return &cinderVolumeMounter{
cinderVolume: &cinderVolume{
......@@ -468,17 +469,13 @@ func (c *cinderVolumeProvisioner) Provision() (*api.PersistentVolume, error) {
return pv, nil
}
func getVolumeSource(spec *volume.Spec) (*api.CinderVolumeSource, bool) {
var readOnly bool
var volumeSource *api.CinderVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.CinderVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.Cinder != nil {
volumeSource = spec.Volume.Cinder
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.Cinder
readOnly = spec.ReadOnly
return spec.Volume.Cinder, spec.Volume.Cinder.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.Cinder != nil {
return spec.PersistentVolume.Spec.Cinder, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a Cinder volume type")
}
......@@ -39,14 +39,14 @@ func TestCanSupport(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/cinder" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/cinder" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{Cinder: &api.CinderVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -135,7 +135,7 @@ func TestPlugin(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/cinder")
if err != nil {
......
......@@ -67,6 +67,10 @@ func (plugin *configMapPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.ConfigMap != nil
}
func (plugin *configMapPlugin) RequiresRemount() bool {
return true
}
func (plugin *configMapPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &configMapVolumeMounter{
configMapVolume: &configMapVolume{spec.Name(), pod.UID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}},
......
......@@ -184,7 +184,7 @@ func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.Vo
t.Fatalf("can't make a temp rootdir: %v", err)
}
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins())
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins(), "" /* rootContext */)
}
func TestCanSupport(t *testing.T) {
......@@ -196,8 +196,8 @@ func TestCanSupport(t *testing.T) {
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plugin.Name() != configMapPluginName {
t.Errorf("Wrong name: %s", plugin.Name())
if plugin.GetPluginName() != configMapPluginName {
t.Errorf("Wrong name: %s", plugin.GetPluginName())
}
if !plugin.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: ""}}}}}) {
t.Errorf("Expected true")
......
......@@ -76,6 +76,10 @@ func (plugin *downwardAPIPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.DownwardAPI != nil
}
func (plugin *downwardAPIPlugin) RequiresRemount() bool {
return true
}
func (plugin *downwardAPIPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
v := &downwardAPIVolume{
volName: spec.Name(),
......
......@@ -48,7 +48,7 @@ func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.Vo
if err != nil {
t.Fatalf("can't make a temp rootdir: %v", err)
}
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins())
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins(), "" /* rootContext */)
}
func TestCanSupport(t *testing.T) {
......@@ -61,8 +61,8 @@ func TestCanSupport(t *testing.T) {
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plugin.Name() != downwardAPIPluginName {
t.Errorf("Wrong name: %s", plugin.Name())
if plugin.GetPluginName() != downwardAPIPluginName {
t.Errorf("Wrong name: %s", plugin.GetPluginName())
}
}
......
......@@ -85,6 +85,10 @@ func (plugin *emptyDirPlugin) CanSupport(spec *volume.Spec) bool {
return false
}
func (plugin *emptyDirPlugin) RequiresRemount() bool {
return false
}
func (plugin *emptyDirPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(), &realMountDetector{plugin.host.GetMounter()}, opts)
}
......@@ -101,7 +105,7 @@ func (plugin *emptyDirPlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod
mounter: mounter,
mountDetector: mountDetector,
plugin: plugin,
rootContext: opts.RootContext,
rootContext: plugin.host.GetRootContext(),
MetricsProvider: volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host)),
}, nil
}
......
......@@ -33,9 +33,9 @@ import (
)
// Construct an instance of a plugin, by name.
func makePluginUnderTest(t *testing.T, plugName, basePath string) volume.VolumePlugin {
func makePluginUnderTest(t *testing.T, plugName, basePath, rootContext string) volume.VolumePlugin {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(basePath, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(basePath, nil, nil, rootContext))
plug, err := plugMgr.FindPluginByName(plugName)
if err != nil {
......@@ -50,10 +50,10 @@ func TestCanSupport(t *testing.T) {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir)
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir, "" /* rootContext */)
if plug.Name() != "kubernetes.io/empty-dir" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/empty-dir" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -130,7 +130,7 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
volumePath = path.Join(basePath, "pods/poduid/volumes/kubernetes.io~empty-dir/test-volume")
metadataDir = path.Join(basePath, "pods/poduid/plugins/kubernetes.io~empty-dir/test-volume")
plug = makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath)
plug = makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath, config.rootContext)
volumeName = "test-volume"
spec = &api.Volume{
Name: volumeName,
......@@ -173,7 +173,7 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
pod,
&physicalMounter,
&mountDetector,
volume.VolumeOptions{RootContext: config.rootContext})
volume.VolumeOptions{})
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
......@@ -258,13 +258,13 @@ func TestPluginBackCompat(t *testing.T) {
}
defer os.RemoveAll(basePath)
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath)
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath, "" /* rootContext */)
spec := &api.Volume{
Name: "vol1",
}
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
mounter, err := plug.NewMounter(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{RootContext: ""})
mounter, err := plug.NewMounter(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{})
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
......@@ -287,13 +287,13 @@ func TestMetrics(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir)
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir, "" /* rootContext */)
spec := &api.Volume{
Name: "vol1",
}
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
mounter, err := plug.NewMounter(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{RootContext: ""})
mounter, err := plug.NewMounter(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{})
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
......
......@@ -56,11 +56,12 @@ func (plugin *fcPlugin) GetPluginName() string {
}
func (plugin *fcPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a FibreChannel volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
// TargetWWNs are the FibreChannel target world wide names
return fmt.Sprintf("%v", volumeSource.TargetWWNs), nil
}
......@@ -72,6 +73,10 @@ func (plugin *fcPlugin) CanSupport(spec *volume.Spec) bool {
return true
}
func (plugin *fcPlugin) RequiresRemount() bool {
return false
}
func (plugin *fcPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -87,14 +92,9 @@ func (plugin *fcPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.Vol
func (plugin *fcPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface) (volume.Mounter, error) {
// fc volumes used directly in a pod have a ReadOnly flag set by the pod author.
// fc volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
var readOnly bool
var fc *api.FCVolumeSource
if spec.Volume != nil && spec.Volume.FC != nil {
fc = spec.Volume.FC
readOnly = fc.ReadOnly
} else {
fc = spec.PersistentVolume.Spec.FC
readOnly = spec.ReadOnly
fc, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
if fc.Lun == nil {
......@@ -207,17 +207,13 @@ func (c *fcDiskUnmounter) TearDownAt(dir string) error {
return diskTearDown(c.manager, *c, dir, c.mounter)
}
func getVolumeSource(spec *volume.Spec) (*api.FCVolumeSource, bool) {
var readOnly bool
var volumeSource *api.FCVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.FCVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.FC != nil {
volumeSource = spec.Volume.FC
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.FC
readOnly = spec.ReadOnly
return spec.Volume.FC, spec.Volume.FC.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.FC != nil {
return spec.PersistentVolume.Spec.FC, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a FibreChannel volume type")
}
......@@ -38,14 +38,14 @@ func TestCanSupport(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/fc")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/fc" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/fc" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{}}}) {
t.Errorf("Expected false")
......@@ -60,7 +60,7 @@ func TestGetAccessModes(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/fc")
if err != nil {
......@@ -131,7 +131,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/fc")
if err != nil {
......@@ -274,7 +274,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(fcPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -78,9 +78,9 @@ func (plugin *flexVolumePlugin) GetPluginName() string {
}
func (plugin *flexVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a Flex volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.Driver, nil
......@@ -88,10 +88,14 @@ func (plugin *flexVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error)
// CanSupport checks whether the plugin can support the input volume spec.
func (plugin *flexVolumePlugin) CanSupport(spec *volume.Spec) bool {
source := plugin.getVolumeSource(spec)
source, _, _ := getVolumeSource(spec)
return (source != nil) && (source.Driver == plugin.driverName)
}
func (plugin *flexVolumePlugin) RequiresRemount() bool {
return false
}
// GetAccessModes gets the allowed access modes for this plugin.
func (plugin *flexVolumePlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
......@@ -100,19 +104,12 @@ func (plugin *flexVolumePlugin) GetAccessModes() []api.PersistentVolumeAccessMod
}
}
func (plugin *flexVolumePlugin) getVolumeSource(spec *volume.Spec) *api.FlexVolumeSource {
var source *api.FlexVolumeSource
if spec.Volume != nil && spec.Volume.FlexVolume != nil {
source = spec.Volume.FlexVolume
} else if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FlexVolume != nil {
source = spec.PersistentVolume.Spec.FlexVolume
}
return source
}
// NewMounter is the mounter routine to build the volume.
func (plugin *flexVolumePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
fv := plugin.getVolumeSource(spec)
fv, _, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
secrets := make(map[string]string)
if fv.SecretRef != nil {
kubeClient := plugin.host.GetKubeClient()
......@@ -135,7 +132,11 @@ func (plugin *flexVolumePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ vo
// newMounterInternal is the internal mounter routine to build the volume.
func (plugin *flexVolumePlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod, manager flexVolumeManager, mounter mount.Interface, runner exec.Interface, secrets map[string]string) (volume.Mounter, error) {
source := plugin.getVolumeSource(spec)
source, _, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
return &flexVolumeMounter{
flexVolumeDisk: &flexVolumeDisk{
podUID: pod.UID,
......@@ -396,17 +397,13 @@ func (f *flexVolumeUnmounter) TearDownAt(dir string) error {
return nil
}
func getVolumeSource(spec *volume.Spec) (*api.FlexVolumeSource, bool) {
var readOnly bool
var volumeSource *api.FlexVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.FlexVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.FlexVolume != nil {
volumeSource = spec.Volume.FlexVolume
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.FlexVolume
readOnly = spec.ReadOnly
return spec.Volume.FlexVolume, spec.Volume.FlexVolume.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.FlexVolume != nil {
return spec.PersistentVolume.Spec.FlexVolume, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a Flex volume type")
}
......@@ -182,13 +182,13 @@ func TestCanSupport(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
installPluginUnderTest(t, "kubernetes.io", "fakeAttacher", tmpDir, execScriptTempl1, nil)
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost("fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost("fake", nil, nil, "" /* rootContext */))
plugin, err := plugMgr.FindPluginByName("kubernetes.io/fakeAttacher")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plugin.Name() != "kubernetes.io/fakeAttacher" {
t.Errorf("Wrong name: %s", plugin.Name())
if plugin.GetPluginName() != "kubernetes.io/fakeAttacher" {
t.Errorf("Wrong name: %s", plugin.GetPluginName())
}
if !plugin.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{FlexVolume: &api.FlexVolumeSource{Driver: "kubernetes.io/fakeAttacher"}}}}) {
t.Errorf("Expected true")
......@@ -210,7 +210,7 @@ func TestGetAccessModes(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
installPluginUnderTest(t, "kubernetes.io", "fakeAttacher", tmpDir, execScriptTempl1, nil)
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plugin, err := plugMgr.FindPersistentPluginByName("kubernetes.io/fakeAttacher")
if err != nil {
......@@ -233,7 +233,7 @@ func contains(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeA
func doTestPluginAttachDetach(t *testing.T, spec *volume.Spec, tmpDir string) {
plugMgr := volume.VolumePluginMgr{}
installPluginUnderTest(t, "kubernetes.io", "fakeAttacher", tmpDir, execScriptTempl1, nil)
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plugin, err := plugMgr.FindPluginByName("kubernetes.io/fakeAttacher")
if err != nil {
t.Errorf("Can't find the plugin by name")
......@@ -314,7 +314,7 @@ func doTestPluginMountUnmount(t *testing.T, spec *volume.Spec, tmpDir string) {
plugMgr := volume.VolumePluginMgr{}
installPluginUnderTest(t, "kubernetes.io", "fakeMounter", tmpDir, execScriptTempl2, nil)
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(tmpDir), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plugin, err := plugMgr.FindPluginByName("kubernetes.io/fakeMounter")
if err != nil {
t.Errorf("Can't find the plugin by name")
......
......@@ -71,9 +71,9 @@ func (p *flockerPlugin) GetPluginName() string {
}
func (p *flockerPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a Flocker volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.DatasetName, nil
......@@ -84,6 +84,10 @@ func (p *flockerPlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.Flocker != nil)
}
func (p *flockerPlugin) RequiresRemount() bool {
return false
}
func (p *flockerPlugin) getFlockerVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool) {
// AFAIK this will always be r/w, but perhaps for the future it will be needed
readOnly := false
......@@ -152,7 +156,12 @@ func (b flockerMounter) newFlockerClient() (*flockerclient.Client, error) {
keyPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CLIENT_KEY_FILE", defaultClientKeyFile)
certPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CLIENT_CERT_FILE", defaultClientCertFile)
c, err := flockerclient.NewClient(host, port, b.flocker.pod.Status.HostIP, caCertPath, keyPath, certPath)
hostIP, err := b.plugin.host.GetHostIP()
if err != nil {
return nil, err
}
c, err := flockerclient.NewClient(host, port, hostIP.String(), caCertPath, keyPath, certPath)
return c, err
}
......@@ -251,17 +260,13 @@ func (b flockerMounter) updateDatasetPrimary(datasetID, primaryUUID string) erro
}
func getVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool) {
var readOnly bool
var volumeSource *api.FlockerVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.Flocker != nil {
volumeSource = spec.Volume.Flocker
readOnly = spec.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.Flocker
readOnly = spec.ReadOnly
return spec.Volume.Flocker, spec.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.Flocker != nil {
return spec.PersistentVolume.Spec.Flocker, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a Flocker volume type")
}
......@@ -35,7 +35,7 @@ func newInitializedVolumePlugMgr(t *testing.T) (*volume.VolumePluginMgr, string)
plugMgr := &volume.VolumePluginMgr{}
dir, err := utiltesting.MkTmpdir("flocker")
assert.NoError(t, err)
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(dir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(dir, nil, nil, "" /* rootContext */))
return plugMgr, dir
}
......
......@@ -61,7 +61,11 @@ func (plugin *gcePersistentDiskPlugin) NewAttacher() (volume.Attacher, error) {
// Callers are responsible for thread safety between concurrent attach and
// detach operations.
func (attacher *gcePersistentDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
volumeSource, readOnly := getVolumeSource(spec)
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return err
}
pdName := volumeSource.PDName
attached, err := attacher.gceDisks.DiskIsAttached(pdName, hostName)
......@@ -92,7 +96,11 @@ func (attacher *gcePersistentDiskAttacher) WaitForAttach(spec *volume.Spec, time
timer := time.NewTimer(timeout)
defer timer.Stop()
volumeSource, _ := getVolumeSource(spec)
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
pdName := volumeSource.PDName
partition := ""
if volumeSource.Partition != 0 {
......@@ -125,13 +133,19 @@ func (attacher *gcePersistentDiskAttacher) WaitForAttach(spec *volume.Spec, time
}
}
func (attacher *gcePersistentDiskAttacher) GetDeviceMountPath(spec *volume.Spec) string {
volumeSource, _ := getVolumeSource(spec)
return makeGlobalPDName(attacher.host, volumeSource.PDName)
func (attacher *gcePersistentDiskAttacher) GetDeviceMountPath(
spec *volume.Spec) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return makeGlobalPDName(attacher.host, volumeSource.PDName), nil
}
func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
// Only mount the PD globally once.
mounter := attacher.host.GetMounter()
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil {
if os.IsNotExist(err) {
......@@ -144,7 +158,10 @@ func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, device
}
}
volumeSource, readOnly := getVolumeSource(spec)
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return err
}
options := []string{}
if readOnly {
......@@ -163,6 +180,7 @@ func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, device
}
type gcePersistentDiskDetacher struct {
host volume.VolumeHost
gceDisks gce.Disks
}
......@@ -175,6 +193,7 @@ func (plugin *gcePersistentDiskPlugin) NewDetacher() (volume.Detacher, error) {
}
return &gcePersistentDiskDetacher{
host: plugin.host,
gceDisks: gceCloud,
}, nil
}
......@@ -232,6 +251,6 @@ func (detacher *gcePersistentDiskDetacher) WaitForDetach(devicePath string, time
}
}
func (detacher *gcePersistentDiskDetacher) UnmountDevice(deviceMountPath string, mounter mount.Interface) error {
return unmountPDAndRemoveGlobalPath(deviceMountPath, mounter)
func (detacher *gcePersistentDiskDetacher) UnmountDevice(deviceMountPath string) error {
return unmountPDAndRemoveGlobalPath(deviceMountPath, detacher.host.GetMounter())
}
......@@ -32,7 +32,7 @@ func TestGetDeviceName_Volume(t *testing.T) {
name := "my-pd-volume"
spec := createVSpec(name, false)
deviceName, err := plugin.GetDeviceName(spec)
deviceName, err := plugin.GetVolumeName(spec)
if err != nil {
t.Errorf("GetDeviceName error: %v", err)
}
......@@ -46,7 +46,7 @@ func TestGetDeviceName_PersistentVolume(t *testing.T) {
name := "my-pd-pv"
spec := createPVSpec(name, true)
deviceName, err := plugin.GetDeviceName(spec)
deviceName, err := plugin.GetVolumeName(spec)
if err != nil {
t.Errorf("GetDeviceName error: %v", err)
}
......@@ -181,7 +181,11 @@ func TestAttachDetach(t *testing.T) {
// newPlugin creates a new gcePersistentDiskPlugin with fake cloud, NewAttacher
// and NewDetacher won't work.
func newPlugin() *gcePersistentDiskPlugin {
host := volumetest.NewFakeVolumeHost("/tmp", nil, nil)
host := volumetest.NewFakeVolumeHost(
"/tmp", /* rootDir */
nil, /* kubeClient */
nil, /* plugins */
"" /* rootContext */)
plugins := ProbeVolumePlugins()
plugin := plugins[0]
plugin.Init(host)
......
......@@ -63,9 +63,9 @@ func (plugin *gcePersistentDiskPlugin) GetPluginName() string {
}
func (plugin *gcePersistentDiskPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a GCE volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.PDName, nil
......@@ -76,6 +76,10 @@ func (plugin *gcePersistentDiskPlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.GCEPersistentDisk != nil)
}
func (plugin *gcePersistentDiskPlugin) RequiresRemount() bool {
return false
}
func (plugin *gcePersistentDiskPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -88,26 +92,35 @@ func (plugin *gcePersistentDiskPlugin) NewMounter(spec *volume.Spec, pod *api.Po
return plugin.newMounterInternal(spec, pod.UID, &GCEDiskUtil{}, plugin.host.GetMounter())
}
func getVolumeSource(spec *volume.Spec) (*api.GCEPersistentDiskVolumeSource, bool) {
var readOnly bool
var volumeSource *api.GCEPersistentDiskVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.GCEPersistentDiskVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.GCEPersistentDisk != nil {
volumeSource = spec.Volume.GCEPersistentDisk
readOnly = volumeSource.ReadOnly
glog.V(4).Infof("volume source %v spec %v, readonly flag retrieved from source: %v", volumeSource.PDName, spec.Name(), readOnly)
} else {
volumeSource = spec.PersistentVolume.Spec.GCEPersistentDisk
readOnly = spec.ReadOnly
glog.V(4).Infof("volume source %v spec %v, readonly flag retrieved from spec: %v", volumeSource.PDName, spec.Name(), readOnly)
glog.V(4).Infof(
"volume source %v spec %v, readonly flag retrieved from source: %v",
spec.Volume.GCEPersistentDisk.PDName,
spec.Name(),
spec.Volume.GCEPersistentDisk.ReadOnly)
return spec.Volume.GCEPersistentDisk, spec.Volume.GCEPersistentDisk.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.GCEPersistentDisk != nil {
glog.V(4).Infof(
"volume source %v spec %v, readonly flag retrieved from spec: %v",
spec.PersistentVolume.Spec.GCEPersistentDisk.PDName,
spec.Name(),
spec.ReadOnly)
return spec.PersistentVolume.Spec.GCEPersistentDisk, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a GCE volume type")
}
func (plugin *gcePersistentDiskPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager pdManager, mounter mount.Interface) (volume.Mounter, error) {
// GCEPDs used directly in a pod have a ReadOnly flag set by the pod author.
// GCEPDs used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
volumeSource, readOnly := getVolumeSource(spec)
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
pdName := volumeSource.PDName
partition := ""
......
......@@ -39,14 +39,14 @@ func TestCanSupport(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/gce-pd")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/gce-pd" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/gce-pd" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -63,7 +63,7 @@ func TestGetAccessModes(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/gce-pd")
if err != nil {
......@@ -106,7 +106,7 @@ func TestPlugin(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/gce-pd")
if err != nil {
......@@ -248,7 +248,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(gcePersistentDiskPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -75,6 +75,10 @@ func (plugin *gitRepoPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.GitRepo != nil
}
func (plugin *gitRepoPlugin) RequiresRemount() bool {
return false
}
func (plugin *gitRepoPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &gitRepoVolumeMounter{
gitRepoVolume: &gitRepoVolume{
......
......@@ -38,7 +38,7 @@ func newTestHost(t *testing.T) (string, volume.VolumeHost) {
if err != nil {
t.Fatalf("can't make a temp rootdir: %v", err)
}
return tempDir, volumetest.NewFakeVolumeHost(tempDir, nil, empty_dir.ProbeVolumePlugins())
return tempDir, volumetest.NewFakeVolumeHost(tempDir, nil, empty_dir.ProbeVolumePlugins(), "" /* rootContext */)
}
func TestCanSupport(t *testing.T) {
......@@ -50,8 +50,8 @@ func TestCanSupport(t *testing.T) {
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/git-repo" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/git-repo" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -230,7 +230,7 @@ func doTestPlugin(scenario struct {
return allErrs
}
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
mounter, err := plug.NewMounter(volume.NewSpecFromVolume(scenario.vol), pod, volume.VolumeOptions{RootContext: ""})
mounter, err := plug.NewMounter(volume.NewSpecFromVolume(scenario.vol), pod, volume.VolumeOptions{})
if err != nil {
allErrs = append(allErrs,
......
......@@ -57,9 +57,9 @@ func (plugin *glusterfsPlugin) GetPluginName() string {
}
func (plugin *glusterfsPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a Gluster volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return fmt.Sprintf(
......@@ -75,7 +75,10 @@ func (plugin *glusterfsPlugin) CanSupport(spec *volume.Spec) bool {
}
return true
}
func (plugin *glusterfsPlugin) RequiresRemount() bool {
return false
}
func (plugin *glusterfsPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
......@@ -288,17 +291,14 @@ func (b *glusterfsMounter) setUpAtInternal(dir string) error {
return fmt.Errorf("glusterfs: mount failed: %v", errs)
}
func getVolumeSource(spec *volume.Spec) (*api.GlusterfsVolumeSource, bool) {
var readOnly bool
var volumeSource *api.GlusterfsVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.GlusterfsVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.Glusterfs != nil {
volumeSource = spec.Volume.Glusterfs
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.Glusterfs
readOnly = spec.ReadOnly
return spec.Volume.Glusterfs, spec.Volume.Glusterfs.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.Glusterfs != nil {
return spec.PersistentVolume.Spec.Glusterfs, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a Gluster volume type")
}
......@@ -39,13 +39,13 @@ func TestCanSupport(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/glusterfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/glusterfs" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/glusterfs" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{}}}}) {
t.Errorf("Expected false")
......@@ -63,7 +63,7 @@ func TestGetAccessModes(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/glusterfs")
if err != nil {
......@@ -91,7 +91,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/glusterfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
......@@ -223,7 +223,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim, ep)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(glusterfsPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -83,9 +83,9 @@ func (plugin *hostPathPlugin) GetPluginName() string {
}
func (plugin *hostPathPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference an HostPath volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.Path, nil
......@@ -96,6 +96,10 @@ func (plugin *hostPathPlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.HostPath != nil)
}
func (plugin *hostPathPlugin) RequiresRemount() bool {
return false
}
func (plugin *hostPathPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -103,19 +107,14 @@ func (plugin *hostPathPlugin) GetAccessModes() []api.PersistentVolumeAccessMode
}
func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
if spec.Volume != nil && spec.Volume.HostPath != nil {
path := spec.Volume.HostPath.Path
return &hostPathMounter{
hostPath: &hostPath{path: path},
readOnly: false,
}, nil
} else {
path := spec.PersistentVolume.Spec.HostPath.Path
return &hostPathMounter{
hostPath: &hostPath{path: path},
readOnly: spec.ReadOnly,
}, nil
hostPathVolumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
return &hostPathMounter{
hostPath: &hostPath{path: hostPathVolumeSource.Path},
readOnly: readOnly,
}, nil
}
func (plugin *hostPathPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
......@@ -313,17 +312,14 @@ func (r *hostPathDeleter) Delete() error {
return os.RemoveAll(r.GetPath())
}
func getVolumeSource(spec *volume.Spec) (*api.HostPathVolumeSource, bool) {
var readOnly bool
var volumeSource *api.HostPathVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.HostPathVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.HostPath != nil {
volumeSource = spec.Volume.HostPath
readOnly = spec.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.HostPath
readOnly = spec.ReadOnly
return spec.Volume.HostPath, spec.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.HostPath != nil {
return spec.PersistentVolume.Spec.HostPath, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference an HostPath volume type")
}
......@@ -34,14 +34,14 @@ import (
func TestCanSupport(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("fake", nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/host-path" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/host-path" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -56,7 +56,7 @@ func TestCanSupport(t *testing.T) {
func TestGetAccessModes(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/host-path")
if err != nil {
......@@ -69,7 +69,7 @@ func TestGetAccessModes(t *testing.T) {
func TestRecycler(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
pluginHost := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)
pluginHost := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */)
plugMgr.InitPlugins([]volume.VolumePlugin{&hostPathPlugin{nil, volumetest.NewFakeRecycler, nil, nil, volume.VolumeConfig{}}}, pluginHost)
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/foo"}}}}}
......@@ -99,7 +99,7 @@ func TestDeleter(t *testing.T) {
}
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */))
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: tempPath}}}}}
plug, err := plugMgr.FindDeletablePluginBySpec(spec)
......@@ -133,7 +133,7 @@ func TestDeleterTempDir(t *testing.T) {
for name, test := range tests {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */))
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: test.path}}}}}
plug, _ := plugMgr.FindDeletablePluginBySpec(spec)
deleter, _ := plug.NewDeleter(spec)
......@@ -153,7 +153,7 @@ func TestProvisioner(t *testing.T) {
err := os.MkdirAll(tempPath, 0750)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */))
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: tempPath}}}}}
plug, err := plugMgr.FindCreatablePluginBySpec(spec)
if err != nil {
......@@ -187,7 +187,7 @@ func TestProvisioner(t *testing.T) {
func TestPlugin(t *testing.T) {
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("fake", nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("fake", nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
if err != nil {
......@@ -259,7 +259,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(hostPathPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -58,9 +58,9 @@ func (plugin *iscsiPlugin) GetPluginName() string {
}
func (plugin *iscsiPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a ISCSI volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return fmt.Sprintf(
......@@ -78,6 +78,10 @@ func (plugin *iscsiPlugin) CanSupport(spec *volume.Spec) bool {
return true
}
func (plugin *iscsiPlugin) RequiresRemount() bool {
return false
}
func (plugin *iscsiPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -93,14 +97,9 @@ func (plugin *iscsiPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.
func (plugin *iscsiPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface) (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
var readOnly bool
var iscsi *api.ISCSIVolumeSource
if spec.Volume != nil && spec.Volume.ISCSI != nil {
iscsi = spec.Volume.ISCSI
readOnly = iscsi.ReadOnly
} else {
iscsi = spec.PersistentVolume.Spec.ISCSI
readOnly = spec.ReadOnly
iscsi, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
lun := strconv.Itoa(int(iscsi.Lun))
......@@ -221,17 +220,13 @@ func portalMounter(portal string) string {
return portal
}
func getVolumeSource(spec *volume.Spec) (*api.ISCSIVolumeSource, bool) {
var readOnly bool
var volumeSource *api.ISCSIVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.ISCSIVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.ISCSI != nil {
volumeSource = spec.Volume.ISCSI
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.ISCSI
readOnly = spec.ReadOnly
return spec.Volume.ISCSI, spec.Volume.ISCSI.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.ISCSI != nil {
return spec.PersistentVolume.Spec.ISCSI, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a ISCSI volume type")
}
......@@ -38,14 +38,14 @@ func TestCanSupport(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/iscsi")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/iscsi" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/iscsi" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{}}}) {
t.Errorf("Expected false")
......@@ -60,7 +60,7 @@ func TestGetAccessModes(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/iscsi")
if err != nil {
......@@ -131,7 +131,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/iscsi")
if err != nil {
......@@ -274,7 +274,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(iscsiPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -68,9 +68,9 @@ func (plugin *nfsPlugin) GetPluginName() string {
}
func (plugin *nfsPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a NFS volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return fmt.Sprintf(
......@@ -84,6 +84,10 @@ func (plugin *nfsPlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.NFS != nil)
}
func (plugin *nfsPlugin) RequiresRemount() bool {
return false
}
func (plugin *nfsPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -97,15 +101,11 @@ func (plugin *nfsPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.Vo
}
func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface) (volume.Mounter, error) {
var source *api.NFSVolumeSource
var readOnly bool
if spec.Volume != nil && spec.Volume.NFS != nil {
source = spec.Volume.NFS
readOnly = spec.Volume.NFS.ReadOnly
} else {
source = spec.PersistentVolume.Spec.NFS
readOnly = spec.ReadOnly
source, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
return &nfsMounter{
nfs: &nfs{
volName: spec.Name(),
......@@ -309,17 +309,13 @@ func (r *nfsRecycler) Recycle() error {
return volume.RecycleVolumeByWatchingPodUntilCompletion(r.pvName, pod, r.host.GetKubeClient())
}
func getVolumeSource(spec *volume.Spec) (*api.NFSVolumeSource, bool) {
var readOnly bool
var volumeSource *api.NFSVolumeSource
func getVolumeSource(spec *volume.Spec) (*api.NFSVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.NFS != nil {
volumeSource = spec.Volume.NFS
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.NFS
readOnly = spec.ReadOnly
return spec.Volume.NFS, spec.Volume.NFS.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.NFS != nil {
return spec.PersistentVolume.Spec.NFS, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a NFS volume type")
}
......@@ -38,13 +38,13 @@ func TestCanSupport(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/nfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/nfs" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/nfs" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{NFS: &api.NFSVolumeSource{}}}}) {
t.Errorf("Expected true")
......@@ -65,7 +65,7 @@ func TestGetAccessModes(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/nfs")
if err != nil {
......@@ -84,7 +84,7 @@ func TestRecycler(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins([]volume.VolumePlugin{&nfsPlugin{nil, newMockRecycler, volume.VolumeConfig{}}}, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins([]volume.VolumePlugin{&nfsPlugin{nil, newMockRecycler, volume.VolumeConfig{}}}, volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{NFS: &api.NFSVolumeSource{Path: "/foo"}}}}}
plug, err := plugMgr.FindRecyclablePluginBySpec(spec)
......@@ -141,7 +141,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/nfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
......@@ -269,7 +269,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(nfsPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -18,6 +18,7 @@ package volume
import (
"fmt"
"net"
"strings"
"sync"
......@@ -35,12 +36,6 @@ import (
// VolumeOptions contains option information about a volume.
type VolumeOptions struct {
// The rootcontext to use when performing mounts for a volume. This is a
// temporary measure in order to set the rootContext of tmpfs mounts
// correctly. it will be replaced and expanded on by future
// SecurityContext work.
RootContext string
// The attributes below are required by volume.Provisioner
// TODO: refactor all of this out of volumes when an admin can configure
// many kinds of provisioners.
......@@ -86,6 +81,11 @@ type VolumePlugin interface {
// const.
CanSupport(spec *Spec) bool
// RequiresRemount returns true if this plugin requires mount calls to be
// reexecuted. Atomically updating volumes, like Downward API, depend on
// this to update the contents of the volume.
RequiresRemount() bool
// NewMounter creates a new volume.Mounter from an API specification.
// Ownership of the spec pointer in *not* transferred.
// - spec: The api.Volume spec
......@@ -196,6 +196,15 @@ type VolumeHost interface {
// Returns the hostname of the host kubelet is running on
GetHostName() string
// Returns host IP or nil in the case of error.
GetHostIP() (net.IP, error)
// Returns the rootcontext to use when performing mounts for a volume.
// This is a temporary measure in order to set the rootContext of tmpfs
// mounts correctly. It will be replaced and expanded on by future
// SecurityContext work.
GetRootContext() string
}
// VolumePluginMgr tracks registered plugins.
......
......@@ -55,9 +55,9 @@ func (plugin *rbdPlugin) GetPluginName() string {
}
func (plugin *rbdPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a RBD volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return fmt.Sprintf(
......@@ -74,6 +74,10 @@ func (plugin *rbdPlugin) CanSupport(spec *volume.Spec) bool {
return true
}
func (plugin *rbdPlugin) RequiresRemount() bool {
return false
}
func (plugin *rbdPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
......@@ -234,17 +238,14 @@ func (plugin *rbdPlugin) execCommand(command string, args []string) ([]byte, err
return cmd.CombinedOutput()
}
func getVolumeSource(spec *volume.Spec) (*api.RBDVolumeSource, bool) {
var readOnly bool
var volumeSource *api.RBDVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.RBDVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.RBD != nil {
volumeSource = spec.Volume.RBD
readOnly = volumeSource.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.RBD
readOnly = spec.ReadOnly
return spec.Volume.RBD, spec.Volume.RBD.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.RBD != nil {
return spec.PersistentVolume.Spec.RBD, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a RBD volume type")
}
......@@ -38,14 +38,14 @@ func TestCanSupport(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/rbd")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/rbd" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/rbd" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{}}}) {
t.Errorf("Expected false")
......@@ -95,7 +95,7 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/rbd")
if err != nil {
......@@ -226,7 +226,7 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil, "" /* rootContext */))
plug, _ := plugMgr.FindPluginByName(rbdPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
......
......@@ -75,6 +75,10 @@ func (plugin *secretPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.Secret != nil
}
func (plugin *secretPlugin) RequiresRemount() bool {
return true
}
func (plugin *secretPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &secretVolumeMounter{
secretVolume: &secretVolume{
......
......@@ -187,7 +187,7 @@ func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.Vo
t.Fatalf("can't make a temp rootdir: %v", err)
}
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins())
return tempDir, volumetest.NewFakeVolumeHost(tempDir, clientset, empty_dir.ProbeVolumePlugins(), "" /* rootContext */)
}
func TestCanSupport(t *testing.T) {
......@@ -199,8 +199,8 @@ func TestCanSupport(t *testing.T) {
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plugin.Name() != secretPluginName {
t.Errorf("Wrong name: %s", plugin.Name())
if plugin.GetPluginName() != secretPluginName {
t.Errorf("Wrong name: %s", plugin.GetPluginName())
}
if !plugin.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: ""}}}}) {
t.Errorf("Expected true")
......
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package attachdetach contains consts and helper methods used by various
// attach/detach components in controller and kubelet
package attachdetach
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/volume"
)
const (
// ControllerManagedAnnotation is the key of the annotation on Node objects
// that indicates attach/detach operations for the node should be managed
// by the attach/detach controller
ControllerManagedAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
)
// GetUniqueDeviceName returns a unique name representing the device with the
// spcified deviceName of the pluginName volume type.
// The returned name can be used to uniquely reference the device. For example,
// to prevent operations (attach/detach) from being triggered on the same volume
func GetUniqueDeviceName(
pluginName, deviceName string) api.UniqueDeviceName {
return api.UniqueDeviceName(fmt.Sprintf("%s/%s", pluginName, deviceName))
}
// GetUniqueDeviceNameFromSpec uses the given AttachableVolumePlugin to
// generate a unique name representing the device defined in the specified
// volume spec.
// This returned name can be used to uniquely reference the device. For example,
// to prevent operations (attach/detach) from being triggered on the same volume.
// If the given plugin does not support the volume spec, this returns an error.
func GetUniqueDeviceNameFromSpec(
attachableVolumePlugin volume.AttachableVolumePlugin,
volumeSpec *volume.Spec) (api.UniqueDeviceName, error) {
if attachableVolumePlugin == nil {
return "", fmt.Errorf(
"attachablePlugin should not be nil. volumeSpec.Name=%q",
volumeSpec.Name())
}
deviceName, err := attachableVolumePlugin.GetDeviceName(volumeSpec)
if err != nil || deviceName == "" {
return "", fmt.Errorf(
"failed to GetDeviceName from AttachablePlugin for volumeSpec %q err=%v",
volumeSpec.Name(),
err)
}
return GetUniqueDeviceName(
attachableVolumePlugin.Name(),
deviceName),
nil
}
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package types defines types used only by volume componenets
package types
import "k8s.io/kubernetes/pkg/types"
// UniquePodName defines the type to key pods off of
type UniquePodName types.UID
......@@ -23,23 +23,32 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util/types"
)
const (
// ControllerManagedAnnotation is the key of the annotation on Node objects
// that indicates attach/detach operations for the node should be managed
// by the attach/detach controller
ControllerManagedAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
// ControllerManagedAttachAnnotation is the key of the annotation on Node
// objects that indicates attach/detach operations for the node should be
// managed by the attach/detach controller
ControllerManagedAttachAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
// VolumeGidAnnotationKey is the of the annotation on the PersistentVolume
// object that specifies a supplemental GID.
VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
)
// GetUniquePodName returns a unique identifier to reference a pod by
func GetUniquePodName(pod *api.Pod) types.UniquePodName {
return types.UniquePodName(pod.UID)
}
// GetUniqueVolumeName returns a unique name representing the volume/plugin.
// Caller should ensure that volumeName is a name/ID uniquely identifying the
// actual backing device, directory, path, etc. for a particular volume.
// The returned name can be used to uniquely reference the volume, for example,
// to prevent operations (attach/detach or mount/unmount) from being triggered
// on the same volume.
func GetUniqueVolumeName(
pluginName string, volumeName string) api.UniqueVolumeName {
func GetUniqueVolumeName(pluginName, volumeName string) api.UniqueVolumeName {
return api.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName))
}
......
......@@ -24,7 +24,6 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/util/mount"
)
// Volume represents a directory used by pods or hosts on a node. All method
......@@ -147,11 +146,11 @@ type Attacher interface {
// GetDeviceMountPath returns a path where the device should
// be mounted after it is attached. This is a global mount
// point which should be bind mounted for individual volumes.
GetDeviceMountPath(spec *Spec) string
GetDeviceMountPath(spec *Spec) (string, error)
// MountDevice mounts the disk to a global path which
// individual pods can then bind mount
MountDevice(spec *Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error
MountDevice(spec *Spec, devicePath string, deviceMountPath string) error
}
// Detacher can detach a volume from a node.
......@@ -167,7 +166,7 @@ type Detacher interface {
// UnmountDevice unmounts the global mount of the disk. This
// should only be called once all bind mounts have been
// unmounted.
UnmountDevice(deviceMountPath string, mounter mount.Interface) error
UnmountDevice(deviceMountPath string) error
}
func RenameDirectory(oldPath, newName string) (string, error) {
......
......@@ -62,9 +62,9 @@ func (plugin *vsphereVolumePlugin) GetPluginName() string {
}
func (plugin *vsphereVolumePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a VSphere volume type")
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return volumeSource.VolumePath, nil
......@@ -75,6 +75,10 @@ func (plugin *vsphereVolumePlugin) CanSupport(spec *volume.Spec) bool {
(spec.Volume != nil && spec.Volume.VsphereVolume != nil)
}
func (plugin *vsphereVolumePlugin) RequiresRemount() bool {
return false
}
func (plugin *vsphereVolumePlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
return plugin.newMounterInternal(spec, pod.UID, &VsphereDiskUtil{}, plugin.host.GetMounter())
}
......@@ -84,11 +88,9 @@ func (plugin *vsphereVolumePlugin) NewUnmounter(volName string, podUID types.UID
}
func (plugin *vsphereVolumePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager vdManager, mounter mount.Interface) (volume.Mounter, error) {
var vvol *api.VsphereVirtualDiskVolumeSource
if spec.Volume != nil && spec.Volume.VsphereVolume != nil {
vvol = spec.Volume.VsphereVolume
} else {
vvol = spec.PersistentVolume.Spec.VsphereVolume
vvol, _, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
volPath := vvol.VolumePath
......@@ -427,17 +429,14 @@ func (v *vsphereVolumeProvisioner) Provision() (*api.PersistentVolume, error) {
return pv, nil
}
func getVolumeSource(spec *volume.Spec) (*api.VsphereVirtualDiskVolumeSource, bool) {
var readOnly bool
var volumeSource *api.VsphereVirtualDiskVolumeSource
func getVolumeSource(
spec *volume.Spec) (*api.VsphereVirtualDiskVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.VsphereVolume != nil {
volumeSource = spec.Volume.VsphereVolume
readOnly = spec.ReadOnly
} else {
volumeSource = spec.PersistentVolume.Spec.VsphereVolume
readOnly = spec.ReadOnly
return spec.Volume.VsphereVolume, spec.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.VsphereVolume != nil {
return spec.PersistentVolume.Spec.VsphereVolume, spec.ReadOnly, nil
}
return volumeSource, readOnly
return nil, false, fmt.Errorf("Spec does not reference a VSphere volume type")
}
......@@ -38,14 +38,14 @@ func TestCanSupport(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/vsphere-volume")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/vsphere-volume" {
t.Errorf("Wrong name: %s", plug.Name())
if plug.GetPluginName() != "kubernetes.io/vsphere-volume" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{VsphereVolume: &api.VsphereVirtualDiskVolumeSource{}}}}) {
......@@ -118,7 +118,7 @@ func TestPlugin(t *testing.T) {
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
plug, err := plugMgr.FindPluginByName("kubernetes.io/vsphere-volume")
if err != nil {
......
......@@ -44,13 +44,16 @@ import (
const (
gcePDDetachTimeout = 10 * time.Minute
gcePDDetachPollTime = 10 * time.Second
nodeStatusTimeout = 1 * time.Minute
nodeStatusPollTime = 1 * time.Second
)
var _ = framework.KubeDescribe("Pod Disks", func() {
var (
podClient client.PodInterface
host0Name string
host1Name string
podClient client.PodInterface
nodeClient client.NodeInterface
host0Name string
host1Name string
)
f := framework.NewDefaultFramework("pod-disks")
......@@ -58,6 +61,7 @@ var _ = framework.KubeDescribe("Pod Disks", func() {
framework.SkipUnlessNodeCountIsAtLeast(2)
podClient = f.Client.Pods(f.Namespace.Name)
nodeClient = f.Client.Nodes()
nodes := framework.GetReadySchedulableNodesOrDie(f.Client)
Expect(len(nodes.Items)).To(BeNumerically(">=", 2), "Requires at least 2 nodes")
......@@ -100,6 +104,9 @@ var _ = framework.KubeDescribe("Pod Disks", func() {
framework.ExpectNoError(f.WriteFileViaContainer(host0Pod.Name, containerName, testFile, testFileContents))
framework.Logf("Wrote value: %v", testFileContents)
// Verify that disk shows up for in node 1's VolumeInUse list
framework.ExpectNoError(waitForPDInVolumesInUse(nodeClient, diskName, host0Name, nodeStatusTimeout, true /* shouldExist */))
By("deleting host0Pod")
framework.ExpectNoError(podClient.Delete(host0Pod.Name, api.NewDeleteOptions(0)), "Failed to delete host0Pod")
......@@ -115,6 +122,9 @@ var _ = framework.KubeDescribe("Pod Disks", func() {
Expect(strings.TrimSpace(v)).To(Equal(strings.TrimSpace(testFileContents)))
// Verify that disk is removed from node 1's VolumeInUse list
framework.ExpectNoError(waitForPDInVolumesInUse(nodeClient, diskName, host0Name, nodeStatusTimeout, false /* shouldExist */))
By("deleting host1Pod")
framework.ExpectNoError(podClient.Delete(host1Pod.Name, api.NewDeleteOptions(0)), "Failed to delete host1Pod")
......@@ -545,3 +555,52 @@ func detachAndDeletePDs(diskName string, hosts []string) {
By(fmt.Sprintf("Deleting PD %q", diskName))
deletePDWithRetry(diskName)
}
func waitForPDInVolumesInUse(
nodeClient client.NodeInterface,
diskName, nodeName string,
timeout time.Duration,
shouldExist bool) error {
logStr := "to contain"
if !shouldExist {
logStr = "to NOT contain"
}
framework.Logf(
"Waiting for node %s's VolumesInUse Status %s PD %q",
nodeName, logStr, diskName)
for start := time.Now(); time.Since(start) < timeout; time.Sleep(nodeStatusPollTime) {
nodeObj, err := nodeClient.Get(nodeName)
if err != nil || nodeObj == nil {
framework.Logf(
"Failed to fetch node object %q from API server. err=%v",
nodeName, err)
continue
}
exists := false
for _, volumeInUse := range nodeObj.Status.VolumesInUse {
volumeInUseStr := string(volumeInUse)
if strings.Contains(volumeInUseStr, diskName) {
if shouldExist {
framework.Logf(
"Found PD %q in node %q's VolumesInUse Status: %q",
diskName, nodeName, volumeInUseStr)
return nil
}
exists = true
}
}
if !shouldExist && !exists {
framework.Logf(
"Verified PD %q does not exist in node %q's VolumesInUse Status.",
diskName, nodeName)
return nil
}
}
return fmt.Errorf(
"Timed out waiting for node %s VolumesInUse Status %s diskName %q",
nodeName, logStr, diskName)
}
......@@ -582,7 +582,7 @@ func createClients(t *testing.T, s *httptest.Server) (*clientset.Clientset, *per
binderClient := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000000, Burst: 1000000})
testClient := clientset.NewForConfigOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, QPS: 1000000, Burst: 1000000})
host := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)
host := volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */)
plugins := []volume.VolumePlugin{&volumetest.FakeVolumePlugin{
PluginName: "plugin-name",
Host: host,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment