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 }