Commit 9d5bcf42 authored by Yifan Gu's avatar Yifan Gu

rkt: Add tests for GarbageCollect().

parent 06b1955c
...@@ -230,7 +230,7 @@ func startComponents(firstManifestURL, secondManifestURL string) (string, string ...@@ -230,7 +230,7 @@ func startComponents(firstManifestURL, secondManifestURL string) (string, string
cadvisorInterface, cadvisorInterface,
configFilePath, configFilePath,
nil, nil,
containertest.FakeOS{}, &containertest.FakeOS{},
1*time.Second, /* FileCheckFrequency */ 1*time.Second, /* FileCheckFrequency */
1*time.Second, /* HTTPCheckFrequency */ 1*time.Second, /* HTTPCheckFrequency */
10*time.Second, /* MinimumGCAge */ 10*time.Second, /* MinimumGCAge */
...@@ -263,7 +263,7 @@ func startComponents(firstManifestURL, secondManifestURL string) (string, string ...@@ -263,7 +263,7 @@ func startComponents(firstManifestURL, secondManifestURL string) (string, string
cadvisorInterface, cadvisorInterface,
"", "",
nil, nil,
containertest.FakeOS{}, &containertest.FakeOS{},
1*time.Second, /* FileCheckFrequency */ 1*time.Second, /* FileCheckFrequency */
1*time.Second, /* HTTPCheckFrequency */ 1*time.Second, /* HTTPCheckFrequency */
10*time.Second, /* MinimumGCAge */ 10*time.Second, /* MinimumGCAge */
......
...@@ -17,7 +17,9 @@ limitations under the License. ...@@ -17,7 +17,9 @@ limitations under the License.
package container package container
import ( import (
"io/ioutil"
"os" "os"
"time"
) )
// OSInterface collects system level operations that need to be mocked out // OSInterface collects system level operations that need to be mocked out
...@@ -26,6 +28,12 @@ type OSInterface interface { ...@@ -26,6 +28,12 @@ type OSInterface interface {
Mkdir(path string, perm os.FileMode) error Mkdir(path string, perm os.FileMode) error
Symlink(oldname string, newname string) error Symlink(oldname string, newname string) error
Stat(path string) (os.FileInfo, error) Stat(path string) (os.FileInfo, error)
Remove(path string) error
Create(path string) (*os.File, error)
Hostname() (name string, err error)
Chtimes(path string, atime time.Time, mtime time.Time) error
Pipe() (r *os.File, w *os.File, err error)
ReadDir(dirname string) ([]os.FileInfo, error)
} }
// RealOS is used to dispatch the real system level operaitons. // RealOS is used to dispatch the real system level operaitons.
...@@ -45,3 +53,34 @@ func (RealOS) Symlink(oldname string, newname string) error { ...@@ -45,3 +53,34 @@ func (RealOS) Symlink(oldname string, newname string) error {
func (RealOS) Stat(path string) (os.FileInfo, error) { func (RealOS) Stat(path string) (os.FileInfo, error) {
return os.Stat(path) return os.Stat(path)
} }
// Remove will call os.Remove to remove the path.
func (RealOS) Remove(path string) error {
return os.Remove(path)
}
// Create will call os.Create to create and return a file
// at path.
func (RealOS) Create(path string) (*os.File, error) {
return os.Create(path)
}
// Hostname will call os.Hostname to return the hostname.
func (RealOS) Hostname() (name string, err error) {
return os.Hostname()
}
// Chtimes will call os.Chtimes to change the atime and mtime of the path
func (RealOS) Chtimes(path string, atime time.Time, mtime time.Time) error {
return os.Chtimes(path, atime, mtime)
}
// Pipe will call os.Pipe to return a connected pair of pipe.
func (RealOS) Pipe() (r *os.File, w *os.File, err error) {
return os.Pipe()
}
// ReadDir will call ioutil.ReadDir to return the files under the directory.
func (RealOS) ReadDir(dirname string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dirname)
}
...@@ -19,14 +19,25 @@ package testing ...@@ -19,14 +19,25 @@ package testing
import ( import (
"errors" "errors"
"os" "os"
"time"
) )
// FakeOS mocks out certain OS calls to avoid perturbing the filesystem // FakeOS mocks out certain OS calls to avoid perturbing the filesystem
// on the test machine.
// If a member of the form `*Fn` is set, that function will be called in place // If a member of the form `*Fn` is set, that function will be called in place
// of the real call. // of the real call.
type FakeOS struct { type FakeOS struct {
StatFn func(string) (os.FileInfo, error) StatFn func(string) (os.FileInfo, error)
ReadDirFn func(string) ([]os.FileInfo, error)
HostName string
Removes []string
Files map[string][]*os.FileInfo
}
func NewFakeOS() *FakeOS {
return &FakeOS{
Removes: []string{},
Files: make(map[string][]*os.FileInfo),
}
} }
// Mkdir is a fake call that just returns nil. // Mkdir is a fake call that just returns nil.
...@@ -46,3 +57,37 @@ func (f FakeOS) Stat(path string) (os.FileInfo, error) { ...@@ -46,3 +57,37 @@ func (f FakeOS) Stat(path string) (os.FileInfo, error) {
} }
return nil, errors.New("unimplemented testing mock") return nil, errors.New("unimplemented testing mock")
} }
// Remove is a fake call that returns nil.
func (f *FakeOS) Remove(path string) error {
f.Removes = append(f.Removes, path)
return nil
}
// Create is a fake call that returns nil.
func (FakeOS) Create(path string) (*os.File, error) {
return nil, nil
}
// Hostname is a fake call that returns nil.
func (f *FakeOS) Hostname() (name string, err error) {
return f.HostName, nil
}
// Chtimes is a fake call that returns nil.
func (FakeOS) Chtimes(path string, atime time.Time, mtime time.Time) error {
return nil
}
// Pipe is a fake call that returns nil.
func (FakeOS) Pipe() (r *os.File, w *os.File, err error) {
return nil, nil, nil
}
// ReadDir is a fake call that returns the files under the directory.
func (f *FakeOS) ReadDir(dirname string) ([]os.FileInfo, error) {
if f.ReadDirFn != nil {
return f.ReadDirFn(dirname)
}
return nil, errors.New("unimplemented testing mock")
}
...@@ -653,7 +653,7 @@ func TestFindContainersByPod(t *testing.T) { ...@@ -653,7 +653,7 @@ func TestFindContainersByPod(t *testing.T) {
fakeClient := NewFakeDockerClient() fakeClient := NewFakeDockerClient()
np, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil), componentconfig.HairpinNone) np, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil), componentconfig.HairpinNone)
// image back-off is set to nil, this test should not pull images // image back-off is set to nil, this test should not pull images
containerManager := NewFakeDockerManager(fakeClient, &record.FakeRecorder{}, nil, nil, &cadvisorapi.MachineInfo{}, options.GetDefaultPodInfraContainerImage(), 0, 0, "", containertest.FakeOS{}, np, nil, nil, nil) containerManager := NewFakeDockerManager(fakeClient, &record.FakeRecorder{}, nil, nil, &cadvisorapi.MachineInfo{}, options.GetDefaultPodInfraContainerImage(), 0, 0, "", &containertest.FakeOS{}, np, nil, nil, nil)
for i, test := range tests { for i, test := range tests {
fakeClient.RunningContainerList = test.runningContainerList fakeClient.RunningContainerList = test.runningContainerList
fakeClient.ExitedContainerList = test.exitedContainerList fakeClient.ExitedContainerList = test.exitedContainerList
......
...@@ -115,7 +115,7 @@ func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerCli ...@@ -115,7 +115,7 @@ func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerCli
&cadvisorapi.MachineInfo{}, &cadvisorapi.MachineInfo{},
options.GetDefaultPodInfraContainerImage(), options.GetDefaultPodInfraContainerImage(),
0, 0, "", 0, 0, "",
containertest.FakeOS{}, &containertest.FakeOS{},
networkPlugin, networkPlugin,
&fakeRuntimeHelper{}, &fakeRuntimeHelper{},
fakeHTTPClient, fakeHTTPClient,
......
...@@ -123,7 +123,7 @@ func newTestKubelet(t *testing.T) *TestKubelet { ...@@ -123,7 +123,7 @@ func newTestKubelet(t *testing.T) *TestKubelet {
fakeKubeClient := &fake.Clientset{} fakeKubeClient := &fake.Clientset{}
kubelet := &Kubelet{} kubelet := &Kubelet{}
kubelet.kubeClient = fakeKubeClient kubelet.kubeClient = fakeKubeClient
kubelet.os = containertest.FakeOS{} kubelet.os = &containertest.FakeOS{}
kubelet.hostname = testKubeletHostname kubelet.hostname = testKubeletHostname
kubelet.nodeName = testKubeletHostname kubelet.nodeName = testKubeletHostname
......
...@@ -154,7 +154,7 @@ func newTestDockerManager() (*dockertools.DockerManager, *dockertools.FakeDocker ...@@ -154,7 +154,7 @@ func newTestDockerManager() (*dockertools.DockerManager, *dockertools.FakeDocker
&cadvisorapi.MachineInfo{}, &cadvisorapi.MachineInfo{},
options.GetDefaultPodInfraContainerImage(), options.GetDefaultPodInfraContainerImage(),
0, 0, "", 0, 0, "",
containertest.FakeOS{}, &containertest.FakeOS{},
networkPlugin, networkPlugin,
nil, nil,
nil, nil,
......
...@@ -19,16 +19,16 @@ package rkt ...@@ -19,16 +19,16 @@ package rkt
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"sync" "sync"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
"github.com/coreos/go-systemd/dbus" "github.com/coreos/go-systemd/dbus"
rktapi "github.com/coreos/rkt/api/v1alpha" rktapi "github.com/coreos/rkt/api/v1alpha"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"k8s.io/kubernetes/pkg/api"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/types"
) )
// fakeRktInterface mocks the rktapi.PublicAPIClient interface for testing purpose. // fakeRktInterface mocks the rktapi.PublicAPIClient interface for testing purpose.
...@@ -147,6 +147,14 @@ func (f *fakeSystemd) Reload() error { ...@@ -147,6 +147,14 @@ func (f *fakeSystemd) Reload() error {
return fmt.Errorf("Not implemented") return fmt.Errorf("Not implemented")
} }
func (f *fakeSystemd) ResetFailed() error {
f.Lock()
defer f.Unlock()
f.called = append(f.called, "ResetFailed")
return f.err
}
// fakeRuntimeHelper implementes kubecontainer.RuntimeHelper interfaces for testing purpose. // fakeRuntimeHelper implementes kubecontainer.RuntimeHelper interfaces for testing purpose.
type fakeRuntimeHelper struct { type fakeRuntimeHelper struct {
dnsServers []string dnsServers []string
...@@ -171,3 +179,44 @@ func (f *fakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *api.Pod) (string, ...@@ -171,3 +179,44 @@ func (f *fakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *api.Pod) (string,
func (f *fakeRuntimeHelper) GetPodDir(podUID types.UID) string { func (f *fakeRuntimeHelper) GetPodDir(podUID types.UID) string {
return "/poddir/" + string(podUID) return "/poddir/" + string(podUID)
} }
type fakeRktCli struct {
sync.Mutex
cmds []string
result []string
err error
}
func newFakeRktCli() *fakeRktCli {
return &fakeRktCli{
cmds: []string{},
result: []string{},
}
}
func (f *fakeRktCli) RunCommand(args ...string) (result []string, err error) {
f.Lock()
defer f.Unlock()
cmd := append([]string{"rkt"}, args...)
f.cmds = append(f.cmds, strings.Join(cmd, " "))
return f.result, f.err
}
func (f *fakeRktCli) Reset() {
f.cmds = []string{}
f.result = []string{}
f.err = nil
}
type fakePodGetter struct {
pods map[types.UID]*api.Pod
}
func newFakePodGetter() *fakePodGetter {
return &fakePodGetter{pods: make(map[types.UID]*api.Pod)}
}
func (f fakePodGetter) GetPodByUID(uid types.UID) (*api.Pod, bool) {
p, found := f.pods[uid]
return p, found
}
...@@ -68,7 +68,7 @@ func (r *Runtime) PullImage(image kubecontainer.ImageSpec, pullSecrets []api.Sec ...@@ -68,7 +68,7 @@ func (r *Runtime) PullImage(image kubecontainer.ImageSpec, pullSecrets []api.Sec
return err return err
} }
if _, err := r.runCommand("fetch", dockerPrefix+img); err != nil { if _, err := r.cli.RunCommand("fetch", dockerPrefix+img); err != nil {
glog.Errorf("Failed to fetch: %v", err) glog.Errorf("Failed to fetch: %v", err)
return err return err
} }
...@@ -104,7 +104,7 @@ func (r *Runtime) RemoveImage(image kubecontainer.ImageSpec) error { ...@@ -104,7 +104,7 @@ func (r *Runtime) RemoveImage(image kubecontainer.ImageSpec) error {
if err != nil { if err != nil {
return err return err
} }
if _, err := r.runCommand("image", "rm", imageID); err != nil { if _, err := r.cli.RunCommand("image", "rm", imageID); err != nil {
return err return err
} }
return nil return nil
......
...@@ -113,6 +113,7 @@ const ( ...@@ -113,6 +113,7 @@ const (
// uses systemd, so in order to run this runtime, systemd must be installed // uses systemd, so in order to run this runtime, systemd must be installed
// on the machine. // on the machine.
type Runtime struct { type Runtime struct {
cli cliInterface
systemd systemdInterface systemd systemdInterface
// The grpc client for rkt api-service. // The grpc client for rkt api-service.
apisvcConn *grpc.ClientConn apisvcConn *grpc.ClientConn
...@@ -203,6 +204,7 @@ func New( ...@@ -203,6 +204,7 @@ func New(
} }
rkt := &Runtime{ rkt := &Runtime{
os: kubecontainer.RealOS{},
systemd: systemd, systemd: systemd,
apisvcConn: apisvcConn, apisvcConn: apisvcConn,
apisvc: rktapi.NewPublicAPIClient(apisvcConn), apisvc: rktapi.NewPublicAPIClient(apisvcConn),
...@@ -235,6 +237,8 @@ func New( ...@@ -235,6 +237,8 @@ func New(
return nil, fmt.Errorf("rkt: error getting version info: %v", err) return nil, fmt.Errorf("rkt: error getting version info: %v", err)
} }
rkt.cli = rkt
return rkt, nil return rkt, nil
} }
...@@ -253,9 +257,9 @@ func convertToACName(name string) appctypes.ACName { ...@@ -253,9 +257,9 @@ func convertToACName(name string) appctypes.ACName {
return *appctypes.MustACName(acname) return *appctypes.MustACName(acname)
} }
// runCommand invokes rkt binary with arguments and returns the result // RunCommand invokes rkt binary with arguments and returns the result
// from stdout in a list of strings. Each string in the list is a line. // from stdout in a list of strings. Each string in the list is a line.
func (r *Runtime) runCommand(args ...string) ([]string, error) { func (r *Runtime) RunCommand(args ...string) ([]string, error) {
glog.V(4).Info("rkt: Run command:", args) glog.V(4).Info("rkt: Run command:", args)
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
...@@ -647,7 +651,7 @@ func (r *Runtime) podFinishedAt(podUID types.UID, rktUID string) time.Time { ...@@ -647,7 +651,7 @@ func (r *Runtime) podFinishedAt(podUID types.UID, rktUID string) time.Time {
return stat.ModTime() return stat.ModTime()
} }
func makeContainerLogMount(opts *kubecontainer.RunContainerOptions, container *api.Container) (*kubecontainer.Mount, error) { func (r *Runtime) makeContainerLogMount(opts *kubecontainer.RunContainerOptions, container *api.Container) (*kubecontainer.Mount, error) {
if opts.PodContainerDir == "" || container.TerminationMessagePath == "" { if opts.PodContainerDir == "" || container.TerminationMessagePath == "" {
return nil, nil return nil, nil
} }
...@@ -659,7 +663,7 @@ func makeContainerLogMount(opts *kubecontainer.RunContainerOptions, container *a ...@@ -659,7 +663,7 @@ func makeContainerLogMount(opts *kubecontainer.RunContainerOptions, container *a
// on the disk. // on the disk.
randomUID := util.NewUUID() randomUID := util.NewUUID()
containerLogPath := path.Join(opts.PodContainerDir, string(randomUID)) containerLogPath := path.Join(opts.PodContainerDir, string(randomUID))
fs, err := os.Create(containerLogPath) fs, err := r.os.Create(containerLogPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -711,7 +715,7 @@ func (r *Runtime) newAppcRuntimeApp(pod *api.Pod, c api.Container, pullSecrets [ ...@@ -711,7 +715,7 @@ func (r *Runtime) newAppcRuntimeApp(pod *api.Pod, c api.Container, pullSecrets [
} }
// create the container log file and make a mount pair. // create the container log file and make a mount pair.
mnt, err := makeContainerLogMount(opts, &c) mnt, err := r.makeContainerLogMount(opts, &c)
if err != nil { if err != nil {
return err return err
} }
...@@ -868,7 +872,7 @@ func (r *Runtime) generateRunCommand(pod *api.Pod, uuid string) (string, error) ...@@ -868,7 +872,7 @@ func (r *Runtime) generateRunCommand(pod *api.Pod, uuid string) (string, error)
runPrepared = append(runPrepared, "--net=host") runPrepared = append(runPrepared, "--net=host")
// TODO(yifan): Let runtimeHelper.GeneratePodHostNameAndDomain() to handle this. // TODO(yifan): Let runtimeHelper.GeneratePodHostNameAndDomain() to handle this.
hostname, err = os.Hostname() hostname, err = r.os.Hostname()
if err != nil { if err != nil {
return "", err return "", err
} }
...@@ -920,7 +924,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k ...@@ -920,7 +924,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k
} }
defer func() { defer func() {
manifestFile.Close() manifestFile.Close()
if err := os.Remove(manifestFile.Name()); err != nil { if err := r.os.Remove(manifestFile.Name()); err != nil {
glog.Warningf("rkt: Cannot remove temp manifest file %q: %v", manifestFile.Name(), err) glog.Warningf("rkt: Cannot remove temp manifest file %q: %v", manifestFile.Name(), err)
} }
}() }()
...@@ -942,7 +946,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k ...@@ -942,7 +946,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k
if r.config.Stage1Image != "" { if r.config.Stage1Image != "" {
cmds = append(cmds, "--stage1-path", r.config.Stage1Image) cmds = append(cmds, "--stage1-path", r.config.Stage1Image)
} }
output, err := r.runCommand(cmds...) output, err := r.cli.RunCommand(cmds...)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
...@@ -972,7 +976,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k ...@@ -972,7 +976,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k
serviceName := makePodServiceFileName(uuid) serviceName := makePodServiceFileName(uuid)
glog.V(4).Infof("rkt: Creating service file %q for pod %q", serviceName, format.Pod(pod)) glog.V(4).Infof("rkt: Creating service file %q for pod %q", serviceName, format.Pod(pod))
serviceFile, err := os.Create(serviceFilePath(serviceName)) serviceFile, err := r.os.Create(serviceFilePath(serviceName))
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
...@@ -1340,7 +1344,7 @@ func (r *Runtime) KillPod(pod *api.Pod, runningPod kubecontainer.Pod, gracePerio ...@@ -1340,7 +1344,7 @@ func (r *Runtime) KillPod(pod *api.Pod, runningPod kubecontainer.Pod, gracePerio
// Touch the systemd service file to update the mod time so it will // Touch the systemd service file to update the mod time so it will
// not be garbage collected too soon. // not be garbage collected too soon.
if err := os.Chtimes(serviceFilePath(serviceName), time.Now(), time.Now()); err != nil { if err := r.os.Chtimes(serviceFilePath(serviceName), time.Now(), time.Now()); err != nil {
glog.Errorf("rkt: Failed to change the modification time of the service file %q: %v", serviceName, err) glog.Errorf("rkt: Failed to change the modification time of the service file %q: %v", serviceName, err)
return err return err
} }
...@@ -1499,7 +1503,7 @@ func (r *Runtime) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy) error ...@@ -1499,7 +1503,7 @@ func (r *Runtime) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy) error
glog.V(4).Infof("rkt: Garbage collecting triggered with policy %v", gcPolicy) glog.V(4).Infof("rkt: Garbage collecting triggered with policy %v", gcPolicy)
if err := exec.Command("systemctl", "reset-failed").Run(); err != nil { if err := r.systemd.ResetFailed(); err != nil {
glog.Errorf("rkt: Failed to reset failed systemd services: %v, continue to gc anyway...", err) glog.Errorf("rkt: Failed to reset failed systemd services: %v, continue to gc anyway...", err)
} }
...@@ -1670,7 +1674,7 @@ func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []s ...@@ -1670,7 +1674,7 @@ func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []s
// This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit', // This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit',
// the call below to command.Run() can unblock because its Stdin is the read half // the call below to command.Run() can unblock because its Stdin is the read half
// of the pipe. // of the pipe.
r, w, err := os.Pipe() r, w, err := r.os.Pipe()
if err != nil { if err != nil {
return err return err
} }
......
...@@ -33,8 +33,10 @@ import ( ...@@ -33,8 +33,10 @@ import (
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
containertesting "k8s.io/kubernetes/pkg/kubelet/container/testing" containertesting "k8s.io/kubernetes/pkg/kubelet/container/testing"
kubetesting "k8s.io/kubernetes/pkg/kubelet/container/testing"
"k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/lifecycle"
"k8s.io/kubernetes/pkg/kubelet/rkt/mock_os" "k8s.io/kubernetes/pkg/kubelet/rkt/mock_os"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/errors"
utiltesting "k8s.io/kubernetes/pkg/util/testing" utiltesting "k8s.io/kubernetes/pkg/util/testing"
) )
...@@ -1072,11 +1074,7 @@ func TestSetApp(t *testing.T) { ...@@ -1072,11 +1074,7 @@ func TestSetApp(t *testing.T) {
} }
func TestGenerateRunCommand(t *testing.T) { func TestGenerateRunCommand(t *testing.T) {
hostName, err := os.Hostname() hostName := "test-hostname"
if err != nil {
t.Fatalf("Cannot get the hostname: %v", err)
}
tests := []struct { tests := []struct {
pod *api.Pod pod *api.Pod
uuid string uuid string
...@@ -1177,6 +1175,7 @@ func TestGenerateRunCommand(t *testing.T) { ...@@ -1177,6 +1175,7 @@ func TestGenerateRunCommand(t *testing.T) {
} }
rkt := &Runtime{ rkt := &Runtime{
os: &kubetesting.FakeOS{HostName: hostName},
config: &Config{ config: &Config{
Path: "/bin/rkt/rkt", Path: "/bin/rkt/rkt",
Stage1Image: "/bin/rkt/stage1-coreos.aci", Stage1Image: "/bin/rkt/stage1-coreos.aci",
...@@ -1397,3 +1396,218 @@ func TestImageStats(t *testing.T) { ...@@ -1397,3 +1396,218 @@ func TestImageStats(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, result, &kubecontainer.ImageStats{TotalStorageBytes: 600}) assert.Equal(t, result, &kubecontainer.ImageStats{TotalStorageBytes: 600})
} }
func TestGarbageCollect(t *testing.T) {
fr := newFakeRktInterface()
fs := newFakeSystemd()
cli := newFakeRktCli()
fakeOS := kubetesting.NewFakeOS()
getter := newFakePodGetter()
rkt := &Runtime{
os: fakeOS,
cli: cli,
apisvc: fr,
podGetter: getter,
systemd: fs,
containerRefManager: kubecontainer.NewRefManager(),
}
fakeApp := &rktapi.App{Name: "app-foo"}
tests := []struct {
gcPolicy kubecontainer.ContainerGCPolicy
apiPods []*api.Pod
pods []*rktapi.Pod
serviceFilesOnDisk []string
expectedCommands []string
expectedServiceFiles []string
}{
// All running pods, should not be gc'd.
// Dead, new pods should not be gc'd.
// Dead, old pods should be gc'd.
// Deleted pods should be gc'd.
// Service files without corresponded pods should be removed.
{
kubecontainer.ContainerGCPolicy{
MinAge: 0,
MaxContainers: 0,
},
[]*api.Pod{
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-1"}},
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-2"}},
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-3"}},
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-4"}},
},
[]*rktapi.Pod{
{
Id: "deleted-foo",
State: rktapi.PodState_POD_STATE_EXITED,
CreatedAt: time.Now().Add(time.Hour).UnixNano(),
StartedAt: time.Now().Add(time.Hour).UnixNano(),
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-0",
},
},
},
{
Id: "running-foo",
State: rktapi.PodState_POD_STATE_RUNNING,
CreatedAt: 0,
StartedAt: 0,
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-1",
},
},
},
{
Id: "running-bar",
State: rktapi.PodState_POD_STATE_RUNNING,
CreatedAt: 0,
StartedAt: 0,
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-2",
},
},
},
{
Id: "dead-old",
State: rktapi.PodState_POD_STATE_EXITED,
CreatedAt: 0,
StartedAt: 0,
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-3",
},
},
},
{
Id: "dead-new",
State: rktapi.PodState_POD_STATE_EXITED,
CreatedAt: time.Now().Add(time.Hour).UnixNano(),
StartedAt: time.Now().Add(time.Hour).UnixNano(),
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-4",
},
},
},
},
[]string{"k8s_dead-old.service", "k8s_deleted-foo.service", "k8s_non-existing-bar.service"},
[]string{"rkt rm dead-old", "rkt rm deleted-foo"},
[]string{"/run/systemd/system/k8s_dead-old.service", "/run/systemd/system/k8s_deleted-foo.service", "/run/systemd/system/k8s_non-existing-bar.service"},
},
// gcPolicy.MaxContainers should be enforced.
// Oldest ones are removed first.
{
kubecontainer.ContainerGCPolicy{
MinAge: 0,
MaxContainers: 1,
},
[]*api.Pod{
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-0"}},
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-1"}},
{ObjectMeta: api.ObjectMeta{UID: "pod-uid-2"}},
},
[]*rktapi.Pod{
{
Id: "dead-2",
State: rktapi.PodState_POD_STATE_EXITED,
CreatedAt: 2,
StartedAt: 2,
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-2",
},
},
},
{
Id: "dead-1",
State: rktapi.PodState_POD_STATE_EXITED,
CreatedAt: 1,
StartedAt: 1,
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-1",
},
},
},
{
Id: "dead-0",
State: rktapi.PodState_POD_STATE_EXITED,
CreatedAt: 0,
StartedAt: 0,
Apps: []*rktapi.App{fakeApp},
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktUIDAnno,
Value: "pod-uid-0",
},
},
},
},
[]string{"k8s_dead-0.service", "k8s_dead-1.service", "k8s_dead-2.service"},
[]string{"rkt rm dead-0", "rkt rm dead-1"},
[]string{"/run/systemd/system/k8s_dead-0.service", "/run/systemd/system/k8s_dead-1.service"},
},
}
for i, tt := range tests {
testCaseHint := fmt.Sprintf("test case #%d", i)
ctrl := gomock.NewController(t)
fakeOS.ReadDirFn = func(dirname string) ([]os.FileInfo, error) {
serviceFileNames := tt.serviceFilesOnDisk
var fileInfos []os.FileInfo
for _, name := range serviceFileNames {
mockFI := mock_os.NewMockFileInfo(ctrl)
mockFI.EXPECT().Name().Return(name)
fileInfos = append(fileInfos, mockFI)
}
return fileInfos, nil
}
fr.pods = tt.pods
for _, p := range tt.apiPods {
getter.pods[p.UID] = p
}
err := rkt.GarbageCollect(tt.gcPolicy)
assert.NoError(t, err, testCaseHint)
sort.Sort(sortedStringList(tt.expectedCommands))
sort.Sort(sortedStringList(cli.cmds))
assert.Equal(t, tt.expectedCommands, cli.cmds, testCaseHint)
sort.Sort(sortedStringList(tt.expectedServiceFiles))
sort.Sort(sortedStringList(fakeOS.Removes))
assert.Equal(t, tt.expectedServiceFiles, fakeOS.Removes, testCaseHint)
// Cleanup after each test.
cli.Reset()
ctrl.Finish()
fakeOS.Removes = []string{}
getter.pods = make(map[types.UID]*api.Pod)
}
}
...@@ -62,6 +62,8 @@ type systemdInterface interface { ...@@ -62,6 +62,8 @@ type systemdInterface interface {
RestartUnit(name string, mode string, ch chan<- string) (int, error) RestartUnit(name string, mode string, ch chan<- string) (int, error)
// Reload is equivalent to 'systemctl daemon-reload'. // Reload is equivalent to 'systemctl daemon-reload'.
Reload() error Reload() error
// ResetFailed is equivalent to 'systemctl reset-failed'.
ResetFailed() error
} }
// systemd implements the systemdInterface using dbus and systemctl. // systemd implements the systemdInterface using dbus and systemctl.
...@@ -101,3 +103,8 @@ func (s *systemd) Version() (systemdVersion, error) { ...@@ -101,3 +103,8 @@ func (s *systemd) Version() (systemdVersion, error) {
} }
return systemdVersion(result), nil return systemdVersion(result), nil
} }
// ResetFailed calls 'systemctl reset failed'
func (s *systemd) ResetFailed() error {
return exec.Command("systemctl", "reset-failed").Run()
}
...@@ -69,7 +69,7 @@ func TestRunOnce(t *testing.T) { ...@@ -69,7 +69,7 @@ func TestRunOnce(t *testing.T) {
statusManager: status.NewManager(nil, podManager), statusManager: status.NewManager(nil, podManager),
containerRefManager: kubecontainer.NewRefManager(), containerRefManager: kubecontainer.NewRefManager(),
podManager: podManager, podManager: podManager,
os: containertest.FakeOS{}, os: &containertest.FakeOS{},
volumeManager: newVolumeManager(), volumeManager: newVolumeManager(),
diskSpaceManager: diskSpaceManager, diskSpaceManager: diskSpaceManager,
containerRuntime: fakeRuntime, containerRuntime: fakeRuntime,
......
...@@ -65,14 +65,14 @@ func NewHollowKubelet( ...@@ -65,14 +65,14 @@ func NewHollowKubelet(
cadvisorInterface, cadvisorInterface,
manifestFilePath, manifestFilePath,
nil, /* cloud-provider */ nil, /* cloud-provider */
containertest.FakeOS{}, /* os-interface */ &containertest.FakeOS{}, /* os-interface */
20*time.Second, /* FileCheckFrequency */ 20*time.Second, /* FileCheckFrequency */
20*time.Second, /* HTTPCheckFrequency */ 20*time.Second, /* HTTPCheckFrequency */
1*time.Minute, /* MinimumGCAge */ 1*time.Minute, /* MinimumGCAge */
10*time.Second, /* NodeStatusUpdateFrequency */ 10*time.Second, /* NodeStatusUpdateFrequency */
10*time.Second, /* SyncFrequency */ 10*time.Second, /* SyncFrequency */
5*time.Minute, /* OutOfDiskTransitionFrequency */ 5*time.Minute, /* OutOfDiskTransitionFrequency */
5*time.Minute, /* EvictionPressureTransitionPeriod */ 5*time.Minute, /* EvictionPressureTransitionPeriod */
maxPods, maxPods,
containerManager, containerManager,
nil, nil,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment