Update dependencies
This commit is contained in:
parent
bf5616c65b
commit
d6d374b28d
13962 changed files with 48226 additions and 3618880 deletions
43
vendor/k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/BUILD
generated
vendored
43
vendor/k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/BUILD
generated
vendored
|
|
@ -1,43 +0,0 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["nestedpendingoperations.go"],
|
||||
deps = [
|
||||
"//pkg/util/goroutinemap/exponentialbackoff:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["nestedpendingoperations_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
2
vendor/k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/OWNERS
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/OWNERS
generated
vendored
|
|
@ -1,2 +0,0 @@
|
|||
approvers:
|
||||
- saad-ali
|
||||
318
vendor/k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go
generated
vendored
318
vendor/k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go
generated
vendored
|
|
@ -1,318 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package nestedpendingoperations is a modified implementation of
|
||||
pkg/util/goroutinemap. It implements a data structure for managing go routines
|
||||
by volume/pod name. It prevents the creation of new go routines if an existing
|
||||
go routine for the volume already exists. It also allows multiple operations to
|
||||
execute in parallel for the same volume as long as they are operating on
|
||||
different pods.
|
||||
*/
|
||||
package nestedpendingoperations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
k8sRuntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// EmptyUniquePodName is a UniquePodName for empty string.
|
||||
EmptyUniquePodName types.UniquePodName = types.UniquePodName("")
|
||||
|
||||
// EmptyUniqueVolumeName is a UniqueVolumeName for empty string
|
||||
EmptyUniqueVolumeName v1.UniqueVolumeName = v1.UniqueVolumeName("")
|
||||
)
|
||||
|
||||
// NestedPendingOperations defines the supported set of operations.
|
||||
type NestedPendingOperations interface {
|
||||
// Run adds the concatenation of volumeName and podName to the list of
|
||||
// running operations and spawns a new go routine to execute operationFunc.
|
||||
// If an operation with the same volumeName and same or empty podName
|
||||
// exists, an AlreadyExists or ExponentialBackoff error is returned.
|
||||
// This enables multiple operations to execute in parallel for the same
|
||||
// volumeName as long as they have different podName.
|
||||
// Once the operation is complete, the go routine is terminated and the
|
||||
// concatenation of volumeName and podName is removed from the list of
|
||||
// executing operations allowing a new operation to be started with the
|
||||
// volumeName without error.
|
||||
Run(volumeName v1.UniqueVolumeName, podName types.UniquePodName, operationFunc func() error, operationCompleteFunc func(error)) error
|
||||
|
||||
// Wait blocks until all operations are completed. This is typically
|
||||
// necessary during tests - the test should wait until all operations finish
|
||||
// and evaluate results after that.
|
||||
Wait()
|
||||
|
||||
// IsOperationPending returns true if an operation for the given volumeName and podName is pending,
|
||||
// otherwise it returns false
|
||||
IsOperationPending(volumeName v1.UniqueVolumeName, podName types.UniquePodName) bool
|
||||
}
|
||||
|
||||
// NewNestedPendingOperations returns a new instance of NestedPendingOperations.
|
||||
func NewNestedPendingOperations(exponentialBackOffOnError bool) NestedPendingOperations {
|
||||
g := &nestedPendingOperations{
|
||||
operations: []operation{},
|
||||
exponentialBackOffOnError: exponentialBackOffOnError,
|
||||
}
|
||||
g.cond = sync.NewCond(&g.lock)
|
||||
return g
|
||||
}
|
||||
|
||||
type nestedPendingOperations struct {
|
||||
operations []operation
|
||||
exponentialBackOffOnError bool
|
||||
cond *sync.Cond
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type operation struct {
|
||||
volumeName v1.UniqueVolumeName
|
||||
podName types.UniquePodName
|
||||
operationPending bool
|
||||
expBackoff exponentialbackoff.ExponentialBackoff
|
||||
}
|
||||
|
||||
func (grm *nestedPendingOperations) Run(
|
||||
volumeName v1.UniqueVolumeName,
|
||||
podName types.UniquePodName,
|
||||
operationFunc func() error,
|
||||
operationCompleteFunc func(error)) error {
|
||||
grm.lock.Lock()
|
||||
defer grm.lock.Unlock()
|
||||
opExists, previousOpIndex := grm.isOperationExists(volumeName, podName)
|
||||
if opExists {
|
||||
previousOp := grm.operations[previousOpIndex]
|
||||
// Operation already exists
|
||||
if previousOp.operationPending {
|
||||
// Operation is pending
|
||||
operationName := getOperationName(volumeName, podName)
|
||||
return NewAlreadyExistsError(operationName)
|
||||
}
|
||||
|
||||
operationName := getOperationName(volumeName, podName)
|
||||
if err := previousOp.expBackoff.SafeToRetry(operationName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update existing operation to mark as pending.
|
||||
grm.operations[previousOpIndex].operationPending = true
|
||||
grm.operations[previousOpIndex].volumeName = volumeName
|
||||
grm.operations[previousOpIndex].podName = podName
|
||||
} else {
|
||||
// Create a new operation
|
||||
grm.operations = append(grm.operations,
|
||||
operation{
|
||||
operationPending: true,
|
||||
volumeName: volumeName,
|
||||
podName: podName,
|
||||
expBackoff: exponentialbackoff.ExponentialBackoff{},
|
||||
})
|
||||
}
|
||||
|
||||
go func() (err error) {
|
||||
// Handle unhandled panics (very unlikely)
|
||||
defer k8sRuntime.HandleCrash()
|
||||
// Handle completion of and error, if any, from operationFunc()
|
||||
defer grm.operationComplete(volumeName, podName, &err)
|
||||
defer operationCompleteFunc(err)
|
||||
// Handle panic, if any, from operationFunc()
|
||||
defer k8sRuntime.RecoverFromPanic(&err)
|
||||
return operationFunc()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grm *nestedPendingOperations) IsOperationPending(
|
||||
volumeName v1.UniqueVolumeName,
|
||||
podName types.UniquePodName) bool {
|
||||
|
||||
grm.lock.RLock()
|
||||
defer grm.lock.RUnlock()
|
||||
|
||||
exist, previousOpIndex := grm.isOperationExists(volumeName, podName)
|
||||
if exist && grm.operations[previousOpIndex].operationPending {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// This is an internal function and caller should acquire and release the lock
|
||||
func (grm *nestedPendingOperations) isOperationExists(
|
||||
volumeName v1.UniqueVolumeName,
|
||||
podName types.UniquePodName) (bool, int) {
|
||||
|
||||
// If volumeName is empty, operation can be executed concurrently
|
||||
if volumeName == EmptyUniqueVolumeName {
|
||||
return false, -1
|
||||
}
|
||||
|
||||
for previousOpIndex, previousOp := range grm.operations {
|
||||
if previousOp.volumeName != volumeName {
|
||||
// No match, keep searching
|
||||
continue
|
||||
}
|
||||
|
||||
if previousOp.podName != EmptyUniquePodName &&
|
||||
podName != EmptyUniquePodName &&
|
||||
previousOp.podName != podName {
|
||||
// No match, keep searching
|
||||
continue
|
||||
}
|
||||
|
||||
// Match
|
||||
return true, previousOpIndex
|
||||
}
|
||||
return false, -1
|
||||
}
|
||||
|
||||
func (grm *nestedPendingOperations) getOperation(
|
||||
volumeName v1.UniqueVolumeName,
|
||||
podName types.UniquePodName) (uint, error) {
|
||||
// Assumes lock has been acquired by caller.
|
||||
|
||||
for i, op := range grm.operations {
|
||||
if op.volumeName == volumeName &&
|
||||
op.podName == podName {
|
||||
return uint(i), nil
|
||||
}
|
||||
}
|
||||
|
||||
logOperationName := getOperationName(volumeName, podName)
|
||||
return 0, fmt.Errorf("Operation %q not found", logOperationName)
|
||||
}
|
||||
|
||||
func (grm *nestedPendingOperations) deleteOperation(
|
||||
// Assumes lock has been acquired by caller.
|
||||
volumeName v1.UniqueVolumeName,
|
||||
podName types.UniquePodName) {
|
||||
|
||||
opIndex := -1
|
||||
for i, op := range grm.operations {
|
||||
if op.volumeName == volumeName &&
|
||||
op.podName == podName {
|
||||
opIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Delete index without preserving order
|
||||
grm.operations[opIndex] = grm.operations[len(grm.operations)-1]
|
||||
grm.operations = grm.operations[:len(grm.operations)-1]
|
||||
}
|
||||
|
||||
func (grm *nestedPendingOperations) operationComplete(
|
||||
volumeName v1.UniqueVolumeName, podName types.UniquePodName, err *error) {
|
||||
// Defer operations are executed in Last-In is First-Out order. In this case
|
||||
// the lock is acquired first when operationCompletes begins, and is
|
||||
// released when the method finishes, after the lock is released cond is
|
||||
// signaled to wake waiting goroutine.
|
||||
defer grm.cond.Signal()
|
||||
grm.lock.Lock()
|
||||
defer grm.lock.Unlock()
|
||||
|
||||
if *err == nil || !grm.exponentialBackOffOnError {
|
||||
// Operation completed without error, or exponentialBackOffOnError disabled
|
||||
grm.deleteOperation(volumeName, podName)
|
||||
if *err != nil {
|
||||
// Log error
|
||||
logOperationName := getOperationName(volumeName, podName)
|
||||
glog.Errorf("operation %s failed with: %v",
|
||||
logOperationName,
|
||||
*err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Operation completed with error and exponentialBackOffOnError Enabled
|
||||
existingOpIndex, getOpErr := grm.getOperation(volumeName, podName)
|
||||
if getOpErr != nil {
|
||||
// Failed to find existing operation
|
||||
logOperationName := getOperationName(volumeName, podName)
|
||||
glog.Errorf("Operation %s completed. error: %v. exponentialBackOffOnError is enabled, but failed to get operation to update.",
|
||||
logOperationName,
|
||||
*err)
|
||||
return
|
||||
}
|
||||
|
||||
grm.operations[existingOpIndex].expBackoff.Update(err)
|
||||
grm.operations[existingOpIndex].operationPending = false
|
||||
|
||||
// Log error
|
||||
operationName :=
|
||||
getOperationName(volumeName, podName)
|
||||
glog.Errorf("%v", grm.operations[existingOpIndex].expBackoff.
|
||||
GenerateNoRetriesPermittedMsg(operationName))
|
||||
}
|
||||
|
||||
func (grm *nestedPendingOperations) Wait() {
|
||||
grm.lock.Lock()
|
||||
defer grm.lock.Unlock()
|
||||
|
||||
for len(grm.operations) > 0 {
|
||||
grm.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func getOperationName(
|
||||
volumeName v1.UniqueVolumeName, podName types.UniquePodName) string {
|
||||
podNameStr := ""
|
||||
if podName != EmptyUniquePodName {
|
||||
podNameStr = fmt.Sprintf(" (%q)", podName)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%q%s",
|
||||
volumeName,
|
||||
podNameStr)
|
||||
}
|
||||
|
||||
// NewAlreadyExistsError returns a new instance of AlreadyExists error.
|
||||
func NewAlreadyExistsError(operationName string) error {
|
||||
return alreadyExistsError{operationName}
|
||||
}
|
||||
|
||||
// IsAlreadyExists returns true if an error returned from
|
||||
// NestedPendingOperations indicates a new operation can not be started because
|
||||
// an operation with the same operation name is already executing.
|
||||
func IsAlreadyExists(err error) bool {
|
||||
switch err.(type) {
|
||||
case alreadyExistsError:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// alreadyExistsError is the error returned by NestedPendingOperations when a
|
||||
// new operation can not be started because an operation with the same operation
|
||||
// name is already executing.
|
||||
type alreadyExistsError struct {
|
||||
operationName string
|
||||
}
|
||||
|
||||
var _ error = alreadyExistsError{}
|
||||
|
||||
func (err alreadyExistsError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Failed to create operation with name %q. An operation with that name is already executing.",
|
||||
err.operationName)
|
||||
}
|
||||
|
|
@ -1,570 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nestedpendingoperations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// testTimeout is a timeout of goroutines to finish. This _should_ be just a
|
||||
// "context switch" and it should take several ms, however, Clayton says "We
|
||||
// have had flakes due to tests that assumed that 15s is long enough to sleep")
|
||||
testTimeout time.Duration = 1 * time.Minute
|
||||
|
||||
// initialOperationWaitTimeShort is the initial amount of time the test will
|
||||
// wait for an operation to complete (each successive failure results in
|
||||
// exponential backoff).
|
||||
initialOperationWaitTimeShort time.Duration = 20 * time.Millisecond
|
||||
|
||||
// initialOperationWaitTimeLong is the initial amount of time the test will
|
||||
// wait for an operation to complete (each successive failure results in
|
||||
// exponential backoff).
|
||||
initialOperationWaitTimeLong time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_SingleOp(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation := func() error { return nil }
|
||||
|
||||
// Act
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_TwoOps(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volume1Name := v1.UniqueVolumeName("volume1-name")
|
||||
volume2Name := v1.UniqueVolumeName("volume2-name")
|
||||
operation := func() error { return nil }
|
||||
|
||||
// Act
|
||||
err1 := grm.Run(volume1Name, "" /* operationSubName */, operation, func(error) {})
|
||||
err2 := grm.Run(volume2Name, "" /* operationSubName */, operation, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", volume1Name, err1)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", volume2Name, err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_TwoSubOps(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1PodName := types.UniquePodName("operation1-podname")
|
||||
operation2PodName := types.UniquePodName("operation2-podname")
|
||||
operation := func() error { return nil }
|
||||
|
||||
// Act
|
||||
err1 := grm.Run(volumeName, operation1PodName, operation, func(error) {})
|
||||
err2 := grm.Run(volumeName, operation2PodName, operation, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", operation1PodName, err1)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
t.Fatalf("NewGoRoutine %q failed. Expected: <no error> Actual: <%v>", operation2PodName, err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_SingleOpWithExpBackoff(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation := func() error { return nil }
|
||||
|
||||
// Act
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletes(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateCallbackFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
<-operation1DoneCh // Force operation1 to complete
|
||||
|
||||
// Act
|
||||
err2 := retryWithExponentialBackOff(
|
||||
time.Duration(initialOperationWaitTimeShort),
|
||||
func() (bool, error) {
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
if err != nil {
|
||||
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
|
||||
// Assert
|
||||
if err2 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletesWithExpBackoff(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateCallbackFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
<-operation1DoneCh // Force operation1 to complete
|
||||
|
||||
// Act
|
||||
err2 := retryWithExponentialBackOff(
|
||||
time.Duration(initialOperationWaitTimeShort),
|
||||
func() (bool, error) {
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
if err != nil {
|
||||
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
|
||||
// Assert
|
||||
if err2 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanics(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1 := generatePanicFunc()
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := retryWithExponentialBackOff(
|
||||
time.Duration(initialOperationWaitTimeShort),
|
||||
func() (bool, error) {
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
if err != nil {
|
||||
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
|
||||
// Assert
|
||||
if err2 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanicsWithExpBackoff(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1 := generatePanicFunc()
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := retryWithExponentialBackOff(
|
||||
time.Duration(initialOperationWaitTimeLong), // Longer duration to accommodate for backoff
|
||||
func() (bool, error) {
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
if err != nil {
|
||||
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
|
||||
// Assert
|
||||
if err2 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletes(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err2 == nil {
|
||||
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName)
|
||||
}
|
||||
if !IsAlreadyExists(err2) {
|
||||
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Negative_SecondSubOpBeforeFirstCompletes2(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operationPodName := types.UniquePodName("operation-podname")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, operationPodName, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := grm.Run(volumeName, operationPodName, operation2, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err2 == nil {
|
||||
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName)
|
||||
}
|
||||
if !IsAlreadyExists(err2) {
|
||||
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Negative_SecondSubOpBeforeFirstCompletes(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operationPodName := types.UniquePodName("operation-podname")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, operationPodName, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := grm.Run(volumeName, operationPodName, operation2, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err2 == nil {
|
||||
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName)
|
||||
}
|
||||
if !IsAlreadyExists(err2) {
|
||||
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletesWithExpBackoff(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err2 == nil {
|
||||
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName)
|
||||
}
|
||||
if !IsAlreadyExists(err2) {
|
||||
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletes(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
operation3 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err2 == nil {
|
||||
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName)
|
||||
}
|
||||
if !IsAlreadyExists(err2) {
|
||||
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
|
||||
}
|
||||
|
||||
// Act
|
||||
operation1DoneCh <- true // Force operation1 to complete
|
||||
err3 := retryWithExponentialBackOff(
|
||||
time.Duration(initialOperationWaitTimeShort),
|
||||
func() (bool, error) {
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation3, func(error) {})
|
||||
if err != nil {
|
||||
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
|
||||
// Assert
|
||||
if err3 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err3)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletesWithExpBackoff(t *testing.T) {
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err1 := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err1 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err1)
|
||||
}
|
||||
operation2 := generateNoopFunc()
|
||||
operation3 := generateNoopFunc()
|
||||
|
||||
// Act
|
||||
err2 := grm.Run(volumeName, "" /* operationSubName */, operation2, func(error) {})
|
||||
|
||||
// Assert
|
||||
if err2 == nil {
|
||||
t.Fatalf("NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>", volumeName)
|
||||
}
|
||||
if !IsAlreadyExists(err2) {
|
||||
t.Fatalf("NewGoRoutine did not return alreadyExistsError, got: %v", err2)
|
||||
}
|
||||
|
||||
// Act
|
||||
operation1DoneCh <- true // Force operation1 to complete
|
||||
err3 := retryWithExponentialBackOff(
|
||||
time.Duration(initialOperationWaitTimeShort),
|
||||
func() (bool, error) {
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation3, func(error) {})
|
||||
if err != nil {
|
||||
t.Logf("Warning: NewGoRoutine failed with %v. Will retry.", err)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
)
|
||||
|
||||
// Assert
|
||||
if err3 != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err3)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_WaitEmpty(t *testing.T) {
|
||||
// Test than Wait() on empty GoRoutineMap always succeeds without blocking
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
|
||||
// Act
|
||||
waitDoneCh := make(chan interface{}, 1)
|
||||
go func() {
|
||||
grm.Wait()
|
||||
waitDoneCh <- true
|
||||
}()
|
||||
|
||||
// Assert
|
||||
err := waitChannelWithTimeout(waitDoneCh, testTimeout)
|
||||
if err != nil {
|
||||
t.Errorf("Error waiting for GoRoutineMap.Wait: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_WaitEmptyWithExpBackoff(t *testing.T) {
|
||||
// Test than Wait() on empty GoRoutineMap always succeeds without blocking
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
|
||||
// Act
|
||||
waitDoneCh := make(chan interface{}, 1)
|
||||
go func() {
|
||||
grm.Wait()
|
||||
waitDoneCh <- true
|
||||
}()
|
||||
|
||||
// Assert
|
||||
err := waitChannelWithTimeout(waitDoneCh, testTimeout)
|
||||
if err != nil {
|
||||
t.Errorf("Error waiting for GoRoutineMap.Wait: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_Wait(t *testing.T) {
|
||||
// Test that Wait() really blocks until the last operation succeeds
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(false /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
|
||||
}
|
||||
|
||||
// Act
|
||||
waitDoneCh := make(chan interface{}, 1)
|
||||
go func() {
|
||||
grm.Wait()
|
||||
waitDoneCh <- true
|
||||
}()
|
||||
|
||||
// Finish the operation
|
||||
operation1DoneCh <- true
|
||||
|
||||
// Assert
|
||||
err = waitChannelWithTimeout(waitDoneCh, testTimeout)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for GoRoutineMap.Wait: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_NewGoRoutineMap_Positive_WaitWithExpBackoff(t *testing.T) {
|
||||
// Test that Wait() really blocks until the last operation succeeds
|
||||
// Arrange
|
||||
grm := NewNestedPendingOperations(true /* exponentialBackOffOnError */)
|
||||
volumeName := v1.UniqueVolumeName("volume-name")
|
||||
operation1DoneCh := make(chan interface{}, 0 /* bufferSize */)
|
||||
operation1 := generateWaitFunc(operation1DoneCh)
|
||||
err := grm.Run(volumeName, "" /* operationSubName */, operation1, func(error) {})
|
||||
if err != nil {
|
||||
t.Fatalf("NewGoRoutine failed. Expected: <no error> Actual: <%v>", err)
|
||||
}
|
||||
|
||||
// Act
|
||||
waitDoneCh := make(chan interface{}, 1)
|
||||
go func() {
|
||||
grm.Wait()
|
||||
waitDoneCh <- true
|
||||
}()
|
||||
|
||||
// Finish the operation
|
||||
operation1DoneCh <- true
|
||||
|
||||
// Assert
|
||||
err = waitChannelWithTimeout(waitDoneCh, testTimeout)
|
||||
if err != nil {
|
||||
t.Fatalf("Error waiting for GoRoutineMap.Wait: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateCallbackFunc(done chan<- interface{}) func() error {
|
||||
return func() error {
|
||||
done <- true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func generateWaitFunc(done <-chan interface{}) func() error {
|
||||
return func() error {
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func generatePanicFunc() func() error {
|
||||
return func() error {
|
||||
panic("testing panic")
|
||||
}
|
||||
}
|
||||
|
||||
func generateNoopFunc() func() error {
|
||||
return func() error { return nil }
|
||||
}
|
||||
|
||||
func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.ConditionFunc) error {
|
||||
backoff := wait.Backoff{
|
||||
Duration: initialDuration,
|
||||
Factor: 3,
|
||||
Jitter: 0,
|
||||
Steps: 4,
|
||||
}
|
||||
return wait.ExponentialBackoff(backoff, fn)
|
||||
}
|
||||
|
||||
func waitChannelWithTimeout(ch <-chan interface{}, timeout time.Duration) error {
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
// Success!
|
||||
return nil
|
||||
case <-timer.C:
|
||||
return fmt.Errorf("timeout after %v", timeout)
|
||||
}
|
||||
}
|
||||
63
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/BUILD
generated
vendored
63
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/BUILD
generated
vendored
|
|
@ -1,63 +0,0 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"operation_executor.go",
|
||||
"operation_generator.go",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/controller/volume/expand/cache:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubelet/events:go_default_library",
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//pkg/volume/util/nestedpendingoperations:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["operation_executor_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/controller/volume/expand/cache:go_default_library",
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
2
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/OWNERS
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/OWNERS
generated
vendored
|
|
@ -1,2 +0,0 @@
|
|||
approvers:
|
||||
- saad-ali
|
||||
767
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/operation_executor.go
generated
vendored
767
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/operation_executor.go
generated
vendored
|
|
@ -1,767 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package operationexecutor implements interfaces that enable execution of
|
||||
// attach, detach, mount, and unmount operations with a
|
||||
// nestedpendingoperations so that more than one operation is never triggered
|
||||
// on the same volume for the same pod.
|
||||
package operationexecutor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations"
|
||||
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
)
|
||||
|
||||
// OperationExecutor defines a set of operations for attaching, detaching,
|
||||
// mounting, or unmounting a volume that are executed with a NewNestedPendingOperations which
|
||||
// prevents more than one operation from being triggered on the same volume.
|
||||
//
|
||||
// These operations should be idempotent (for example, AttachVolume should
|
||||
// still succeed if the volume is already attached to the node, etc.). However,
|
||||
// they depend on the volume plugins to implement this behavior.
|
||||
//
|
||||
// Once an operation completes successfully, the actualStateOfWorld is updated
|
||||
// to indicate the volume is attached/detached/mounted/unmounted.
|
||||
//
|
||||
// If the OperationExecutor fails to start the operation because, for example,
|
||||
// an operation with the same UniqueVolumeName is already pending, a non-nil
|
||||
// error is returned.
|
||||
//
|
||||
// Once the operation is started, since it is executed asynchronously,
|
||||
// errors are simply logged and the goroutine is terminated without updating
|
||||
// actualStateOfWorld (callers are responsible for retrying as needed).
|
||||
//
|
||||
// Some of these operations may result in calls to the API server; callers are
|
||||
// responsible for rate limiting on errors.
|
||||
type OperationExecutor interface {
|
||||
// AttachVolume attaches the volume to the node specified in volumeToAttach.
|
||||
// It then updates the actual state of the world to reflect that.
|
||||
AttachVolume(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error
|
||||
|
||||
// VerifyVolumesAreAttachedPerNode verifies the given list of volumes to see whether they are still attached to the node.
|
||||
// If any volume is not attached right now, it will update the actual state of the world to reflect that.
|
||||
// Note that this operation could be operated concurrently with other attach/detach operations.
|
||||
// In theory (but very unlikely in practise), race condition among these operations might mark volume as detached
|
||||
// even if it is attached. But reconciler can correct this in a short period of time.
|
||||
VerifyVolumesAreAttachedPerNode(AttachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error
|
||||
|
||||
// VerifyVolumesAreAttached verifies volumes being used in entire cluster and if they are still attached to the node
|
||||
// If any volume is not attached right now, it will update actual state of world to reflect that.
|
||||
VerifyVolumesAreAttached(volumesToVerify map[types.NodeName][]AttachedVolume, actualStateOfWorld ActualStateOfWorldAttacherUpdater)
|
||||
|
||||
// DetachVolume detaches the volume from the node specified in
|
||||
// volumeToDetach, and updates the actual state of the world to reflect
|
||||
// that. If verifySafeToDetach is set, a call is made to the fetch the node
|
||||
// object and it is used to verify that the volume does not exist in Node's
|
||||
// Status.VolumesInUse list (operation fails with error if it is).
|
||||
DetachVolume(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error
|
||||
|
||||
// MountVolume mounts the volume to the pod specified in volumeToMount.
|
||||
// Specifically it will:
|
||||
// * Wait for the device to finish attaching (for attachable volumes only).
|
||||
// * Mount device to global mount path (for attachable volumes only).
|
||||
// * Update actual state of world to reflect volume is globally mounted (for
|
||||
// attachable volumes only).
|
||||
// * Mount the volume to the pod specific path.
|
||||
// * Update actual state of world to reflect volume is mounted to the pod
|
||||
// path.
|
||||
// The parameter "isRemount" is informational and used to adjust logging
|
||||
// verbosity. An initial mount is more log-worthy than a remount, for
|
||||
// example.
|
||||
MountVolume(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorld ActualStateOfWorldMounterUpdater, isRemount bool) error
|
||||
|
||||
// UnmountVolume unmounts the volume from the pod specified in
|
||||
// volumeToUnmount and updates the actual state of the world to reflect that.
|
||||
UnmountVolume(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) error
|
||||
|
||||
// UnmountDevice unmounts the volumes global mount path from the device (for
|
||||
// attachable volumes only, freeing it for detach. It then updates the
|
||||
// actual state of the world to reflect that.
|
||||
UnmountDevice(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) error
|
||||
|
||||
// VerifyControllerAttachedVolume checks if the specified volume is present
|
||||
// in the specified nodes AttachedVolumes Status field. It uses kubeClient
|
||||
// to fetch the node object.
|
||||
// If the volume is found, the actual state of the world is updated to mark
|
||||
// the volume as attached.
|
||||
// If the volume does not implement the attacher interface, it is assumed to
|
||||
// be attached and the actual state of the world is updated accordingly.
|
||||
// If the volume is not found or there is an error (fetching the node
|
||||
// object, for example) then an error is returned which triggers exponential
|
||||
// back off on retries.
|
||||
VerifyControllerAttachedVolume(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) error
|
||||
|
||||
// IsOperationPending returns true if an operation for the given volumeName and podName is pending,
|
||||
// otherwise it returns false
|
||||
IsOperationPending(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool
|
||||
// Expand Volume will grow size available to PVC
|
||||
ExpandVolume(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) error
|
||||
}
|
||||
|
||||
// NewOperationExecutor returns a new instance of OperationExecutor.
|
||||
func NewOperationExecutor(
|
||||
operationGenerator OperationGenerator) OperationExecutor {
|
||||
|
||||
return &operationExecutor{
|
||||
pendingOperations: nestedpendingoperations.NewNestedPendingOperations(
|
||||
true /* exponentialBackOffOnError */),
|
||||
operationGenerator: operationGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
// ActualStateOfWorldMounterUpdater defines a set of operations updating the actual
|
||||
// state of the world cache after successful mount/unmount.
|
||||
type ActualStateOfWorldMounterUpdater interface {
|
||||
// Marks the specified volume as mounted to the specified pod
|
||||
MarkVolumeAsMounted(podName volumetypes.UniquePodName, podUID types.UID, volumeName v1.UniqueVolumeName, mounter volume.Mounter, outerVolumeSpecName string, volumeGidValue string) error
|
||||
|
||||
// Marks the specified volume as unmounted from the specified pod
|
||||
MarkVolumeAsUnmounted(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName) error
|
||||
|
||||
// Marks the specified volume as having been globally mounted.
|
||||
MarkDeviceAsMounted(volumeName v1.UniqueVolumeName) error
|
||||
|
||||
// Marks the specified volume as having its global mount unmounted.
|
||||
MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error
|
||||
}
|
||||
|
||||
// ActualStateOfWorldAttacherUpdater defines a set of operations updating the
|
||||
// actual state of the world cache after successful attach/detach/mount/unmount.
|
||||
type ActualStateOfWorldAttacherUpdater interface {
|
||||
// Marks the specified volume as attached to the specified node. If the
|
||||
// volume name is supplied, that volume name will be used. If not, the
|
||||
// volume name is computed using the result from querying the plugin.
|
||||
//
|
||||
// TODO: in the future, we should be able to remove the volumeName
|
||||
// argument to this method -- since it is used only for attachable
|
||||
// volumes. See issue 29695.
|
||||
MarkVolumeAsAttached(volumeName v1.UniqueVolumeName, volumeSpec *volume.Spec, nodeName types.NodeName, devicePath string) error
|
||||
|
||||
// Marks the specified volume as detached from the specified node
|
||||
MarkVolumeAsDetached(volumeName v1.UniqueVolumeName, nodeName types.NodeName)
|
||||
|
||||
// Marks desire to detach the specified volume (remove the volume from the node's
|
||||
// volumesToReportAsAttached list)
|
||||
RemoveVolumeFromReportAsAttached(volumeName v1.UniqueVolumeName, nodeName types.NodeName) error
|
||||
|
||||
// Unmarks the desire to detach for the specified volume (add the volume back to
|
||||
// the node's volumesToReportAsAttached list)
|
||||
AddVolumeToReportAsAttached(volumeName v1.UniqueVolumeName, nodeName types.NodeName)
|
||||
}
|
||||
|
||||
// VolumeLogger defines a set of operations for generating volume-related logging and error msgs
|
||||
type VolumeLogger interface {
|
||||
// Creates a detailed msg that can be used in logs
|
||||
// The msg format follows the pattern "<prefixMsg> <volume details> <suffixMsg>",
|
||||
// where each implementation provides the volume details
|
||||
GenerateMsgDetailed(prefixMsg, suffixMsg string) (detailedMsg string)
|
||||
|
||||
// Creates a detailed error that can be used in logs.
|
||||
// The msg format follows the pattern "<prefixMsg> <volume details>: <err> ",
|
||||
GenerateErrorDetailed(prefixMsg string, err error) (detailedErr error)
|
||||
|
||||
// Creates a simple msg that is user friendly and a detailed msg that can be used in logs
|
||||
// The msg format follows the pattern "<prefixMsg> <volume details> <suffixMsg>",
|
||||
// where each implementation provides the volume details
|
||||
GenerateMsg(prefixMsg, suffixMsg string) (simpleMsg, detailedMsg string)
|
||||
|
||||
// Creates a simple error that is user friendly and a detailed error that can be used in logs.
|
||||
// The msg format follows the pattern "<prefixMsg> <volume details>: <err> ",
|
||||
GenerateError(prefixMsg string, err error) (simpleErr, detailedErr error)
|
||||
}
|
||||
|
||||
// Generates an error string with the format ": <err>" if err exists
|
||||
func errSuffix(err error) string {
|
||||
errStr := ""
|
||||
if err != nil {
|
||||
errStr = fmt.Sprintf(": %v", err)
|
||||
}
|
||||
return errStr
|
||||
}
|
||||
|
||||
// Generate a detailed error msg for logs
|
||||
func generateVolumeMsgDetailed(prefixMsg, suffixMsg, volumeName, details string) (detailedMsg string) {
|
||||
return fmt.Sprintf("%v for volume %q %v %v", prefixMsg, volumeName, details, suffixMsg)
|
||||
}
|
||||
|
||||
// Generate a simplified error msg for events and a detailed error msg for logs
|
||||
func generateVolumeMsg(prefixMsg, suffixMsg, volumeName, details string) (simpleMsg, detailedMsg string) {
|
||||
simpleMsg = fmt.Sprintf("%v for volume %q %v", prefixMsg, volumeName, suffixMsg)
|
||||
return simpleMsg, generateVolumeMsgDetailed(prefixMsg, suffixMsg, volumeName, details)
|
||||
}
|
||||
|
||||
// VolumeToAttach represents a volume that should be attached to a node.
|
||||
type VolumeToAttach struct {
|
||||
// MultiAttachErrorReported indicates whether the multi-attach error has been reported for the given volume.
|
||||
// It is used to to prevent reporting the error from being reported more than once for a given volume.
|
||||
MultiAttachErrorReported bool
|
||||
|
||||
// VolumeName is the unique identifier for the volume that should be
|
||||
// attached.
|
||||
VolumeName v1.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 types.NodeName
|
||||
|
||||
// scheduledPods is a map containing the set of pods that reference this
|
||||
// volume and are scheduled to the underlying node. The key in the map is
|
||||
// the name of the pod and the value is a pod object containing more
|
||||
// information about the pod.
|
||||
ScheduledPods []*v1.Pod
|
||||
}
|
||||
|
||||
// GenerateMsgDetailed returns detailed msgs for volumes to attach
|
||||
func (volume *VolumeToAttach) GenerateMsgDetailed(prefixMsg, suffixMsg string) (detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) from node %q", volume.VolumeName, volume.NodeName)
|
||||
volumeSpecName := "nil"
|
||||
if volume.VolumeSpec != nil {
|
||||
volumeSpecName = volume.VolumeSpec.Name()
|
||||
}
|
||||
return generateVolumeMsgDetailed(prefixMsg, suffixMsg, volumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateMsg returns simple and detailed msgs for volumes to attach
|
||||
func (volume *VolumeToAttach) GenerateMsg(prefixMsg, suffixMsg string) (simpleMsg, detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) from node %q", volume.VolumeName, volume.NodeName)
|
||||
volumeSpecName := "nil"
|
||||
if volume.VolumeSpec != nil {
|
||||
volumeSpecName = volume.VolumeSpec.Name()
|
||||
}
|
||||
return generateVolumeMsg(prefixMsg, suffixMsg, volumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateErrorDetailed returns detailed errors for volumes to attach
|
||||
func (volume *VolumeToAttach) GenerateErrorDetailed(prefixMsg string, err error) (detailedErr error) {
|
||||
return fmt.Errorf(volume.GenerateMsgDetailed(prefixMsg, errSuffix(err)))
|
||||
}
|
||||
|
||||
// GenerateError returns simple and detailed errors for volumes to attach
|
||||
func (volume *VolumeToAttach) GenerateError(prefixMsg string, err error) (simpleErr, detailedErr error) {
|
||||
simpleMsg, detailedMsg := volume.GenerateMsg(prefixMsg, errSuffix(err))
|
||||
return fmt.Errorf(simpleMsg), fmt.Errorf(detailedMsg)
|
||||
}
|
||||
|
||||
// VolumeToMount represents a volume that should be attached to this node and
|
||||
// mounted to the PodName.
|
||||
type VolumeToMount struct {
|
||||
// VolumeName is the unique identifier for the volume that should be
|
||||
// mounted.
|
||||
VolumeName v1.UniqueVolumeName
|
||||
|
||||
// PodName is the unique identifier for the pod that the volume should be
|
||||
// mounted to after it is attached.
|
||||
PodName volumetypes.UniquePodName
|
||||
|
||||
// VolumeSpec is a volume spec containing the specification for the volume
|
||||
// that should be mounted. Used to create NewMounter. Used to generate
|
||||
// InnerVolumeSpecName.
|
||||
VolumeSpec *volume.Spec
|
||||
|
||||
// outerVolumeSpecName is the podSpec.Volume[x].Name of the volume. If the
|
||||
// volume was referenced through a persistent volume claim, this contains
|
||||
// the podSpec.Volume[x].Name of the persistent volume claim.
|
||||
OuterVolumeSpecName string
|
||||
|
||||
// Pod to mount the volume to. Used to create NewMounter.
|
||||
Pod *v1.Pod
|
||||
|
||||
// PluginIsAttachable indicates that the plugin for this volume implements
|
||||
// the volume.Attacher interface
|
||||
PluginIsAttachable bool
|
||||
|
||||
// VolumeGidValue contains the value of the GID annotation, if present.
|
||||
VolumeGidValue string
|
||||
|
||||
// DevicePath contains the path on the node where the volume is attached.
|
||||
// For non-attachable volumes this is empty.
|
||||
DevicePath string
|
||||
|
||||
// ReportedInUse indicates that the volume was successfully added to the
|
||||
// VolumesInUse field in the node's status.
|
||||
ReportedInUse bool
|
||||
}
|
||||
|
||||
// GenerateMsgDetailed returns detailed msgs for volumes to mount
|
||||
func (volume *VolumeToMount) GenerateMsgDetailed(prefixMsg, suffixMsg string) (detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) pod %q (UID: %q)", volume.VolumeName, volume.Pod.Name, volume.Pod.UID)
|
||||
volumeSpecName := "nil"
|
||||
if volume.VolumeSpec != nil {
|
||||
volumeSpecName = volume.VolumeSpec.Name()
|
||||
}
|
||||
return generateVolumeMsgDetailed(prefixMsg, suffixMsg, volumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateMsg returns simple and detailed msgs for volumes to mount
|
||||
func (volume *VolumeToMount) GenerateMsg(prefixMsg, suffixMsg string) (simpleMsg, detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) pod %q (UID: %q)", volume.VolumeName, volume.Pod.Name, volume.Pod.UID)
|
||||
volumeSpecName := "nil"
|
||||
if volume.VolumeSpec != nil {
|
||||
volumeSpecName = volume.VolumeSpec.Name()
|
||||
}
|
||||
return generateVolumeMsg(prefixMsg, suffixMsg, volumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateErrorDetailed returns detailed errors for volumes to mount
|
||||
func (volume *VolumeToMount) GenerateErrorDetailed(prefixMsg string, err error) (detailedErr error) {
|
||||
return fmt.Errorf(volume.GenerateMsgDetailed(prefixMsg, errSuffix(err)))
|
||||
}
|
||||
|
||||
// GenerateError returns simple and detailed errors for volumes to mount
|
||||
func (volume *VolumeToMount) GenerateError(prefixMsg string, err error) (simpleErr, detailedErr error) {
|
||||
simpleMsg, detailedMsg := volume.GenerateMsg(prefixMsg, errSuffix(err))
|
||||
return fmt.Errorf(simpleMsg), fmt.Errorf(detailedMsg)
|
||||
}
|
||||
|
||||
// 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 v1.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 types.NodeName
|
||||
|
||||
// PluginIsAttachable indicates that the plugin for this volume implements
|
||||
// the volume.Attacher interface
|
||||
PluginIsAttachable bool
|
||||
|
||||
// DevicePath contains the path on the node where the volume is attached.
|
||||
// For non-attachable volumes this is empty.
|
||||
DevicePath string
|
||||
}
|
||||
|
||||
// GenerateMsgDetailed returns detailed msgs for attached volumes
|
||||
func (volume *AttachedVolume) GenerateMsgDetailed(prefixMsg, suffixMsg string) (detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) on node %q", volume.VolumeName, volume.NodeName)
|
||||
volumeSpecName := "nil"
|
||||
if volume.VolumeSpec != nil {
|
||||
volumeSpecName = volume.VolumeSpec.Name()
|
||||
}
|
||||
return generateVolumeMsgDetailed(prefixMsg, suffixMsg, volumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateMsg returns simple and detailed msgs for attached volumes
|
||||
func (volume *AttachedVolume) GenerateMsg(prefixMsg, suffixMsg string) (simpleMsg, detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) on node %q", volume.VolumeName, volume.NodeName)
|
||||
volumeSpecName := "nil"
|
||||
if volume.VolumeSpec != nil {
|
||||
volumeSpecName = volume.VolumeSpec.Name()
|
||||
}
|
||||
return generateVolumeMsg(prefixMsg, suffixMsg, volumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateErrorDetailed returns detailed errors for attached volumes
|
||||
func (volume *AttachedVolume) GenerateErrorDetailed(prefixMsg string, err error) (detailedErr error) {
|
||||
return fmt.Errorf(volume.GenerateMsgDetailed(prefixMsg, errSuffix(err)))
|
||||
}
|
||||
|
||||
// GenerateError returns simple and detailed errors for attached volumes
|
||||
func (volume *AttachedVolume) GenerateError(prefixMsg string, err error) (simpleErr, detailedErr error) {
|
||||
simpleMsg, detailedMsg := volume.GenerateMsg(prefixMsg, errSuffix(err))
|
||||
return fmt.Errorf(simpleMsg), fmt.Errorf(detailedMsg)
|
||||
}
|
||||
|
||||
// MountedVolume represents a volume that has successfully been mounted to a pod.
|
||||
type MountedVolume struct {
|
||||
// PodName is the unique identifier of the pod mounted to.
|
||||
PodName volumetypes.UniquePodName
|
||||
|
||||
// VolumeName is the unique identifier of the volume mounted to the pod.
|
||||
VolumeName v1.UniqueVolumeName
|
||||
|
||||
// InnerVolumeSpecName is the volume.Spec.Name() of the volume. If the
|
||||
// volume was referenced through a persistent volume claims, this contains
|
||||
// the name of the bound persistent volume object.
|
||||
// It is the name that plugins use in their pod mount path, i.e.
|
||||
// /var/lib/kubelet/pods/{podUID}/volumes/{escapeQualifiedPluginName}/{innerVolumeSpecName}/
|
||||
// PVC example,
|
||||
// apiVersion: v1
|
||||
// kind: PersistentVolume
|
||||
// metadata:
|
||||
// name: pv0003 <- InnerVolumeSpecName
|
||||
// spec:
|
||||
// capacity:
|
||||
// storage: 5Gi
|
||||
// accessModes:
|
||||
// - ReadWriteOnce
|
||||
// persistentVolumeReclaimPolicy: Recycle
|
||||
// nfs:
|
||||
// path: /tmp
|
||||
// server: 172.17.0.2
|
||||
// Non-PVC example:
|
||||
// apiVersion: v1
|
||||
// kind: Pod
|
||||
// metadata:
|
||||
// name: test-pd
|
||||
// spec:
|
||||
// containers:
|
||||
// - image: gcr.io/google_containers/test-webserver
|
||||
// name: test-container
|
||||
// volumeMounts:
|
||||
// - mountPath: /test-pd
|
||||
// name: test-volume
|
||||
// volumes:
|
||||
// - name: test-volume <- InnerVolumeSpecName
|
||||
// gcePersistentDisk:
|
||||
// pdName: my-data-disk
|
||||
// fsType: ext4
|
||||
InnerVolumeSpecName string
|
||||
|
||||
// outerVolumeSpecName is the podSpec.Volume[x].Name of the volume. If the
|
||||
// volume was referenced through a persistent volume claim, this contains
|
||||
// the podSpec.Volume[x].Name of the persistent volume claim.
|
||||
// PVC example:
|
||||
// kind: Pod
|
||||
// apiVersion: v1
|
||||
// metadata:
|
||||
// name: mypod
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: myfrontend
|
||||
// image: dockerfile/nginx
|
||||
// volumeMounts:
|
||||
// - mountPath: "/var/www/html"
|
||||
// name: mypd
|
||||
// volumes:
|
||||
// - name: mypd <- OuterVolumeSpecName
|
||||
// persistentVolumeClaim:
|
||||
// claimName: myclaim
|
||||
// Non-PVC example:
|
||||
// apiVersion: v1
|
||||
// kind: Pod
|
||||
// metadata:
|
||||
// name: test-pd
|
||||
// spec:
|
||||
// containers:
|
||||
// - image: gcr.io/google_containers/test-webserver
|
||||
// name: test-container
|
||||
// volumeMounts:
|
||||
// - mountPath: /test-pd
|
||||
// name: test-volume
|
||||
// volumes:
|
||||
// - name: test-volume <- OuterVolumeSpecName
|
||||
// gcePersistentDisk:
|
||||
// pdName: my-data-disk
|
||||
// fsType: ext4
|
||||
OuterVolumeSpecName string
|
||||
|
||||
// PluginName is the "Unescaped Qualified" name of the volume plugin used to
|
||||
// mount and unmount this volume. It can be used to fetch the volume plugin
|
||||
// to unmount with, on demand. It is also the name that plugins use, though
|
||||
// escaped, in their pod mount path, i.e.
|
||||
// /var/lib/kubelet/pods/{podUID}/volumes/{escapeQualifiedPluginName}/{outerVolumeSpecName}/
|
||||
PluginName string
|
||||
|
||||
// PodUID is the UID of the pod mounted to. It is also the string used by
|
||||
// plugins in their pod mount path, i.e.
|
||||
// /var/lib/kubelet/pods/{podUID}/volumes/{escapeQualifiedPluginName}/{outerVolumeSpecName}/
|
||||
PodUID types.UID
|
||||
|
||||
// Mounter is the volume mounter used to mount this volume. It is required
|
||||
// by kubelet to create container.VolumeMap.
|
||||
Mounter volume.Mounter
|
||||
|
||||
// VolumeGidValue contains the value of the GID annotation, if present.
|
||||
VolumeGidValue string
|
||||
}
|
||||
|
||||
// GenerateMsgDetailed returns detailed msgs for mounted volumes
|
||||
func (volume *MountedVolume) GenerateMsgDetailed(prefixMsg, suffixMsg string) (detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) pod %q (UID: %q)", volume.VolumeName, volume.PodName, volume.PodUID)
|
||||
return generateVolumeMsgDetailed(prefixMsg, suffixMsg, volume.OuterVolumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateMsg returns simple and detailed msgs for mounted volumes
|
||||
func (volume *MountedVolume) GenerateMsg(prefixMsg, suffixMsg string) (simpleMsg, detailedMsg string) {
|
||||
detailedStr := fmt.Sprintf("(UniqueName: %q) pod %q (UID: %q)", volume.VolumeName, volume.PodName, volume.PodUID)
|
||||
return generateVolumeMsg(prefixMsg, suffixMsg, volume.OuterVolumeSpecName, detailedStr)
|
||||
}
|
||||
|
||||
// GenerateErrorDetailed returns simple and detailed errors for mounted volumes
|
||||
func (volume *MountedVolume) GenerateErrorDetailed(prefixMsg string, err error) (detailedErr error) {
|
||||
return fmt.Errorf(volume.GenerateMsgDetailed(prefixMsg, errSuffix(err)))
|
||||
}
|
||||
|
||||
// GenerateError returns simple and detailed errors for mounted volumes
|
||||
func (volume *MountedVolume) GenerateError(prefixMsg string, err error) (simpleErr, detailedErr error) {
|
||||
simpleMsg, detailedMsg := volume.GenerateMsg(prefixMsg, errSuffix(err))
|
||||
return fmt.Errorf(simpleMsg), fmt.Errorf(detailedMsg)
|
||||
}
|
||||
|
||||
type operationExecutor struct {
|
||||
// pendingOperations keeps track of pending attach and detach operations so
|
||||
// multiple operations are not started on the same volume
|
||||
pendingOperations nestedpendingoperations.NestedPendingOperations
|
||||
|
||||
// operationGenerator is an interface that provides implementations for
|
||||
// generating volume function
|
||||
operationGenerator OperationGenerator
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) IsOperationPending(volumeName v1.UniqueVolumeName, podName volumetypes.UniquePodName) bool {
|
||||
return oe.pendingOperations.IsOperationPending(volumeName, podName)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) AttachVolume(
|
||||
volumeToAttach VolumeToAttach,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) error {
|
||||
attachFunc, plugin, err :=
|
||||
oe.operationGenerator.GenerateAttachVolumeFunc(volumeToAttach, actualStateOfWorld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook(plugin, "volume_attach")
|
||||
return oe.pendingOperations.Run(
|
||||
volumeToAttach.VolumeName, "" /* podName */, attachFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) DetachVolume(
|
||||
volumeToDetach AttachedVolume,
|
||||
verifySafeToDetach bool,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) error {
|
||||
detachFunc, plugin, err :=
|
||||
oe.operationGenerator.GenerateDetachVolumeFunc(volumeToDetach, verifySafeToDetach, actualStateOfWorld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook(plugin, "volume_detach")
|
||||
return oe.pendingOperations.Run(
|
||||
volumeToDetach.VolumeName, "" /* podName */, detachFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) VerifyVolumesAreAttached(
|
||||
attachedVolumes map[types.NodeName][]AttachedVolume,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) {
|
||||
|
||||
// A map of plugin names and nodes on which they exist with volumes they manage
|
||||
bulkVerifyPluginsByNode := make(map[string]map[types.NodeName][]*volume.Spec)
|
||||
volumeSpecMapByPlugin := make(map[string]map[*volume.Spec]v1.UniqueVolumeName)
|
||||
|
||||
for node, nodeAttachedVolumes := range attachedVolumes {
|
||||
for _, volumeAttached := range nodeAttachedVolumes {
|
||||
if volumeAttached.VolumeSpec == nil {
|
||||
glog.Errorf("VerifyVolumesAreAttached: nil spec for volume %s", volumeAttached.VolumeName)
|
||||
continue
|
||||
}
|
||||
volumePlugin, err :=
|
||||
oe.operationGenerator.GetVolumePluginMgr().FindPluginBySpec(volumeAttached.VolumeSpec)
|
||||
|
||||
if err != nil || volumePlugin == nil {
|
||||
glog.Errorf(
|
||||
"VolumesAreAttached.FindPluginBySpec failed for volume %q (spec.Name: %q) on node %q with error: %v",
|
||||
volumeAttached.VolumeName,
|
||||
volumeAttached.VolumeSpec.Name(),
|
||||
volumeAttached.NodeName,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
pluginName := volumePlugin.GetPluginName()
|
||||
|
||||
if volumePlugin.SupportsBulkVolumeVerification() {
|
||||
pluginNodes, pluginNodesExist := bulkVerifyPluginsByNode[pluginName]
|
||||
|
||||
if !pluginNodesExist {
|
||||
pluginNodes = make(map[types.NodeName][]*volume.Spec)
|
||||
}
|
||||
|
||||
volumeSpecList, nodeExists := pluginNodes[node]
|
||||
if !nodeExists {
|
||||
volumeSpecList = []*volume.Spec{}
|
||||
}
|
||||
volumeSpecList = append(volumeSpecList, volumeAttached.VolumeSpec)
|
||||
pluginNodes[node] = volumeSpecList
|
||||
|
||||
bulkVerifyPluginsByNode[pluginName] = pluginNodes
|
||||
volumeSpecMap, mapExists := volumeSpecMapByPlugin[pluginName]
|
||||
|
||||
if !mapExists {
|
||||
volumeSpecMap = make(map[*volume.Spec]v1.UniqueVolumeName)
|
||||
}
|
||||
volumeSpecMap[volumeAttached.VolumeSpec] = volumeAttached.VolumeName
|
||||
volumeSpecMapByPlugin[pluginName] = volumeSpecMap
|
||||
continue
|
||||
}
|
||||
|
||||
// If node doesn't support Bulk volume polling it is best to poll individually
|
||||
nodeError := oe.VerifyVolumesAreAttachedPerNode(nodeAttachedVolumes, node, actualStateOfWorld)
|
||||
if nodeError != nil {
|
||||
glog.Errorf("BulkVerifyVolumes.VerifyVolumesAreAttached verifying volumes on node %q with %v", node, nodeError)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for pluginName, pluginNodeVolumes := range bulkVerifyPluginsByNode {
|
||||
bulkVerifyVolumeFunc, err := oe.operationGenerator.GenerateBulkVolumeVerifyFunc(
|
||||
pluginNodeVolumes,
|
||||
pluginName,
|
||||
volumeSpecMapByPlugin[pluginName],
|
||||
actualStateOfWorld)
|
||||
if err != nil {
|
||||
glog.Errorf("BulkVerifyVolumes.GenerateBulkVolumeVerifyFunc error bulk verifying volumes for plugin %q with %v", pluginName, err)
|
||||
}
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook(pluginName, "verify_volumes_are_attached")
|
||||
// Ugly hack to ensure - we don't do parallel bulk polling of same volume plugin
|
||||
uniquePluginName := v1.UniqueVolumeName(pluginName)
|
||||
err = oe.pendingOperations.Run(uniquePluginName, "" /* Pod Name */, bulkVerifyVolumeFunc, opCompleteFunc)
|
||||
if err != nil {
|
||||
glog.Errorf("BulkVerifyVolumes.Run Error bulk volume verification for plugin %q with %v", pluginName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) VerifyVolumesAreAttachedPerNode(
|
||||
attachedVolumes []AttachedVolume,
|
||||
nodeName types.NodeName,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) error {
|
||||
volumesAreAttachedFunc, err :=
|
||||
oe.operationGenerator.GenerateVolumesAreAttachedFunc(attachedVolumes, nodeName, actualStateOfWorld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook("<n/a>", "verify_volumes_are_attached_per_node")
|
||||
// Give an empty UniqueVolumeName so that this operation could be executed concurrently.
|
||||
return oe.pendingOperations.Run("" /* volumeName */, "" /* podName */, volumesAreAttachedFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) MountVolume(
|
||||
waitForAttachTimeout time.Duration,
|
||||
volumeToMount VolumeToMount,
|
||||
actualStateOfWorld ActualStateOfWorldMounterUpdater,
|
||||
isRemount bool) error {
|
||||
mountFunc, plugin, err := oe.operationGenerator.GenerateMountVolumeFunc(
|
||||
waitForAttachTimeout, volumeToMount, actualStateOfWorld, isRemount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
podName := nestedpendingoperations.EmptyUniquePodName
|
||||
// TODO: remove this -- not necessary
|
||||
if !volumeToMount.PluginIsAttachable {
|
||||
// Non-attachable volume plugins can execute mount for multiple pods
|
||||
// referencing the same volume in parallel
|
||||
podName = volumehelper.GetUniquePodName(volumeToMount.Pod)
|
||||
}
|
||||
|
||||
// TODO mount_device
|
||||
opCompleteFunc := util.OperationCompleteHook(plugin, "volume_mount")
|
||||
return oe.pendingOperations.Run(
|
||||
volumeToMount.VolumeName, podName, mountFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) UnmountVolume(
|
||||
volumeToUnmount MountedVolume,
|
||||
actualStateOfWorld ActualStateOfWorldMounterUpdater) error {
|
||||
|
||||
unmountFunc, plugin, err :=
|
||||
oe.operationGenerator.GenerateUnmountVolumeFunc(volumeToUnmount, actualStateOfWorld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// All volume plugins can execute mount for multiple pods referencing the
|
||||
// same volume in parallel
|
||||
podName := volumetypes.UniquePodName(volumeToUnmount.PodUID)
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook(plugin, "volume_unmount")
|
||||
return oe.pendingOperations.Run(
|
||||
volumeToUnmount.VolumeName, podName, unmountFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) UnmountDevice(
|
||||
deviceToDetach AttachedVolume,
|
||||
actualStateOfWorld ActualStateOfWorldMounterUpdater,
|
||||
mounter mount.Interface) error {
|
||||
unmountDeviceFunc, plugin, err :=
|
||||
oe.operationGenerator.GenerateUnmountDeviceFunc(deviceToDetach, actualStateOfWorld, mounter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook(plugin, "unmount_device")
|
||||
return oe.pendingOperations.Run(
|
||||
deviceToDetach.VolumeName, "" /* podName */, unmountDeviceFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) ExpandVolume(pvcWithResizeRequest *expandcache.PVCWithResizeRequest, resizeMap expandcache.VolumeResizeMap) error {
|
||||
expandFunc, pluginName, err := oe.operationGenerator.GenerateExpandVolumeFunc(pvcWithResizeRequest, resizeMap)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uniqueVolumeKey := v1.UniqueVolumeName(pvcWithResizeRequest.UniquePVCKey())
|
||||
opCompleteFunc := util.OperationCompleteHook(pluginName, "expand_volume")
|
||||
return oe.pendingOperations.Run(uniqueVolumeKey, "", expandFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
func (oe *operationExecutor) VerifyControllerAttachedVolume(
|
||||
volumeToMount VolumeToMount,
|
||||
nodeName types.NodeName,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) error {
|
||||
verifyControllerAttachedVolumeFunc, plugin, err :=
|
||||
oe.operationGenerator.GenerateVerifyControllerAttachedVolumeFunc(volumeToMount, nodeName, actualStateOfWorld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opCompleteFunc := util.OperationCompleteHook(plugin, "verify_controller_attached_volume")
|
||||
return oe.pendingOperations.Run(
|
||||
volumeToMount.VolumeName, "" /* podName */, verifyControllerAttachedVolumeFunc, opCompleteFunc)
|
||||
}
|
||||
|
||||
// TODO: this is a workaround for the unmount device issue caused by gci mounter.
|
||||
// In GCI cluster, if gci mounter is used for mounting, the container started by mounter
|
||||
// script will cause additional mounts created in the container. Since these mounts are
|
||||
// irrelavant to the original mounts, they should be not considered when checking the
|
||||
// mount references. Current solution is to filter out those mount paths that contain
|
||||
// the string of original mount path.
|
||||
// Plan to work on better approach to solve this issue.
|
||||
|
||||
func hasMountRefs(mountPath string, mountRefs []string) bool {
|
||||
count := 0
|
||||
for _, ref := range mountRefs {
|
||||
if !strings.Contains(ref, mountPath) {
|
||||
count = count + 1
|
||||
}
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
431
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/operation_executor_test.go
generated
vendored
431
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/operation_executor_test.go
generated
vendored
|
|
@ -1,431 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package operationexecutor
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
|
||||
)
|
||||
|
||||
const (
|
||||
numVolumesToMount = 2
|
||||
numAttachableVolumesToUnmount = 2
|
||||
numNonAttachableVolumesToUnmount = 2
|
||||
numDevicesToUnmount = 2
|
||||
numVolumesToAttach = 2
|
||||
numVolumesToDetach = 2
|
||||
numVolumesToVerifyAttached = 2
|
||||
numVolumesToVerifyControllerAttached = 2
|
||||
)
|
||||
|
||||
var _ OperationGenerator = &fakeOperationGenerator{}
|
||||
|
||||
func TestOperationExecutor_MountVolume_ConcurrentMountForNonAttachablePlugins(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
volumesToMount := make([]VolumeToMount, numVolumesToMount)
|
||||
secretName := "secret-volume"
|
||||
volumeName := v1.UniqueVolumeName(secretName)
|
||||
|
||||
// Act
|
||||
for i := range volumesToMount {
|
||||
podName := "pod-" + strconv.Itoa((i + 1))
|
||||
pod := getTestPodWithSecret(podName, secretName)
|
||||
volumesToMount[i] = VolumeToMount{
|
||||
Pod: pod,
|
||||
VolumeName: volumeName,
|
||||
PluginIsAttachable: false, // this field determines whether the plugin is attachable
|
||||
ReportedInUse: true,
|
||||
}
|
||||
oe.MountVolume(0 /* waitForAttachTimeOut */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false /* isRemount */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunConcurrently(ch, quit, numVolumesToMount) {
|
||||
t.Fatalf("Unable to start mount operations in Concurrent for non-attachable volumes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_MountVolume_ConcurrentMountForAttachablePlugins(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
volumesToMount := make([]VolumeToMount, numVolumesToAttach)
|
||||
pdName := "pd-volume"
|
||||
volumeName := v1.UniqueVolumeName(pdName)
|
||||
|
||||
// Act
|
||||
for i := range volumesToMount {
|
||||
podName := "pod-" + strconv.Itoa((i + 1))
|
||||
pod := getTestPodWithGCEPD(podName, pdName)
|
||||
volumesToMount[i] = VolumeToMount{
|
||||
Pod: pod,
|
||||
VolumeName: volumeName,
|
||||
PluginIsAttachable: true, // this field determines whether the plugin is attachable
|
||||
ReportedInUse: true,
|
||||
}
|
||||
oe.MountVolume(0 /* waitForAttachTimeout */, volumesToMount[i], nil /* actualStateOfWorldMounterUpdater */, false /* isRemount */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunSerially(ch, quit) {
|
||||
t.Fatalf("Mount operations should not start concurrently for attachable volumes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_UnmountVolume_ConcurrentUnmountForAllPlugins(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
volumesToUnmount := make([]MountedVolume, numAttachableVolumesToUnmount+numNonAttachableVolumesToUnmount)
|
||||
pdName := "pd-volume"
|
||||
secretName := "secret-volume"
|
||||
|
||||
// Act
|
||||
for i := 0; i < numNonAttachableVolumesToUnmount+numAttachableVolumesToUnmount; i++ {
|
||||
podName := "pod-" + strconv.Itoa(i+1)
|
||||
if i < numNonAttachableVolumesToUnmount {
|
||||
pod := getTestPodWithSecret(podName, secretName)
|
||||
volumesToUnmount[i] = MountedVolume{
|
||||
PodName: volumetypes.UniquePodName(podName),
|
||||
VolumeName: v1.UniqueVolumeName(secretName),
|
||||
PodUID: pod.UID,
|
||||
}
|
||||
} else {
|
||||
pod := getTestPodWithGCEPD(podName, pdName)
|
||||
volumesToUnmount[i] = MountedVolume{
|
||||
PodName: volumetypes.UniquePodName(podName),
|
||||
VolumeName: v1.UniqueVolumeName(pdName),
|
||||
PodUID: pod.UID,
|
||||
}
|
||||
}
|
||||
oe.UnmountVolume(volumesToUnmount[i], nil /* actualStateOfWorldMounterUpdater */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunConcurrently(ch, quit, numNonAttachableVolumesToUnmount+numAttachableVolumesToUnmount) {
|
||||
t.Fatalf("Unable to start unmount operations concurrently for volume plugins")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_UnmountDeviceConcurrently(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
attachedVolumes := make([]AttachedVolume, numDevicesToUnmount)
|
||||
pdName := "pd-volume"
|
||||
|
||||
// Act
|
||||
for i := range attachedVolumes {
|
||||
attachedVolumes[i] = AttachedVolume{
|
||||
VolumeName: v1.UniqueVolumeName(pdName),
|
||||
NodeName: "node-name",
|
||||
}
|
||||
oe.UnmountDevice(attachedVolumes[i], nil /* actualStateOfWorldMounterUpdater */, nil /* mount.Interface */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunSerially(ch, quit) {
|
||||
t.Fatalf("Unmount device operations should not start concurrently")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_AttachVolumeConcurrently(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
volumesToAttach := make([]VolumeToAttach, numVolumesToAttach)
|
||||
pdName := "pd-volume"
|
||||
|
||||
// Act
|
||||
for i := range volumesToAttach {
|
||||
volumesToAttach[i] = VolumeToAttach{
|
||||
VolumeName: v1.UniqueVolumeName(pdName),
|
||||
NodeName: "node",
|
||||
}
|
||||
oe.AttachVolume(volumesToAttach[i], nil /* actualStateOfWorldAttacherUpdater */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunSerially(ch, quit) {
|
||||
t.Fatalf("Attach volume operations should not start concurrently")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_DetachVolumeConcurrently(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
attachedVolumes := make([]AttachedVolume, numVolumesToDetach)
|
||||
pdName := "pd-volume"
|
||||
|
||||
// Act
|
||||
for i := range attachedVolumes {
|
||||
attachedVolumes[i] = AttachedVolume{
|
||||
VolumeName: v1.UniqueVolumeName(pdName),
|
||||
NodeName: "node",
|
||||
}
|
||||
oe.DetachVolume(attachedVolumes[i], true /* verifySafeToDetach */, nil /* actualStateOfWorldAttacherUpdater */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunSerially(ch, quit) {
|
||||
t.Fatalf("DetachVolume operations should not run concurrently")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_VerifyVolumesAreAttachedConcurrently(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
|
||||
// Act
|
||||
for i := 0; i < numVolumesToVerifyAttached; i++ {
|
||||
oe.VerifyVolumesAreAttachedPerNode(nil /* attachedVolumes */, "node-name", nil /* actualStateOfWorldAttacherUpdater */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunConcurrently(ch, quit, numVolumesToVerifyAttached) {
|
||||
t.Fatalf("VerifyVolumesAreAttached operation is not being run concurrently")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperationExecutor_VerifyControllerAttachedVolumeConcurrently(t *testing.T) {
|
||||
// Arrange
|
||||
ch, quit, oe := setup()
|
||||
volumesToMount := make([]VolumeToMount, numVolumesToVerifyControllerAttached)
|
||||
pdName := "pd-volume"
|
||||
|
||||
// Act
|
||||
for i := range volumesToMount {
|
||||
volumesToMount[i] = VolumeToMount{
|
||||
VolumeName: v1.UniqueVolumeName(pdName),
|
||||
}
|
||||
oe.VerifyControllerAttachedVolume(volumesToMount[i], types.NodeName("node-name"), nil /* actualStateOfWorldMounterUpdater */)
|
||||
}
|
||||
|
||||
// Assert
|
||||
if !isOperationRunSerially(ch, quit) {
|
||||
t.Fatalf("VerifyControllerAttachedVolume should not run concurrently")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeOperationGenerator struct {
|
||||
ch chan interface{}
|
||||
quit chan interface{}
|
||||
}
|
||||
|
||||
func newFakeOperationGenerator(ch chan interface{}, quit chan interface{}) OperationGenerator {
|
||||
return &fakeOperationGenerator{
|
||||
ch: ch,
|
||||
quit: quit,
|
||||
}
|
||||
}
|
||||
|
||||
func (fopg *fakeOperationGenerator) GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
func (fopg *fakeOperationGenerator) GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
func (fopg *fakeOperationGenerator) GenerateAttachVolumeFunc(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
func (fopg *fakeOperationGenerator) GenerateDetachVolumeFunc(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
func (fopg *fakeOperationGenerator) GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
func (fopg *fakeOperationGenerator) GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
func (fopg *fakeOperationGenerator) GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
|
||||
func (fopg *fakeOperationGenerator) GenerateExpandVolumeFunc(pvcWithResizeRequest *expandcache.PVCWithResizeRequest,
|
||||
resizeMap expandcache.VolumeResizeMap) (func() error, string, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, "", nil
|
||||
}
|
||||
|
||||
func (fopg *fakeOperationGenerator) GenerateBulkVolumeVerifyFunc(
|
||||
pluginNodeVolumes map[types.NodeName][]*volume.Spec,
|
||||
pluginNane string,
|
||||
volumeSpecMap map[*volume.Spec]v1.UniqueVolumeName,
|
||||
actualStateOfWorldAttacherUpdater ActualStateOfWorldAttacherUpdater) (func() error, error) {
|
||||
return func() error {
|
||||
startOperationAndBlock(fopg.ch, fopg.quit)
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fopg *fakeOperationGenerator) GetVolumePluginMgr() *volume.VolumePluginMgr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTestPodWithSecret(podName, secretName string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
UID: types.UID(podName),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: secretName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{
|
||||
SecretName: secretName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "secret-volume-test",
|
||||
Image: "gcr.io/google_containers/mounttest:0.8",
|
||||
Args: []string{
|
||||
"--file_content=/etc/secret-volume/data-1",
|
||||
"--file_mode=/etc/secret-volume/data-1"},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: secretName,
|
||||
MountPath: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestPodWithGCEPD(podName, pdName string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
UID: types.UID(podName + string(uuid.NewUUID())),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: pdName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
|
||||
PDName: pdName,
|
||||
FSType: "ext4",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "pd-volume-test",
|
||||
Image: "gcr.io/google_containers/mounttest:0.8",
|
||||
Args: []string{
|
||||
"--file_content=/etc/pd-volume/data-1",
|
||||
},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{
|
||||
Name: pdName,
|
||||
MountPath: "/data",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func isOperationRunSerially(ch <-chan interface{}, quit chan<- interface{}) bool {
|
||||
defer close(quit)
|
||||
numOperationsStarted := 0
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
numOperationsStarted++
|
||||
if numOperationsStarted > 1 {
|
||||
return false
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isOperationRunConcurrently(ch <-chan interface{}, quit chan<- interface{}, numOperationsToRun int) bool {
|
||||
defer close(quit)
|
||||
numOperationsStarted := 0
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
numOperationsStarted++
|
||||
if numOperationsStarted == numOperationsToRun {
|
||||
return true
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setup() (chan interface{}, chan interface{}, OperationExecutor) {
|
||||
ch, quit := make(chan interface{}), make(chan interface{})
|
||||
return ch, quit, NewOperationExecutor(newFakeOperationGenerator(ch, quit))
|
||||
}
|
||||
|
||||
// This function starts by writing to ch and blocks on the quit channel
|
||||
// until it is closed by the currently running test
|
||||
func startOperationAndBlock(ch chan<- interface{}, quit <-chan interface{}) {
|
||||
ch <- nil
|
||||
<-quit
|
||||
}
|
||||
818
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/operation_generator.go
generated
vendored
818
vendor/k8s.io/kubernetes/pkg/volume/util/operationexecutor/operation_generator.go
generated
vendored
|
|
@ -1,818 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package operationexecutor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/record"
|
||||
expandcache "k8s.io/kubernetes/pkg/controller/volume/expand/cache"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kevents "k8s.io/kubernetes/pkg/kubelet/events"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
)
|
||||
|
||||
var _ OperationGenerator = &operationGenerator{}
|
||||
|
||||
type operationGenerator struct {
|
||||
// Used to fetch objects from the API server like Node in the
|
||||
// VerifyControllerAttachedVolume operation.
|
||||
kubeClient clientset.Interface
|
||||
|
||||
// volumePluginMgr is the volume plugin manager used to create volume
|
||||
// plugin objects.
|
||||
volumePluginMgr *volume.VolumePluginMgr
|
||||
|
||||
// recorder is used to record events in the API server
|
||||
recorder record.EventRecorder
|
||||
|
||||
// checkNodeCapabilitiesBeforeMount, if set, enables the CanMount check,
|
||||
// which verifies that the components (binaries, etc.) required to mount
|
||||
// the volume are available on the underlying node before attempting mount.
|
||||
checkNodeCapabilitiesBeforeMount bool
|
||||
}
|
||||
|
||||
// NewOperationGenerator is returns instance of operationGenerator
|
||||
func NewOperationGenerator(kubeClient clientset.Interface,
|
||||
volumePluginMgr *volume.VolumePluginMgr,
|
||||
recorder record.EventRecorder,
|
||||
checkNodeCapabilitiesBeforeMount bool) OperationGenerator {
|
||||
|
||||
return &operationGenerator{
|
||||
kubeClient: kubeClient,
|
||||
volumePluginMgr: volumePluginMgr,
|
||||
recorder: recorder,
|
||||
checkNodeCapabilitiesBeforeMount: checkNodeCapabilitiesBeforeMount,
|
||||
}
|
||||
}
|
||||
|
||||
// OperationGenerator interface that extracts out the functions from operation_executor to make it dependency injectable
|
||||
type OperationGenerator interface {
|
||||
// Generates the MountVolume function needed to perform the mount of a volume plugin
|
||||
GenerateMountVolumeFunc(waitForAttachTimeout time.Duration, volumeToMount VolumeToMount, actualStateOfWorldMounterUpdater ActualStateOfWorldMounterUpdater, isRemount bool) (func() error, string, error)
|
||||
|
||||
// Generates the UnmountVolume function needed to perform the unmount of a volume plugin
|
||||
GenerateUnmountVolumeFunc(volumeToUnmount MountedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater) (func() error, string, error)
|
||||
|
||||
// Generates the AttachVolume function needed to perform attach of a volume plugin
|
||||
GenerateAttachVolumeFunc(volumeToAttach VolumeToAttach, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error)
|
||||
|
||||
// Generates the DetachVolume function needed to perform the detach of a volume plugin
|
||||
GenerateDetachVolumeFunc(volumeToDetach AttachedVolume, verifySafeToDetach bool, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error)
|
||||
|
||||
// Generates the VolumesAreAttached function needed to verify if volume plugins are attached
|
||||
GenerateVolumesAreAttachedFunc(attachedVolumes []AttachedVolume, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error)
|
||||
|
||||
// Generates the UnMountDevice function needed to perform the unmount of a device
|
||||
GenerateUnmountDeviceFunc(deviceToDetach AttachedVolume, actualStateOfWorld ActualStateOfWorldMounterUpdater, mounter mount.Interface) (func() error, string, error)
|
||||
|
||||
// Generates the function needed to check if the attach_detach controller has attached the volume plugin
|
||||
GenerateVerifyControllerAttachedVolumeFunc(volumeToMount VolumeToMount, nodeName types.NodeName, actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error)
|
||||
|
||||
// GetVolumePluginMgr returns volume plugin manager
|
||||
GetVolumePluginMgr() *volume.VolumePluginMgr
|
||||
|
||||
GenerateBulkVolumeVerifyFunc(
|
||||
map[types.NodeName][]*volume.Spec,
|
||||
string,
|
||||
map[*volume.Spec]v1.UniqueVolumeName, ActualStateOfWorldAttacherUpdater) (func() error, error)
|
||||
|
||||
GenerateExpandVolumeFunc(*expandcache.PVCWithResizeRequest, expandcache.VolumeResizeMap) (func() error, string, error)
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateVolumesAreAttachedFunc(
|
||||
attachedVolumes []AttachedVolume,
|
||||
nodeName types.NodeName,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) {
|
||||
|
||||
// volumesPerPlugin maps from a volume plugin to a list of volume specs which belong
|
||||
// to this type of plugin
|
||||
volumesPerPlugin := make(map[string][]*volume.Spec)
|
||||
// volumeSpecMap maps from a volume spec to its unique volumeName which will be used
|
||||
// when calling MarkVolumeAsDetached
|
||||
volumeSpecMap := make(map[*volume.Spec]v1.UniqueVolumeName)
|
||||
// Iterate each volume spec and put them into a map index by the pluginName
|
||||
for _, volumeAttached := range attachedVolumes {
|
||||
if volumeAttached.VolumeSpec == nil {
|
||||
glog.Errorf("VerifyVolumesAreAttached.GenerateVolumesAreAttachedFunc: nil spec for volume %s", volumeAttached.VolumeName)
|
||||
continue
|
||||
}
|
||||
volumePlugin, err :=
|
||||
og.volumePluginMgr.FindPluginBySpec(volumeAttached.VolumeSpec)
|
||||
if err != nil || volumePlugin == nil {
|
||||
glog.Errorf(volumeAttached.GenerateErrorDetailed("VolumesAreAttached.FindPluginBySpec failed", err).Error())
|
||||
}
|
||||
volumeSpecList, pluginExists := volumesPerPlugin[volumePlugin.GetPluginName()]
|
||||
if !pluginExists {
|
||||
volumeSpecList = []*volume.Spec{}
|
||||
}
|
||||
volumeSpecList = append(volumeSpecList, volumeAttached.VolumeSpec)
|
||||
volumesPerPlugin[volumePlugin.GetPluginName()] = volumeSpecList
|
||||
volumeSpecMap[volumeAttached.VolumeSpec] = volumeAttached.VolumeName
|
||||
}
|
||||
|
||||
return func() error {
|
||||
|
||||
// For each volume plugin, pass the list of volume specs to VolumesAreAttached to check
|
||||
// whether the volumes are still attached.
|
||||
for pluginName, volumesSpecs := range volumesPerPlugin {
|
||||
attachableVolumePlugin, err :=
|
||||
og.volumePluginMgr.FindAttachablePluginByName(pluginName)
|
||||
if err != nil || attachableVolumePlugin == nil {
|
||||
glog.Errorf(
|
||||
"VolumeAreAttached.FindAttachablePluginBySpec failed for plugin %q with: %v",
|
||||
pluginName,
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher()
|
||||
if newAttacherErr != nil {
|
||||
glog.Errorf(
|
||||
"VolumesAreAttached.NewAttacher failed for getting plugin %q with: %v",
|
||||
pluginName,
|
||||
newAttacherErr)
|
||||
continue
|
||||
}
|
||||
|
||||
attached, areAttachedErr := volumeAttacher.VolumesAreAttached(volumesSpecs, nodeName)
|
||||
if areAttachedErr != nil {
|
||||
glog.Errorf(
|
||||
"VolumesAreAttached failed for checking on node %q with: %v",
|
||||
nodeName,
|
||||
areAttachedErr)
|
||||
continue
|
||||
}
|
||||
|
||||
for spec, check := range attached {
|
||||
if !check {
|
||||
actualStateOfWorld.MarkVolumeAsDetached(volumeSpecMap[spec], nodeName)
|
||||
glog.V(1).Infof("VerifyVolumesAreAttached determined volume %q (spec.Name: %q) is no longer attached to node %q, therefore it was marked as detached.",
|
||||
volumeSpecMap[spec], spec.Name(), nodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateBulkVolumeVerifyFunc(
|
||||
pluginNodeVolumes map[types.NodeName][]*volume.Spec,
|
||||
pluginName string,
|
||||
volumeSpecMap map[*volume.Spec]v1.UniqueVolumeName,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, error) {
|
||||
|
||||
return func() error {
|
||||
attachableVolumePlugin, err :=
|
||||
og.volumePluginMgr.FindAttachablePluginByName(pluginName)
|
||||
if err != nil || attachableVolumePlugin == nil {
|
||||
glog.Errorf(
|
||||
"BulkVerifyVolume.FindAttachablePluginBySpec failed for plugin %q with: %v",
|
||||
pluginName,
|
||||
err)
|
||||
return nil
|
||||
}
|
||||
|
||||
volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher()
|
||||
|
||||
if newAttacherErr != nil {
|
||||
glog.Errorf(
|
||||
"BulkVerifyVolume.NewAttacher failed for getting plugin %q with: %v",
|
||||
attachableVolumePlugin,
|
||||
newAttacherErr)
|
||||
return nil
|
||||
}
|
||||
bulkVolumeVerifier, ok := volumeAttacher.(volume.BulkVolumeVerifier)
|
||||
|
||||
if !ok {
|
||||
glog.Errorf("BulkVerifyVolume failed to type assert attacher %q", bulkVolumeVerifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
attached, bulkAttachErr := bulkVolumeVerifier.BulkVerifyVolumes(pluginNodeVolumes)
|
||||
if bulkAttachErr != nil {
|
||||
glog.Errorf("BulkVerifyVolume.BulkVerifyVolumes Error checking volumes are attached with %v", bulkAttachErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
for nodeName, volumeSpecs := range pluginNodeVolumes {
|
||||
for _, volumeSpec := range volumeSpecs {
|
||||
nodeVolumeSpecs, nodeChecked := attached[nodeName]
|
||||
|
||||
if !nodeChecked {
|
||||
glog.V(2).Infof("VerifyVolumesAreAttached.BulkVerifyVolumes failed for node %q and leaving volume %q as attached",
|
||||
nodeName,
|
||||
volumeSpec.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
check := nodeVolumeSpecs[volumeSpec]
|
||||
|
||||
if !check {
|
||||
glog.V(2).Infof("VerifyVolumesAreAttached.BulkVerifyVolumes failed for node %q and volume %q",
|
||||
nodeName,
|
||||
volumeSpec.Name())
|
||||
actualStateOfWorld.MarkVolumeAsDetached(volumeSpecMap[volumeSpec], nodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateAttachVolumeFunc(
|
||||
volumeToAttach VolumeToAttach,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) {
|
||||
// Get attacher plugin
|
||||
attachableVolumePlugin, err :=
|
||||
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToAttach.VolumeSpec)
|
||||
if err != nil || attachableVolumePlugin == nil {
|
||||
return nil, "", volumeToAttach.GenerateErrorDetailed("AttachVolume.FindAttachablePluginBySpec failed", err)
|
||||
}
|
||||
|
||||
volumeAttacher, newAttacherErr := attachableVolumePlugin.NewAttacher()
|
||||
if newAttacherErr != nil {
|
||||
return nil, attachableVolumePlugin.GetPluginName(), volumeToAttach.GenerateErrorDetailed("AttachVolume.NewAttacher failed", newAttacherErr)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
// Execute attach
|
||||
devicePath, attachErr := volumeAttacher.Attach(
|
||||
volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
|
||||
|
||||
if attachErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
eventErr, detailedErr := volumeToAttach.GenerateError("AttachVolume.Attach failed", attachErr)
|
||||
for _, pod := range volumeToAttach.ScheduledPods {
|
||||
og.recorder.Eventf(pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
|
||||
}
|
||||
return detailedErr
|
||||
}
|
||||
|
||||
glog.Infof(volumeToAttach.GenerateMsgDetailed("AttachVolume.Attach succeeded", ""))
|
||||
|
||||
// Update actual state of world
|
||||
addVolumeNodeErr := actualStateOfWorld.MarkVolumeAsAttached(
|
||||
v1.UniqueVolumeName(""), volumeToAttach.VolumeSpec, volumeToAttach.NodeName, devicePath)
|
||||
if addVolumeNodeErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToAttach.GenerateErrorDetailed("AttachVolume.MarkVolumeAsAttached failed", addVolumeNodeErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, attachableVolumePlugin.GetPluginName(), nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GetVolumePluginMgr() *volume.VolumePluginMgr {
|
||||
return og.volumePluginMgr
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateDetachVolumeFunc(
|
||||
volumeToDetach AttachedVolume,
|
||||
verifySafeToDetach bool,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) {
|
||||
var volumeName string
|
||||
var attachableVolumePlugin volume.AttachableVolumePlugin
|
||||
var pluginName string
|
||||
var err error
|
||||
|
||||
if volumeToDetach.VolumeSpec != nil {
|
||||
// Get attacher plugin
|
||||
attachableVolumePlugin, err =
|
||||
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToDetach.VolumeSpec)
|
||||
if err != nil || attachableVolumePlugin == nil {
|
||||
return nil, "", volumeToDetach.GenerateErrorDetailed("DetachVolume.FindAttachablePluginBySpec failed", err)
|
||||
}
|
||||
|
||||
volumeName, err =
|
||||
attachableVolumePlugin.GetVolumeName(volumeToDetach.VolumeSpec)
|
||||
if err != nil {
|
||||
return nil, attachableVolumePlugin.GetPluginName(), volumeToDetach.GenerateErrorDetailed("DetachVolume.GetVolumeName failed", err)
|
||||
}
|
||||
} else {
|
||||
// Get attacher plugin and the volumeName by splitting the volume unique name in case
|
||||
// there's no VolumeSpec: this happens only on attach/detach controller crash recovery
|
||||
// when a pod has been deleted during the controller downtime
|
||||
pluginName, volumeName, err = volumehelper.SplitUniqueName(volumeToDetach.VolumeName)
|
||||
if err != nil {
|
||||
return nil, pluginName, volumeToDetach.GenerateErrorDetailed("DetachVolume.SplitUniqueName failed", err)
|
||||
}
|
||||
attachableVolumePlugin, err = og.volumePluginMgr.FindAttachablePluginByName(pluginName)
|
||||
if err != nil {
|
||||
return nil, pluginName, volumeToDetach.GenerateErrorDetailed("DetachVolume.FindAttachablePluginBySpec failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
if pluginName == "" {
|
||||
pluginName = attachableVolumePlugin.GetPluginName()
|
||||
}
|
||||
|
||||
volumeDetacher, err := attachableVolumePlugin.NewDetacher()
|
||||
if err != nil {
|
||||
return nil, pluginName, volumeToDetach.GenerateErrorDetailed("DetachVolume.NewDetacher failed", err)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
var err error
|
||||
if verifySafeToDetach {
|
||||
err = og.verifyVolumeIsSafeToDetach(volumeToDetach)
|
||||
}
|
||||
if err == nil {
|
||||
err = volumeDetacher.Detach(volumeName, volumeToDetach.NodeName)
|
||||
}
|
||||
if err != nil {
|
||||
// On failure, add volume back to ReportAsAttached list
|
||||
actualStateOfWorld.AddVolumeToReportAsAttached(
|
||||
volumeToDetach.VolumeName, volumeToDetach.NodeName)
|
||||
return volumeToDetach.GenerateErrorDetailed("DetachVolume.Detach failed", err)
|
||||
}
|
||||
|
||||
glog.Infof(volumeToDetach.GenerateMsgDetailed("DetachVolume.Detach succeeded", ""))
|
||||
|
||||
// Update actual state of world
|
||||
actualStateOfWorld.MarkVolumeAsDetached(
|
||||
volumeToDetach.VolumeName, volumeToDetach.NodeName)
|
||||
|
||||
return nil
|
||||
}, pluginName, nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateMountVolumeFunc(
|
||||
waitForAttachTimeout time.Duration,
|
||||
volumeToMount VolumeToMount,
|
||||
actualStateOfWorld ActualStateOfWorldMounterUpdater,
|
||||
isRemount bool) (func() error, string, error) {
|
||||
// Get mounter plugin
|
||||
volumePlugin, err :=
|
||||
og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)
|
||||
if err != nil || volumePlugin == nil {
|
||||
return nil, "", volumeToMount.GenerateErrorDetailed("MountVolume.FindPluginBySpec failed", err)
|
||||
}
|
||||
|
||||
affinityErr := checkNodeAffinity(og, volumeToMount, volumePlugin)
|
||||
if affinityErr != nil {
|
||||
return nil, volumePlugin.GetPluginName(), affinityErr
|
||||
}
|
||||
|
||||
volumeMounter, newMounterErr := volumePlugin.NewMounter(
|
||||
volumeToMount.VolumeSpec,
|
||||
volumeToMount.Pod,
|
||||
volume.VolumeOptions{})
|
||||
if newMounterErr != nil {
|
||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.NewMounter initialization failed", newMounterErr)
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
|
||||
return nil, volumePlugin.GetPluginName(), detailedErr
|
||||
}
|
||||
|
||||
mountCheckError := checkMountOptionSupport(og, volumeToMount, volumePlugin)
|
||||
|
||||
if mountCheckError != nil {
|
||||
return nil, volumePlugin.GetPluginName(), mountCheckError
|
||||
}
|
||||
|
||||
// Get attacher, if possible
|
||||
attachableVolumePlugin, _ :=
|
||||
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToMount.VolumeSpec)
|
||||
var volumeAttacher volume.Attacher
|
||||
if attachableVolumePlugin != nil {
|
||||
volumeAttacher, _ = attachableVolumePlugin.NewAttacher()
|
||||
}
|
||||
|
||||
var fsGroup *int64
|
||||
if volumeToMount.Pod.Spec.SecurityContext != nil &&
|
||||
volumeToMount.Pod.Spec.SecurityContext.FSGroup != nil {
|
||||
fsGroup = volumeToMount.Pod.Spec.SecurityContext.FSGroup
|
||||
}
|
||||
|
||||
return func() error {
|
||||
if volumeAttacher != nil {
|
||||
// Wait for attachable volumes to finish attaching
|
||||
glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.WaitForAttach entering", fmt.Sprintf("DevicePath %q", volumeToMount.DevicePath)))
|
||||
|
||||
devicePath, err := volumeAttacher.WaitForAttach(
|
||||
volumeToMount.VolumeSpec, volumeToMount.DevicePath, volumeToMount.Pod, waitForAttachTimeout)
|
||||
if err != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("MountVolume.WaitForAttach failed", err)
|
||||
}
|
||||
|
||||
glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.WaitForAttach succeeded", ""))
|
||||
|
||||
deviceMountPath, err :=
|
||||
volumeAttacher.GetDeviceMountPath(volumeToMount.VolumeSpec)
|
||||
if err != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("MountVolume.GetDeviceMountPath failed", err)
|
||||
}
|
||||
|
||||
// Mount device to global mount path
|
||||
err = volumeAttacher.MountDevice(
|
||||
volumeToMount.VolumeSpec,
|
||||
devicePath,
|
||||
deviceMountPath)
|
||||
if err != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MountDevice failed", err)
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
|
||||
return detailedErr
|
||||
}
|
||||
|
||||
glog.Infof(volumeToMount.GenerateMsgDetailed("MountVolume.MountDevice succeeded", fmt.Sprintf("device mount path %q", deviceMountPath)))
|
||||
|
||||
// Update actual state of world to reflect volume is globally mounted
|
||||
markDeviceMountedErr := actualStateOfWorld.MarkDeviceAsMounted(
|
||||
volumeToMount.VolumeName)
|
||||
if markDeviceMountedErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("MountVolume.MarkDeviceAsMounted failed", markDeviceMountedErr)
|
||||
}
|
||||
}
|
||||
|
||||
if og.checkNodeCapabilitiesBeforeMount {
|
||||
if canMountErr := volumeMounter.CanMount(); canMountErr != nil {
|
||||
err = fmt.Errorf(
|
||||
"Verify that your node machine has the required components before attempting to mount this volume type. %s",
|
||||
canMountErr)
|
||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.CanMount failed", err)
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
|
||||
return detailedErr
|
||||
}
|
||||
}
|
||||
|
||||
// Execute mount
|
||||
mountErr := volumeMounter.SetUp(fsGroup)
|
||||
if mountErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.SetUp failed", mountErr)
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
|
||||
return detailedErr
|
||||
}
|
||||
|
||||
simpleMsg, detailedMsg := volumeToMount.GenerateMsg("MountVolume.SetUp succeeded", "")
|
||||
verbosity := glog.Level(1)
|
||||
if isRemount {
|
||||
verbosity = glog.Level(7)
|
||||
} else {
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeNormal, kevents.SuccessfulMountVolume, simpleMsg)
|
||||
}
|
||||
glog.V(verbosity).Infof(detailedMsg)
|
||||
|
||||
// Update actual state of world
|
||||
markVolMountedErr := actualStateOfWorld.MarkVolumeAsMounted(
|
||||
volumeToMount.PodName,
|
||||
volumeToMount.Pod.UID,
|
||||
volumeToMount.VolumeName,
|
||||
volumeMounter,
|
||||
volumeToMount.OuterVolumeSpecName,
|
||||
volumeToMount.VolumeGidValue)
|
||||
if markVolMountedErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("MountVolume.MarkVolumeAsMounted failed", markVolMountedErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, volumePlugin.GetPluginName(), nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateUnmountVolumeFunc(
|
||||
volumeToUnmount MountedVolume,
|
||||
actualStateOfWorld ActualStateOfWorldMounterUpdater) (func() error, string, error) {
|
||||
// Get mountable plugin
|
||||
volumePlugin, err :=
|
||||
og.volumePluginMgr.FindPluginByName(volumeToUnmount.PluginName)
|
||||
if err != nil || volumePlugin == nil {
|
||||
return nil, "", volumeToUnmount.GenerateErrorDetailed("UnmountVolume.FindPluginByName failed", err)
|
||||
}
|
||||
|
||||
volumeUnmounter, newUnmounterErr := volumePlugin.NewUnmounter(
|
||||
volumeToUnmount.InnerVolumeSpecName, volumeToUnmount.PodUID)
|
||||
if newUnmounterErr != nil {
|
||||
return nil, volumePlugin.GetPluginName(), volumeToUnmount.GenerateErrorDetailed("UnmountVolume.NewUnmounter failed", newUnmounterErr)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
// Execute unmount
|
||||
unmountErr := volumeUnmounter.TearDown()
|
||||
if unmountErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToUnmount.GenerateErrorDetailed("UnmountVolume.TearDown failed", unmountErr)
|
||||
}
|
||||
|
||||
glog.Infof(
|
||||
"UnmountVolume.TearDown succeeded for volume %q (OuterVolumeSpecName: %q) pod %q (UID: %q). InnerVolumeSpecName %q. PluginName %q, VolumeGidValue %q",
|
||||
volumeToUnmount.VolumeName,
|
||||
volumeToUnmount.OuterVolumeSpecName,
|
||||
volumeToUnmount.PodName,
|
||||
volumeToUnmount.PodUID,
|
||||
volumeToUnmount.InnerVolumeSpecName,
|
||||
volumeToUnmount.PluginName,
|
||||
volumeToUnmount.VolumeGidValue)
|
||||
|
||||
// Update actual state of world
|
||||
markVolMountedErr := actualStateOfWorld.MarkVolumeAsUnmounted(
|
||||
volumeToUnmount.PodName, volumeToUnmount.VolumeName)
|
||||
if markVolMountedErr != nil {
|
||||
// On failure, just log and exit
|
||||
glog.Errorf(volumeToUnmount.GenerateErrorDetailed("UnmountVolume.MarkVolumeAsUnmounted failed", markVolMountedErr).Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}, volumePlugin.GetPluginName(), nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateUnmountDeviceFunc(
|
||||
deviceToDetach AttachedVolume,
|
||||
actualStateOfWorld ActualStateOfWorldMounterUpdater,
|
||||
mounter mount.Interface) (func() error, string, error) {
|
||||
// Get attacher plugin
|
||||
attachableVolumePlugin, err :=
|
||||
og.volumePluginMgr.FindAttachablePluginBySpec(deviceToDetach.VolumeSpec)
|
||||
if err != nil || attachableVolumePlugin == nil {
|
||||
return nil, "", deviceToDetach.GenerateErrorDetailed("UnmountDevice.FindAttachablePluginBySpec failed", err)
|
||||
}
|
||||
|
||||
volumeDetacher, err := attachableVolumePlugin.NewDetacher()
|
||||
if err != nil {
|
||||
return nil, attachableVolumePlugin.GetPluginName(), deviceToDetach.GenerateErrorDetailed("UnmountDevice.NewDetacher failed", err)
|
||||
}
|
||||
|
||||
volumeAttacher, err := attachableVolumePlugin.NewAttacher()
|
||||
if err != nil {
|
||||
return nil, attachableVolumePlugin.GetPluginName(), deviceToDetach.GenerateErrorDetailed("UnmountDevice.NewAttacher failed", err)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
deviceMountPath, err :=
|
||||
volumeAttacher.GetDeviceMountPath(deviceToDetach.VolumeSpec)
|
||||
if err != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return deviceToDetach.GenerateErrorDetailed("GetDeviceMountPath failed", err)
|
||||
}
|
||||
refs, err := attachableVolumePlugin.GetDeviceMountRefs(deviceMountPath)
|
||||
|
||||
if err != nil || hasMountRefs(deviceMountPath, refs) {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("The device mount path %q is still mounted by other references %v", deviceMountPath, refs)
|
||||
}
|
||||
return deviceToDetach.GenerateErrorDetailed("GetDeviceMountRefs check failed", err)
|
||||
}
|
||||
// Execute unmount
|
||||
unmountDeviceErr := volumeDetacher.UnmountDevice(deviceMountPath)
|
||||
if unmountDeviceErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return deviceToDetach.GenerateErrorDetailed("UnmountDevice failed", unmountDeviceErr)
|
||||
}
|
||||
// Before logging that UnmountDevice succeeded and moving on,
|
||||
// use mounter.PathIsDevice to check if the path is a device,
|
||||
// if so use mounter.DeviceOpened to check if the device is in use anywhere
|
||||
// else on the system. Retry if it returns true.
|
||||
isDevicePath, devicePathErr := mounter.PathIsDevice(deviceToDetach.DevicePath)
|
||||
var deviceOpened bool
|
||||
var deviceOpenedErr error
|
||||
if !isDevicePath && devicePathErr == nil {
|
||||
// not a device path or path doesn't exist
|
||||
//TODO: refer to #36092
|
||||
glog.V(3).Infof("Not checking device path %s", deviceToDetach.DevicePath)
|
||||
deviceOpened = false
|
||||
} else {
|
||||
deviceOpened, deviceOpenedErr = mounter.DeviceOpened(deviceToDetach.DevicePath)
|
||||
if deviceOpenedErr != nil {
|
||||
return deviceToDetach.GenerateErrorDetailed("UnmountDevice.DeviceOpened failed", deviceOpenedErr)
|
||||
}
|
||||
}
|
||||
// The device is still in use elsewhere. Caller will log and retry.
|
||||
if deviceOpened {
|
||||
return deviceToDetach.GenerateErrorDetailed(
|
||||
"UnmountDevice failed",
|
||||
fmt.Errorf("the device is in use when it was no longer expected to be in use"))
|
||||
}
|
||||
|
||||
glog.Infof(deviceToDetach.GenerateMsgDetailed("UnmountDevice succeeded", ""))
|
||||
|
||||
// Update actual state of world
|
||||
markDeviceUnmountedErr := actualStateOfWorld.MarkDeviceAsUnmounted(
|
||||
deviceToDetach.VolumeName)
|
||||
if markDeviceUnmountedErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return deviceToDetach.GenerateErrorDetailed("MarkDeviceAsUnmounted failed", markDeviceUnmountedErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, attachableVolumePlugin.GetPluginName(), nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateVerifyControllerAttachedVolumeFunc(
|
||||
volumeToMount VolumeToMount,
|
||||
nodeName types.NodeName,
|
||||
actualStateOfWorld ActualStateOfWorldAttacherUpdater) (func() error, string, error) {
|
||||
volumePlugin, err :=
|
||||
og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)
|
||||
if err != nil || volumePlugin == nil {
|
||||
return nil, "", volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.FindPluginBySpec failed", err)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
if !volumeToMount.PluginIsAttachable {
|
||||
// If the volume does not implement the attacher interface, it is
|
||||
// assumed to be attached and the actual state of the world is
|
||||
// updated accordingly.
|
||||
|
||||
addVolumeNodeErr := actualStateOfWorld.MarkVolumeAsAttached(
|
||||
volumeToMount.VolumeName, volumeToMount.VolumeSpec, nodeName, "" /* devicePath */)
|
||||
if addVolumeNodeErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.MarkVolumeAsAttachedByUniqueVolumeName failed", addVolumeNodeErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if !volumeToMount.ReportedInUse {
|
||||
// If the given volume has not yet been added to the list of
|
||||
// VolumesInUse in the node's volume status, do not proceed, return
|
||||
// error. Caller will log and retry. The node status is updated
|
||||
// periodically by kubelet, so it may take as much as 10 seconds
|
||||
// before this clears.
|
||||
// Issue #28141 to enable on demand status updates.
|
||||
return volumeToMount.GenerateErrorDetailed("Volume has not been added to the list of VolumesInUse in the node's volume status", nil)
|
||||
}
|
||||
|
||||
// Fetch current node object
|
||||
node, fetchErr := og.kubeClient.Core().Nodes().Get(string(nodeName), metav1.GetOptions{})
|
||||
if fetchErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume failed fetching node from API server", fetchErr)
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed(
|
||||
"VerifyControllerAttachedVolume failed",
|
||||
fmt.Errorf("Node object retrieved from API server is nil"))
|
||||
}
|
||||
|
||||
for _, attachedVolume := range node.Status.VolumesAttached {
|
||||
if attachedVolume.Name == volumeToMount.VolumeName {
|
||||
addVolumeNodeErr := actualStateOfWorld.MarkVolumeAsAttached(
|
||||
v1.UniqueVolumeName(""), volumeToMount.VolumeSpec, nodeName, attachedVolume.DevicePath)
|
||||
glog.Infof(volumeToMount.GenerateMsgDetailed("Controller attach succeeded", fmt.Sprintf("device path: %q", attachedVolume.DevicePath)))
|
||||
if addVolumeNodeErr != nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("VerifyControllerAttachedVolume.MarkVolumeAsAttached failed", addVolumeNodeErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Volume not attached, return error. Caller will log and retry.
|
||||
return volumeToMount.GenerateErrorDetailed("Volume not attached according to node status", nil)
|
||||
}, volumePlugin.GetPluginName(), nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) verifyVolumeIsSafeToDetach(
|
||||
volumeToDetach AttachedVolume) error {
|
||||
// Fetch current node object
|
||||
node, fetchErr := og.kubeClient.Core().Nodes().Get(string(volumeToDetach.NodeName), metav1.GetOptions{})
|
||||
if fetchErr != nil {
|
||||
if errors.IsNotFound(fetchErr) {
|
||||
glog.Warningf(volumeToDetach.GenerateMsgDetailed("Node not found on API server. DetachVolume will skip safe to detach check", ""))
|
||||
return nil
|
||||
}
|
||||
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToDetach.GenerateErrorDetailed("DetachVolume failed fetching node from API server", fetchErr)
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
// On failure, return error. Caller will log and retry.
|
||||
return volumeToDetach.GenerateErrorDetailed(
|
||||
"DetachVolume failed fetching node from API server",
|
||||
fmt.Errorf("node object retrieved from API server is nil"))
|
||||
}
|
||||
|
||||
for _, inUseVolume := range node.Status.VolumesInUse {
|
||||
if inUseVolume == volumeToDetach.VolumeName {
|
||||
return volumeToDetach.GenerateErrorDetailed(
|
||||
"DetachVolume failed",
|
||||
fmt.Errorf("volume is still in use by node, according to Node status"))
|
||||
}
|
||||
}
|
||||
|
||||
// Volume is not marked as in use by node
|
||||
glog.Infof(volumeToDetach.GenerateMsgDetailed("Verified volume is safe to detach", ""))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (og *operationGenerator) GenerateExpandVolumeFunc(
|
||||
pvcWithResizeRequest *expandcache.PVCWithResizeRequest,
|
||||
resizeMap expandcache.VolumeResizeMap) (func() error, string, error) {
|
||||
|
||||
volumeSpec := volume.NewSpecFromPersistentVolume(pvcWithResizeRequest.PersistentVolume, false)
|
||||
|
||||
volumePlugin, err := og.volumePluginMgr.FindExpandablePluginBySpec(volumeSpec)
|
||||
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Error finding plugin for expanding volume: %q with error %v", pvcWithResizeRequest.QualifiedName(), err)
|
||||
}
|
||||
|
||||
expandFunc := func() error {
|
||||
newSize := pvcWithResizeRequest.ExpectedSize
|
||||
pvSize := pvcWithResizeRequest.PersistentVolume.Spec.Capacity[v1.ResourceStorage]
|
||||
if pvSize.Cmp(newSize) < 0 {
|
||||
updatedSize, expandErr := volumePlugin.ExpandVolumeDevice(
|
||||
volumeSpec,
|
||||
pvcWithResizeRequest.ExpectedSize,
|
||||
pvcWithResizeRequest.CurrentSize)
|
||||
|
||||
if expandErr != nil {
|
||||
glog.Errorf("Error expanding volume %q of plugin %s : %v", pvcWithResizeRequest.QualifiedName(), volumePlugin.GetPluginName(), expandErr)
|
||||
og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, expandErr.Error())
|
||||
return expandErr
|
||||
}
|
||||
newSize = updatedSize
|
||||
updateErr := resizeMap.UpdatePVSize(pvcWithResizeRequest, newSize)
|
||||
|
||||
if updateErr != nil {
|
||||
glog.V(4).Infof("Error updating PV spec capacity for volume %q with : %v", pvcWithResizeRequest.QualifiedName(), updateErr)
|
||||
og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, updateErr.Error())
|
||||
return updateErr
|
||||
}
|
||||
}
|
||||
|
||||
// No Cloudprovider resize needed, lets mark resizing as done
|
||||
if !volumePlugin.RequiresFSResize() {
|
||||
glog.V(4).Infof("Controller resizing done for PVC %s", pvcWithResizeRequest.QualifiedName())
|
||||
err := resizeMap.MarkAsResized(pvcWithResizeRequest, newSize)
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf("Error marking pvc %s as resized : %v", pvcWithResizeRequest.QualifiedName(), err)
|
||||
og.recorder.Eventf(pvcWithResizeRequest.PVC, v1.EventTypeWarning, kevents.VolumeResizeFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
return expandFunc, volumePlugin.GetPluginName(), nil
|
||||
}
|
||||
|
||||
func checkMountOptionSupport(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
|
||||
mountOptions := volume.MountOptionFromSpec(volumeToMount.VolumeSpec)
|
||||
|
||||
if len(mountOptions) > 0 && !plugin.SupportsMountOption() {
|
||||
eventErr, detailedErr := volumeToMount.GenerateError("Mount options are not supported for this volume type", nil)
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.UnsupportedMountOption, eventErr.Error())
|
||||
return detailedErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
|
||||
// This ensures that we don't mount a volume that doesn't belong to this node
|
||||
func checkNodeAffinity(og *operationGenerator, volumeToMount VolumeToMount, plugin volume.VolumePlugin) error {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentLocalVolumes) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pv := volumeToMount.VolumeSpec.PersistentVolume
|
||||
if pv != nil {
|
||||
nodeLabels, err := og.volumePluginMgr.Host.GetNodeLabels()
|
||||
if err != nil {
|
||||
return volumeToMount.GenerateErrorDetailed("Error getting node labels", err)
|
||||
}
|
||||
|
||||
err = util.CheckNodeAffinity(pv, nodeLabels)
|
||||
if err != nil {
|
||||
eventErr, detailedErr := volumeToMount.GenerateError("Storage node affinity check failed", err)
|
||||
og.recorder.Eventf(volumeToMount.Pod, v1.EventTypeWarning, kevents.FailedMountVolume, eventErr.Error())
|
||||
return detailedErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
25
vendor/k8s.io/kubernetes/pkg/volume/util/types/BUILD
generated
vendored
25
vendor/k8s.io/kubernetes/pkg/volume/util/types/BUILD
generated
vendored
|
|
@ -1,25 +0,0 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["types.go"],
|
||||
deps = ["//vendor/k8s.io/apimachinery/pkg/types:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
26
vendor/k8s.io/kubernetes/pkg/volume/util/types/types.go
generated
vendored
26
vendor/k8s.io/kubernetes/pkg/volume/util/types/types.go
generated
vendored
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package types defines types used only by volume components
|
||||
package types
|
||||
|
||||
import "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
// UniquePodName defines the type to key pods off of
|
||||
type UniquePodName types.UID
|
||||
|
||||
// UniquePVCName defines the type to key pvc off
|
||||
type UniquePVCName types.UID
|
||||
30
vendor/k8s.io/kubernetes/pkg/volume/util/volumehelper/BUILD
generated
vendored
30
vendor/k8s.io/kubernetes/pkg/volume/util/volumehelper/BUILD
generated
vendored
|
|
@ -1,30 +0,0 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["volumehelper.go"],
|
||||
deps = [
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
138
vendor/k8s.io/kubernetes/pkg/volume/util/volumehelper/volumehelper.go
generated
vendored
138
vendor/k8s.io/kubernetes/pkg/volume/util/volumehelper/volumehelper.go
generated
vendored
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package volumehelper contains consts and helper methods used by various
|
||||
// volume components (attach/detach controller, kubelet, etc.).
|
||||
package volumehelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// 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"
|
||||
|
||||
// KeepTerminatedPodVolumesAnnotation is the key of the annotation on Node
|
||||
// that decides if pod volumes are unmounted when pod is terminated
|
||||
KeepTerminatedPodVolumesAnnotation string = "volumes.kubernetes.io/keep-terminated-pod-volumes"
|
||||
|
||||
// VolumeGidAnnotationKey is the of the annotation on the PersistentVolume
|
||||
// object that specifies a supplemental GID.
|
||||
VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
|
||||
|
||||
// VolumeDynamicallyCreatedByKey is the key of the annotation on PersistentVolume
|
||||
// object created dynamically
|
||||
VolumeDynamicallyCreatedByKey = "kubernetes.io/createdby"
|
||||
)
|
||||
|
||||
// GetUniquePodName returns a unique identifier to reference a pod by
|
||||
func GetUniquePodName(pod *v1.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, volumeName string) v1.UniqueVolumeName {
|
||||
return v1.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName))
|
||||
}
|
||||
|
||||
// GetUniqueVolumeNameForNonAttachableVolume returns the unique volume name
|
||||
// for a non-attachable volume.
|
||||
func GetUniqueVolumeNameForNonAttachableVolume(
|
||||
podName types.UniquePodName, volumePlugin volume.VolumePlugin, volumeSpec *volume.Spec) v1.UniqueVolumeName {
|
||||
return v1.UniqueVolumeName(
|
||||
fmt.Sprintf("%s/%v-%s", volumePlugin.GetPluginName(), podName, volumeSpec.Name()))
|
||||
}
|
||||
|
||||
// GetUniqueVolumeNameFromSpec uses the given VolumePlugin to generate a unique
|
||||
// name representing the volume defined in the specified volume spec.
|
||||
// This returned name can be used to uniquely reference the actual backing
|
||||
// device, directory, path, etc. referenced by the given volumeSpec.
|
||||
// If the given plugin does not support the volume spec, this returns an error.
|
||||
func GetUniqueVolumeNameFromSpec(
|
||||
volumePlugin volume.VolumePlugin,
|
||||
volumeSpec *volume.Spec) (v1.UniqueVolumeName, error) {
|
||||
if volumePlugin == nil {
|
||||
return "", fmt.Errorf(
|
||||
"volumePlugin should not be nil. volumeSpec.Name=%q",
|
||||
volumeSpec.Name())
|
||||
}
|
||||
|
||||
volumeName, err := volumePlugin.GetVolumeName(volumeSpec)
|
||||
if err != nil || volumeName == "" {
|
||||
return "", fmt.Errorf(
|
||||
"failed to GetVolumeName from volumePlugin for volumeSpec %q err=%v",
|
||||
volumeSpec.Name(),
|
||||
err)
|
||||
}
|
||||
|
||||
return GetUniqueVolumeName(
|
||||
volumePlugin.GetPluginName(),
|
||||
volumeName),
|
||||
nil
|
||||
}
|
||||
|
||||
// IsPodTerminated checks if pod is terminated
|
||||
func IsPodTerminated(pod *v1.Pod, podStatus v1.PodStatus) bool {
|
||||
return podStatus.Phase == v1.PodFailed || podStatus.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(podStatus.ContainerStatuses))
|
||||
}
|
||||
|
||||
// notRunning returns true if every status is terminated or waiting, or the status list
|
||||
// is empty.
|
||||
func notRunning(statuses []v1.ContainerStatus) bool {
|
||||
for _, status := range statuses {
|
||||
if status.State.Terminated == nil && status.State.Waiting == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SplitUniqueName splits the unique name to plugin name and volume name strings. It expects the uniqueName to follow
|
||||
// the fromat plugin_name/volume_name and the plugin name must be namespaced as descibed by the plugin interface,
|
||||
// i.e. namespace/plugin containing exactly one '/'. This means the unique name will always be in the form of
|
||||
// plugin_namespace/plugin/volume_name, see k8s.io/kubernetes/pkg/volume/plugins.go VolumePlugin interface
|
||||
// description and pkg/volume/util/volumehelper/volumehelper.go GetUniqueVolumeNameFromSpec that constructs
|
||||
// the unique volume names.
|
||||
func SplitUniqueName(uniqueName v1.UniqueVolumeName) (string, string, error) {
|
||||
components := strings.SplitN(string(uniqueName), "/", 3)
|
||||
if len(components) != 3 {
|
||||
return "", "", fmt.Errorf("cannot split volume unique name %s to plugin/volume components", uniqueName)
|
||||
}
|
||||
pluginName := fmt.Sprintf("%s/%s", components[0], components[1])
|
||||
return pluginName, components[2], nil
|
||||
}
|
||||
|
||||
// NewSafeFormatAndMountFromHost creates a new SafeFormatAndMount with Mounter
|
||||
// and Exec taken from given VolumeHost.
|
||||
func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *mount.SafeFormatAndMount {
|
||||
mounter := host.GetMounter(pluginName)
|
||||
exec := host.GetExec(pluginName)
|
||||
return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue