Merge pull request #18643 from yifan-gu/rkt_get_pod_status

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2015-12-19 02:38:31 -08:00
commit b7dd32ad70
2 changed files with 602 additions and 197 deletions

View File

@ -25,6 +25,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"sort"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -40,6 +41,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"golang.org/x/net/context" "golang.org/x/net/context"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/record" "k8s.io/kubernetes/pkg/client/record"
"k8s.io/kubernetes/pkg/credentialprovider" "k8s.io/kubernetes/pkg/credentialprovider"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
@ -89,6 +91,7 @@ const (
defaultImageTag = "latest" defaultImageTag = "latest"
defaultRktAPIServiceAddr = "localhost:15441" defaultRktAPIServiceAddr = "localhost:15441"
defaultNetworkName = "rkt.kubernetes.io"
) )
// Runtime implements the Containerruntime for rkt. The implementation // Runtime implements the Containerruntime for rkt. The implementation
@ -680,7 +683,7 @@ func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *k
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork { if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork {
runPrepared = fmt.Sprintf("%s run-prepared --mds-register=false --net=host %s", r.rktBinAbsPath, uuid) runPrepared = fmt.Sprintf("%s run-prepared --mds-register=false --net=host %s", r.rktBinAbsPath, uuid)
} else { } else {
runPrepared = fmt.Sprintf("%s run-prepared --mds-register=false %s", r.rktBinAbsPath, uuid) runPrepared = fmt.Sprintf("%s run-prepared --mds-register=false --net=%s %s", r.rktBinAbsPath, defaultNetworkName, uuid)
} }
// TODO handle pod.Spec.HostPID // TODO handle pod.Spec.HostPID
@ -842,35 +845,22 @@ func (r *Runtime) convertRktPod(rktpod rktapi.Pod) (*kubecontainer.Pod, error) {
return nil, fmt.Errorf("couldn't parse pod creation timestamp: %v", err) return nil, fmt.Errorf("couldn't parse pod creation timestamp: %v", err)
} }
var state kubecontainer.ContainerState
switch rktpod.State {
case rktapi.PodState_POD_STATE_RUNNING:
state = kubecontainer.ContainerStateRunning
case rktapi.PodState_POD_STATE_ABORTED_PREPARE, rktapi.PodState_POD_STATE_EXITED,
rktapi.PodState_POD_STATE_DELETING, rktapi.PodState_POD_STATE_GARBAGE:
state = kubecontainer.ContainerStateExited
default:
state = kubecontainer.ContainerStateUnknown
}
kubepod := &kubecontainer.Pod{ kubepod := &kubecontainer.Pod{
ID: types.UID(podUID), ID: types.UID(podUID),
Name: podName, Name: podName,
Namespace: podNamespace, Namespace: podNamespace,
} }
for _, app := range rktpod.Apps {
manifest := &appcschema.ImageManifest{} for i, app := range rktpod.Apps {
err := json.Unmarshal(app.Image.Manifest, manifest) // The order of the apps is determined by the rkt pod manifest.
if err != nil { // TODO(yifan): Let the server to unmarshal the annotations? https://github.com/coreos/rkt/issues/1872
return nil, err hashStr, ok := manifest.Apps[i].Annotations.Get(k8sRktContainerHashAnno)
}
containerHashString, ok := manifest.Annotations.Get(k8sRktContainerHashAnno)
if !ok { if !ok {
return nil, fmt.Errorf("app is missing annotation %s", k8sRktContainerHashAnno) return nil, fmt.Errorf("app %q is missing annotation %s", app.Name, k8sRktContainerHashAnno)
} }
containerHash, err := strconv.ParseUint(containerHashString, 10, 64) containerHash, err := strconv.ParseUint(hashStr, 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't parse container's hash: %v", err) return nil, fmt.Errorf("couldn't parse container's hash %q: %v", hashStr, err)
} }
kubepod.Containers = append(kubepod.Containers, &kubecontainer.Container{ kubepod.Containers = append(kubepod.Containers, &kubecontainer.Container{
@ -879,7 +869,7 @@ func (r *Runtime) convertRktPod(rktpod rktapi.Pod) (*kubecontainer.Pod, error) {
Image: app.Image.Name, Image: app.Image.Name,
Hash: containerHash, Hash: containerHash,
Created: podCreated, Created: podCreated,
State: state, State: appStateToContainerState(app.State),
}) })
} }
@ -1519,16 +1509,234 @@ func (r *Runtime) RemoveImage(image kubecontainer.ImageSpec) error {
return nil return nil
} }
func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { // appStateToContainerState converts rktapi.AppState to kubecontainer.ContainerState.
return nil, fmt.Errorf("Not implemented yet") func appStateToContainerState(state rktapi.AppState) kubecontainer.ContainerState {
switch state {
case rktapi.AppState_APP_STATE_RUNNING:
return kubecontainer.ContainerStateRunning
case rktapi.AppState_APP_STATE_EXITED:
return kubecontainer.ContainerStateExited
}
return kubecontainer.ContainerStateUnknown
} }
func (r *Runtime) ConvertPodStatusToAPIPodStatus(_ *api.Pod, _ *kubecontainer.PodStatus) (*api.PodStatus, error) { // TODO(yifan): Rename to getPodInfo when the old getPodInfo is removed.
return nil, fmt.Errorf("Not implemented yet") func retrievePodInfo(pod *rktapi.Pod) (podManifest *appcschema.PodManifest, creationTime time.Time, restartCount int, err error) {
var manifest appcschema.PodManifest
if err = json.Unmarshal(pod.Manifest, &manifest); err != nil {
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 {
return
}
}
return &manifest, time.Unix(unixSec, 0), restartCount, nil
}
func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) {
podStatus := &kubecontainer.PodStatus{
ID: uid,
Name: name,
Namespace: namespace,
}
// TODO(yifan): Use ListPods(detail=true) to avoid another InspectPod rpc here.
listReq := &rktapi.ListPodsRequest{
Filter: &rktapi.PodFilter{
Annotations: []*rktapi.KeyValue{
{
Key: k8sRktKubeletAnno,
Value: k8sRktKubeletAnnoValue,
},
{
Key: k8sRktUIDAnno,
Value: string(uid),
},
},
},
}
listResp, err := r.apisvc.ListPods(context.Background(), listReq)
if err != nil {
return nil, fmt.Errorf("couldn't list pods: %v", err)
}
var latestPod *rktapi.Pod
var latestRestartCount int = -1
// Only use the latest pod to get the information, (which has the
// largest restart count).
for _, rktpod := range listResp.Pods {
inspectReq := &rktapi.InspectPodRequest{rktpod.Id}
inspectResp, err := r.apisvc.InspectPod(context.Background(), inspectReq)
if err != nil {
glog.Warningf("rkt: Couldn't inspect rkt pod, (uuid %q): %v", rktpod.Id, err)
continue
}
pod := inspectResp.Pod
manifest, creationTime, restartCount, err := retrievePodInfo(pod)
if err != nil {
glog.Warning("rkt: Couldn't get necessary info from the rkt pod, (uuid %q): %v", pod.Id, err)
continue
}
if restartCount > latestRestartCount {
latestPod = pod
latestRestartCount = restartCount
}
for i, app := range pod.Apps {
// The order of the apps is determined by the rkt pod manifest.
// TODO(yifan): Let the server to unmarshal the annotations?
hashStr, ok := manifest.Apps[i].Annotations.Get(k8sRktContainerHashAnno)
if !ok {
glog.Warningf("rkt: No container hash in pod manifest for rkt pod, (uuid %q, app %q)", pod.Id, app.Name)
continue
}
hashNum, err := strconv.ParseUint(hashStr, 10, 64)
if err != nil {
glog.Warningf("rkt: Invalid hash value %q for rkt pod, (uuid %q, app %q)", pod.Id, app.Name)
continue
}
cs := 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,
ExitCode: int(app.ExitCode),
Image: app.Image.Name,
ImageID: "rkt://" + app.Image.Id, // TODO(yifan): Add the prefix only in api.PodStatus.
Hash: hashNum,
// TODO(yifan): Note that now all apps share the same restart count, this might
// change once apps don't share the same lifecycle.
// See https://github.com/appc/spec/pull/547.
RestartCount: restartCount,
// TODO(yifan): Add reason and message field.
}
podStatus.ContainerStatuses = append(podStatus.ContainerStatuses, &cs)
}
}
if latestPod != nil {
// Try to fill the IP info.
for _, n := range latestPod.Networks {
if n.Name == defaultNetworkName {
podStatus.IP = n.Ipv4
}
}
}
return podStatus, nil
}
type sortByRestartCount []*kubecontainer.ContainerStatus
func (s sortByRestartCount) Len() int { return len(s) }
func (s sortByRestartCount) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortByRestartCount) Less(i, j int) bool { return s[i].RestartCount > s[j].RestartCount }
// TODO(yifan): Delete this function when the logic is moved to kubelet.
func (r *Runtime) ConvertPodStatusToAPIPodStatus(pod *api.Pod, status *kubecontainer.PodStatus) (*api.PodStatus, error) {
apiPodStatus := &api.PodStatus{
// TODO(yifan): Add reason and message field.
PodIP: status.IP,
}
sort.Sort(sortByRestartCount(status.ContainerStatuses))
containerStatuses := make(map[string]*api.ContainerStatus)
for _, c := range status.ContainerStatuses {
var st api.ContainerState
switch c.State {
case kubecontainer.ContainerStateRunning:
st.Running = &api.ContainerStateRunning{
StartedAt: unversioned.NewTime(c.StartedAt),
}
case kubecontainer.ContainerStateExited:
if pod.Spec.RestartPolicy == api.RestartPolicyAlways ||
pod.Spec.RestartPolicy == api.RestartPolicyOnFailure && c.ExitCode != 0 {
// TODO(yifan): Add reason and message.
st.Waiting = &api.ContainerStateWaiting{}
break
}
st.Terminated = &api.ContainerStateTerminated{
ExitCode: c.ExitCode,
StartedAt: unversioned.NewTime(c.StartedAt),
// TODO(yifan): Add reason, message, finishedAt, signal.
ContainerID: c.ID.String(),
}
default:
// Unknown state.
// TODO(yifan): Add reason and message.
st.Waiting = &api.ContainerStateWaiting{}
}
status, ok := containerStatuses[c.Name]
if !ok {
containerStatuses[c.Name] = &api.ContainerStatus{
Name: c.Name,
Image: c.Image,
ImageID: c.ImageID,
ContainerID: c.ID.String(),
RestartCount: c.RestartCount,
State: st,
}
continue
}
// Found multiple container statuses, fill that as last termination state.
if status.LastTerminationState.Waiting == nil &&
status.LastTerminationState.Running == nil &&
status.LastTerminationState.Terminated == nil {
status.LastTerminationState = st
}
}
for _, c := range pod.Spec.Containers {
cs, ok := containerStatuses[c.Name]
if !ok {
cs = &api.ContainerStatus{
Name: c.Name,
Image: c.Image,
// TODO(yifan): Add reason and message.
State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
}
}
apiPodStatus.ContainerStatuses = append(apiPodStatus.ContainerStatuses, *cs)
}
return apiPodStatus, nil
} }
func (r *Runtime) GetPodStatusAndAPIPodStatus(pod *api.Pod) (*kubecontainer.PodStatus, *api.PodStatus, error) { func (r *Runtime) GetPodStatusAndAPIPodStatus(pod *api.Pod) (*kubecontainer.PodStatus, *api.PodStatus, error) {
podStatus, err := r.GetAPIPodStatus(pod) // Get the pod status.
return nil, podStatus, err podStatus, err := r.GetPodStatus(pod.UID, pod.Name, pod.Namespace)
if err != nil {
return nil, nil, err
}
var apiPodStatus *api.PodStatus
apiPodStatus, err = r.ConvertPodStatusToAPIPodStatus(pod, podStatus)
return podStatus, apiPodStatus, err
} }

