github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/k8s/client.go (about)

     1  // Package k8s: initialization, client, and misc. helpers
     2  /*
     3   * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package k8s
     6  
     7  import (
     8  	"context"
     9  	"io"
    10  	"os"
    11  	"strings"
    12  
    13  	"github.com/NVIDIA/aistore/api/env"
    14  	"github.com/NVIDIA/aistore/cmn/debug"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/fields"
    18  	"k8s.io/client-go/kubernetes"
    19  	tcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    20  	"k8s.io/client-go/rest"
    21  )
    22  
    23  type (
    24  	// Client is simplified version of default `kubernetes.Interface` client.
    25  	Client interface {
    26  		Create(v any) error
    27  		Delete(entityType, entityName string) error
    28  		CheckExists(entityType, entityName string) (bool, error)
    29  		Pod(name string) (*corev1.Pod, error)
    30  		Pods() (*corev1.PodList, error)
    31  		Service(name string) (*corev1.Service, error)
    32  		Node(name string) (*corev1.Node, error)
    33  		Logs(podName string) ([]byte, error)
    34  		Health(podName string) (string, error)
    35  		CheckMetricsAvailability() error
    36  	}
    37  
    38  	// defaultClient implements Client interface.
    39  	defaultClient struct {
    40  		client    kubernetes.Interface
    41  		config    *rest.Config
    42  		namespace string
    43  		err       error
    44  	}
    45  )
    46  
    47  var (
    48  	_defaultK8sClient *defaultClient
    49  )
    50  
    51  func _initClient() {
    52  	config, err := rest.InClusterConfig()
    53  	if err != nil {
    54  		_defaultK8sClient = &defaultClient{err: err}
    55  		return
    56  	}
    57  	client, err := kubernetes.NewForConfig(config)
    58  	if err != nil {
    59  		_defaultK8sClient = &defaultClient{err: err}
    60  		return
    61  	}
    62  	_defaultK8sClient = &defaultClient{
    63  		namespace: _namespace(),
    64  		client:    client,
    65  		config:    config,
    66  	}
    67  }
    68  
    69  // Retrieve pod namespace
    70  // See:
    71  //   - topic: "how to get current namespace of an in-cluster go Kubernetes client"
    72  //   - https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
    73  func _namespace() (namespace string) {
    74  	// production
    75  	if namespace = os.Getenv(env.AIS.K8sNamespace); namespace != "" {
    76  		debug.Func(func() {
    77  			ns := os.Getenv(defaultNamespaceEnv)
    78  			debug.Assertf(ns == "" || ns == namespace, "%q vs %q", ns, namespace)
    79  		})
    80  		return
    81  	}
    82  	// otherwise, try default env var
    83  	if namespace = os.Getenv(defaultNamespaceEnv); namespace != "" {
    84  		return
    85  	}
    86  	// finally, last resort kludge
    87  	if ns, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
    88  		if namespace = strings.TrimSpace(string(ns)); namespace != "" {
    89  			return
    90  		}
    91  	}
    92  	return "default"
    93  }
    94  
    95  func GetClient() (Client, error) {
    96  	if _defaultK8sClient.err != nil {
    97  		return nil, _defaultK8sClient.err
    98  	}
    99  	return _defaultK8sClient, nil
   100  }
   101  
   102  ///////////////////
   103  // defaultClient //
   104  ///////////////////
   105  
   106  func (c *defaultClient) pods() tcorev1.PodInterface {
   107  	return c.client.CoreV1().Pods(c.namespace)
   108  }
   109  
   110  func (c *defaultClient) services() tcorev1.ServiceInterface {
   111  	return c.client.CoreV1().Services(c.namespace)
   112  }
   113  
   114  func (c *defaultClient) Create(v any) (err error) {
   115  	ctx := context.Background()
   116  	switch t := v.(type) {
   117  	case *corev1.Pod:
   118  		_, err = c.pods().Create(ctx, t, metav1.CreateOptions{})
   119  	case *corev1.Service:
   120  		_, err = c.services().Create(ctx, t, metav1.CreateOptions{})
   121  	default:
   122  		debug.FailTypeCast(v)
   123  	}
   124  	return
   125  }
   126  
   127  func (c *defaultClient) Delete(entityType, entityName string) (err error) {
   128  	ctx := context.Background()
   129  	switch entityType {
   130  	case Pod:
   131  		err = c.pods().Delete(ctx, entityName, *metav1.NewDeleteOptions(0))
   132  	case Svc:
   133  		err = c.services().Delete(ctx, entityName, *metav1.NewDeleteOptions(0))
   134  	default:
   135  		debug.Assert(false, "unknown entity type", entityType)
   136  	}
   137  	return
   138  }
   139  
   140  func (c *defaultClient) CheckExists(entityType, entityName string) (exists bool, err error) {
   141  	var (
   142  		ctx         = context.Background()
   143  		listOptions = metav1.ListOptions{
   144  			FieldSelector: fields.OneTermEqualSelector("metadata.name", entityName).String(),
   145  		}
   146  	)
   147  	switch entityType {
   148  	case Pod:
   149  		var pods *corev1.PodList
   150  		pods, err = c.pods().List(ctx, listOptions)
   151  		if err != nil {
   152  			return false, err
   153  		}
   154  		if len(pods.Items) == 0 {
   155  			return false, nil
   156  		}
   157  	case Svc:
   158  		var services *corev1.ServiceList
   159  		services, err = c.services().List(ctx, listOptions)
   160  		if err != nil {
   161  			return false, err
   162  		}
   163  		if len(services.Items) == 0 {
   164  			return false, nil
   165  		}
   166  	default:
   167  		debug.Assert(false, "unknown entity type", entityType)
   168  	}
   169  	return true, nil
   170  }
   171  
   172  func (c *defaultClient) Pod(name string) (*corev1.Pod, error) {
   173  	return c.pods().Get(context.Background(), name, metav1.GetOptions{})
   174  }
   175  
   176  func (c *defaultClient) Pods() (*corev1.PodList, error) {
   177  	return c.pods().List(context.Background(), metav1.ListOptions{})
   178  }
   179  
   180  func (c *defaultClient) Service(name string) (*corev1.Service, error) {
   181  	return c.services().Get(context.Background(), name, metav1.GetOptions{})
   182  }
   183  
   184  func (c *defaultClient) Node(name string) (*corev1.Node, error) {
   185  	return c.client.CoreV1().Nodes().Get(context.Background(), name, metav1.GetOptions{})
   186  }
   187  
   188  func (c *defaultClient) Logs(podName string) ([]byte, error) {
   189  	logStream, err := c.pods().GetLogs(podName, &corev1.PodLogOptions{}).Stream(context.Background())
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	defer logStream.Close()
   194  	return io.ReadAll(logStream)
   195  }
   196  
   197  func (c *defaultClient) CheckMetricsAvailability() error {
   198  	_, err := c.client.CoreV1().RESTClient().Get().AbsPath("/apis/metrics.k8s.io/v1beta1/pods").DoRaw(context.Background())
   199  	return err
   200  }
   201  
   202  func (c *defaultClient) Health(podName string) (string, error) {
   203  	response, err := c.pods().Get(context.Background(), podName, metav1.GetOptions{})
   204  	if err != nil {
   205  		return "Error", err
   206  	}
   207  	return string(response.Status.Phase), nil
   208  }