github.com/spotahome/redis-operator@v1.2.4/service/k8s/pod.go (about)

     1  package k8s
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  
     7  	"k8s.io/apimachinery/pkg/types"
     8  
     9  	corev1 "k8s.io/api/core/v1"
    10  	"k8s.io/apimachinery/pkg/api/errors"
    11  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/client-go/kubernetes"
    13  
    14  	"github.com/spotahome/redis-operator/log"
    15  	"github.com/spotahome/redis-operator/metrics"
    16  )
    17  
    18  // Pod the ServiceAccount service that knows how to interact with k8s to manage them
    19  type Pod interface {
    20  	GetPod(namespace string, name string) (*corev1.Pod, error)
    21  	CreatePod(namespace string, pod *corev1.Pod) error
    22  	UpdatePod(namespace string, pod *corev1.Pod) error
    23  	CreateOrUpdatePod(namespace string, pod *corev1.Pod) error
    24  	DeletePod(namespace string, name string) error
    25  	ListPods(namespace string) (*corev1.PodList, error)
    26  	UpdatePodLabels(namespace, podName string, labels map[string]string) error
    27  }
    28  
    29  // PodService is the pod service implementation using API calls to kubernetes.
    30  type PodService struct {
    31  	kubeClient      kubernetes.Interface
    32  	logger          log.Logger
    33  	metricsRecorder metrics.Recorder
    34  }
    35  
    36  // NewPodService returns a new Pod KubeService.
    37  func NewPodService(kubeClient kubernetes.Interface, logger log.Logger, metricsRecorder metrics.Recorder) *PodService {
    38  	logger = logger.With("service", "k8s.pod")
    39  	return &PodService{
    40  		kubeClient:      kubeClient,
    41  		logger:          logger,
    42  		metricsRecorder: metricsRecorder,
    43  	}
    44  }
    45  
    46  func (p *PodService) GetPod(namespace string, name string) (*corev1.Pod, error) {
    47  	pod, err := p.kubeClient.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    48  	recordMetrics(namespace, "Pod", name, "GET", err, p.metricsRecorder)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	return pod, err
    53  }
    54  
    55  func (p *PodService) CreatePod(namespace string, pod *corev1.Pod) error {
    56  	_, err := p.kubeClient.CoreV1().Pods(namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
    57  	recordMetrics(namespace, "Pod", pod.GetName(), "CREATE", err, p.metricsRecorder)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	p.logger.WithField("namespace", namespace).WithField("pod", pod.Name).Debugf("pod created")
    62  	return nil
    63  }
    64  func (p *PodService) UpdatePod(namespace string, pod *corev1.Pod) error {
    65  	_, err := p.kubeClient.CoreV1().Pods(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{})
    66  	recordMetrics(namespace, "Pod", pod.GetName(), "UPDATE", err, p.metricsRecorder)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	p.logger.WithField("namespace", namespace).WithField("pod", pod.Name).Debugf("pod updated")
    71  	return nil
    72  }
    73  func (p *PodService) CreateOrUpdatePod(namespace string, pod *corev1.Pod) error {
    74  	storedPod, err := p.GetPod(namespace, pod.Name)
    75  	if err != nil {
    76  		// If no resource we need to create.
    77  		if errors.IsNotFound(err) {
    78  			return p.CreatePod(namespace, pod)
    79  		}
    80  		return err
    81  	}
    82  
    83  	// Already exists, need to Update.
    84  	// Set the correct resource version to ensure we are on the latest version. This way the only valid
    85  	// namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency),
    86  	// we will replace the current namespace state.
    87  	pod.ResourceVersion = storedPod.ResourceVersion
    88  	return p.UpdatePod(namespace, pod)
    89  }
    90  
    91  func (p *PodService) DeletePod(namespace string, name string) error {
    92  	err := p.kubeClient.CoreV1().Pods(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
    93  	recordMetrics(namespace, "Pod", name, "DELETE", err, p.metricsRecorder)
    94  	return err
    95  }
    96  
    97  func (p *PodService) ListPods(namespace string) (*corev1.PodList, error) {
    98  	pods, err := p.kubeClient.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
    99  	recordMetrics(namespace, "Pod", metrics.NOT_APPLICABLE, "LIST", err, p.metricsRecorder)
   100  	return pods, err
   101  }
   102  
   103  // PatchStringValue specifies a patch operation for a string.
   104  type PatchStringValue struct {
   105  	Op    string      `json:"op"`
   106  	Path  string      `json:"path"`
   107  	Value interface{} `json:"value"`
   108  }
   109  
   110  func (p *PodService) UpdatePodLabels(namespace, podName string, labels map[string]string) error {
   111  	p.logger.Infof("Update pod label, namespace: %s, pod name: %s, labels: %v", namespace, podName, labels)
   112  
   113  	var payloads []interface{}
   114  	for labelKey, labelValue := range labels {
   115  		payload := PatchStringValue{
   116  			Op:    "replace",
   117  			Path:  "/metadata/labels/" + labelKey,
   118  			Value: labelValue,
   119  		}
   120  		payloads = append(payloads, payload)
   121  	}
   122  	payloadBytes, _ := json.Marshal(payloads)
   123  
   124  	_, err := p.kubeClient.CoreV1().Pods(namespace).Patch(context.TODO(), podName, types.JSONPatchType, payloadBytes, metav1.PatchOptions{})
   125  	recordMetrics(namespace, "Pod", podName, "PATCH", err, p.metricsRecorder)
   126  	if err != nil {
   127  		p.logger.Errorf("Update pod labels failed, namespace: %s, pod name: %s, error: %v", namespace, podName, err)
   128  	}
   129  	return err
   130  }