View File

@ -20,14 +20,128 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"testing" "testing"
"time"
appcschema "github.com/appc/spec/schema" appcschema "github.com/appc/spec/schema"
appctypes "github.com/appc/spec/schema/types" appctypes "github.com/appc/spec/schema/types"
rktapi "github.com/coreos/rkt/api/v1alpha" rktapi "github.com/coreos/rkt/api/v1alpha"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/types" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
) )
func mustMarshalPodManifest(man *appcschema.PodManifest) []byte {
manblob, err := json.Marshal(man)
if err != nil {
panic(err)
}
return manblob
}
func mustMarshalImageManifest(man *appcschema.ImageManifest) []byte {
manblob, err := json.Marshal(man)
if err != nil {
panic(err)
}
return manblob
}
func mustRktHash(hash string) *appctypes.Hash {
h, err := appctypes.NewHash(hash)
if err != nil {
panic(err)
}
return h
}
func makeRktPod(rktPodState rktapi.PodState,
rktPodID, podUID, podName, podNamespace,
podIP, podCreationTs, podRestartCount string,
appNames, imgIDs, imgNames, containerHashes []string,
appStates []rktapi.AppState) *rktapi.Pod {
podManifest := &appcschema.PodManifest{
ACKind: appcschema.PodManifestKind,
ACVersion: appcschema.AppContainerVersion,
Annotations: appctypes.Annotations{
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktKubeletAnno),
Value: k8sRktKubeletAnnoValue,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktUIDAnno),
Value: podUID,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktNameAnno),
Value: podName,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktNamespaceAnno),
Value: podNamespace,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktCreationTimeAnno),
Value: podCreationTs,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktRestartCountAnno),
Value: podRestartCount,
},
},
}
appNum := len(appNames)
if appNum != len(imgNames) ||
appNum != len(imgIDs) ||
appNum != len(containerHashes) ||
appNum != len(appStates) {
panic("inconsistent app number")
}
apps := make([]*rktapi.App, appNum)
for i := range appNames {
apps[i] = &rktapi.App{
Name: appNames[i],
State: appStates[i],
Image: &rktapi.Image{
Id: imgIDs[i],
Name: imgNames[i],
Manifest: mustMarshalImageManifest(
&appcschema.ImageManifest{
ACKind: appcschema.ImageManifestKind,
ACVersion: appcschema.AppContainerVersion,
Name: *appctypes.MustACIdentifier(imgNames[i]),
Annotations: appctypes.Annotations{
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno),
Value: containerHashes[i],
},
},
},
),
},
}
podManifest.Apps = append(podManifest.Apps, appcschema.RuntimeApp{
Name: *appctypes.MustACName(appNames[i]),
Image: appcschema.RuntimeImage{ID: *mustRktHash("sha512-foo")},
Annotations: appctypes.Annotations{
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno),
Value: containerHashes[i],
},
},
})
}
return &rktapi.Pod{
Id: rktPodID,
State: rktPodState,
Networks: []*rktapi.Network{{Name: defaultNetworkName, Ipv4: podIP}},
Apps: apps,
Manifest: mustMarshalPodManifest(podManifest),
}
}
func TestCheckVersion(t *testing.T) { func TestCheckVersion(t *testing.T) {
fr := newFakeRktInterface() fr := newFakeRktInterface()
fs := newFakeSystemd() fs := newFakeSystemd()
@ -208,190 +322,133 @@ func TestGetPods(t *testing.T) {
r := &Runtime{apisvc: fr, systemd: fs} r := &Runtime{apisvc: fr, systemd: fs}
tests := []struct { tests := []struct {
k8sUID types.UID pods []*rktapi.Pod
k8sName string result []*kubecontainer.Pod
k8sNamespace string
k8sCreation int64
k8sRestart int
k8sContHashes []uint64
rktPodState rktapi.PodState
pods []*rktapi.Pod
}{ }{
// No pods.
{}, {},
// One pod.
{ {
k8sUID: types.UID("0"), []*rktapi.Pod{
k8sName: "guestbook", makeRktPod(rktapi.PodState_POD_STATE_RUNNING,
k8sNamespace: "default", "uuid-4002", "42", "guestbook", "default",
k8sCreation: 10000000000, "10.10.10.42", "100000", "7",
k8sRestart: 1, []string{"app-1", "app-2"},
k8sContHashes: []uint64{2353434678}, []string{"img-id-1", "img-id-2"},
rktPodState: rktapi.PodState_POD_STATE_RUNNING, []string{"img-name-1", "img-name-2"},
pods: []*rktapi.Pod{ []string{"1001", "1002"},
[]rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED},
),
},
[]*kubecontainer.Pod{
{ {
State: rktapi.PodState_POD_STATE_RUNNING, ID: "42",
Apps: []*rktapi.App{ Name: "guestbook",
Namespace: "default",
Containers: []*kubecontainer.Container{
{ {
Name: "test", ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"),
Image: &rktapi.Image{ Name: "app-1",
Name: "test", Image: "img-name-1",
Manifest: mustMarshalImageManifest( Hash: 1001,
&appcschema.ImageManifest{ Created: 100000,
ACKind: appcschema.ImageManifestKind, State: "running",
ACVersion: appcschema.AppContainerVersion, },
Name: *appctypes.MustACIdentifier("test"), {
Annotations: appctypes.Annotations{ ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"),
appctypes.Annotation{ Name: "app-2",
Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno), Image: "img-name-2",
Value: "2353434678", Hash: 1002,
}, Created: 100000,
}, State: "exited",
},
),
},
}, },
}, },
Manifest: mustMarshalPodManifest(
&appcschema.PodManifest{
ACKind: appcschema.PodManifestKind,
ACVersion: appcschema.AppContainerVersion,
Annotations: appctypes.Annotations{
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktKubeletAnno),
Value: k8sRktKubeletAnnoValue,
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktUIDAnno),
Value: "0",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktNameAnno),
Value: "guestbook",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktNamespaceAnno),
Value: "default",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktCreationTimeAnno),
Value: "10000000000",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktRestartCountAnno),
Value: "1",
},
},
},
),
}, },
}, },
}, },
// Multiple pods.
{ {
k8sUID: types.UID("1"), []*rktapi.Pod{
k8sName: "test-pod", makeRktPod(rktapi.PodState_POD_STATE_RUNNING,
k8sNamespace: "default", "uuid-4002", "42", "guestbook", "default",
k8sCreation: 10000000001, "10.10.10.42", "100000", "7",
k8sRestart: 3, []string{"app-1", "app-2"},
k8sContHashes: []uint64{2353434682, 8732645}, []string{"img-id-1", "img-id-2"},
rktPodState: rktapi.PodState_POD_STATE_EXITED, []string{"img-name-1", "img-name-2"},
pods: []*rktapi.Pod{ []string{"1001", "1002"},
[]rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED},
),
makeRktPod(rktapi.PodState_POD_STATE_EXITED,
"uuid-4003", "43", "guestbook", "default",
"10.10.10.43", "90000", "7",
[]string{"app-11", "app-22"},
[]string{"img-id-11", "img-id-22"},
[]string{"img-name-11", "img-name-22"},
[]string{"10011", "10022"},
[]rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED},
),
},
[]*kubecontainer.Pod{
{ {
State: rktapi.PodState_POD_STATE_EXITED, ID: "42",
Apps: []*rktapi.App{ Name: "guestbook",
Namespace: "default",
Containers: []*kubecontainer.Container{
{ {
Name: "test", ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"),
Image: &rktapi.Image{ Name: "app-1",
Name: "test", Image: "img-name-1",
Manifest: mustMarshalImageManifest( Hash: 1001,
&appcschema.ImageManifest{ Created: 100000,
ACKind: appcschema.ImageManifestKind, State: "running",
ACVersion: appcschema.AppContainerVersion,
Name: *appctypes.MustACIdentifier("test"),
Annotations: appctypes.Annotations{
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno),
Value: "2353434682",
},
},
},
),
},
}, },
{ {
Name: "test2", ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"),
Image: &rktapi.Image{ Name: "app-2",
Name: "test2", Image: "img-name-2",
Manifest: mustMarshalImageManifest( Hash: 1002,
&appcschema.ImageManifest{ Created: 100000,
ACKind: appcschema.ImageManifestKind, State: "exited",
ACVersion: appcschema.AppContainerVersion,
Name: *appctypes.MustACIdentifier("test2"),
Annotations: appctypes.Annotations{
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno),
Value: "8732645",
},
},
},
),
},
}, },
}, },
Manifest: mustMarshalPodManifest( },
&appcschema.PodManifest{ {
ACKind: appcschema.PodManifestKind, ID: "43",
ACVersion: appcschema.AppContainerVersion, Name: "guestbook",
Annotations: appctypes.Annotations{ Namespace: "default",
appctypes.Annotation{ Containers: []*kubecontainer.Container{
Name: *appctypes.MustACIdentifier(k8sRktKubeletAnno), {
Value: k8sRktKubeletAnnoValue, ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-11"),
}, Name: "app-11",
appctypes.Annotation{ Image: "img-name-11",
Name: *appctypes.MustACIdentifier(k8sRktUIDAnno), Hash: 10011,
Value: "1", Created: 90000,
}, State: "running",
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktNameAnno),
Value: "test-pod",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktNamespaceAnno),
Value: "default",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktCreationTimeAnno),
Value: "10000000001",
},
appctypes.Annotation{
Name: *appctypes.MustACIdentifier(k8sRktRestartCountAnno),
Value: "3",
},
},
}, },
), {
ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-22"),
Name: "app-22",
Image: "img-name-22",
Hash: 10022,
Created: 90000,
State: "exited",
},
},
}, },
}, },
}, },
} }
for i, tt := range tests { for i, tt := range tests {
testCaseHint := fmt.Sprintf("test case #%d", i)
fr.pods = tt.pods fr.pods = tt.pods
pods, err := r.GetPods(true) pods, err := r.GetPods(true)
if err != nil { if err != nil {
t.Errorf("%v", err) t.Errorf("test case #%d: unexpected error: %v", i, err)
} }
assert.Equal(t, len(pods), len(tt.pods), fmt.Sprintf("test case %d: mismatched number of pods", i))
for j, pod := range pods { assert.Equal(t, tt.result, pods, testCaseHint)
assert.Equal(t, pod.ID, tt.k8sUID, fmt.Sprintf("test case %d: mismatched UIDs", i))
assert.Equal(t, pod.Name, tt.k8sName, fmt.Sprintf("test case %d: mismatched Names", i))
assert.Equal(t, pod.Namespace, tt.k8sNamespace, fmt.Sprintf("test case %d: mismatched Namespaces", i))
assert.Equal(t, len(pod.Containers), len(tt.pods[j].Apps), fmt.Sprintf("test case %d: mismatched number of containers", i))
for k, cont := range pod.Containers {
assert.Equal(t, cont.Created, tt.k8sCreation, fmt.Sprintf("test case %d: mismatched creation times", i))
assert.Equal(t, cont.Hash, tt.k8sContHashes[k], fmt.Sprintf("test case %d: mismatched container hashes", i))
}
}
var inspectPodCalls []string var inspectPodCalls []string
for range pods { for range pods {
@ -444,18 +501,158 @@ func TestGetPodsFilter(t *testing.T) {
} }
} }
func mustMarshalPodManifest(man *appcschema.PodManifest) []byte { func TestGetPodStatus(t *testing.T) {
manblob, err := json.Marshal(man) fr := newFakeRktInterface()
if err != nil { fs := newFakeSystemd()
panic(err) r := &Runtime{apisvc: fr, systemd: fs}
}
return manblob
}
func mustMarshalImageManifest(man *appcschema.ImageManifest) []byte { tests := []struct {
manblob, err := json.Marshal(man) pods []*rktapi.Pod
if err != nil { result *kubecontainer.PodStatus
panic(err) }{
// No pods.
{
nil,
&kubecontainer.PodStatus{ID: "42", Name: "guestbook", Namespace: "default"},
},
// One pod.
{
[]*rktapi.Pod{
makeRktPod(rktapi.PodState_POD_STATE_RUNNING,
"uuid-4002", "42", "guestbook", "default",
"10.10.10.42", "100000", "7",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
[]string{"1001", "1002"},
[]rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED},
),
},
&kubecontainer.PodStatus{
ID: "42",
Name: "guestbook",
Namespace: "default",
IP: "10.10.10.42",
ContainerStatuses: []*kubecontainer.ContainerStatus{
{
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"),
Name: "app-1",
State: kubecontainer.ContainerStateRunning,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
Image: "img-name-1",
ImageID: "rkt://img-id-1",
Hash: 1001,
RestartCount: 7,
},
{
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"),
Name: "app-2",
State: kubecontainer.ContainerStateExited,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
Image: "img-name-2",
ImageID: "rkt://img-id-2",
Hash: 1002,
RestartCount: 7,
},
},
},
},
// Multiple pods.
{
[]*rktapi.Pod{
makeRktPod(rktapi.PodState_POD_STATE_EXITED,
"uuid-4002", "42", "guestbook", "default",
"10.10.10.42", "90000", "7",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
[]string{"1001", "1002"},
[]rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED},
),
makeRktPod(rktapi.PodState_POD_STATE_RUNNING, // The latest pod is running.
"uuid-4003", "42", "guestbook", "default",
"10.10.10.42", "100000", "10",
[]string{"app-1", "app-2"},
[]string{"img-id-1", "img-id-2"},
[]string{"img-name-1", "img-name-2"},
[]string{"1001", "1002"},
[]rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED},
),
},
&kubecontainer.PodStatus{
ID: "42",
Name: "guestbook",
Namespace: "default",
IP: "10.10.10.42",
// Result should contain all contianers.
ContainerStatuses: []*kubecontainer.ContainerStatus{
{
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"),
Name: "app-1",
State: kubecontainer.ContainerStateRunning,
CreatedAt: time.Unix(90000, 0),
StartedAt: time.Unix(90000, 0),
Image: "img-name-1",
ImageID: "rkt://img-id-1",
Hash: 1001,
RestartCount: 7,
},
{
ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"),
Name: "app-2",
State: kubecontainer.ContainerStateExited,
CreatedAt: time.Unix(90000, 0),
StartedAt: time.Unix(90000, 0),
Image: "img-name-2",
ImageID: "rkt://img-id-2",
Hash: 1002,
RestartCount: 7,
},
{
ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-1"),
Name: "app-1",
State: kubecontainer.ContainerStateRunning,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
Image: "img-name-1",
ImageID: "rkt://img-id-1",
Hash: 1001,
RestartCount: 10,
},
{
ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-2"),
Name: "app-2",
State: kubecontainer.ContainerStateExited,
CreatedAt: time.Unix(100000, 0),
StartedAt: time.Unix(100000, 0),
Image: "img-name-2",
ImageID: "rkt://img-id-2",
Hash: 1002,
RestartCount: 10,
},
},
},
},
}
for i, tt := range tests {
testCaseHint := fmt.Sprintf("test case #%d", i)
fr.pods = tt.pods
status, err := r.GetPodStatus("42", "guestbook", "default")
if err != nil {
t.Errorf("test case #%d: unexpected error: %v", i, err)
}
assert.Equal(t, tt.result, status, testCaseHint)
var inspectPodCalls []string
for range tt.pods {
inspectPodCalls = append(inspectPodCalls, "InspectPod")
}
assert.Equal(t, append([]string{"ListPods"}, inspectPodCalls...), fr.called, testCaseHint)
fr.CleanCalls()
} }
return manblob
} }