summaryrefslogtreecommitdiff
path: root/vendor/k8s.io/kubernetes/pkg/volume/util
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/k8s.io/kubernetes/pkg/volume/util')
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go462
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go31
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go61
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go24
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/doc.go18
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/fs.go96
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/fs_unsupported.go38
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go47
-rw-r--r--vendor/k8s.io/kubernetes/pkg/volume/util/util.go213
9 files changed, 990 insertions, 0 deletions
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go b/vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go
new file mode 100644
index 000000000..5eef55b45
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/atomic_writer.go
@@ -0,0 +1,462 @@
+/*
+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"
+
+ "github.com/golang/glog"
+
+ "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
+}
+
+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 user-visible portion of the volume is walked to determine whether any
+// portion of the payload was deleted and is still present on disk.
+// If the payload is already present on disk and there are no deleted files,
+// the function returns
+// 3. A check is made to determine whether data present in the payload has changed
+// 4.  A new timestamped dir is created
+// 5. The payload is written to the new timestamped directory
+// 6.  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/labels -> ../..data/usr/labels
+// <target-dir>/k8s/annotations -> ../..data/k8s/annotations
+//
+// Relative links are created into the data directory for files in subdirectories.
+//
+// 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/
+// 7.  The current timestamped directory is detected by reading the data directory
+// symlink
+// 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 {
+ glog.Errorf("%s: invalid payload: %v", w.logContext, err)
+ return err
+ }
+
+ // (2)
+ pathsToRemove, err := w.pathsToRemove(cleanPayload)
+ if err != nil {
+ glog.Errorf("%s: error determining user-visible files to remove: %v", w.logContext, err)
+ return err
+ }
+
+ // (3)
+ if should, err := w.shouldWritePayload(cleanPayload); err != nil {
+ glog.Errorf("%s: error determining whether payload should be written to disk: %v", w.logContext, err)
+ return err
+ } else if !should && len(pathsToRemove) == 0 {
+ glog.V(4).Infof("%s: no update required for target directory %v", w.logContext, w.targetDir)
+ return nil
+ } else {
+ glog.V(4).Infof("%s: write required for target directory %v", w.logContext, w.targetDir)
+ }
+
+ // (4)
+ tsDir, err := w.newTimestampDir()
+ if err != nil {
+ glog.V(4).Infof("%s: error creating new ts data directory: %v", w.logContext, err)
+ return err
+ }
+
+ // (5)
+ if err = w.writePayloadToDir(cleanPayload, tsDir); err != nil {
+ glog.Errorf("%s: error writing payload to ts data directory %s: %v", w.logContext, tsDir, err)
+ return err
+ } else {
+ glog.V(4).Infof("%s: performed write of new data to ts data directory: %s", w.logContext, tsDir)
+ }
+
+ // (6)
+ if err = w.createUserVisibleFiles(cleanPayload); err != nil {
+ glog.Errorf("%s: error creating visible symlinks in %s: %v", w.logContext, w.targetDir, err)
+ return err
+ }
+
+ // (7)
+ _, tsDirName := filepath.Split(tsDir)
+ dataDirPath := path.Join(w.targetDir, dataDirName)
+ oldTsDir, err := os.Readlink(dataDirPath)
+ if err != nil && !os.IsNotExist(err) {
+ glog.Errorf("%s: error reading link for data directory: %v", w.logContext, err)
+ return err
+ }
+
+ // (8)
+ newDataDirPath := path.Join(w.targetDir, newDataDirName)
+ if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
+ os.RemoveAll(tsDir)
+ glog.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)
+ glog.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 {
+ glog.Errorf("%s: error removing old visible symlinks: %v", w.logContext, err)
+ return err
+ }
+
+ // (11)
+ if len(oldTsDir) > 0 {
+ if err = os.RemoveAll(path.Join(w.targetDir, oldTsDir)); err != nil {
+ glog.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[path.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 %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 %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 (w *AtomicWriter) shouldWritePayload(payload map[string]FileProjection) (bool, error) {
+ for userVisiblePath, fileProjection := range payload {
+ shouldWrite, err := w.shouldWriteFile(path.Join(w.targetDir, 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 (w *AtomicWriter) 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 user-visible portion of the target 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) (sets.String, error) {
+ paths := sets.NewString()
+ visitor := func(path string, info os.FileInfo, err error) error {
+ if path == w.targetDir {
+ return nil
+ }
+
+ relativePath := strings.TrimPrefix(path, w.targetDir)
+ if runtime.GOOS == "windows" {
+ relativePath = strings.TrimPrefix(relativePath, "\\")
+ } else {
+ relativePath = strings.TrimPrefix(relativePath, "/")
+ }
+ if strings.HasPrefix(relativePath, "..") {
+ return nil
+ }
+
+ paths.Insert(relativePath)
+ return nil
+ }
+
+ err := filepath.Walk(w.targetDir, visitor)
+ if os.IsNotExist(err) {
+ return nil, nil
+ } else if err != nil {
+ return nil, err
+ }
+ glog.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, "/")
+ }
+ }
+ glog.V(5).Infof("%s: new paths: %+v", w.targetDir, newPaths.List())
+
+ result := paths.Difference(newPaths)
+ glog.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, fmt.Sprintf("..%s.", time.Now().Format("1981_02_01_15_04_05")))
+ if err != nil {
+ glog.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 {
+ glog.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 {
+ glog.Errorf("%s: unable to create directory %s: %v", w.logContext, baseDir, err)
+ return err
+ }
+
+ err = ioutil.WriteFile(fullPath, content, mode)
+ if err != nil {
+ glog.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 {
+ glog.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 and subdirectories are created:
+// bar -> ..data/bar
+// foo/bar -> ../..data/foo/bar
+// baz/bar -> ../..data/baz/bar
+// foo/baz/blah -> ../../..data/foo/baz/blah
+func (w *AtomicWriter) createUserVisibleFiles(payload map[string]FileProjection) error {
+ for userVisiblePath := range payload {
+ dir, _ := filepath.Split(userVisiblePath)
+ subDirs := 0
+ if len(dir) > 0 {
+ // If dir is not empty, the projection path contains at least one
+ // subdirectory (example: userVisiblePath := "foo/bar").
+ // Since filepath.Split leaves a trailing path separator, in this
+ // example, dir = "foo/". In order to calculate the number of
+ // subdirectories, we must subtract 1 from the number returned by split.
+ subDirs = len(strings.Split(dir, "/")) - 1
+ err := os.MkdirAll(path.Join(w.targetDir, dir), os.ModePerm)
+ if err != nil {
+ return err
+ }
+ }
+ _, err := os.Readlink(path.Join(w.targetDir, userVisiblePath))
+ if err != nil && os.IsNotExist(err) {
+ // The link into the data directory for this path doesn't exist; create it,
+ // respecting the number of subdirectories necessary to link
+ // correctly back into the data directory.
+ visibleFile := path.Join(w.targetDir, userVisiblePath)
+ dataDirFile := path.Join(strings.Repeat("../", subDirs), dataDirName, userVisiblePath)
+
+ 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 {
+ orderedPaths := paths.List()
+ for ii := len(orderedPaths) - 1; ii >= 0; ii-- {
+ if err := os.Remove(path.Join(w.targetDir, orderedPaths[ii])); err != nil {
+ glog.Errorf("%s: error pruning old user-visible path %s: %v", w.logContext, orderedPaths[ii], err)
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go b/vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go
new file mode 100644
index 000000000..9098d7b85
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/device_util.go
@@ -0,0 +1,31 @@
+/*
+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
+}
+
+type deviceHandler struct {
+ get_io IoUtil
+}
+
+//NewDeviceHandler Create a new IoHandler implementation
+func NewDeviceHandler(io IoUtil) DeviceUtil {
+ return &deviceHandler{get_io: io}
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go b/vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go
new file mode 100644
index 000000000..0d9851140
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/device_util_linux.go
@@ -0,0 +1,61 @@
+// +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"
+ "strings"
+)
+
+// FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent
+func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
+ io := handler.get_io
+ 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)
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go b/vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go
new file mode 100644
index 000000000..6afb1f139
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/device_util_unsupported.go
@@ -0,0 +1,24 @@
+// +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 ""
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/doc.go b/vendor/k8s.io/kubernetes/pkg/volume/util/doc.go
new file mode 100644
index 000000000..620add69d
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/doc.go
@@ -0,0 +1,18 @@
+/*
+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.
+*/
+
+// Contains utility code for use by volume plugins.
+package util // import "k8s.io/kubernetes/pkg/volume/util"
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/fs.go b/vendor/k8s.io/kubernetes/pkg/volume/util/fs.go
new file mode 100644
index 000000000..cfa7e30b4
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/fs.go
@@ -0,0 +1,96 @@
+// +build linux darwin
+
+/*
+Copyright 2014 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"
+ "os/exec"
+ "strings"
+ "syscall"
+
+ "k8s.io/apimachinery/pkg/api/resource"
+)
+
+// FSInfo linux returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error)
+// for the filesystem that path resides upon.
+func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
+ statfs := &syscall.Statfs_t{}
+ err := syscall.Statfs(path, statfs)
+ if err != nil {
+ return 0, 0, 0, 0, 0, 0, err
+ }
+
+ // Available is blocks available * fragment size
+ available := int64(statfs.Bavail) * int64(statfs.Bsize)
+
+ // Capacity is total block count * fragment size
+ capacity := int64(statfs.Blocks) * int64(statfs.Bsize)
+
+ // Usage is block being used * fragment size (aka block size).
+ usage := (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize)
+
+ inodes := int64(statfs.Files)
+ inodesFree := int64(statfs.Ffree)
+ inodesUsed := inodes - inodesFree
+
+ return available, capacity, usage, inodes, inodesFree, inodesUsed, nil
+}
+
+func Du(path string) (*resource.Quantity, error) {
+ // Uses the same niceness level as cadvisor.fs does when running du
+ // Uses -B 1 to always scale to a blocksize of 1 byte
+ out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", path).CombinedOutput()
+ if err != nil {
+ return nil, fmt.Errorf("failed command 'du' ($ nice -n 19 du -s -B 1) on path %s with error %v", path, err)
+ }
+ used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
+ }
+ used.Format = resource.BinarySI
+ return &used, nil
+}
+
+// Find uses the equivalent of the command `find <path> -dev -printf '.' | wc -c` to count files and directories.
+// While this is not an exact measure of inodes used, it is a very good approximation.
+func Find(path string) (int64, error) {
+ if path == "" {
+ return 0, fmt.Errorf("invalid directory")
+ }
+ var counter byteCounter
+ var stderr bytes.Buffer
+ findCmd := exec.Command("find", path, "-xdev", "-printf", ".")
+ findCmd.Stdout, findCmd.Stderr = &counter, &stderr
+ if err := findCmd.Start(); err != nil {
+ return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr: %v", findCmd.Args, err, stderr.String())
+ }
+ if err := findCmd.Wait(); err != nil {
+ return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", findCmd.Args, stderr.String(), err)
+ }
+ return counter.bytesWritten, nil
+}
+
+// Simple io.Writer implementation that counts how many bytes were written.
+type byteCounter struct{ bytesWritten int64 }
+
+func (b *byteCounter) Write(p []byte) (int, error) {
+ b.bytesWritten += int64(len(p))
+ return len(p), nil
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/fs_unsupported.go b/vendor/k8s.io/kubernetes/pkg/volume/util/fs_unsupported.go
new file mode 100644
index 000000000..8d35d5dae
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/fs_unsupported.go
@@ -0,0 +1,38 @@
+// +build !linux,!darwin
+
+/*
+Copyright 2014 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/apimachinery/pkg/api/resource"
+)
+
+// FSInfo unsupported returns 0 values for available and capacity and an error.
+func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
+ return 0, 0, 0, 0, 0, 0, fmt.Errorf("FsInfo not supported for this build.")
+}
+
+func Du(path string) (*resource.Quantity, error) {
+ return nil, fmt.Errorf("Du not supported for this build.")
+}
+
+func Find(path string) (int64, error) {
+ return 0, fmt.Errorf("Find not supported for this build.")
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go b/vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go
new file mode 100644
index 000000000..e1f30f5c3
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/io_util.go
@@ -0,0 +1,47 @@
+/*
+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 {
+ 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) 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)
+}
diff --git a/vendor/k8s.io/kubernetes/pkg/volume/util/util.go b/vendor/k8s.io/kubernetes/pkg/volume/util/util.go
new file mode 100644
index 000000000..660c3c9db
--- /dev/null
+++ b/vendor/k8s.io/kubernetes/pkg/volume/util/util.go
@@ -0,0 +1,213 @@
+/*
+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"
+ "os"
+ "path"
+
+ "github.com/golang/glog"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/kubernetes/pkg/api/v1"
+ v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
+ storage "k8s.io/kubernetes/pkg/apis/storage/v1"
+ "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
+ "k8s.io/kubernetes/pkg/util/mount"
+)
+
+const readyFileName = "ready"
+
+// 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() {
+ glog.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) {
+ glog.Errorf("Can't mkdir %s: %v", dir, err)
+ return
+ }
+
+ readyFile := path.Join(dir, readyFileName)
+ file, err := os.Create(readyFile)
+ if err != nil {
+ glog.Errorf("Can't touch %s: %v", readyFile, err)
+ return
+ }
+ file.Close()
+}
+
+// UnmountPath is a common unmount routine that unmounts the given path and
+// deletes the remaining directory if successful.
+func UnmountPath(mountPath string, mounter mount.Interface) error {
+ return UnmountMountPoint(mountPath, mounter, false /* extensiveMountPointCheck */)
+}
+
+// UnmountMountPoint is a common unmount routine that unmounts the given path and
+// deletes the remaining directory if successful.
+// if extensiveMountPointCheck is true
+// IsNotMountPoint will be called instead of IsLikelyNotMountPoint.
+// IsNotMountPoint is more expensive but properly handles bind mounts.
+func UnmountMountPoint(mountPath string, mounter mount.Interface, extensiveMountPointCheck bool) error {
+ if pathExists, pathErr := PathExists(mountPath); pathErr != nil {
+ return fmt.Errorf("Error checking if path exists: %v", pathErr)
+ } else if !pathExists {
+ glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath)
+ return nil
+ }
+
+ var notMnt bool
+ var err error
+
+ if extensiveMountPointCheck {
+ notMnt, err = mount.IsNotMountPoint(mounter, mountPath)
+ } else {
+ notMnt, err = mounter.IsLikelyNotMountPoint(mountPath)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ if notMnt {
+ glog.Warningf("Warning: %q is not a mountpoint, deleting", mountPath)
+ return os.Remove(mountPath)
+ }
+
+ // Unmount the mount path
+ glog.V(4).Infof("%q is a mountpoint, unmounting", mountPath)
+ if err := mounter.Unmount(mountPath); err != nil {
+ return err
+ }
+ notMnt, mntErr := mounter.IsLikelyNotMountPoint(mountPath)
+ if mntErr != nil {
+ return err
+ }
+ if notMnt {
+ glog.V(4).Infof("%q is unmounted, deleting the directory", mountPath)
+ return os.Remove(mountPath)
+ }
+ return fmt.Errorf("Failed to unmount path %v", mountPath)
+}
+
+// PathExists returns true if the specified path exists.
+func PathExists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true, nil
+ } else if os.IsNotExist(err) {
+ return false, nil
+ } else {
+ return false, err
+ }
+}
+
+// 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.Core().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.Core().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
+}
+
+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 {
+ affinity, err := v1helper.GetStorageNodeAffinityFromAnnotation(pv.Annotations)
+ if err != nil {
+ return fmt.Errorf("Error getting storage node affinity: %v", err)
+ }
+ if affinity == nil {
+ return nil
+ }
+
+ if affinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
+ terms := affinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms
+ glog.V(10).Infof("Match for RequiredDuringSchedulingIgnoredDuringExecution node selector terms %+v", terms)
+ for _, term := range terms {
+ selector, err := v1helper.NodeSelectorRequirementsAsSelector(term.MatchExpressions)
+ if err != nil {
+ return fmt.Errorf("Failed to parse MatchExpressions: %v", err)
+ }
+ if !selector.Matches(labels.Set(nodeLabels)) {
+ return fmt.Errorf("NodeSelectorTerm %+v does not match node labels", term.MatchExpressions)
+ }
+ }
+ }
+ return nil
+}