k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/devicemanager/plugin/v1beta1/server.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1beta1
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"path/filepath"
    25  	"sync"
    26  
    27  	"github.com/opencontainers/selinux/go-selinux"
    28  	"google.golang.org/grpc"
    29  
    30  	core "k8s.io/api/core/v1"
    31  	"k8s.io/klog/v2"
    32  	api "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"
    33  	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
    34  	"k8s.io/kubernetes/pkg/kubelet/config"
    35  	"k8s.io/kubernetes/pkg/kubelet/metrics"
    36  	"k8s.io/kubernetes/pkg/kubelet/pluginmanager/cache"
    37  )
    38  
    39  // Server interface provides methods for Device plugin registration server.
    40  type Server interface {
    41  	cache.PluginHandler
    42  	Start() error
    43  	Stop() error
    44  	SocketPath() string
    45  }
    46  
    47  type server struct {
    48  	socketName string
    49  	socketDir  string
    50  	mutex      sync.Mutex
    51  	wg         sync.WaitGroup
    52  	grpc       *grpc.Server
    53  	rhandler   RegistrationHandler
    54  	chandler   ClientHandler
    55  	clients    map[string]Client
    56  }
    57  
    58  // NewServer returns an initialized device plugin registration server.
    59  func NewServer(socketPath string, rh RegistrationHandler, ch ClientHandler) (Server, error) {
    60  	if socketPath == "" || !filepath.IsAbs(socketPath) {
    61  		return nil, fmt.Errorf(errBadSocket+" %s", socketPath)
    62  	}
    63  
    64  	dir, name := filepath.Split(socketPath)
    65  
    66  	klog.V(2).InfoS("Creating device plugin registration server", "version", api.Version, "socket", socketPath)
    67  	s := &server{
    68  		socketName: name,
    69  		socketDir:  dir,
    70  		rhandler:   rh,
    71  		chandler:   ch,
    72  		clients:    make(map[string]Client),
    73  	}
    74  
    75  	return s, nil
    76  }
    77  
    78  func (s *server) Start() error {
    79  	klog.V(2).InfoS("Starting device plugin registration server")
    80  
    81  	if err := os.MkdirAll(s.socketDir, 0750); err != nil {
    82  		klog.ErrorS(err, "Failed to create the device plugin socket directory", "directory", s.socketDir)
    83  		return err
    84  	}
    85  
    86  	if selinux.GetEnabled() {
    87  		if err := selinux.SetFileLabel(s.socketDir, config.KubeletPluginsDirSELinuxLabel); err != nil {
    88  			klog.InfoS("Unprivileged containerized plugins might not work. Could not set selinux context on socket dir", "path", s.socketDir, "err", err)
    89  		}
    90  	}
    91  
    92  	// For now we leave cleanup of the *entire* directory up to the Handler
    93  	// (even though we should in theory be able to just wipe the whole directory)
    94  	// because the Handler stores its checkpoint file (amongst others) in here.
    95  	if err := s.rhandler.CleanupPluginDirectory(s.socketDir); err != nil {
    96  		klog.ErrorS(err, "Failed to cleanup the device plugin directory", "directory", s.socketDir)
    97  		return err
    98  	}
    99  
   100  	ln, err := net.Listen("unix", s.SocketPath())
   101  	if err != nil {
   102  		klog.ErrorS(err, "Failed to listen to socket while starting device plugin registry")
   103  		return err
   104  	}
   105  
   106  	s.wg.Add(1)
   107  	s.grpc = grpc.NewServer([]grpc.ServerOption{}...)
   108  
   109  	api.RegisterRegistrationServer(s.grpc, s)
   110  	go func() {
   111  		defer s.wg.Done()
   112  		s.grpc.Serve(ln)
   113  	}()
   114  
   115  	return nil
   116  }
   117  
   118  func (s *server) Stop() error {
   119  	s.visitClients(func(r string, c Client) {
   120  		if err := s.disconnectClient(r, c); err != nil {
   121  			klog.InfoS("Error disconnecting device plugin client", "resourceName", r, "err", err)
   122  		}
   123  	})
   124  
   125  	s.mutex.Lock()
   126  	defer s.mutex.Unlock()
   127  
   128  	if s.grpc == nil {
   129  		return nil
   130  	}
   131  
   132  	s.grpc.Stop()
   133  	s.wg.Wait()
   134  	s.grpc = nil
   135  
   136  	return nil
   137  }
   138  
   139  func (s *server) SocketPath() string {
   140  	return filepath.Join(s.socketDir, s.socketName)
   141  }
   142  
   143  func (s *server) Register(ctx context.Context, r *api.RegisterRequest) (*api.Empty, error) {
   144  	klog.InfoS("Got registration request from device plugin with resource", "resourceName", r.ResourceName)
   145  	metrics.DevicePluginRegistrationCount.WithLabelValues(r.ResourceName).Inc()
   146  
   147  	if !s.isVersionCompatibleWithPlugin(r.Version) {
   148  		err := fmt.Errorf(errUnsupportedVersion, r.Version, api.SupportedVersions)
   149  		klog.InfoS("Bad registration request from device plugin with resource", "resourceName", r.ResourceName, "err", err)
   150  		return &api.Empty{}, err
   151  	}
   152  
   153  	if !v1helper.IsExtendedResourceName(core.ResourceName(r.ResourceName)) {
   154  		err := fmt.Errorf(errInvalidResourceName, r.ResourceName)
   155  		klog.InfoS("Bad registration request from device plugin", "err", err)
   156  		return &api.Empty{}, err
   157  	}
   158  
   159  	if err := s.connectClient(r.ResourceName, filepath.Join(s.socketDir, r.Endpoint)); err != nil {
   160  		klog.InfoS("Error connecting to device plugin client", "err", err)
   161  		return &api.Empty{}, err
   162  	}
   163  
   164  	return &api.Empty{}, nil
   165  }
   166  
   167  func (s *server) isVersionCompatibleWithPlugin(versions ...string) bool {
   168  	// TODO(vikasc): Currently this is fine as we only have a single supported version. When we do need to support
   169  	// multiple versions in the future, we may need to extend this function to return a supported version.
   170  	// E.g., say kubelet supports v1beta1 and v1beta2, and we get v1alpha1 and v1beta1 from a device plugin,
   171  	// this function should return v1beta1
   172  	for _, version := range versions {
   173  		for _, supportedVersion := range api.SupportedVersions {
   174  			if version == supportedVersion {
   175  				return true
   176  			}
   177  		}
   178  	}
   179  	return false
   180  }
   181  
   182  func (s *server) visitClients(visit func(r string, c Client)) {
   183  	s.mutex.Lock()
   184  	for r, c := range s.clients {
   185  		s.mutex.Unlock()
   186  		visit(r, c)
   187  		s.mutex.Lock()
   188  	}
   189  	s.mutex.Unlock()
   190  }