rkt: Implement pod FinishedAt

This is implemented via touching a file on stop as a hook in the systemd
unit. The ctime of this file is then used to get the `finishedAt` time
in the future.
In addition, this changes the `startedAt` and `createdAt` to use the api
server's results rather than the annotations it previously used.

It's possible we might want to move this into the api in the future.

Fixes #23887
This commit is contained in:
Euan Kemp 2016-04-14 18:01:40 -07:00
parent 82f3ec14fb
commit a6718f5969
9 changed files with 306 additions and 85 deletions

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/client/record"
"k8s.io/kubernetes/pkg/kubelet/util/format"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
hashutil "k8s.io/kubernetes/pkg/util/hash"
"k8s.io/kubernetes/third_party/golang/expansion"
@ -42,6 +43,7 @@ type RuntimeHelper interface {
GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*RunContainerOptions, error)
GetClusterDNS(pod *api.Pod) (dnsServers []string, dnsSearches []string, err error)
GeneratePodHostNameAndDomain(pod *api.Pod) (hostname string, hostDomain string)
GetPodDir(podUID types.UID) string
}
// ShouldContainerBeRestarted checks whether a container needs to be restarted.

View File

@ -25,6 +25,7 @@ import (
type OSInterface interface {
Mkdir(path string, perm os.FileMode) error
Symlink(oldname string, newname string) error
Stat(path string) (os.FileInfo, error)
}
// RealOS is used to dispatch the real system level operaitons.
@ -39,3 +40,8 @@ func (RealOS) Mkdir(path string, perm os.FileMode) error {
func (RealOS) Symlink(oldname string, newname string) error {
return os.Symlink(oldname, newname)
}
// Stat will call os.Stat to get the FileInfo for a given path
func (RealOS) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}

View File

@ -17,12 +17,17 @@ limitations under the License.
package testing
import (
"errors"
"os"
)
// FakeOS mocks out certain OS calls to avoid perturbing the filesystem
// on the test machine.
type FakeOS struct{}
// If a member of the form `*Fn` is set, that function will be called in place
// of the real call.
type FakeOS struct {
StatFn func(string) (os.FileInfo, error)
}
// Mkdir is a fake call that just returns nil.
func (FakeOS) Mkdir(path string, perm os.FileMode) error {
@ -33,3 +38,11 @@ func (FakeOS) Mkdir(path string, perm os.FileMode) error {
func (FakeOS) Symlink(oldname string, newname string) error {
return nil
}
// Stat is a fake that returns an error
func (f FakeOS) Stat(path string) (os.FileInfo, error) {
if f.StatFn != nil {
return f.StatFn(path)
}
return nil, errors.New("unimplemented testing mock")
}

View File

@ -93,6 +93,10 @@ func (f *fakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *api.Pod) (string,
return "", ""
}
func (f *fakeRuntimeHelper) GetPodDir(types.UID) string {
return ""
}
func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerClient) (*DockerManager, *FakeDockerClient) {
if fakeHTTPClient == nil {
fakeHTTPClient = &fakeHTTP{}

View File

@ -72,6 +72,7 @@ import (
"k8s.io/kubernetes/pkg/util/atomic"
"k8s.io/kubernetes/pkg/util/bandwidth"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
utilexec "k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/flowcontrol"
kubeio "k8s.io/kubernetes/pkg/util/io"
"k8s.io/kubernetes/pkg/util/mount"
@ -426,6 +427,8 @@ func NewMainKubelet(
klet.livenessManager,
klet.volumeManager,
klet.httpClient,
utilexec.New(),
kubecontainer.RealOS{},
imageBackOff,
serializeImagePulls,
)
@ -830,8 +833,12 @@ func (kl *Kubelet) getPluginDir(pluginName string) string {
return path.Join(kl.getPluginsDir(), pluginName)
}
// getPodDir returns the full path to the per-pod data directory for the
// GetPodDir returns the full path to the per-pod data directory for the
// specified pod. This directory may not exist if the pod does not exist.
func (kl *Kubelet) GetPodDir(podUID types.UID) string {
return kl.getPodDir(podUID)
}
func (kl *Kubelet) getPodDir(podUID types.UID) string {
// Backwards compat. The "old" stuff should be removed before 1.0
// release. The thinking here is this:

View File

@ -22,6 +22,7 @@ import (
"sync"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
"github.com/coreos/go-systemd/dbus"
rktapi "github.com/coreos/rkt/api/v1alpha"
@ -166,3 +167,7 @@ func (f *fakeRuntimeHelper) GetClusterDNS(pod *api.Pod) ([]string, []string, err
func (f *fakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *api.Pod) (string, string) {
return f.hostName, f.hostDomain
}
func (f *fakeRuntimeHelper) GetPodDir(podUID types.UID) string {
return "/poddir/" + string(podUID)
}

View File

@ -0,0 +1,109 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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.
*/
// Generated via: mockgen os FileInfo
// Edited to include required boilerplate
// Source: os (interfaces: FileInfo)
package mock_os
import (
os "os"
time "time"
gomock "github.com/golang/mock/gomock"
)
// Mock of FileInfo interface
type MockFileInfo struct {
ctrl *gomock.Controller
recorder *_MockFileInfoRecorder
}
// Recorder for MockFileInfo (not exported)
type _MockFileInfoRecorder struct {
mock *MockFileInfo
}
func NewMockFileInfo(ctrl *gomock.Controller) *MockFileInfo {
mock := &MockFileInfo{ctrl: ctrl}
mock.recorder = &_MockFileInfoRecorder{mock}
return mock
}
func (_m *MockFileInfo) EXPECT() *_MockFileInfoRecorder {
return _m.recorder
}
func (_m *MockFileInfo) IsDir() bool {
ret := _m.ctrl.Call(_m, "IsDir")
ret0, _ := ret[0].(bool)
return ret0
}
func (_mr *_MockFileInfoRecorder) IsDir() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "IsDir")
}
func (_m *MockFileInfo) ModTime() time.Time {
ret := _m.ctrl.Call(_m, "ModTime")
ret0, _ := ret[0].(time.Time)
return ret0
}
func (_mr *_MockFileInfoRecorder) ModTime() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "ModTime")
}
func (_m *MockFileInfo) Mode() os.FileMode {
ret := _m.ctrl.Call(_m, "Mode")
ret0, _ := ret[0].(os.FileMode)
return ret0
}
func (_mr *_MockFileInfoRecorder) Mode() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Mode")
}
func (_m *MockFileInfo) Name() string {
ret := _m.ctrl.Call(_m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
func (_mr *_MockFileInfoRecorder) Name() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Name")
}
func (_m *MockFileInfo) Size() int64 {
ret := _m.ctrl.Call(_m, "Size")
ret0, _ := ret[0].(int64)
return ret0
}
func (_mr *_MockFileInfoRecorder) Size() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Size")
}
func (_m *MockFileInfo) Sys() interface{} {
ret := _m.ctrl.Call(_m, "Sys")
ret0, _ := ret[0].(interface{})
return ret0
}
func (_mr *_MockFileInfoRecorder) Sys() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Sys")
}

View File

@ -25,6 +25,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
@ -81,8 +82,6 @@ const (
k8sRktUIDAnno = "rkt.kubernetes.io/uid"
k8sRktNameAnno = "rkt.kubernetes.io/name"
k8sRktNamespaceAnno = "rkt.kubernetes.io/namespace"
//TODO: remove the creation time annotation once this is closed: https://github.com/coreos/rkt/issues/1789
k8sRktCreationTimeAnno = "rkt.kubernetes.io/created"
k8sRktContainerHashAnno = "rkt.kubernetes.io/container-hash"
k8sRktRestartCountAnno = "rkt.kubernetes.io/restart-count"
k8sRktTerminationMessagePathAnno = "rkt.kubernetes.io/termination-message-path"
@ -128,6 +127,11 @@ type Runtime struct {
volumeGetter volumeGetter
imagePuller kubecontainer.ImagePuller
runner kubecontainer.HandlerRunner
execer utilexec.Interface
os kubecontainer.OSInterface
// used for a systemd Exec, which requires the full path.
touchPath string
versions versions
}
@ -151,6 +155,8 @@ func New(
livenessManager proberesults.Manager,
volumeGetter volumeGetter,
httpClient kubetypes.HttpGetter,
execer utilexec.Interface,
os kubecontainer.OSInterface,
imageBackOff *flowcontrol.Backoff,
serializeImagePulls bool,
) (*Runtime, error) {
@ -170,12 +176,17 @@ func New(
if config.Path == "" {
// No default rkt path was set, so try to find one in $PATH.
var err error
config.Path, err = exec.LookPath("rkt")
config.Path, err = execer.LookPath("rkt")
if err != nil {
return nil, fmt.Errorf("cannot find rkt binary: %v", err)
}
}
touchPath, err := execer.LookPath("touch")
if err != nil {
return nil, fmt.Errorf("cannot find touch binary: %v", err)
}
rkt := &Runtime{
systemd: systemd,
apisvcConn: apisvcConn,
@ -187,6 +198,8 @@ func New(
recorder: recorder,
livenessManager: livenessManager,
volumeGetter: volumeGetter,
execer: execer,
touchPath: touchPath,
}
rkt.config, err = rkt.getConfig(rkt.config)
@ -541,7 +554,6 @@ func (r *Runtime) makePodManifest(pod *api.Pod, pullSecrets []api.Secret) (*appc
manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktUIDAnno), string(pod.UID))
manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktNameAnno), pod.Name)
manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktNamespaceAnno), pod.Namespace)
manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktCreationTimeAnno), strconv.FormatInt(time.Now().Unix(), 10))
manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktRestartCountAnno), strconv.Itoa(restartCount))
for _, c := range pod.Spec.Containers {
@ -587,6 +599,34 @@ func makeHostNetworkMount(opts *kubecontainer.RunContainerOptions) (*kubecontain
return &hostsMount, &resolvMount
}
// podFinishedMarkerPath returns the path to a file which should be used to
// indicate the pod exiting, and the time thereof.
// If the file at the path does not exist, the pod should not be exited. If it
// does exist, then the ctime of the file should indicate the time the pod
// exited.
func podFinishedMarkerPath(podDir string, rktUID string) string {
return filepath.Join(podDir, "finished-"+rktUID)
}
func podFinishedMarkCommand(touchPath, podDir, rktUID string) string {
// TODO, if the path has a `'` character in it, this breaks.
return touchPath + " " + podFinishedMarkerPath(podDir, rktUID)
}
// podFinishedAt returns the time that a pod exited, or a zero time if it has
// not.
func (r *Runtime) podFinishedAt(podUID types.UID, rktUID string) time.Time {
markerFile := podFinishedMarkerPath(r.runtimeHelper.GetPodDir(podUID), rktUID)
stat, err := r.os.Stat(markerFile)
if err != nil {
if !os.IsNotExist(err) {
glog.Warningf("rkt: unexpected fs error checking pod finished marker: %v", err)
}
return time.Time{}
}
return stat.ModTime()
}
func makeContainerLogMount(opts *kubecontainer.RunContainerOptions, container *api.Container) (*kubecontainer.Mount, error) {
if opts.PodContainerDir == "" || container.TerminationMessagePath == "" {
return nil, nil
@ -884,8 +924,11 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k
// TODO handle pod.Spec.HostPID
// TODO handle pod.Spec.HostIPC
// TODO per container finishedAt, not just per pod
markPodFinished := podFinishedMarkCommand(r.touchPath, r.runtimeHelper.GetPodDir(pod.UID), uuid)
units := []*unit.UnitOption{
newUnitOption("Service", "ExecStart", runPrepared),
newUnitOption("Service", "ExecStopPost", markPodFinished),
// This enables graceful stop.
newUnitOption("Service", "KillMode", "mixed"),
}
@ -1045,14 +1088,6 @@ func (r *Runtime) convertRktPod(rktpod *rktapi.Pod) (*kubecontainer.Pod, error)
if !ok {
return nil, fmt.Errorf("pod is missing annotation %s", k8sRktNamespaceAnno)
}
podCreatedString, ok := manifest.Annotations.Get(k8sRktCreationTimeAnno)
if !ok {
return nil, fmt.Errorf("pod is missing annotation %s", k8sRktCreationTimeAnno)
}
podCreated, err := strconv.ParseInt(podCreatedString, 10, 64)
if err != nil {
return nil, fmt.Errorf("couldn't parse pod creation timestamp: %v", err)
}
kubepod := &kubecontainer.Pod{
ID: types.UID(podUID),
@ -1078,8 +1113,8 @@ func (r *Runtime) convertRktPod(rktpod *rktapi.Pod) (*kubecontainer.Pod, error)
// By default, the version returned by rkt API service will be "latest" if not specified.
Image: fmt.Sprintf("%s:%s", app.Image.Name, app.Image.Version),
Hash: containerHash,
Created: podCreated,
State: appStateToContainerState(app.State),
Created: time.Unix(0, rktpod.CreatedAt).Unix(), // convert ns to s
})
}
@ -1526,7 +1561,7 @@ func appStateToContainerState(state rktapi.AppState) kubecontainer.ContainerStat
}
// getPodInfo returns the pod manifest, creation time and restart count of the pod.
func getPodInfo(pod *rktapi.Pod) (podManifest *appcschema.PodManifest, creationTime time.Time, restartCount int, err error) {
func getPodInfo(pod *rktapi.Pod) (podManifest *appcschema.PodManifest, restartCount int, err error) {
// TODO(yifan): The manifest is only used for getting the annotations.
// Consider to let the server to unmarshal the annotations.
var manifest appcschema.PodManifest
@ -1534,16 +1569,6 @@ func getPodInfo(pod *rktapi.Pod) (podManifest *appcschema.PodManifest, creationT
return
}
creationTimeStr, ok := manifest.Annotations.Get(k8sRktCreationTimeAnno)
if !ok {
err = fmt.Errorf("no creation timestamp in pod manifest")
return
}
unixSec, err := strconv.ParseInt(creationTimeStr, 10, 64)
if err != nil {
return
}
if countString, ok := manifest.Annotations.Get(k8sRktRestartCountAnno); ok {
restartCount, err = strconv.Atoi(countString)
if err != nil {
@ -1551,11 +1576,11 @@ func getPodInfo(pod *rktapi.Pod) (podManifest *appcschema.PodManifest, creationT
}
}
return &manifest, time.Unix(unixSec, 0), restartCount, nil
return &manifest, restartCount, nil
}
// populateContainerStatus fills the container status according to the app's information.
func populateContainerStatus(pod rktapi.Pod, app rktapi.App, runtimeApp appcschema.RuntimeApp, restartCount int, creationTime time.Time) (*kubecontainer.ContainerStatus, error) {
func populateContainerStatus(pod rktapi.Pod, app rktapi.App, runtimeApp appcschema.RuntimeApp, restartCount int, finishedTime time.Time) (*kubecontainer.ContainerStatus, error) {
hashStr, ok := runtimeApp.Annotations.Get(k8sRktContainerHashAnno)
if !ok {
return nil, fmt.Errorf("No container hash in pod manifest")
@ -1584,13 +1609,16 @@ func populateContainerStatus(pod rktapi.Pod, app rktapi.App, runtimeApp appcsche
}
}
createdTime := time.Unix(0, pod.CreatedAt)
startedTime := time.Unix(0, pod.StartedAt)
return &kubecontainer.ContainerStatus{
ID: buildContainerID(&containerID{uuid: pod.Id, appName: app.Name}),
Name: app.Name,
State: appStateToContainerState(app.State),
// TODO(yifan): Use the creation/start/finished timestamp when it's implemented.
CreatedAt: creationTime,
StartedAt: creationTime,
CreatedAt: createdTime,
StartedAt: startedTime,
FinishedAt: finishedTime,
ExitCode: int(app.ExitCode),
// By default, the version returned by rkt API service will be "latest" if not specified.
Image: fmt.Sprintf("%s:%s", app.Image.Name, app.Image.Version),
@ -1605,6 +1633,13 @@ func populateContainerStatus(pod rktapi.Pod, app rktapi.App, runtimeApp appcsche
}, nil
}
// GetPodStatus returns the status for a pod specified by a given UID, name,
// and namespace. It will attempt to find pod's information via a request to
// the rkt api server.
// An error will be returned if the api server returns an error. If the api
// server doesn't error, but doesn't provide meaningful information about the
// pod, a status with no information (other than the passed in arguments) is
// returned anyways.
func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) {
podStatus := &kubecontainer.PodStatus{
ID: uid,
@ -1626,7 +1661,7 @@ func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecont
// In this loop, we group all containers from all pods together,
// also we try to find the latest pod, so we can fill other info of the pod below.
for _, pod := range listResp.Pods {
manifest, creationTime, restartCount, err := getPodInfo(pod)
manifest, restartCount, err := getPodInfo(pod)
if err != nil {
glog.Warningf("rkt: Couldn't get necessary info from the rkt pod, (uuid %q): %v", pod.Id, err)
continue
@ -1637,11 +1672,10 @@ func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecont
latestRestartCount = restartCount
}
finishedTime := r.podFinishedAt(uid, pod.Id)
for i, app := range pod.Apps {
// The order of the apps is determined by the rkt pod manifest.
// TODO(yifan): Save creationTime, restartCount in each app's annotation,
// so we don't need to pass them.
cs, err := populateContainerStatus(*pod, *app, manifest.Apps[i], restartCount, creationTime)
cs, err := populateContainerStatus(*pod, *app, manifest.Apps[i], restartCount, finishedTime)
if err != nil {
glog.Warningf("rkt: Failed to populate container status(uuid %q, app %q): %v", pod.Id, app.Name, err)
continue

View File

@ -27,11 +27,14 @@ import (
appcschema "github.com/appc/spec/schema"
appctypes "github.com/appc/spec/schema/types"
rktapi "github.com/coreos/rkt/api/v1alpha"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
containertesting "k8s.io/kubernetes/pkg/kubelet/container/testing"
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
"k8s.io/kubernetes/pkg/kubelet/rkt/mock_os"
"k8s.io/kubernetes/pkg/util/errors"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
)
@ -62,9 +65,10 @@ func mustRktHash(hash string) *appctypes.Hash {
func makeRktPod(rktPodState rktapi.PodState,
rktPodID, podUID, podName, podNamespace,
podIP, podCreationTs, podRestartCount string,
appNames, imgIDs, imgNames, containerHashes []string,
appStates []rktapi.AppState, exitcodes []int32) *rktapi.Pod {
podIP string, podCreatedAt, podStartedAt int64,
podRestartCount string, appNames, imgIDs, imgNames,
containerHashes []string, appStates []rktapi.AppState,
exitcodes []int32) *rktapi.Pod {
podManifest := &appcschema.PodManifest{
ACKind: appcschema.PodManifestKind,
@ -86,10 +90,6 @@ func makeRktPod(rktPodState rktapi.PodState,
Name: *appctypes.MustACIdentifier(k8sRktNamespaceAnno),
Value: podNamespace,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktCreationTimeAnno),
Value: podCreationTs,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktRestartCountAnno),
Value: podRestartCount,
@ -148,6 +148,8 @@ func makeRktPod(rktPodState rktapi.PodState,
Networks: []*rktapi.Network{{Name: defaultNetworkName, Ipv4: podIP}},
Apps: apps,
Manifest: mustMarshalPodManifest(podManifest),
StartedAt: podStartedAt,
CreatedAt: podCreatedAt,
}
}
@ -346,6 +348,10 @@ func TestGetPods(t *testing.T) {
fs := newFakeSystemd()
r := &Runtime{apisvc: fr, systemd: fs}
ns := func(seconds int64) int64 {
return seconds * 1e9
}
tests := []struct {
pods []*rktapi.Pod
result []*kubecontainer.Pod
@ -357,7 +363,7 @@ func TestGetPods(t *testing.T) {
[]*rktapi.Pod{
makeRktPod(rktapi.PodState_POD_STATE_RUNNING,
"uuid-4002", "42", "guestbook", "default",
"10.10.10.42", "100000", "7",
"10.10.10.42", ns(10), ns(10), "7",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
@ -377,7 +383,7 @@ func TestGetPods(t *testing.T) {
Name: "app-1",
Image: "img-name-1:latest",
Hash: 1001,
Created: 100000,
Created: 10,
State: "running",
},
{
@ -385,7 +391,7 @@ func TestGetPods(t *testing.T) {
Name: "app-2",
Image: "img-name-2:latest",
Hash: 1002,
Created: 100000,
Created: 10,
State: "exited",
},
},
@ -397,7 +403,7 @@ func TestGetPods(t *testing.T) {
[]*rktapi.Pod{
makeRktPod(rktapi.PodState_POD_STATE_RUNNING,
"uuid-4002", "42", "guestbook", "default",
"10.10.10.42", "100000", "7",
"10.10.10.42", ns(10), ns(20), "7",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
@ -407,7 +413,7 @@ func TestGetPods(t *testing.T) {
),
makeRktPod(rktapi.PodState_POD_STATE_EXITED,
"uuid-4003", "43", "guestbook", "default",
"10.10.10.43", "90000", "7",
"10.10.10.43", ns(30), ns(40), "7",
[]string{"app-11", "app-22"},
[]string{"img-id-11", "img-id-22"},
[]string{"img-name-11", "img-name-22"},
@ -417,7 +423,7 @@ func TestGetPods(t *testing.T) {
),
makeRktPod(rktapi.PodState_POD_STATE_EXITED,
"uuid-4004", "43", "guestbook", "default",
"10.10.10.44", "100000", "8",
"10.10.10.44", ns(50), ns(60), "8",
[]string{"app-11", "app-22"},
[]string{"img-id-11", "img-id-22"},
[]string{"img-name-11", "img-name-22"},
@ -437,7 +443,7 @@ func TestGetPods(t *testing.T) {
Name: "app-1",
Image: "img-name-1:latest",
Hash: 1001,
Created: 100000,
Created: 10,
State: "running",
},
{
@ -445,7 +451,7 @@ func TestGetPods(t *testing.T) {
Name: "app-2",
Image: "img-name-2:latest",
Hash: 1002,
Created: 100000,
Created: 10,
State: "exited",
},
},
@ -460,7 +466,7 @@ func TestGetPods(t *testing.T) {
Name: "app-11",
Image: "img-name-11:latest",
Hash: 10011,
Created: 90000,
Created: 30,
State: "exited",
},
{
@ -468,7 +474,7 @@ func TestGetPods(t *testing.T) {
Name: "app-22",
Image: "img-name-22:latest",
Hash: 10022,
Created: 90000,
Created: 30,
State: "exited",
},
{
@ -476,7 +482,7 @@ func TestGetPods(t *testing.T) {
Name: "app-11",
Image: "img-name-11:latest",
Hash: 10011,
Created: 100000,
Created: 50,
State: "running",
},
{
@ -484,7 +490,7 @@ func TestGetPods(t *testing.T) {
Name: "app-22",
Image: "img-name-22:latest",
Hash: 10022,
Created: 100000,
Created: 50,
State: "running",
},
},
@ -557,7 +563,18 @@ func TestGetPodsFilters(t *testing.T) {
func TestGetPodStatus(t *testing.T) {
fr := newFakeRktInterface()
fs := newFakeSystemd()
r := &Runtime{apisvc: fr, systemd: fs}
fos := &containertesting.FakeOS{}
frh := &fakeRuntimeHelper{}
r := &Runtime{
apisvc: fr,
systemd: fs,
runtimeHelper: frh,
os: fos,
}
ns := func(seconds int64) int64 {
return seconds * 1e9
}
tests := []struct {
pods []*rktapi.Pod
@ -573,7 +590,7 @@ func TestGetPodStatus(t *testing.T) {
[]*rktapi.Pod{
makeRktPod(rktapi.PodState_POD_STATE_RUNNING,
"uuid-4002", "42", "guestbook", "default",
"10.10.10.42", "100000", "7",
"10.10.10.42", ns(10), ns(20), "7",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
@ -592,8 +609,9 @@ func TestGetPodStatus(t *testing.T) {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"),
Name: "app-1",
State: kubecontainer.ContainerStateRunning,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
CreatedAt: time.Unix(10, 0),
StartedAt: time.Unix(20, 0),
FinishedAt: time.Unix(0, 30),
Image: "img-name-1:latest",
ImageID: "rkt://img-id-1",
Hash: 1001,
@ -603,8 +621,9 @@ func TestGetPodStatus(t *testing.T) {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"),
Name: "app-2",
State: kubecontainer.ContainerStateExited,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
CreatedAt: time.Unix(10, 0),
StartedAt: time.Unix(20, 0),
FinishedAt: time.Unix(0, 30),
Image: "img-name-2:latest",
ImageID: "rkt://img-id-2",
Hash: 1002,
@ -619,7 +638,7 @@ func TestGetPodStatus(t *testing.T) {
[]*rktapi.Pod{
makeRktPod(rktapi.PodState_POD_STATE_EXITED,
"uuid-4002", "42", "guestbook", "default",
"10.10.10.42", "90000", "7",
"10.10.10.42", ns(10), ns(20), "7",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
@ -629,7 +648,7 @@ func TestGetPodStatus(t *testing.T) {
),
makeRktPod(rktapi.PodState_POD_STATE_RUNNING, // The latest pod is running.
"uuid-4003", "42", "guestbook", "default",
"10.10.10.42", "100000", "10",
"10.10.10.42", ns(10), ns(20), "10",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
@ -649,8 +668,9 @@ func TestGetPodStatus(t *testing.T) {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"),
Name: "app-1",
State: kubecontainer.ContainerStateRunning,
CreatedAt: time.Unix(90000, 0),
StartedAt: time.Unix(90000, 0),
CreatedAt: time.Unix(10, 0),
StartedAt: time.Unix(20, 0),
FinishedAt: time.Unix(0, 30),
Image: "img-name-1:latest",
ImageID: "rkt://img-id-1",
Hash: 1001,
@ -660,8 +680,9 @@ func TestGetPodStatus(t *testing.T) {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"),
Name: "app-2",
State: kubecontainer.ContainerStateExited,
CreatedAt: time.Unix(90000, 0),
StartedAt: time.Unix(90000, 0),
CreatedAt: time.Unix(10, 0),
StartedAt: time.Unix(20, 0),
FinishedAt: time.Unix(0, 30),
Image: "img-name-2:latest",
ImageID: "rkt://img-id-2",
Hash: 1002,
@ -672,8 +693,9 @@ func TestGetPodStatus(t *testing.T) {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-1"),
Name: "app-1",
State: kubecontainer.ContainerStateRunning,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
CreatedAt: time.Unix(10, 0),
StartedAt: time.Unix(20, 0),
FinishedAt: time.Unix(0, 30),
Image: "img-name-1:latest",
ImageID: "rkt://img-id-1",
Hash: 1001,
@ -683,8 +705,9 @@ func TestGetPodStatus(t *testing.T) {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-2"),
Name: "app-2",
State: kubecontainer.ContainerStateExited,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
CreatedAt: time.Unix(10, 0),
StartedAt: time.Unix(20, 0),
FinishedAt: time.Unix(0, 30),
Image: "img-name-2:latest",
ImageID: "rkt://img-id-2",
Hash: 1002,
@ -697,10 +720,28 @@ func TestGetPodStatus(t *testing.T) {
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
for i, tt := range tests {
testCaseHint := fmt.Sprintf("test case #%d", i)
fr.pods = tt.pods
podTimes := map[string]time.Time{}
for _, pod := range tt.pods {
podTimes[podFinishedMarkerPath(r.runtimeHelper.GetPodDir(tt.result.ID), pod.Id)] = tt.result.ContainerStatuses[0].FinishedAt
}
r.os.(*containertesting.FakeOS).StatFn = func(name string) (os.FileInfo, error) {
podTime, ok := podTimes[name]
if !ok {
t.Errorf("osStat called with %v, but only knew about %#v", name, podTimes)
}
mockFI := mock_os.NewMockFileInfo(ctrl)
mockFI.EXPECT().ModTime().Return(podTime)
return mockFI, nil
}
status, err := r.GetPodStatus("42", "guestbook", "default")
if err != nil {
t.Errorf("test case #%d: unexpected error: %v", i, err)