Rainbond/worker/server/pod.go
2020-09-06 11:11:11 +08:00

227 lines
6.6 KiB
Go

package server
import (
"context"
"errors"
"fmt"
"sort"
"time"
"github.com/sirupsen/logrus"
k8sutil "github.com/goodrain/rainbond/util/k8s"
"github.com/goodrain/rainbond/worker/server/pb"
"github.com/goodrain/rainbond/worker/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/reference"
)
var (
// ErrAppServiceNotFound app service not found error, happens when haven't find any matched data when looking up with a service id
ErrAppServiceNotFound = errors.New("app service not found")
// ErrPodNotFound pod not found error, happens when haven't find any matched data when looking up with a pod name
ErrPodNotFound = errors.New("pod not found")
)
// GetPodDetail returns detail information of the pod based on pod name.
func (r *RuntimeServer) GetPodDetail(ctx context.Context, req *pb.GetPodDetailReq) (podDetail *pb.PodDetail, err error) {
pod, err := r.getPodByName(req.Sid, req.PodName)
if err != nil {
logrus.Errorf("name: %s; error getting pod: %v", req.PodName, err)
return
}
if pod == nil {
err = ErrPodNotFound
return
}
// describe pod
podDetail = &pb.PodDetail{}
podDetail.Name = pod.Name
podDetail.Version = func() string {
labels := pod.GetLabels()
if labels == nil {
return ""
}
return labels["version"]
}()
if pod.Status.StartTime != nil {
podDetail.StartTime = pod.Status.StartTime.Time.Format(time.RFC3339)
}
podDetail.InitContainers = make([]*pb.PodContainer, len(pod.Spec.InitContainers))
podDetail.Containers = make([]*pb.PodContainer, len(pod.Spec.Containers))
podDetail.Status = &pb.PodStatus{}
podDetail.Events = []*pb.PodEvent{}
if pod.Spec.NodeName != "" {
podDetail.Node = pod.Spec.NodeName
}
if pod.Status.StartTime != nil {
podDetail.StartTime = pod.Status.StartTime.Time.Format(time.RFC3339)
}
podDetail.NodeIp = pod.Status.HostIP
podDetail.Ip = pod.Status.PodIP
events := r.listPodEventsByPod(pod)
if len(events) != 0 {
podDetail.Events = append(podDetail.Events, events...)
}
if len(pod.Spec.InitContainers) != 0 {
describeContainers(pod.Spec.InitContainers, pod.Status.InitContainerStatuses, &podDetail.InitContainers)
}
describeContainers(pod.Spec.Containers, pod.Status.ContainerStatuses, &podDetail.Containers)
util.DescribePodStatus(r.clientset, pod, podDetail.Status, k8sutil.DefListEventsByPod)
return podDetail, nil
}
func (r *RuntimeServer) getPodByName(sid, name string) (*corev1.Pod, error) {
app := r.store.GetAppService(sid)
if app == nil {
return nil, ErrAppServiceNotFound
}
return app.GetPodsByName(name), nil
}
// GetPodEvents -
func (r *RuntimeServer) listPodEventsByPod(pod *corev1.Pod) []*pb.PodEvent {
ref, err := reference.GetReference(scheme.Scheme, pod)
if err != nil {
logrus.Errorf("Unable to construct reference to '%#v': %v", pod, err)
// custome event
event := &pb.PodEvent{
Type: "Warning", // TODO: use k8s enum
Reason: fmt.Sprintf("error getting pod events."),
Message: err.Error(),
}
return []*pb.PodEvent{event}
}
ref.Kind = ""
if _, isMirrorPod := pod.Annotations[corev1.MirrorPodAnnotationKey]; isMirrorPod {
ref.UID = types.UID(pod.Annotations[corev1.MirrorPodAnnotationKey])
}
events, _ := r.clientset.CoreV1().Events(pod.GetNamespace()).Search(scheme.Scheme, ref)
podEvents := DescribeEvents(events)
return podEvents
}
// GetPodEventsByName -
func (r RuntimeServer) listPodEventsByName(name, namespace string) []*pb.PodEvent {
eventsInterface := r.clientset.CoreV1().Events(namespace)
selector := eventsInterface.GetFieldSelector(&name, &namespace, nil, nil)
options := metav1.ListOptions{FieldSelector: selector.String()}
events, err := eventsInterface.List(options)
if err == nil && len(events.Items) > 0 {
podEvents := DescribeEvents(events)
return podEvents
}
if err != nil {
// custome event
event := &pb.PodEvent{
Type: "Warning", // TODO: use k8s enum
Reason: fmt.Sprintf("error getting pod events."),
Message: err.Error(),
}
return []*pb.PodEvent{event}
}
return nil
}
func describeContainers(containers []corev1.Container, containerStatuses []corev1.ContainerStatus, podContainers *[]*pb.PodContainer) {
statuses := map[string]corev1.ContainerStatus{}
for _, status := range containerStatuses {
statuses[status.Name] = status
}
for idx, container := range containers {
status, ok := statuses[container.Name]
pc := &pb.PodContainer{
Image: container.Image,
}
if ok {
describeContainerState(status, pc)
}
describeContainerResource(container, pc)
pcs := *podContainers
pcs[idx] = pc
}
}
func describeContainerState(status corev1.ContainerStatus, podContainer *pb.PodContainer) {
describeStatus(status.State, podContainer)
// TODO: LastTerminationState
// TODO: Ready
// TODO: Restart Count
}
func describeStatus(state corev1.ContainerState, podContainer *pb.PodContainer) {
switch {
case state.Running != nil:
podContainer.State = "Running"
podContainer.Started = state.Running.StartedAt.Time.Format(time.RFC3339)
case state.Waiting != nil:
podContainer.State = "Waiting"
if state.Waiting.Reason != "" {
podContainer.Reason = state.Waiting.Reason
}
case state.Terminated != nil:
podContainer.State = "Terminated"
if state.Terminated.Reason != "" {
podContainer.Reason = state.Terminated.Reason
}
default:
podContainer.State = "Waiting"
}
}
func describeContainerResource(container corev1.Container, pc *pb.PodContainer) {
resources := container.Resources
for _, name := range SortedResourceNames(resources.Limits) {
quantity := resources.Limits[name]
if name == "memory" {
pc.LimitMemory = quantity.String()
}
if name == "cpu" {
pc.LimitCpu = quantity.String()
}
}
for _, name := range SortedResourceNames(resources.Requests) {
quantity := resources.Limits[name]
if name == "memory" {
pc.RequestMemory = quantity.String()
}
if name == "cpu" {
pc.RequestCpu = quantity.String()
}
}
}
// SortableResourceNames -
type SortableResourceNames []corev1.ResourceName
func (list SortableResourceNames) Len() int {
return len(list)
}
func (list SortableResourceNames) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}
func (list SortableResourceNames) Less(i, j int) bool {
return list[i] < list[j]
}
// SortedResourceNames returns the sorted resource names of a resource list.
func SortedResourceNames(list corev1.ResourceList) []corev1.ResourceName {
resources := make([]corev1.ResourceName, 0, len(list))
for res := range list {
resources = append(resources, res)
}
sort.Sort(SortableResourceNames(resources))
return resources
}