mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-22 10:06:15 +00:00
Splits volume interface into Builders and Cleaners
Different information is needed to perform setup versus teardown. It makes sense to separate these two interfaces since when we call teardown from the reconciliation loop, we cannot rely on having the information provided by the api definition of the volume.
This commit is contained in:
parent
dfc9cb86f0
commit
47bca30edc
@ -259,7 +259,7 @@ func milliCPUToShares(milliCPU int) int {
|
|||||||
func (kl *Kubelet) mountExternalVolumes(manifest *api.ContainerManifest) (volumeMap, error) {
|
func (kl *Kubelet) mountExternalVolumes(manifest *api.ContainerManifest) (volumeMap, error) {
|
||||||
podVolumes := make(volumeMap)
|
podVolumes := make(volumeMap)
|
||||||
for _, vol := range manifest.Volumes {
|
for _, vol := range manifest.Volumes {
|
||||||
extVolume, err := volume.CreateVolume(&vol, manifest.ID, kl.rootDirectory)
|
extVolume, err := volume.CreateVolumeBuilder(&vol, manifest.ID, kl.rootDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -451,46 +451,58 @@ type podContainer struct {
|
|||||||
containerName string
|
containerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stores all volumes defined by the set of pods in a map.
|
||||||
func determineValidVolumes(pods []Pod) map[string]api.Volume {
|
func determineValidVolumes(pods []Pod) map[string]api.Volume {
|
||||||
validVolumes := make(map[string]api.Volume)
|
validVolumes := 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 := pod.Manifest.ID + volume.Name
|
identifier := pod.Manifest.ID + "/" + volume.Name
|
||||||
validVolumes[identifier] = volume
|
validVolumes[identifier] = volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return validVolumes
|
return validVolumes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kl *Kubelet) determineActiveVolumes() map[string]volume.Interface {
|
// Examines directory structure to determine volumes that are presently
|
||||||
activeVolumes := make(map[string]volume.Interface)
|
// active and mounted. Builds their respective Cleaner type in case they need to be deleted.
|
||||||
|
func (kl *Kubelet) determineActiveVolumes() map[string]volume.Cleaner {
|
||||||
|
activeVolumes := make(map[string]volume.Cleaner)
|
||||||
filepath.Walk(kl.rootDirectory, func(path string, info os.FileInfo, err error) error {
|
filepath.Walk(kl.rootDirectory, func(path string, info os.FileInfo, err error) error {
|
||||||
|
// Search for volume dir structure : $ROOTDIR/$PODID/volumes/$VOLUMETYPE/$VOLUMENAME
|
||||||
var name string
|
var name string
|
||||||
var podID string
|
var podID string
|
||||||
|
// Extract volume type for dir structure
|
||||||
dir := getDir(path)
|
dir := getDir(path)
|
||||||
glog.Infof("Traversing filepath %s", path)
|
glog.Infof("Traversing filepath %s", path)
|
||||||
|
// Handle emptyDirectory types.
|
||||||
if dir == "empty" {
|
if dir == "empty" {
|
||||||
name = info.Name()
|
name = info.Name()
|
||||||
|
// Retrieve podID from dir structure
|
||||||
podID = getDir(filepath.Dir(filepath.Dir(path)))
|
podID = getDir(filepath.Dir(filepath.Dir(path)))
|
||||||
glog.Infof("Adding active volume %s of pod %s", name, podID)
|
glog.Infof("Found active volume %s of pod %s", name, podID)
|
||||||
activeVolumes[podID+name] = &volume.EmptyDirectory{name, podID, kl.rootDirectory}
|
identifier := podID + "/" + name
|
||||||
|
activeVolumes[identifier] = &volume.EmptyDirectoryCleaner{path}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return activeVolumes
|
return activeVolumes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility function to extract only the directory name.
|
||||||
func getDir(path string) string {
|
func getDir(path string) string {
|
||||||
return filepath.Base(filepath.Dir(path))
|
return filepath.Base(filepath.Dir(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
validVolumes := determineValidVolumes(pods)
|
||||||
activeVolumes := kl.determineActiveVolumes()
|
activeVolumes := kl.determineActiveVolumes()
|
||||||
glog.Infof("ValidVolumes: %v \n ActiveVolumes: %v", validVolumes, activeVolumes)
|
glog.Infof("ValidVolumes: %v", validVolumes)
|
||||||
|
glog.Infof("ActiveVolumes: %v", activeVolumes)
|
||||||
for name, volume := range activeVolumes {
|
for name, volume := range activeVolumes {
|
||||||
if _, ok := validVolumes[name]; !ok {
|
if _, ok := validVolumes[name]; !ok {
|
||||||
glog.Infof("Volume found with no respective pod, tearing down volume %s", name)
|
glog.Infof("Orphaned volume %s found, tearing down volume", name)
|
||||||
volume.TearDown()
|
volume.TearDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -528,6 +540,8 @@ func (kl *Kubelet) SyncPods(pods []Pod) error {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove any orphaned volumes.
|
||||||
kl.reconcileVolumes(pods)
|
kl.reconcileVolumes(pods)
|
||||||
|
|
||||||
// Kill any containers we don't need
|
// Kill any containers we don't need
|
||||||
|
@ -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.EmptyDirectory{"disk5", "podID", "/var/lib/kubelet"}
|
podVolumes["disk5"] = &volume.EmptyDirectoryBuilder{"disk5", "podID", "/var/lib/kubelet"}
|
||||||
|
|
||||||
volumes, binds := makeVolumesAndBinds(&pod, &container, podVolumes)
|
volumes, binds := makeVolumesAndBinds(&pod, &container, podVolumes)
|
||||||
|
|
||||||
|
@ -26,15 +26,25 @@ import (
|
|||||||
|
|
||||||
var ErrUnsupportedVolumeType = errors.New("unsupported volume type")
|
var ErrUnsupportedVolumeType = errors.New("unsupported volume type")
|
||||||
|
|
||||||
// Interface is a directory used by pods or hosts. Interface implementations
|
// Interface is a directory used by pods or hosts. All volume interface implementations
|
||||||
// must be idempotent.
|
// must be idempotent.
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
// SetUp prepares and mounts/unpacks the volume to a directory path.
|
|
||||||
SetUp() error
|
|
||||||
|
|
||||||
// GetPath returns the directory path the volume is mounted to.
|
// GetPath returns the directory path the volume is mounted to.
|
||||||
GetPath() string
|
GetPath() string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Builder interface provides the method to set up/mount the volume.
|
||||||
|
type Builder interface {
|
||||||
|
Interface
|
||||||
|
// SetUp prepares and mounts/unpacks the volume to a directory path.
|
||||||
|
SetUp() error
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Cleaner interface provides the method to cleanup/unmount the volumes.
|
||||||
|
type Cleaner interface {
|
||||||
|
Interface
|
||||||
// TearDown unmounts the volume and removes traces of the SetUp procedure.
|
// TearDown unmounts the volume and removes traces of the SetUp procedure.
|
||||||
TearDown() error
|
TearDown() error
|
||||||
}
|
}
|
||||||
@ -61,14 +71,14 @@ func (hostVol *HostDirectory) GetPath() string {
|
|||||||
|
|
||||||
// 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 EmptyDirectory struct {
|
type EmptyDirectoryBuilder 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 *EmptyDirectory) SetUp() error {
|
func (emptyDir *EmptyDirectoryBuilder) SetUp() error {
|
||||||
path := emptyDir.GetPath()
|
path := emptyDir.GetPath()
|
||||||
err := os.MkdirAll(path, 0750)
|
err := os.MkdirAll(path, 0750)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -77,42 +87,50 @@ func (emptyDir *EmptyDirectory) SetUp() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jonesdl) when we can properly invoke TearDown(), we should delete
|
func (emptyDir *EmptyDirectoryBuilder) GetPath() string {
|
||||||
// the directory created by SetUp.
|
|
||||||
func (emptyDir *EmptyDirectory) TearDown() error {
|
|
||||||
return os.RemoveAll(emptyDir.GetPath())
|
|
||||||
}
|
|
||||||
|
|
||||||
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 the are cleaning
|
||||||
|
type EmptyDirectoryCleaner struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simply delete everything in the directory.
|
||||||
|
func (emptyDir *EmptyDirectoryCleaner) TearDown() error {
|
||||||
|
return os.RemoveAll(emptyDir.GetPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (emptyDir *EmptyDirectoryCleaner) GetPath() string {
|
||||||
|
return emptyDir.Path
|
||||||
|
}
|
||||||
|
|
||||||
// Interprets API volume as a HostDirectory
|
// Interprets API volume as a HostDirectory
|
||||||
func CreateHostDirectory(volume *api.Volume) *HostDirectory {
|
func CreateHostDirectoryBuilder(volume *api.Volume) *HostDirectory {
|
||||||
return &HostDirectory{volume.Source.HostDirectory.Path}
|
return &HostDirectory{volume.Source.HostDirectory.Path}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interprets API volume as an EmptyDirectory
|
// Interprets API volume as an EmptyDirectoryBuilder
|
||||||
func CreateEmptyDirectory(volume *api.Volume, podID string, rootDir string) *EmptyDirectory {
|
func CreateEmptyDirectoryBuilder(volume *api.Volume, podID string, rootDir string) *EmptyDirectoryBuilder {
|
||||||
return &EmptyDirectory{volume.Name, podID, rootDir}
|
return &EmptyDirectoryBuilder{volume.Name, podID, rootDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVolume returns an Interface capable of mounting a volume described by an
|
// CreateVolumeBuilder returns a Builder capable of mounting a volume described by an
|
||||||
// *api.Volume and whether or not it is mounted, or an error.
|
// *api.Volume, or an error.
|
||||||
func CreateVolume(volume *api.Volume, podID string, rootDir string) (Interface, error) {
|
func CreateVolumeBuilder(volume *api.Volume, podID string, rootDir string) (Builder, error) {
|
||||||
source := volume.Source
|
source := volume.Source
|
||||||
// TODO(jonesdl) We will want to throw an error here when we no longer
|
// TODO(jonesdl) We will want to throw an error here when we no longer
|
||||||
// support the default behavior.
|
// support the default behavior.
|
||||||
if source == nil {
|
if source == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var vol Interface
|
var vol Builder
|
||||||
// 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 = CreateHostDirectory(volume)
|
vol = CreateHostDirectoryBuilder(volume)
|
||||||
} else if source.EmptyDirectory != nil {
|
} else if source.EmptyDirectory != nil {
|
||||||
vol = CreateEmptyDirectory(volume, podID, rootDir)
|
vol = CreateEmptyDirectoryBuilder(volume, podID, rootDir)
|
||||||
} else {
|
} else {
|
||||||
return nil, ErrUnsupportedVolumeType
|
return nil, ErrUnsupportedVolumeType
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateVolumes(t *testing.T) {
|
func TestCreateVolumeBuilders(t *testing.T) {
|
||||||
tempDir, err := ioutil.TempDir("", "CreateVolumes")
|
tempDir, err := ioutil.TempDir("", "CreateVolumes")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
@ -35,6 +35,7 @@ func TestCreateVolumes(t *testing.T) {
|
|||||||
volume api.Volume
|
volume api.Volume
|
||||||
path string
|
path string
|
||||||
podID string
|
podID string
|
||||||
|
kind string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
api.Volume{
|
api.Volume{
|
||||||
@ -45,6 +46,7 @@ func TestCreateVolumes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"/dir/path",
|
"/dir/path",
|
||||||
"my-id",
|
"my-id",
|
||||||
|
"host",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api.Volume{
|
api.Volume{
|
||||||
@ -55,6 +57,7 @@ func TestCreateVolumes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
path.Join(tempDir, "/my-id/volumes/empty/empty-dir"),
|
path.Join(tempDir, "/my-id/volumes/empty/empty-dir"),
|
||||||
"my-id",
|
"my-id",
|
||||||
|
"empty",
|
||||||
},
|
},
|
||||||
{api.Volume{}, "", ""},
|
{api.Volume{}, "", ""},
|
||||||
{
|
{
|
||||||
@ -64,11 +67,12 @@ func TestCreateVolumes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, createVolumesTest := range createVolumesTests {
|
for _, createVolumesTest := range createVolumesTests {
|
||||||
tt := createVolumesTest
|
tt := createVolumesTest
|
||||||
v, err := CreateVolume(&tt.volume, tt.podID, tempDir)
|
v, err := CreateVolumeBuilder(&tt.volume, tt.podID, tempDir)
|
||||||
if tt.volume.Source == nil {
|
if tt.volume.Source == nil {
|
||||||
if v != nil {
|
if v != nil {
|
||||||
t.Errorf("Expected volume to be nil")
|
t.Errorf("Expected volume to be nil")
|
||||||
@ -92,6 +96,12 @@ func TestCreateVolumes(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)
|
||||||
}
|
}
|
||||||
|
v, err = CreateVolumeCleaner(tt.kind)
|
||||||
|
if tt.kind == "" {
|
||||||
|
if err != ErrUnsupportedVolumeType {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
err = v.TearDown()
|
err = v.TearDown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user