Switch to go modules
This commit is contained in:
parent
461954facb
commit
1720059244
763 changed files with 24896 additions and 177398 deletions
97
vendor/k8s.io/kubernetes/pkg/volume/util/BUILD
generated
vendored
97
vendor/k8s.io/kubernetes/pkg/volume/util/BUILD
generated
vendored
|
|
@ -1,97 +0,0 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"atomic_writer.go",
|
||||
"attach_limit.go",
|
||||
"device_util.go",
|
||||
"device_util_linux.go",
|
||||
"device_util_unsupported.go",
|
||||
"doc.go",
|
||||
"finalizer.go",
|
||||
"io_util.go",
|
||||
"metrics.go",
|
||||
"nested_volumes.go",
|
||||
"resize_util.go",
|
||||
"util.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/volume/util",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/util/resizefs:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util/types:go_default_library",
|
||||
"//pkg/volume/util/volumepathhandler:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/storage/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
"//vendor/k8s.io/utils/strings:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"atomic_writer_test.go",
|
||||
"attach_limit_test.go",
|
||||
"device_util_linux_test.go",
|
||||
"nested_volumes_test.go",
|
||||
"resize_util_test.go",
|
||||
"util_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core/install:go_default_library",
|
||||
"//pkg/apis/core/v1/helper:go_default_library",
|
||||
"//pkg/util/slice:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux": [
|
||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/volume/util/fs:all-srcs",
|
||||
"//pkg/volume/util/nestedpendingoperations:all-srcs",
|
||||
"//pkg/volume/util/operationexecutor:all-srcs",
|
||||
"//pkg/volume/util/recyclerclient:all-srcs",
|
||||
"//pkg/volume/util/subpath:all-srcs",
|
||||
"//pkg/volume/util/types:all-srcs",
|
||||
"//pkg/volume/util/volumepathhandler:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
9
vendor/k8s.io/kubernetes/pkg/volume/util/OWNERS
generated
vendored
9
vendor/k8s.io/kubernetes/pkg/volume/util/OWNERS
generated
vendored
|
|
@ -1,9 +0,0 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- saad-ali
|
||||
reviewers:
|
||||
- saad-ali
|
||||
- rootfs
|
||||
- jingxu97
|
||||
- screeley44
|
||||
454
vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go
generated
vendored
454
vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go
generated
vendored
|
|
@ -1,454 +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 util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFileNameLength = 255
|
||||
maxPathLength = 4096
|
||||
)
|
||||
|
||||
// AtomicWriter handles atomically projecting content for a set of files into
|
||||
// a target directory.
|
||||
//
|
||||
// Note:
|
||||
//
|
||||
// 1. AtomicWriter reserves the set of pathnames starting with `..`.
|
||||
// 2. AtomicWriter offers no concurrency guarantees and must be synchronized
|
||||
// by the caller.
|
||||
//
|
||||
// The visible files in this volume are symlinks to files in the writer's data
|
||||
// directory. Actual files are stored in a hidden timestamped directory which
|
||||
// is symlinked to by the data directory. The timestamped directory and
|
||||
// data directory symlink are created in the writer's target dir. This scheme
|
||||
// allows the files to be atomically updated by changing the target of the
|
||||
// data directory symlink.
|
||||
//
|
||||
// Consumers of the target directory can monitor the ..data symlink using
|
||||
// inotify or fanotify to receive events when the content in the volume is
|
||||
// updated.
|
||||
type AtomicWriter struct {
|
||||
targetDir string
|
||||
logContext string
|
||||
}
|
||||
|
||||
// FileProjection contains file Data and access Mode
|
||||
type FileProjection struct {
|
||||
Data []byte
|
||||
Mode int32
|
||||
}
|
||||
|
||||
// NewAtomicWriter creates a new AtomicWriter configured to write to the given
|
||||
// target directory, or returns an error if the target directory does not exist.
|
||||
func NewAtomicWriter(targetDir string, logContext string) (*AtomicWriter, error) {
|
||||
_, err := os.Stat(targetDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AtomicWriter{targetDir: targetDir, logContext: logContext}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
dataDirName = "..data"
|
||||
newDataDirName = "..data_tmp"
|
||||
)
|
||||
|
||||
// Write does an atomic projection of the given payload into the writer's target
|
||||
// directory. Input paths must not begin with '..'.
|
||||
//
|
||||
// The Write algorithm is:
|
||||
//
|
||||
// 1. The payload is validated; if the payload is invalid, the function returns
|
||||
// 2. The current timestamped directory is detected by reading the data directory
|
||||
// symlink
|
||||
// 3. The old version of the volume is walked to determine whether any
|
||||
// portion of the payload was deleted and is still present on disk.
|
||||
// 4. The data in the current timestamped directory is compared to the projected
|
||||
// data to determine if an update is required.
|
||||
// 5. A new timestamped dir is created
|
||||
// 6. The payload is written to the new timestamped directory
|
||||
// 7. Symlinks and directory for new user-visible files are created (if needed).
|
||||
//
|
||||
// For example, consider the files:
|
||||
// <target-dir>/podName
|
||||
// <target-dir>/user/labels
|
||||
// <target-dir>/k8s/annotations
|
||||
//
|
||||
// The user visible files are symbolic links into the internal data directory:
|
||||
// <target-dir>/podName -> ..data/podName
|
||||
// <target-dir>/usr -> ..data/usr
|
||||
// <target-dir>/k8s -> ..data/k8s
|
||||
//
|
||||
// The data directory itself is a link to a timestamped directory with
|
||||
// the real data:
|
||||
// <target-dir>/..data -> ..2016_02_01_15_04_05.12345678/
|
||||
// 8. A symlink to the new timestamped directory ..data_tmp is created that will
|
||||
// become the new data directory
|
||||
// 9. The new data directory symlink is renamed to the data directory; rename is atomic
|
||||
// 10. Old paths are removed from the user-visible portion of the target directory
|
||||
// 11. The previous timestamped directory is removed, if it exists
|
||||
func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
||||
// (1)
|
||||
cleanPayload, err := validatePayload(payload)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: invalid payload: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (2)
|
||||
dataDirPath := path.Join(w.targetDir, dataDirName)
|
||||
oldTsDir, err := os.Readlink(dataDirPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
klog.Errorf("%s: error reading link for data directory: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
// although Readlink() returns "" on err, don't be fragile by relying on it (since it's not specified in docs)
|
||||
// empty oldTsDir indicates that it didn't exist
|
||||
oldTsDir = ""
|
||||
}
|
||||
oldTsPath := path.Join(w.targetDir, oldTsDir)
|
||||
|
||||
var pathsToRemove sets.String
|
||||
// if there was no old version, there's nothing to remove
|
||||
if len(oldTsDir) != 0 {
|
||||
// (3)
|
||||
pathsToRemove, err = w.pathsToRemove(cleanPayload, oldTsPath)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: error determining user-visible files to remove: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (4)
|
||||
if should, err := shouldWritePayload(cleanPayload, oldTsPath); err != nil {
|
||||
klog.Errorf("%s: error determining whether payload should be written to disk: %v", w.logContext, err)
|
||||
return err
|
||||
} else if !should && len(pathsToRemove) == 0 {
|
||||
klog.V(4).Infof("%s: no update required for target directory %v", w.logContext, w.targetDir)
|
||||
return nil
|
||||
} else {
|
||||
klog.V(4).Infof("%s: write required for target directory %v", w.logContext, w.targetDir)
|
||||
}
|
||||
}
|
||||
|
||||
// (5)
|
||||
tsDir, err := w.newTimestampDir()
|
||||
if err != nil {
|
||||
klog.V(4).Infof("%s: error creating new ts data directory: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
tsDirName := filepath.Base(tsDir)
|
||||
|
||||
// (6)
|
||||
if err = w.writePayloadToDir(cleanPayload, tsDir); err != nil {
|
||||
klog.Errorf("%s: error writing payload to ts data directory %s: %v", w.logContext, tsDir, err)
|
||||
return err
|
||||
}
|
||||
klog.V(4).Infof("%s: performed write of new data to ts data directory: %s", w.logContext, tsDir)
|
||||
|
||||
// (7)
|
||||
if err = w.createUserVisibleFiles(cleanPayload); err != nil {
|
||||
klog.Errorf("%s: error creating visible symlinks in %s: %v", w.logContext, w.targetDir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (8)
|
||||
newDataDirPath := path.Join(w.targetDir, newDataDirName)
|
||||
if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
|
||||
os.RemoveAll(tsDir)
|
||||
klog.Errorf("%s: error creating symbolic link for atomic update: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (9)
|
||||
if runtime.GOOS == "windows" {
|
||||
os.Remove(dataDirPath)
|
||||
err = os.Symlink(tsDirName, dataDirPath)
|
||||
os.Remove(newDataDirPath)
|
||||
} else {
|
||||
err = os.Rename(newDataDirPath, dataDirPath)
|
||||
}
|
||||
if err != nil {
|
||||
os.Remove(newDataDirPath)
|
||||
os.RemoveAll(tsDir)
|
||||
klog.Errorf("%s: error renaming symbolic link for data directory %s: %v", w.logContext, newDataDirPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (10)
|
||||
if err = w.removeUserVisiblePaths(pathsToRemove); err != nil {
|
||||
klog.Errorf("%s: error removing old visible symlinks: %v", w.logContext, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// (11)
|
||||
if len(oldTsDir) > 0 {
|
||||
if err = os.RemoveAll(oldTsPath); err != nil {
|
||||
klog.Errorf("%s: error removing old data directory %s: %v", w.logContext, oldTsDir, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePayload returns an error if any path in the payload returns a copy of the payload with the paths cleaned.
|
||||
func validatePayload(payload map[string]FileProjection) (map[string]FileProjection, error) {
|
||||
cleanPayload := make(map[string]FileProjection)
|
||||
for k, content := range payload {
|
||||
if err := validatePath(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanPayload[filepath.Clean(k)] = content
|
||||
}
|
||||
|
||||
return cleanPayload, nil
|
||||
}
|
||||
|
||||
// validatePath validates a single path, returning an error if the path is
|
||||
// invalid. paths may not:
|
||||
//
|
||||
// 1. be absolute
|
||||
// 2. contain '..' as an element
|
||||
// 3. start with '..'
|
||||
// 4. contain filenames larger than 255 characters
|
||||
// 5. be longer than 4096 characters
|
||||
func validatePath(targetPath string) error {
|
||||
// TODO: somehow unify this with the similar api validation,
|
||||
// validateVolumeSourcePath; the error semantics are just different enough
|
||||
// from this that it was time-prohibitive trying to find the right
|
||||
// refactoring to re-use.
|
||||
if targetPath == "" {
|
||||
return fmt.Errorf("invalid path: must not be empty: %q", targetPath)
|
||||
}
|
||||
if path.IsAbs(targetPath) {
|
||||
return fmt.Errorf("invalid path: must be relative path: %s", targetPath)
|
||||
}
|
||||
|
||||
if len(targetPath) > maxPathLength {
|
||||
return fmt.Errorf("invalid path: must be less than or equal to %d characters", maxPathLength)
|
||||
}
|
||||
|
||||
items := strings.Split(targetPath, string(os.PathSeparator))
|
||||
for _, item := range items {
|
||||
if item == ".." {
|
||||
return fmt.Errorf("invalid path: must not contain '..': %s", targetPath)
|
||||
}
|
||||
if len(item) > maxFileNameLength {
|
||||
return fmt.Errorf("invalid path: filenames must be less than or equal to %d characters", maxFileNameLength)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(items[0], "..") && len(items[0]) > 2 {
|
||||
return fmt.Errorf("invalid path: must not start with '..': %s", targetPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldWritePayload returns whether the payload should be written to disk.
|
||||
func shouldWritePayload(payload map[string]FileProjection, oldTsDir string) (bool, error) {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
shouldWrite, err := shouldWriteFile(path.Join(oldTsDir, userVisiblePath), fileProjection.Data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if shouldWrite {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// shouldWriteFile returns whether a new version of a file should be written to disk.
|
||||
func shouldWriteFile(path string, content []byte) (bool, error) {
|
||||
_, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
contentOnFs, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return (bytes.Compare(content, contentOnFs) != 0), nil
|
||||
}
|
||||
|
||||
// pathsToRemove walks the current version of the data directory and
|
||||
// determines which paths should be removed (if any) after the payload is
|
||||
// written to the target directory.
|
||||
func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection, oldTsDir string) (sets.String, error) {
|
||||
paths := sets.NewString()
|
||||
visitor := func(path string, info os.FileInfo, err error) error {
|
||||
relativePath := strings.TrimPrefix(path, oldTsDir)
|
||||
relativePath = strings.TrimPrefix(relativePath, string(os.PathSeparator))
|
||||
if relativePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
paths.Insert(relativePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(oldTsDir, visitor)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klog.V(5).Infof("%s: current paths: %+v", w.targetDir, paths.List())
|
||||
|
||||
newPaths := sets.NewString()
|
||||
for file := range payload {
|
||||
// add all subpaths for the payload to the set of new paths
|
||||
// to avoid attempting to remove non-empty dirs
|
||||
for subPath := file; subPath != ""; {
|
||||
newPaths.Insert(subPath)
|
||||
subPath, _ = filepath.Split(subPath)
|
||||
subPath = strings.TrimSuffix(subPath, string(os.PathSeparator))
|
||||
}
|
||||
}
|
||||
klog.V(5).Infof("%s: new paths: %+v", w.targetDir, newPaths.List())
|
||||
|
||||
result := paths.Difference(newPaths)
|
||||
klog.V(5).Infof("%s: paths to remove: %+v", w.targetDir, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTimestampDir creates a new timestamp directory
|
||||
func (w *AtomicWriter) newTimestampDir() (string, error) {
|
||||
tsDir, err := ioutil.TempDir(w.targetDir, time.Now().UTC().Format("..2006_01_02_15_04_05."))
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to create new temp directory: %v", w.logContext, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 0755 permissions are needed to allow 'group' and 'other' to recurse the
|
||||
// directory tree. do a chmod here to ensure that permissions are set correctly
|
||||
// regardless of the process' umask.
|
||||
err = os.Chmod(tsDir, 0755)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to set mode on new temp directory: %v", w.logContext, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tsDir, nil
|
||||
}
|
||||
|
||||
// writePayloadToDir writes the given payload to the given directory. The
|
||||
// directory must exist.
|
||||
func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir string) error {
|
||||
for userVisiblePath, fileProjection := range payload {
|
||||
content := fileProjection.Data
|
||||
mode := os.FileMode(fileProjection.Mode)
|
||||
fullPath := path.Join(dir, userVisiblePath)
|
||||
baseDir, _ := filepath.Split(fullPath)
|
||||
|
||||
err := os.MkdirAll(baseDir, os.ModePerm)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to create directory %s: %v", w.logContext, baseDir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(fullPath, content, mode)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to write file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
||||
return err
|
||||
}
|
||||
// Chmod is needed because ioutil.WriteFile() ends up calling
|
||||
// open(2) to create the file, so the final mode used is "mode &
|
||||
// ~umask". But we want to make sure the specified mode is used
|
||||
// in the file no matter what the umask is.
|
||||
err = os.Chmod(fullPath, mode)
|
||||
if err != nil {
|
||||
klog.Errorf("%s: unable to write file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createUserVisibleFiles creates the relative symlinks for all the
|
||||
// files configured in the payload. If the directory in a file path does not
|
||||
// exist, it is created.
|
||||
//
|
||||
// Viz:
|
||||
// For files: "bar", "foo/bar", "baz/bar", "foo/baz/blah"
|
||||
// the following symlinks are created:
|
||||
// bar -> ..data/bar
|
||||
// foo -> ..data/foo
|
||||
// baz -> ..data/baz
|
||||
func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) error {
|
||||
for userVisiblePath := range payload {
|
||||
slashpos := strings.Index(userVisiblePath, string(os.PathSeparator))
|
||||
if slashpos == -1 {
|
||||
slashpos = len(userVisiblePath)
|
||||
}
|
||||
linkname := userVisiblePath[:slashpos]
|
||||
_, err := os.Readlink(path.Join(w.targetDir, linkname))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// The link into the data directory for this path doesn't exist; create it
|
||||
visibleFile := path.Join(w.targetDir, linkname)
|
||||
dataDirFile := path.Join(dataDirName, linkname)
|
||||
|
||||
err = os.Symlink(dataDirFile, visibleFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeUserVisiblePaths removes the set of paths from the user-visible
|
||||
// portion of the writer's target directory.
|
||||
func (w *AtomicWriter) removeUserVisiblePaths(paths sets.String) error {
|
||||
ps := string(os.PathSeparator)
|
||||
var lasterr error
|
||||
for p := range paths {
|
||||
// only remove symlinks from the volume root directory (i.e. items that don't contain '/')
|
||||
if strings.Contains(p, ps) {
|
||||
continue
|
||||
}
|
||||
if err := os.Remove(path.Join(w.targetDir, p)); err != nil {
|
||||
klog.Errorf("%s: error pruning old user-visible path %s: %v", w.logContext, p, err)
|
||||
lasterr = err
|
||||
}
|
||||
}
|
||||
|
||||
return lasterr
|
||||
}
|
||||
70
vendor/k8s.io/kubernetes/pkg/volume/util/attach_limit.go
generated
vendored
70
vendor/k8s.io/kubernetes/pkg/volume/util/attach_limit.go
generated
vendored
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// This file is a common place holder for volume limit utility constants
|
||||
// shared between volume package and scheduler
|
||||
|
||||
const (
|
||||
// EBSVolumeLimitKey resource name that will store volume limits for EBS
|
||||
EBSVolumeLimitKey = "attachable-volumes-aws-ebs"
|
||||
// EBSNitroLimitRegex finds nitro instance types with different limit than EBS defaults
|
||||
EBSNitroLimitRegex = "^[cmr]5.*|t3|z1d"
|
||||
// DefaultMaxEBSVolumes is the limit for volumes attached to an instance.
|
||||
// Amazon recommends no more than 40; the system root volume uses at least one.
|
||||
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html#linux-specific-volume-limits
|
||||
DefaultMaxEBSVolumes = 39
|
||||
// DefaultMaxEBSNitroVolumeLimit is default EBS volume limit on m5 and c5 instances
|
||||
DefaultMaxEBSNitroVolumeLimit = 25
|
||||
// AzureVolumeLimitKey stores resource name that will store volume limits for Azure
|
||||
AzureVolumeLimitKey = "attachable-volumes-azure-disk"
|
||||
// GCEVolumeLimitKey stores resource name that will store volume limits for GCE node
|
||||
GCEVolumeLimitKey = "attachable-volumes-gce-pd"
|
||||
|
||||
// CinderVolumeLimitKey contains Volume limit key for Cinder
|
||||
CinderVolumeLimitKey = "attachable-volumes-cinder"
|
||||
// DefaultMaxCinderVolumes defines the maximum number of PD Volumes for Cinder
|
||||
// For Openstack we are keeping this to a high enough value so as depending on backend
|
||||
// cluster admins can configure it.
|
||||
DefaultMaxCinderVolumes = 256
|
||||
|
||||
// CSIAttachLimitPrefix defines prefix used for CSI volumes
|
||||
CSIAttachLimitPrefix = "attachable-volumes-csi-"
|
||||
|
||||
// ResourceNameLengthLimit stores maximum allowed Length for a ResourceName
|
||||
ResourceNameLengthLimit = 63
|
||||
)
|
||||
|
||||
// GetCSIAttachLimitKey returns limit key used for CSI volumes
|
||||
func GetCSIAttachLimitKey(driverName string) string {
|
||||
csiPrefixLength := len(CSIAttachLimitPrefix)
|
||||
totalkeyLength := csiPrefixLength + len(driverName)
|
||||
if totalkeyLength >= ResourceNameLengthLimit {
|
||||
charsFromDriverName := driverName[:23]
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(driverName))
|
||||
hashed := hex.EncodeToString(hash.Sum(nil))
|
||||
hashed = hashed[:16]
|
||||
return CSIAttachLimitPrefix + charsFromDriverName + hashed
|
||||
}
|
||||
return CSIAttachLimitPrefix + driverName
|
||||
}
|
||||
34
vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go
generated
vendored
34
vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go
generated
vendored
|
|
@ -1,34 +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 util
|
||||
|
||||
//DeviceUtil is a util for common device methods
|
||||
type DeviceUtil interface {
|
||||
FindMultipathDeviceForDevice(disk string) string
|
||||
FindSlaveDevicesOnMultipath(disk string) []string
|
||||
GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error)
|
||||
FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error)
|
||||
}
|
||||
|
||||
type deviceHandler struct {
|
||||
getIo IoUtil
|
||||
}
|
||||
|
||||
//NewDeviceHandler Create a new IoHandler implementation
|
||||
func NewDeviceHandler(io IoUtil) DeviceUtil {
|
||||
return &deviceHandler{getIo: io}
|
||||
}
|
||||
299
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go
generated
vendored
299
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go
generated
vendored
|
|
@ -1,299 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
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 util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent
|
||||
func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
|
||||
io := handler.getIo
|
||||
disk, err := findDeviceForPath(device, io)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
sysPath := "/sys/block/"
|
||||
if dirs, err := io.ReadDir(sysPath); err == nil {
|
||||
for _, f := range dirs {
|
||||
name := f.Name()
|
||||
if strings.HasPrefix(name, "dm-") {
|
||||
if _, err1 := io.Lstat(sysPath + name + "/slaves/" + disk); err1 == nil {
|
||||
return "/dev/" + name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findDeviceForPath Find the underlaying disk for a linked path such as /dev/disk/by-path/XXXX or /dev/mapper/XXXX
|
||||
// will return sdX or hdX etc, if /dev/sdX is passed in then sdX will be returned
|
||||
func findDeviceForPath(path string, io IoUtil) (string, error) {
|
||||
devicePath, err := io.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// if path /dev/hdX split into "", "dev", "hdX" then we will
|
||||
// return just the last part
|
||||
parts := strings.Split(devicePath, "/")
|
||||
if len(parts) == 3 && strings.HasPrefix(parts[1], "dev") {
|
||||
return parts[2], nil
|
||||
}
|
||||
return "", errors.New("Illegal path for device " + devicePath)
|
||||
}
|
||||
|
||||
// FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices
|
||||
// which are managed by the devicemapper dm-1.
|
||||
func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string {
|
||||
var devices []string
|
||||
io := handler.getIo
|
||||
// Split path /dev/dm-1 into "", "dev", "dm-1"
|
||||
parts := strings.Split(dm, "/")
|
||||
if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") {
|
||||
return devices
|
||||
}
|
||||
disk := parts[2]
|
||||
slavesPath := path.Join("/sys/block/", disk, "/slaves/")
|
||||
if files, err := io.ReadDir(slavesPath); err == nil {
|
||||
for _, f := range files {
|
||||
devices = append(devices, path.Join("/dev/", f.Name()))
|
||||
}
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
// GetISCSIPortalHostMapForTarget given a target iqn, find all the scsi hosts logged into
|
||||
// that target. Returns a map of iSCSI portals (string) to SCSI host numbers (integers).
|
||||
// For example: {
|
||||
// "192.168.30.7:3260": 2,
|
||||
// "192.168.30.8:3260": 3,
|
||||
// }
|
||||
func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
|
||||
portalHostMap := make(map[string]int)
|
||||
io := handler.getIo
|
||||
|
||||
// Iterate over all the iSCSI hosts in sysfs
|
||||
sysPath := "/sys/class/iscsi_host"
|
||||
hostDirs, err := io.ReadDir(sysPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return portalHostMap, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
for _, hostDir := range hostDirs {
|
||||
// iSCSI hosts are always of the format "host%d"
|
||||
// See drivers/scsi/hosts.c in Linux
|
||||
hostName := hostDir.Name()
|
||||
if !strings.HasPrefix(hostName, "host") {
|
||||
continue
|
||||
}
|
||||
hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
|
||||
if err != nil {
|
||||
klog.Errorf("Could not get number from iSCSI host: %s", hostName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate over the children of the iscsi_host device
|
||||
// We are looking for the associated session
|
||||
devicePath := sysPath + "/" + hostName + "/device"
|
||||
deviceDirs, err := io.ReadDir(devicePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, deviceDir := range deviceDirs {
|
||||
// Skip over files that aren't the session
|
||||
// Sessions are of the format "session%u"
|
||||
// See drivers/scsi/scsi_transport_iscsi.c in Linux
|
||||
sessionName := deviceDir.Name()
|
||||
if !strings.HasPrefix(sessionName, "session") {
|
||||
continue
|
||||
}
|
||||
|
||||
sessionPath := devicePath + "/" + sessionName
|
||||
|
||||
// Read the target name for the iSCSI session
|
||||
targetNamePath := sessionPath + "/iscsi_session/" + sessionName + "/targetname"
|
||||
targetName, err := io.ReadFile(targetNamePath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ignore hosts that don't matchthe target we were looking for.
|
||||
if strings.TrimSpace(string(targetName)) != targetIqn {
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate over the children of the iSCSI session looking
|
||||
// for the iSCSI connection.
|
||||
dirs2, err := io.ReadDir(sessionPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
|
||||
continue
|
||||
}
|
||||
for _, dir2 := range dirs2 {
|
||||
// Skip over files that aren't the connection
|
||||
// Connections are of the format "connection%d:%u"
|
||||
// See drivers/scsi/scsi_transport_iscsi.c in Linux
|
||||
dirName := dir2.Name()
|
||||
if !strings.HasPrefix(dirName, "connection") {
|
||||
continue
|
||||
}
|
||||
|
||||
connectionPath := sessionPath + "/" + dirName + "/iscsi_connection/" + dirName
|
||||
|
||||
// Read the current and persistent portal information for the connection.
|
||||
addrPath := connectionPath + "/address"
|
||||
addr, err := io.ReadFile(addrPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
portPath := connectionPath + "/port"
|
||||
port, err := io.ReadFile(portPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
persistentAddrPath := connectionPath + "/persistent_address"
|
||||
persistentAddr, err := io.ReadFile(persistentAddrPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
persistentPortPath := connectionPath + "/persistent_port"
|
||||
persistentPort, err := io.ReadFile(persistentPortPath)
|
||||
if err != nil {
|
||||
klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add entries to the map for both the current and persistent portals
|
||||
// pointing to the SCSI host for those connections
|
||||
portal := strings.TrimSpace(string(addr)) + ":" +
|
||||
strings.TrimSpace(string(port))
|
||||
portalHostMap[portal] = hostNumber
|
||||
|
||||
persistentPortal := strings.TrimSpace(string(persistentAddr)) + ":" +
|
||||
strings.TrimSpace(string(persistentPort))
|
||||
portalHostMap[persistentPortal] = hostNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return portalHostMap, nil
|
||||
}
|
||||
|
||||
// FindDevicesForISCSILun given an iqn, and lun number, find all the devices
|
||||
// corresponding to that LUN.
|
||||
func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
|
||||
devices := make([]string, 0)
|
||||
io := handler.getIo
|
||||
|
||||
// Iterate over all the iSCSI hosts in sysfs
|
||||
sysPath := "/sys/class/iscsi_host"
|
||||
hostDirs, err := io.ReadDir(sysPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, hostDir := range hostDirs {
|
||||
// iSCSI hosts are always of the format "host%d"
|
||||
// See drivers/scsi/hosts.c in Linux
|
||||
hostName := hostDir.Name()
|
||||
if !strings.HasPrefix(hostName, "host") {
|
||||
continue
|
||||
}
|
||||
hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
|
||||
if err != nil {
|
||||
klog.Errorf("Could not get number from iSCSI host: %s", hostName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate over the children of the iscsi_host device
|
||||
// We are looking for the associated session
|
||||
devicePath := sysPath + "/" + hostName + "/device"
|
||||
deviceDirs, err := io.ReadDir(devicePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, deviceDir := range deviceDirs {
|
||||
// Skip over files that aren't the session
|
||||
// Sessions are of the format "session%u"
|
||||
// See drivers/scsi/scsi_transport_iscsi.c in Linux
|
||||
sessionName := deviceDir.Name()
|
||||
if !strings.HasPrefix(sessionName, "session") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the target name for the iSCSI session
|
||||
targetNamePath := devicePath + "/" + sessionName + "/iscsi_session/" + sessionName + "/targetname"
|
||||
targetName, err := io.ReadFile(targetNamePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only if the session matches the target we were looking for,
|
||||
// add it to the map
|
||||
if strings.TrimSpace(string(targetName)) != targetIqn {
|
||||
continue
|
||||
}
|
||||
|
||||
// The list of block devices on the scsi bus will be in a
|
||||
// directory called "target%d:%d:%d".
|
||||
// See drivers/scsi/scsi_scan.c in Linux
|
||||
// We assume the channel/bus and device/controller are always zero for iSCSI
|
||||
targetPath := devicePath + "/" + sessionName + fmt.Sprintf("/target%d:0:0", hostNumber)
|
||||
|
||||
// The block device for a given lun will be "%d:%d:%d:%d" --
|
||||
// host:channel:bus:LUN
|
||||
blockDevicePath := targetPath + fmt.Sprintf("/%d:0:0:%d", hostNumber, lun)
|
||||
|
||||
// If the LUN doesn't exist on this bus, continue on
|
||||
_, err = io.Lstat(blockDevicePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the block directory, there should only be one child --
|
||||
// the block device "sd*"
|
||||
path := blockDevicePath + "/block"
|
||||
dirs, err := io.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if 0 < len(dirs) {
|
||||
devices = append(devices, dirs[0].Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
42
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go
generated
vendored
42
vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go
generated
vendored
|
|
@ -1,42 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
/*
|
||||
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 util
|
||||
|
||||
// FindMultipathDeviceForDevice unsupported returns ""
|
||||
func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindSlaveDevicesOnMultipath unsupported returns ""
|
||||
func (handler *deviceHandler) FindSlaveDevicesOnMultipath(disk string) []string {
|
||||
out := []string{}
|
||||
return out
|
||||
}
|
||||
|
||||
// GetISCSIPortalHostMapForTarget unsupported returns nil
|
||||
func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
|
||||
portalHostMap := make(map[string]int)
|
||||
return portalHostMap, nil
|
||||
}
|
||||
|
||||
// FindDevicesForISCSILun unsupported returns nil
|
||||
func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
|
||||
devices := []string{}
|
||||
return devices, nil
|
||||
}
|
||||
18
vendor/k8s.io/kubernetes/pkg/volume/util/doc.go
generated
vendored
18
vendor/k8s.io/kubernetes/pkg/volume/util/doc.go
generated
vendored
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package util contains utility code for use by volume plugins.
|
||||
package util // import "k8s.io/kubernetes/pkg/volume/util"
|
||||
25
vendor/k8s.io/kubernetes/pkg/volume/util/finalizer.go
generated
vendored
25
vendor/k8s.io/kubernetes/pkg/volume/util/finalizer.go
generated
vendored
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
const (
|
||||
// PVCProtectionFinalizer is the name of finalizer on PVCs that have a running pod.
|
||||
PVCProtectionFinalizer = "kubernetes.io/pvc-protection"
|
||||
|
||||
// PVProtectionFinalizer is the name of finalizer on PVs that are bound by PVCs
|
||||
PVProtectionFinalizer = "kubernetes.io/pv-protection"
|
||||
)
|
||||
51
vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go
generated
vendored
51
vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go
generated
vendored
|
|
@ -1,51 +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 util
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// IoUtil is a mockable util for common IO operations
|
||||
type IoUtil interface {
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
Lstat(name string) (os.FileInfo, error)
|
||||
EvalSymlinks(path string) (string, error)
|
||||
}
|
||||
|
||||
type osIOHandler struct{}
|
||||
|
||||
//NewIOHandler Create a new IoHandler implementation
|
||||
func NewIOHandler() IoUtil {
|
||||
return &osIOHandler{}
|
||||
}
|
||||
|
||||
func (handler *osIOHandler) ReadFile(filename string) ([]byte, error) {
|
||||
return ioutil.ReadFile(filename)
|
||||
}
|
||||
func (handler *osIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
return ioutil.ReadDir(dirname)
|
||||
}
|
||||
func (handler *osIOHandler) Lstat(name string) (os.FileInfo, error) {
|
||||
return os.Lstat(name)
|
||||
}
|
||||
func (handler *osIOHandler) EvalSymlinks(path string) (string, error) {
|
||||
return filepath.EvalSymlinks(path)
|
||||
}
|
||||
78
vendor/k8s.io/kubernetes/pkg/volume/util/metrics.go
generated
vendored
78
vendor/k8s.io/kubernetes/pkg/volume/util/metrics.go
generated
vendored
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
var storageOperationMetric = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "storage_operation_duration_seconds",
|
||||
Help: "Storage operation duration",
|
||||
Buckets: []float64{.1, .25, .5, 1, 2.5, 5, 10, 15, 25, 50},
|
||||
},
|
||||
[]string{"volume_plugin", "operation_name"},
|
||||
)
|
||||
|
||||
var storageOperationErrorMetric = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "storage_operation_errors_total",
|
||||
Help: "Storage operation errors",
|
||||
},
|
||||
[]string{"volume_plugin", "operation_name"},
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMetrics()
|
||||
}
|
||||
|
||||
func registerMetrics() {
|
||||
prometheus.MustRegister(storageOperationMetric)
|
||||
prometheus.MustRegister(storageOperationErrorMetric)
|
||||
}
|
||||
|
||||
// OperationCompleteHook returns a hook to call when an operation is completed
|
||||
func OperationCompleteHook(plugin, operationName string) func(*error) {
|
||||
requestTime := time.Now()
|
||||
opComplete := func(err *error) {
|
||||
timeTaken := time.Since(requestTime).Seconds()
|
||||
// Create metric with operation name and plugin name
|
||||
if *err != nil {
|
||||
storageOperationErrorMetric.WithLabelValues(plugin, operationName).Inc()
|
||||
} else {
|
||||
storageOperationMetric.WithLabelValues(plugin, operationName).Observe(timeTaken)
|
||||
}
|
||||
}
|
||||
return opComplete
|
||||
}
|
||||
|
||||
// GetFullQualifiedPluginNameForVolume returns full qualified plugin name for
|
||||
// given volume. For CSI plugin, it appends plugin driver name at the end of
|
||||
// plugin name, e.g. kubernetes.io/csi:csi-hostpath. It helps to distinguish
|
||||
// between metrics emitted for CSI volumes which may be handled by different
|
||||
// CSI plugin drivers.
|
||||
func GetFullQualifiedPluginNameForVolume(pluginName string, spec *volume.Spec) string {
|
||||
if spec != nil && spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil {
|
||||
return fmt.Sprintf("%s:%s", pluginName, spec.PersistentVolume.Spec.CSI.Driver)
|
||||
}
|
||||
return pluginName
|
||||
}
|
||||
99
vendor/k8s.io/kubernetes/pkg/volume/util/nested_volumes.go
generated
vendored
99
vendor/k8s.io/kubernetes/pkg/volume/util/nested_volumes.go
generated
vendored
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/api/core/v1"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// getNestedMountpoints returns a list of mountpoint directories that should be created
|
||||
// for the volume indicated by name.
|
||||
// note: the returned list is relative to baseDir
|
||||
func getNestedMountpoints(name, baseDir string, pod v1.Pod) ([]string, error) {
|
||||
var retval []string
|
||||
checkContainer := func(container *v1.Container) error {
|
||||
var allMountPoints []string // all mount points in this container
|
||||
var myMountPoints []string // mount points that match name
|
||||
for _, vol := range container.VolumeMounts {
|
||||
cleaned := filepath.Clean(vol.MountPath)
|
||||
allMountPoints = append(allMountPoints, cleaned)
|
||||
if vol.Name == name {
|
||||
myMountPoints = append(myMountPoints, cleaned)
|
||||
}
|
||||
}
|
||||
sort.Strings(allMountPoints)
|
||||
parentPrefix := ".." + string(os.PathSeparator)
|
||||
// Examine each place where this volume is mounted
|
||||
for _, myMountPoint := range myMountPoints {
|
||||
if strings.HasPrefix(myMountPoint, parentPrefix) {
|
||||
// Don't let a container trick us into creating directories outside of its rootfs
|
||||
return fmt.Errorf("Invalid container mount point %v", myMountPoint)
|
||||
}
|
||||
myMPSlash := myMountPoint + string(os.PathSeparator)
|
||||
// The previously found nested mountpoint (or "" if none found yet)
|
||||
prevNestedMP := ""
|
||||
// examine each mount point to see if it's nested beneath this volume
|
||||
// (but skip any that are double-nested beneath this volume)
|
||||
// For example, if this volume is mounted as /dir and other volumes are mounted
|
||||
// as /dir/nested and /dir/nested/other, only create /dir/nested.
|
||||
for _, mp := range allMountPoints {
|
||||
if !strings.HasPrefix(mp, myMPSlash) {
|
||||
continue // skip -- not nested beneath myMountPoint
|
||||
}
|
||||
if prevNestedMP != "" && strings.HasPrefix(mp, prevNestedMP) {
|
||||
continue // skip -- double nested beneath myMountPoint
|
||||
}
|
||||
// since this mount point is nested, remember it so that we can check that following ones aren't nested beneath this one
|
||||
prevNestedMP = mp + string(os.PathSeparator)
|
||||
retval = append(retval, mp[len(myMPSlash):])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
if err := checkContainer(&container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if err := checkContainer(&container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
// MakeNestedMountpoints creates mount points in baseDir for volumes mounted beneath name
|
||||
func MakeNestedMountpoints(name, baseDir string, pod v1.Pod) error {
|
||||
dirs, err := getNestedMountpoints(name, baseDir, pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
err := os.MkdirAll(path.Join(baseDir, dir), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create nested volume mountpoints: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
139
vendor/k8s.io/kubernetes/pkg/volume/util/resize_util.go
generated
vendored
139
vendor/k8s.io/kubernetes/pkg/volume/util/resize_util.go
generated
vendored
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/util/resizefs"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
var (
|
||||
knownResizeConditions = map[v1.PersistentVolumeClaimConditionType]bool{
|
||||
v1.PersistentVolumeClaimFileSystemResizePending: true,
|
||||
v1.PersistentVolumeClaimResizing: true,
|
||||
}
|
||||
)
|
||||
|
||||
type resizeProcessStatus struct {
|
||||
condition v1.PersistentVolumeClaimCondition
|
||||
processed bool
|
||||
}
|
||||
|
||||
// ClaimToClaimKey return namespace/name string for pvc
|
||||
func ClaimToClaimKey(claim *v1.PersistentVolumeClaim) string {
|
||||
return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name)
|
||||
}
|
||||
|
||||
// MarkFSResizeFinished marks file system resizing as done
|
||||
func MarkFSResizeFinished(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
capacity v1.ResourceList,
|
||||
kubeClient clientset.Interface) error {
|
||||
newPVC := pvc.DeepCopy()
|
||||
newPVC.Status.Capacity = capacity
|
||||
newPVC = MergeResizeConditionOnPVC(newPVC, []v1.PersistentVolumeClaimCondition{})
|
||||
_, err := PatchPVCStatus(pvc /*oldPVC*/, newPVC, kubeClient)
|
||||
return err
|
||||
}
|
||||
|
||||
// PatchPVCStatus updates PVC status using PATCH verb
|
||||
func PatchPVCStatus(
|
||||
oldPVC *v1.PersistentVolumeClaim,
|
||||
newPVC *v1.PersistentVolumeClaim,
|
||||
kubeClient clientset.Interface) (*v1.PersistentVolumeClaim, error) {
|
||||
pvcName := oldPVC.Name
|
||||
|
||||
oldData, err := json.Marshal(oldPVC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PatchPVCStatus.Failed to marshal oldData for pvc %q with %v", pvcName, err)
|
||||
}
|
||||
|
||||
newData, err := json.Marshal(newPVC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PatchPVCStatus.Failed to marshal newData for pvc %q with %v", pvcName, err)
|
||||
}
|
||||
|
||||
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, oldPVC)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PatchPVCStatus.Failed to CreateTwoWayMergePatch for pvc %q with %v ", pvcName, err)
|
||||
}
|
||||
updatedClaim, updateErr := kubeClient.CoreV1().PersistentVolumeClaims(oldPVC.Namespace).
|
||||
Patch(pvcName, types.StrategicMergePatchType, patchBytes, "status")
|
||||
if updateErr != nil {
|
||||
return nil, fmt.Errorf("PatchPVCStatus.Failed to patch PVC %q with %v", pvcName, updateErr)
|
||||
}
|
||||
return updatedClaim, nil
|
||||
}
|
||||
|
||||
// MergeResizeConditionOnPVC updates pvc with requested resize conditions
|
||||
// leaving other conditions untouched.
|
||||
func MergeResizeConditionOnPVC(
|
||||
pvc *v1.PersistentVolumeClaim,
|
||||
resizeConditions []v1.PersistentVolumeClaimCondition) *v1.PersistentVolumeClaim {
|
||||
resizeConditionMap := map[v1.PersistentVolumeClaimConditionType]*resizeProcessStatus{}
|
||||
|
||||
for _, condition := range resizeConditions {
|
||||
resizeConditionMap[condition.Type] = &resizeProcessStatus{condition, false}
|
||||
}
|
||||
|
||||
oldConditions := pvc.Status.Conditions
|
||||
newConditions := []v1.PersistentVolumeClaimCondition{}
|
||||
for _, condition := range oldConditions {
|
||||
// If Condition is of not resize type, we keep it.
|
||||
if _, ok := knownResizeConditions[condition.Type]; !ok {
|
||||
newConditions = append(newConditions, condition)
|
||||
continue
|
||||
}
|
||||
|
||||
if newCondition, ok := resizeConditionMap[condition.Type]; ok {
|
||||
if newCondition.condition.Status != condition.Status {
|
||||
newConditions = append(newConditions, newCondition.condition)
|
||||
} else {
|
||||
newConditions = append(newConditions, condition)
|
||||
}
|
||||
newCondition.processed = true
|
||||
}
|
||||
}
|
||||
|
||||
// append all unprocessed conditions
|
||||
for _, newCondition := range resizeConditionMap {
|
||||
if !newCondition.processed {
|
||||
newConditions = append(newConditions, newCondition.condition)
|
||||
}
|
||||
}
|
||||
pvc.Status.Conditions = newConditions
|
||||
return pvc
|
||||
}
|
||||
|
||||
// GenericResizeFS : call generic filesystem resizer for plugins that don't have any special filesystem resize requirements
|
||||
func GenericResizeFS(host volume.VolumeHost, pluginName, devicePath, deviceMountPath string) (bool, error) {
|
||||
mounter := host.GetMounter(pluginName)
|
||||
diskFormatter := &mount.SafeFormatAndMount{
|
||||
Interface: mounter,
|
||||
Exec: host.GetExec(pluginName),
|
||||
}
|
||||
resizer := resizefs.NewResizeFs(diskFormatter)
|
||||
return resizer.Resize(devicePath, deviceMountPath)
|
||||
}
|
||||
532
vendor/k8s.io/kubernetes/pkg/volume/util/util.go
generated
vendored
532
vendor/k8s.io/kubernetes/pkg/volume/util/util.go
generated
vendored
|
|
@ -1,532 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
storage "k8s.io/api/storage/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
||||
utilstrings "k8s.io/utils/strings"
|
||||
)
|
||||
|
||||
const (
|
||||
readyFileName = "ready"
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// IsReady checks for the existence of a regular file
|
||||
// called 'ready' in the given directory and returns
|
||||
// true if that file exists.
|
||||
func IsReady(dir string) bool {
|
||||
readyFile := path.Join(dir, readyFileName)
|
||||
s, err := os.Stat(readyFile)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !s.Mode().IsRegular() {
|
||||
klog.Errorf("ready-file is not a file: %s", readyFile)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// SetReady creates a file called 'ready' in the given
|
||||
// directory. It logs an error if the file cannot be
|
||||
// created.
|
||||
func SetReady(dir string) {
|
||||
if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
|
||||
klog.Errorf("Can't mkdir %s: %v", dir, err)
|
||||
return
|
||||
}
|
||||
|
||||
readyFile := path.Join(dir, readyFileName)
|
||||
file, err := os.Create(readyFile)
|
||||
if err != nil {
|
||||
klog.Errorf("Can't touch %s: %v", readyFile, err)
|
||||
return
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
// GetSecretForPod locates secret by name in the pod's namespace and returns secret map
|
||||
func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
|
||||
secret := make(map[string]string)
|
||||
if kubeClient == nil {
|
||||
return secret, fmt.Errorf("Cannot get kube client")
|
||||
}
|
||||
secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return secret, err
|
||||
}
|
||||
for name, data := range secrets.Data {
|
||||
secret[name] = string(data)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map
|
||||
func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
|
||||
secret := make(map[string]string)
|
||||
if kubeClient == nil {
|
||||
return secret, fmt.Errorf("Cannot get kube client")
|
||||
}
|
||||
secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return secret, err
|
||||
}
|
||||
if secrets.Type != v1.SecretType(volumePluginName) {
|
||||
return secret, fmt.Errorf("Cannot get secret of type %s", volumePluginName)
|
||||
}
|
||||
for name, data := range secrets.Data {
|
||||
secret[name] = string(data)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// GetClassForVolume locates storage class by persistent volume
|
||||
func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) {
|
||||
if kubeClient == nil {
|
||||
return nil, fmt.Errorf("Cannot get kube client")
|
||||
}
|
||||
className := v1helper.GetPersistentVolumeClass(pv)
|
||||
if className == "" {
|
||||
return nil, fmt.Errorf("Volume has no storage class")
|
||||
}
|
||||
|
||||
class, err := kubeClient.StorageV1().StorageClasses().Get(className, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return class, 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(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
|
||||
return checkVolumeNodeAffinity(pv, nodeLabels)
|
||||
}
|
||||
|
||||
func checkVolumeNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
|
||||
if pv.Spec.NodeAffinity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if pv.Spec.NodeAffinity.Required != nil {
|
||||
terms := pv.Spec.NodeAffinity.Required.NodeSelectorTerms
|
||||
klog.V(10).Infof("Match for Required node selector terms %+v", terms)
|
||||
if !v1helper.MatchNodeSelectorTerms(terms, labels.Set(nodeLabels), nil) {
|
||||
return fmt.Errorf("No matching NodeSelectorTerms")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPodFromFile will read, decode, and return a Pod from a file.
|
||||
func LoadPodFromFile(filePath string) (*v1.Pod, error) {
|
||||
if filePath == "" {
|
||||
return nil, fmt.Errorf("file path not specified")
|
||||
}
|
||||
podDef, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err)
|
||||
}
|
||||
if len(podDef) == 0 {
|
||||
return nil, fmt.Errorf("file was empty: %s", filePath)
|
||||
}
|
||||
pod := &v1.Pod{}
|
||||
|
||||
codec := legacyscheme.Codecs.UniversalDecoder()
|
||||
if err := runtime.DecodeInto(codec, podDef, pod); err != nil {
|
||||
return nil, fmt.Errorf("failed decoding file: %v", err)
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// CalculateTimeoutForVolume calculates time for a Recycler pod to complete a
|
||||
// recycle operation. The calculation and return value is either the
|
||||
// minimumTimeout or the timeoutIncrement per Gi of storage size, whichever is
|
||||
// greater.
|
||||
func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.PersistentVolume) int64 {
|
||||
giQty := resource.MustParse("1Gi")
|
||||
pvQty := pv.Spec.Capacity[v1.ResourceStorage]
|
||||
giSize := giQty.Value()
|
||||
pvSize := pvQty.Value()
|
||||
timeout := (pvSize / giSize) * int64(timeoutIncrement)
|
||||
if timeout < int64(minimumTimeout) {
|
||||
return int64(minimumTimeout)
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
|
||||
// GenerateVolumeName returns a PV name with clusterName prefix. The function
|
||||
// should be used to generate a name of GCE PD or Cinder volume. It basically
|
||||
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
|
||||
// string fits given length and cuts "dynamic" if not.
|
||||
func GenerateVolumeName(clusterName, pvName string, maxLength int) string {
|
||||
prefix := clusterName + "-dynamic"
|
||||
pvLen := len(pvName)
|
||||
|
||||
// cut the "<clusterName>-dynamic" to fit full pvName into maxLength
|
||||
// +1 for the '-' dash
|
||||
if pvLen+1+len(prefix) > maxLength {
|
||||
prefix = prefix[:maxLength-pvLen-1]
|
||||
}
|
||||
return prefix + "-" + pvName
|
||||
}
|
||||
|
||||
// GetPath checks if the path from the mounter is empty.
|
||||
func GetPath(mounter volume.Mounter) (string, error) {
|
||||
path := mounter.GetPath()
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("Path is empty %s", reflect.TypeOf(mounter).String())
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// UnmountViaEmptyDir delegates the tear down operation for secret, configmap, git_repo and downwardapi
|
||||
// to empty_dir
|
||||
func UnmountViaEmptyDir(dir string, host volume.VolumeHost, volName string, volSpec volume.Spec, podUID utypes.UID) error {
|
||||
klog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir)
|
||||
|
||||
// Wrap EmptyDir, let it do the teardown.
|
||||
wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wrapped.TearDownAt(dir)
|
||||
}
|
||||
|
||||
// MountOptionFromSpec extracts and joins mount options from volume spec with supplied options
|
||||
func MountOptionFromSpec(spec *volume.Spec, options ...string) []string {
|
||||
pv := spec.PersistentVolume
|
||||
|
||||
if pv != nil {
|
||||
// Use beta annotation first
|
||||
if mo, ok := pv.Annotations[v1.MountOptionAnnotation]; ok {
|
||||
moList := strings.Split(mo, ",")
|
||||
return JoinMountOptions(moList, options)
|
||||
}
|
||||
|
||||
if len(pv.Spec.MountOptions) > 0 {
|
||||
return JoinMountOptions(pv.Spec.MountOptions, options)
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// JoinMountOptions joins mount options eliminating duplicates
|
||||
func JoinMountOptions(userOptions []string, systemOptions []string) []string {
|
||||
allMountOptions := sets.NewString()
|
||||
|
||||
for _, mountOption := range userOptions {
|
||||
if len(mountOption) > 0 {
|
||||
allMountOptions.Insert(mountOption)
|
||||
}
|
||||
}
|
||||
|
||||
for _, mountOption := range systemOptions {
|
||||
allMountOptions.Insert(mountOption)
|
||||
}
|
||||
return allMountOptions.List()
|
||||
}
|
||||
|
||||
// AccessModesContains returns whether the requested mode is contained by modes
|
||||
func AccessModesContains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
|
||||
for _, m := range modes {
|
||||
if m == mode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AccessModesContainedInAll returns whether all of the requested modes are contained by modes
|
||||
func AccessModesContainedInAll(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
|
||||
for _, mode := range requestedModes {
|
||||
if !AccessModesContains(indexedModes, mode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetWindowsPath get a windows path
|
||||
func GetWindowsPath(path string) string {
|
||||
windowsPath := strings.Replace(path, "/", "\\", -1)
|
||||
if strings.HasPrefix(windowsPath, "\\") {
|
||||
windowsPath = "c:" + windowsPath
|
||||
}
|
||||
return windowsPath
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// GetUniqueVolumeNameFromSpecWithPod returns a unique volume name with pod
|
||||
// name included. This is useful to generate different names for different pods
|
||||
// on same volume.
|
||||
func GetUniqueVolumeNameFromSpecWithPod(
|
||||
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 format plugin_name/volume_name and the plugin name must be namespaced as described 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}
|
||||
}
|
||||
|
||||
// GetVolumeMode retrieves VolumeMode from pv.
|
||||
// If the volume doesn't have PersistentVolume, it's an inline volume,
|
||||
// should return volumeMode as filesystem to keep existing behavior.
|
||||
func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) {
|
||||
if volumeSpec == nil || volumeSpec.PersistentVolume == nil {
|
||||
return v1.PersistentVolumeFilesystem, nil
|
||||
}
|
||||
if volumeSpec.PersistentVolume.Spec.VolumeMode != nil {
|
||||
return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil
|
||||
}
|
||||
return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name())
|
||||
}
|
||||
|
||||
// GetPersistentVolumeClaimVolumeMode retrieves VolumeMode from pvc.
|
||||
func GetPersistentVolumeClaimVolumeMode(claim *v1.PersistentVolumeClaim) (v1.PersistentVolumeMode, error) {
|
||||
if claim.Spec.VolumeMode != nil {
|
||||
return *claim.Spec.VolumeMode, nil
|
||||
}
|
||||
return "", fmt.Errorf("cannot get volumeMode from pvc: %v", claim.Name)
|
||||
}
|
||||
|
||||
// GetPersistentVolumeClaimQualifiedName returns a qualified name for pvc.
|
||||
func GetPersistentVolumeClaimQualifiedName(claim *v1.PersistentVolumeClaim) string {
|
||||
return utilstrings.JoinQualifiedName(claim.GetNamespace(), claim.GetName())
|
||||
}
|
||||
|
||||
// CheckVolumeModeFilesystem checks VolumeMode.
|
||||
// If the mode is Filesystem, return true otherwise return false.
|
||||
func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
|
||||
volumeMode, err := GetVolumeMode(volumeSpec)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if volumeMode == v1.PersistentVolumeBlock {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CheckPersistentVolumeClaimModeBlock checks VolumeMode.
|
||||
// If the mode is Block, return true otherwise return false.
|
||||
func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool {
|
||||
return utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock
|
||||
}
|
||||
|
||||
// IsWindowsUNCPath checks if path is prefixed with \\
|
||||
// This can be used to skip any processing of paths
|
||||
// that point to SMB shares, local named pipes and local UNC path
|
||||
func IsWindowsUNCPath(goos, path string) bool {
|
||||
if goos != "windows" {
|
||||
return false
|
||||
}
|
||||
// Check for UNC prefix \\
|
||||
if strings.HasPrefix(path, `\\`) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsWindowsLocalPath checks if path is a local path
|
||||
// prefixed with "/" or "\" like "/foo/bar" or "\foo\bar"
|
||||
func IsWindowsLocalPath(goos, path string) bool {
|
||||
if goos != "windows" {
|
||||
return false
|
||||
}
|
||||
if IsWindowsUNCPath(goos, path) {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(path, ":") {
|
||||
return false
|
||||
}
|
||||
if !(strings.HasPrefix(path, `/`) || strings.HasPrefix(path, `\`)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MakeAbsolutePath convert path to absolute path according to GOOS
|
||||
func MakeAbsolutePath(goos, path string) string {
|
||||
if goos != "windows" {
|
||||
return filepath.Clean("/" + path)
|
||||
}
|
||||
// These are all for windows
|
||||
// If there is a colon, give up.
|
||||
if strings.Contains(path, ":") {
|
||||
return path
|
||||
}
|
||||
// If there is a slash, but no drive, add 'c:'
|
||||
if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") {
|
||||
return "c:" + path
|
||||
}
|
||||
// Otherwise, add 'c:\'
|
||||
return "c:\\" + path
|
||||
}
|
||||
|
||||
// MapBlockVolume is a utility function to provide a common way of mounting
|
||||
// block device path for a specified volume and pod. This function should be
|
||||
// called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
|
||||
func MapBlockVolume(
|
||||
devicePath,
|
||||
globalMapPath,
|
||||
podVolumeMapPath,
|
||||
volumeMapName string,
|
||||
podUID utypes.UID,
|
||||
) error {
|
||||
blkUtil := volumepathhandler.NewBlockVolumePathHandler()
|
||||
|
||||
// map devicePath to global node path
|
||||
mapErr := blkUtil.MapDevice(devicePath, globalMapPath, string(podUID))
|
||||
if mapErr != nil {
|
||||
return mapErr
|
||||
}
|
||||
|
||||
// map devicePath to pod volume path
|
||||
mapErr = blkUtil.MapDevice(devicePath, podVolumeMapPath, volumeMapName)
|
||||
if mapErr != nil {
|
||||
return mapErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue