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  }