mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-18 16:21:13 +00:00
Reorganization; Directory traversal less ugly
Directory traversal is no longer recursive and only goes as deep as it needs to. Moved GetActiveVolumes to volume packages and added a simple test.
This commit is contained in:
parent
3f7f6cb2dc
commit
7c28e0849f
@ -93,7 +93,7 @@ kubelet:
|
|||||||
- system: True
|
- system: True
|
||||||
- gid_from_name: True
|
- gid_from_name: True
|
||||||
- shell: /sbin/nologin
|
- shell: /sbin/nologin
|
||||||
- home: /var/kubelet
|
- home: /var/lib/kubelet
|
||||||
- groups:
|
- groups:
|
||||||
- docker
|
- docker
|
||||||
- require:
|
- require:
|
||||||
|
@ -21,10 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -453,66 +450,33 @@ type podContainer struct {
|
|||||||
containerName string
|
containerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores all volumes defined by the set of pods in a map.
|
// Stores all volumes defined by the set of pods into a map.
|
||||||
func determineValidVolumes(pods []Pod) map[string]api.Volume {
|
// Keys for each entry are in the format (POD_ID)/(VOLUME_NAME)
|
||||||
validVolumes := make(map[string]api.Volume)
|
func getDesiredVolumes(pods []Pod) map[string]api.Volume {
|
||||||
|
desiredVolumes := make(map[string]api.Volume)
|
||||||
for _, pod := range pods {
|
for _, pod := range pods {
|
||||||
for _, volume := range pod.Manifest.Volumes {
|
for _, volume := range pod.Manifest.Volumes {
|
||||||
identifier := path.Join(pod.Manifest.ID, volume.Name)
|
identifier := path.Join(pod.Manifest.ID, volume.Name)
|
||||||
validVolumes[identifier] = volume
|
desiredVolumes[identifier] = volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return validVolumes
|
return desiredVolumes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Examines directory structure to determine volumes that are presently
|
// Compares the map of current volumes to the map of desired volumes.
|
||||||
// active and mounted. Builds their respective Cleaner type in case they need to be deleted.
|
// If an active volume does not have a respective desired volume, clean it up.
|
||||||
func (kl *Kubelet) determineActiveVolumes() map[string]volume.Cleaner {
|
|
||||||
activeVolumes := make(map[string]volume.Cleaner)
|
|
||||||
filepath.Walk(kl.rootDirectory, func(fullPath string, info os.FileInfo, err error) error {
|
|
||||||
// Search for volume dir structure : (ROOT_DIR)/(POD_ID)/volumes/(VOLUME_KIND)/(VOLUME_NAME)
|
|
||||||
podIDRegex := "(?P<podID>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)"
|
|
||||||
volumeNameRegex := "(?P<volumeName>[a-z0-9]([-a-z0-9]*[a-z0-9])?)"
|
|
||||||
kindRegex := "(?P<volumeKind>(empty))"
|
|
||||||
regex := path.Join(kl.rootDirectory, podIDRegex, "volumes", kindRegex, volumeNameRegex)
|
|
||||||
regexMatcher, _ := regexp.Compile(regex)
|
|
||||||
if regexMatcher.MatchString(fullPath) {
|
|
||||||
// Extract info from the directory structure.
|
|
||||||
result := make(map[string]string)
|
|
||||||
substrings := regexMatcher.FindStringSubmatch(fullPath)
|
|
||||||
for i, label := range regexMatcher.SubexpNames() {
|
|
||||||
result[label] = substrings[i]
|
|
||||||
}
|
|
||||||
kind := result["volumeKind"]
|
|
||||||
name := result["volumeName"]
|
|
||||||
podID := result["podID"]
|
|
||||||
identifier := path.Join(podID, name)
|
|
||||||
cleaner, err := volume.CreateVolumeCleaner(kind, fullPath)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Could not create cleaner for volume %v.", identifier)
|
|
||||||
}
|
|
||||||
if cleaner != nil {
|
|
||||||
activeVolumes[identifier] = cleaner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return activeVolumes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compares the map of active volumes to the map of valid volumes.
|
|
||||||
// If an active volume does not have a respective valid volume, clean it up.
|
|
||||||
func (kl *Kubelet) reconcileVolumes(pods []Pod) error {
|
func (kl *Kubelet) reconcileVolumes(pods []Pod) error {
|
||||||
validVolumes := determineValidVolumes(pods)
|
desiredVolumes := getDesiredVolumes(pods)
|
||||||
activeVolumes := kl.determineActiveVolumes()
|
currentVolumes := volume.GetCurrentVolumes(kl.rootDirectory)
|
||||||
glog.Infof("ValidVolumes: %v", validVolumes)
|
for name, vol := range currentVolumes {
|
||||||
glog.Infof("ActiveVolumes: %v", activeVolumes)
|
if _, ok := desiredVolumes[name]; !ok {
|
||||||
for name, volume := range activeVolumes {
|
//TODO (jonesdl) We should somehow differentiate between volumes that are supposed
|
||||||
if _, ok := validVolumes[name]; !ok {
|
//to be deleted and volumes that are leftover after a crash.
|
||||||
glog.Infof("Orphaned volume %s found, tearing down volume", name)
|
glog.Infof("Orphaned volume %s found, tearing down volume", name)
|
||||||
err := volume.TearDown()
|
//TODO (jonesdl) This should not block other kubelet synchronization procedures
|
||||||
|
err := vol.TearDown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Could not tear down volume %s", name)
|
glog.Infof("Could not tear down volume %s (%s)", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -551,9 +515,6 @@ func (kl *Kubelet) SyncPods(pods []Pod) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove any orphaned volumes.
|
|
||||||
kl.reconcileVolumes(pods)
|
|
||||||
|
|
||||||
// Kill any containers we don't need
|
// Kill any containers we don't need
|
||||||
existingContainers, err := getKubeletDockerContainers(kl.dockerClient)
|
existingContainers, err := getKubeletDockerContainers(kl.dockerClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -570,6 +531,10 @@ func (kl *Kubelet) SyncPods(pods []Pod) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove any orphaned volumes.
|
||||||
|
kl.reconcileVolumes(pods)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,7 +512,7 @@ func TestMakeVolumesAndBinds(t *testing.T) {
|
|||||||
|
|
||||||
podVolumes := make(volumeMap)
|
podVolumes := make(volumeMap)
|
||||||
podVolumes["disk4"] = &volume.HostDirectory{"/mnt/host"}
|
podVolumes["disk4"] = &volume.HostDirectory{"/mnt/host"}
|
||||||
podVolumes["disk5"] = &volume.EmptyDirectoryBuilder{"disk5", "podID", "/var/lib/kubelet"}
|
podVolumes["disk5"] = &volume.EmptyDirectory{"disk5", "podID", "/var/lib/kubelet"}
|
||||||
|
|
||||||
volumes, binds := makeVolumesAndBinds(&pod, &container, podVolumes)
|
volumes, binds := makeVolumesAndBinds(&pod, &container, podVolumes)
|
||||||
|
|
||||||
|
@ -18,16 +18,18 @@ package volume
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedVolumeType = errors.New("unsupported volume type")
|
var ErrUnsupportedVolumeType = errors.New("unsupported volume type")
|
||||||
|
|
||||||
// Interface is a directory used by pods or hosts. All volume interface implementations
|
// Interface is a directory used by pods or hosts.
|
||||||
// must be idempotent.
|
// All method implementations of methods in the volume interface must be idempotent
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
// GetPath returns the directory path the volume is mounted to.
|
// GetPath returns the directory path the volume is mounted to.
|
||||||
GetPath() string
|
GetPath() string
|
||||||
@ -35,6 +37,7 @@ type Interface interface {
|
|||||||
|
|
||||||
// The Builder interface provides the method to set up/mount the volume.
|
// The Builder interface provides the method to set up/mount the volume.
|
||||||
type Builder interface {
|
type Builder interface {
|
||||||
|
// Uses Interface to provide the path for Docker binds.
|
||||||
Interface
|
Interface
|
||||||
// SetUp prepares and mounts/unpacks the volume to a directory path.
|
// SetUp prepares and mounts/unpacks the volume to a directory path.
|
||||||
SetUp() error
|
SetUp() error
|
||||||
@ -58,24 +61,20 @@ func (hostVol *HostDirectory) SetUp() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hostVol *HostDirectory) TearDown() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hostVol *HostDirectory) GetPath() string {
|
func (hostVol *HostDirectory) GetPath() string {
|
||||||
return hostVol.Path
|
return hostVol.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmptyDirectory volumes are temporary directories exposed to the pod.
|
// EmptyDirectory volumes are temporary directories exposed to the pod.
|
||||||
// These do not persist beyond the lifetime of a pod.
|
// These do not persist beyond the lifetime of a pod.
|
||||||
type EmptyDirectoryBuilder struct {
|
type EmptyDirectory struct {
|
||||||
Name string
|
Name string
|
||||||
PodID string
|
PodID string
|
||||||
RootDir string
|
RootDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUp creates the new directory.
|
// SetUp creates the new directory.
|
||||||
func (emptyDir *EmptyDirectoryBuilder) SetUp() error {
|
func (emptyDir *EmptyDirectory) SetUp() error {
|
||||||
path := emptyDir.GetPath()
|
path := emptyDir.GetPath()
|
||||||
err := os.MkdirAll(path, 0750)
|
err := os.MkdirAll(path, 0750)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,28 +83,44 @@ func (emptyDir *EmptyDirectoryBuilder) SetUp() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emptyDir *EmptyDirectoryBuilder) GetPath() string {
|
func (emptyDir *EmptyDirectory) GetPath() string {
|
||||||
return path.Join(emptyDir.RootDir, emptyDir.PodID, "volumes", "empty", emptyDir.Name)
|
return path.Join(emptyDir.RootDir, emptyDir.PodID, "volumes", "empty", emptyDir.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmptyDirectoryCleaners only need to know what path they are cleaning
|
func (emptyDir *EmptyDirectory) renameDirectory() (string, error) {
|
||||||
type EmptyDirectoryCleaner struct {
|
oldPath := emptyDir.GetPath()
|
||||||
Path string
|
newPath, err := ioutil.TempDir(path.Dir(oldPath), emptyDir.Name+".deleting~")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = os.Rename(oldPath, newPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return newPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simply delete everything in the directory.
|
// Simply delete everything in the directory.
|
||||||
func (emptyDir *EmptyDirectoryCleaner) TearDown() error {
|
func (emptyDir *EmptyDirectory) TearDown() error {
|
||||||
return os.RemoveAll(emptyDir.Path)
|
tmpDir, err := emptyDir.renameDirectory()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interprets API volume as a HostDirectory
|
// Interprets API volume as a HostDirectory
|
||||||
func CreateHostDirectoryBuilder(volume *api.Volume) *HostDirectory {
|
func createHostDirectory(volume *api.Volume) *HostDirectory {
|
||||||
return &HostDirectory{volume.Source.HostDirectory.Path}
|
return &HostDirectory{volume.Source.HostDirectory.Path}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interprets API volume as an EmptyDirectoryBuilder
|
// Interprets API volume as an EmptyDirectory
|
||||||
func CreateEmptyDirectoryBuilder(volume *api.Volume, podID string, rootDir string) *EmptyDirectoryBuilder {
|
func createEmptyDirectory(volume *api.Volume, podID string, rootDir string) *EmptyDirectory {
|
||||||
return &EmptyDirectoryBuilder{volume.Name, podID, rootDir}
|
return &EmptyDirectory{volume.Name, podID, rootDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVolumeBuilder returns a Builder capable of mounting a volume described by an
|
// CreateVolumeBuilder returns a Builder capable of mounting a volume described by an
|
||||||
@ -121,9 +136,9 @@ func CreateVolumeBuilder(volume *api.Volume, podID string, rootDir string) (Buil
|
|||||||
// TODO(jonesdl) We should probably not check every pointer and directly
|
// TODO(jonesdl) We should probably not check every pointer and directly
|
||||||
// resolve these types instead.
|
// resolve these types instead.
|
||||||
if source.HostDirectory != nil {
|
if source.HostDirectory != nil {
|
||||||
vol = CreateHostDirectoryBuilder(volume)
|
vol = createHostDirectory(volume)
|
||||||
} else if source.EmptyDirectory != nil {
|
} else if source.EmptyDirectory != nil {
|
||||||
vol = CreateEmptyDirectoryBuilder(volume, podID, rootDir)
|
vol = createEmptyDirectory(volume, podID, rootDir)
|
||||||
} else {
|
} else {
|
||||||
return nil, ErrUnsupportedVolumeType
|
return nil, ErrUnsupportedVolumeType
|
||||||
}
|
}
|
||||||
@ -131,11 +146,52 @@ func CreateVolumeBuilder(volume *api.Volume, podID string, rootDir string) (Buil
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateVolumeCleaner returns a Cleaner capable of tearing down a volume.
|
// CreateVolumeCleaner returns a Cleaner capable of tearing down a volume.
|
||||||
func CreateVolumeCleaner(kind string, path string) (Cleaner, error) {
|
func CreateVolumeCleaner(kind string, name string, podID string, rootDir string) (Cleaner, error) {
|
||||||
switch kind {
|
switch kind {
|
||||||
case "empty":
|
case "empty":
|
||||||
return &EmptyDirectoryCleaner{path}, nil
|
return &EmptyDirectory{name, podID, rootDir}, nil
|
||||||
default:
|
default:
|
||||||
return nil, ErrUnsupportedVolumeType
|
return nil, ErrUnsupportedVolumeType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Examines directory structure to determine volumes that are presently
|
||||||
|
// active and mounted. Returns a map of Cleaner types.
|
||||||
|
func GetCurrentVolumes(rootDirectory string) map[string]Cleaner {
|
||||||
|
currentVolumes := make(map[string]Cleaner)
|
||||||
|
mountPath := rootDirectory
|
||||||
|
podIDDirs, err := ioutil.ReadDir(mountPath)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not read directory: %s, (%s)", mountPath, err)
|
||||||
|
}
|
||||||
|
// Volume information is extracted from the directory structure:
|
||||||
|
// (ROOT_DIR)/(POD_ID)/volumes/(VOLUME_KIND)/(VOLUME_NAME)
|
||||||
|
for _, podIDDir := range podIDDirs {
|
||||||
|
podID := podIDDir.Name()
|
||||||
|
podIDPath := path.Join(mountPath, podID, "volumes")
|
||||||
|
volumeKindDirs, err := ioutil.ReadDir(podIDPath)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not read directory: %s, (%s)", podIDPath, err)
|
||||||
|
}
|
||||||
|
for _, volumeKindDir := range volumeKindDirs {
|
||||||
|
volumeKind := volumeKindDir.Name()
|
||||||
|
volumeKindPath := path.Join(podIDPath, volumeKind)
|
||||||
|
volumeNameDirs, err := ioutil.ReadDir(volumeKindPath)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not read directory: %s, (%s)", volumeKindPath, err)
|
||||||
|
}
|
||||||
|
for _, volumeNameDir := range volumeNameDirs {
|
||||||
|
volumeName := volumeNameDir.Name()
|
||||||
|
identifier := path.Join(podID, volumeName)
|
||||||
|
// TODO(thockin) This should instead return a reference to an extant volume object
|
||||||
|
cleaner, err := CreateVolumeCleaner(volumeKind, volumeName, podID, rootDirectory)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not create volume cleaner: %s, (%s)", volumeNameDirs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentVolumes[identifier] = cleaner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentVolumes
|
||||||
|
}
|
||||||
|
@ -96,7 +96,7 @@ func TestCreateVolumeBuilders(t *testing.T) {
|
|||||||
if path != tt.path {
|
if path != tt.path {
|
||||||
t.Errorf("Unexpected bind path. Expected %v, got %v", tt.path, path)
|
t.Errorf("Unexpected bind path. Expected %v, got %v", tt.path, path)
|
||||||
}
|
}
|
||||||
vc, err := CreateVolumeCleaner(tt.kind, vb.GetPath())
|
vc, err := CreateVolumeCleaner(tt.kind, tt.volume.Name, tt.podID, tempDir)
|
||||||
if tt.kind == "" {
|
if tt.kind == "" {
|
||||||
if err != ErrUnsupportedVolumeType {
|
if err != ErrUnsupportedVolumeType {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
@ -107,5 +107,37 @@ func TestCreateVolumeBuilders(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
t.Errorf("TearDown() failed, original volume path not properly removed: %v", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetActiveVolumes(t *testing.T) {
|
||||||
|
tempDir, err := ioutil.TempDir("", "CreateVolumes")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
getActiveVolumesTests := []struct {
|
||||||
|
name string
|
||||||
|
podID string
|
||||||
|
kind string
|
||||||
|
identifier string
|
||||||
|
}{
|
||||||
|
{"fakeName", "fakeID", "empty", "fakeID/fakeName"},
|
||||||
|
{"fakeName2", "fakeID2", "empty", "fakeID2/fakeName2"},
|
||||||
|
}
|
||||||
|
expectedIdentifiers := []string{}
|
||||||
|
for _, test := range getActiveVolumesTests {
|
||||||
|
volumeDir := path.Join(tempDir, test.podID, "volumes", test.kind, test.name)
|
||||||
|
os.MkdirAll(volumeDir, 0750)
|
||||||
|
expectedIdentifiers = append(expectedIdentifiers, test.identifier)
|
||||||
|
}
|
||||||
|
volumeMap := GetCurrentVolumes(tempDir)
|
||||||
|
for _, name := range expectedIdentifiers {
|
||||||
|
if _, ok := volumeMap[name]; !ok {
|
||||||
|
t.Errorf("Expected volume map entry not found: %v", name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user