k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/images/sample-device-plugin/sampledeviceplugin.go (about) 1 /* 2 Copyright 2018 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 main 18 19 import ( 20 "fmt" 21 "os" 22 "os/signal" 23 "path/filepath" 24 "syscall" 25 "time" 26 27 "github.com/fsnotify/fsnotify" 28 "k8s.io/klog/v2" 29 pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1" 30 plugin "k8s.io/kubernetes/pkg/kubelet/cm/devicemanager/plugin/v1beta1" 31 ) 32 33 const ( 34 resourceName = "example.com/resource" 35 cdiPath = "/var/run/cdi/example.com.json" 36 cdiVersion = "0.3.0" 37 cdiPrefix = "CDI-" 38 ) 39 40 // stubAllocFunc creates and returns allocation response for the input allocate request 41 func stubAllocFunc(r *pluginapi.AllocateRequest, devs map[string]pluginapi.Device) (*pluginapi.AllocateResponse, error) { 42 var responses pluginapi.AllocateResponse 43 for _, req := range r.ContainerRequests { 44 response := &pluginapi.ContainerAllocateResponse{} 45 for _, requestID := range req.DevicesIDs { 46 dev, ok := devs[requestID] 47 if !ok { 48 return nil, fmt.Errorf("invalid allocation request with non-existing device %s", requestID) 49 } 50 51 if dev.Health != pluginapi.Healthy { 52 return nil, fmt.Errorf("invalid allocation request with unhealthy device: %s", requestID) 53 } 54 55 // create fake device file 56 fpath := filepath.Join("/tmp", dev.ID) 57 58 // clean first 59 if err := os.RemoveAll(fpath); err != nil { 60 return nil, fmt.Errorf("failed to clean fake device file from previous run: %s", err) 61 } 62 63 f, err := os.Create(fpath) 64 if err != nil && !os.IsExist(err) { 65 return nil, fmt.Errorf("failed to create fake device file: %s", err) 66 } 67 68 f.Close() 69 70 response.Mounts = append(response.Mounts, &pluginapi.Mount{ 71 ContainerPath: fpath, 72 HostPath: fpath, 73 }) 74 75 if os.Getenv("CDI_ENABLED") != "" { 76 // add the CDI device ID to the response. 77 cdiDevice := &pluginapi.CDIDevice{ 78 Name: fmt.Sprintf("%s=%s", resourceName, cdiPrefix+dev.ID), 79 } 80 response.CDIDevices = append(response.CDIDevices, cdiDevice) 81 } 82 } 83 responses.ContainerResponses = append(responses.ContainerResponses, response) 84 } 85 86 return &responses, nil 87 } 88 89 // stubAllocFunc creates and returns allocation response for the input allocate request 90 func stubRegisterControlFunc() bool { 91 return false 92 } 93 94 func main() { 95 // respond to syscalls for termination 96 sigCh := make(chan os.Signal, 1) 97 signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 98 99 devs := []*pluginapi.Device{ 100 {ID: "Dev-1", Health: pluginapi.Healthy}, 101 {ID: "Dev-2", Health: pluginapi.Healthy}, 102 } 103 104 pluginSocksDir := os.Getenv("PLUGIN_SOCK_DIR") 105 klog.Infof("pluginSocksDir: %s", pluginSocksDir) 106 if pluginSocksDir == "" { 107 pluginSocksDir = pluginapi.DevicePluginPath 108 } 109 110 socketPath := pluginSocksDir + "/dp." + fmt.Sprintf("%d", time.Now().Unix()) 111 112 dp1 := plugin.NewDevicePluginStub(devs, socketPath, resourceName, false, false) 113 if err := dp1.Start(); err != nil { 114 panic(err) 115 116 } 117 dp1.SetAllocFunc(stubAllocFunc) 118 119 cdiEnabled := os.Getenv("CDI_ENABLED") 120 klog.Infof("CDI_ENABLED: %s", cdiEnabled) 121 if cdiEnabled != "" { 122 if err := createCDIFile(devs); err != nil { 123 panic(err) 124 } 125 defer func() { 126 // Remove CDI file 127 if _, err := os.Stat(cdiPath); err == nil || os.IsExist(err) { 128 err := os.Remove(cdiPath) 129 if err != nil { 130 panic(err) 131 } 132 } 133 }() 134 } 135 136 var registerControlFile string 137 autoregister := true 138 139 if registerControlFile = os.Getenv("REGISTER_CONTROL_FILE"); registerControlFile != "" { 140 autoregister = false 141 dp1.SetRegisterControlFunc(stubRegisterControlFunc) 142 } 143 144 if !autoregister { 145 go dp1.Watch(pluginapi.KubeletSocket, resourceName, pluginapi.DevicePluginPath) 146 147 triggerPath := filepath.Dir(registerControlFile) 148 149 klog.InfoS("Registration process will be managed explicitly", "triggerPath", triggerPath, "triggerEntry", registerControlFile) 150 151 watcher, err := fsnotify.NewWatcher() 152 if err != nil { 153 klog.Errorf("Watcher creation failed: %v ", err) 154 panic(err) 155 } 156 defer watcher.Close() 157 158 updateCh := make(chan bool) 159 defer close(updateCh) 160 161 go handleRegistrationProcess(registerControlFile, dp1, watcher, updateCh) 162 163 err = watcher.Add(triggerPath) 164 if err != nil { 165 klog.Errorf("Failed to add watch to %q: %v", triggerPath, err) 166 panic(err) 167 } 168 for { 169 select { 170 case received := <-updateCh: 171 if received { 172 if err := dp1.Register(pluginapi.KubeletSocket, resourceName, pluginapi.DevicePluginPath); err != nil { 173 panic(err) 174 } 175 klog.InfoS("Control file was deleted, registration succeeded") 176 } 177 // Catch termination signals 178 case sig := <-sigCh: 179 klog.InfoS("Shutting down, received signal", "signal", sig) 180 if err := dp1.Stop(); err != nil { 181 panic(err) 182 } 183 return 184 } 185 time.Sleep(5 * time.Second) 186 } 187 } else { 188 if err := dp1.Register(pluginapi.KubeletSocket, resourceName, pluginapi.DevicePluginPath); err != nil { 189 panic(err) 190 } 191 192 go dp1.Watch(pluginapi.KubeletSocket, resourceName, pluginapi.DevicePluginPath) 193 // Catch termination signals 194 sig := <-sigCh 195 klog.InfoS("Shutting down, received signal", "signal", sig) 196 if err := dp1.Stop(); err != nil { 197 panic(err) 198 } 199 return 200 } 201 } 202 203 func handleRegistrationProcess(registerControlFile string, dpStub *plugin.Stub, watcher *fsnotify.Watcher, updateCh chan<- bool) { 204 klog.InfoS("Starting watching routine") 205 for { 206 klog.InfoS("handleRegistrationProcess for loop") 207 select { 208 case event, ok := <-watcher.Events: 209 if !ok { 210 return 211 } 212 klog.InfoS("Received event", "name", event.Name, "operation", event.Op) 213 if event.Op&fsnotify.Remove == fsnotify.Remove { 214 if event.Name == registerControlFile { 215 klog.InfoS("Expected delete", "name", event.Name, "operation", event.Op) 216 updateCh <- true 217 continue 218 } 219 klog.InfoS("Spurious delete", "name", event.Name, "operation", event.Op) 220 } 221 case err, ok := <-watcher.Errors: 222 if !ok { 223 return 224 } 225 klog.ErrorS(err, "error") 226 panic(err) 227 default: 228 time.Sleep(5 * time.Second) 229 } 230 } 231 } 232 233 func createCDIFile(devs []*pluginapi.Device) error { 234 content := fmt.Sprintf(`{"cdiVersion":"%s","kind":"%s","devices":[`, cdiVersion, resourceName) 235 for i, dev := range devs { 236 name := cdiPrefix + dev.ID 237 content += fmt.Sprintf(`{"name":"%s","containerEdits":{"env":["CDI_DEVICE=%s"],"deviceNodes":[{"path":"/tmp/%s","type":"b","major":1,"minor":%d}]}}`, name, name, name, i) 238 if i < len(devs)-1 { 239 content += "," 240 } 241 } 242 content += "]}" 243 if err := os.WriteFile(cdiPath, []byte(content), 0644); err != nil { 244 return fmt.Errorf("failed to create CDI file: %s", err) 245 } 246 klog.InfoS("Created CDI file", "path", cdiPath, "devices", devs) 247 return nil 248 }