140 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright © 2021-2022 The CDI Authors
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package cdi
 | 
						|
 | 
						|
import (
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// AnnotationPrefix is the prefix for CDI container annotation keys.
 | 
						|
	AnnotationPrefix = "cdi.k8s.io/"
 | 
						|
)
 | 
						|
 | 
						|
// UpdateAnnotations updates annotations with a plugin-specific CDI device
 | 
						|
// injection request for the given devices. Upon any error a non-nil error
 | 
						|
// is returned and annotations are left intact. By convention plugin should
 | 
						|
// be in the format of "vendor.device-type".
 | 
						|
func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) {
 | 
						|
	key, err := AnnotationKey(plugin, deviceID)
 | 
						|
	if err != nil {
 | 
						|
		return annotations, errors.Wrap(err, "CDI annotation failed")
 | 
						|
	}
 | 
						|
	if _, ok := annotations[key]; ok {
 | 
						|
		return annotations, errors.Errorf("CDI annotation failed, key %q used", key)
 | 
						|
	}
 | 
						|
	value, err := AnnotationValue(devices)
 | 
						|
	if err != nil {
 | 
						|
		return annotations, errors.Wrap(err, "CDI annotation failed")
 | 
						|
	}
 | 
						|
 | 
						|
	if annotations == nil {
 | 
						|
		annotations = make(map[string]string)
 | 
						|
	}
 | 
						|
	annotations[key] = value
 | 
						|
 | 
						|
	return annotations, nil
 | 
						|
}
 | 
						|
 | 
						|
// ParseAnnotations parses annotations for CDI device injection requests.
 | 
						|
// The keys and devices from all such requests are collected into slices
 | 
						|
// which are returned as the result. All devices are expected to be fully
 | 
						|
// qualified CDI device names. If any device fails this check empty slices
 | 
						|
// are returned along with a non-nil error. The annotations are expected
 | 
						|
// to be formatted by, or in a compatible fashion to UpdateAnnotations().
 | 
						|
func ParseAnnotations(annotations map[string]string) ([]string, []string, error) {
 | 
						|
	var (
 | 
						|
		keys    []string
 | 
						|
		devices []string
 | 
						|
	)
 | 
						|
 | 
						|
	for key, value := range annotations {
 | 
						|
		if !strings.HasPrefix(key, AnnotationPrefix) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for _, d := range strings.Split(value, ",") {
 | 
						|
			if !IsQualifiedName(d) {
 | 
						|
				return nil, nil, errors.Errorf("invalid CDI device name %q", d)
 | 
						|
			}
 | 
						|
			devices = append(devices, d)
 | 
						|
		}
 | 
						|
		keys = append(keys, key)
 | 
						|
	}
 | 
						|
 | 
						|
	return keys, devices, nil
 | 
						|
}
 | 
						|
 | 
						|
// AnnotationKey returns a unique annotation key for an device allocation
 | 
						|
// by a K8s device plugin. pluginName should be in the format of
 | 
						|
// "vendor.device-type". deviceID is the ID of the device the plugin is
 | 
						|
// allocating. It is used to make sure that the generated key is unique
 | 
						|
// even if multiple allocations by a single plugin needs to be annotated.
 | 
						|
func AnnotationKey(pluginName, deviceID string) (string, error) {
 | 
						|
	const maxNameLen = 63
 | 
						|
 | 
						|
	if pluginName == "" {
 | 
						|
		return "", errors.New("invalid plugin name, empty")
 | 
						|
	}
 | 
						|
	if deviceID == "" {
 | 
						|
		return "", errors.New("invalid deviceID, empty")
 | 
						|
	}
 | 
						|
 | 
						|
	name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_")
 | 
						|
 | 
						|
	if len(name) > maxNameLen {
 | 
						|
		return "", errors.Errorf("invalid plugin+deviceID %q, too long", name)
 | 
						|
	}
 | 
						|
 | 
						|
	if c := rune(name[0]); !isAlphaNumeric(c) {
 | 
						|
		return "", errors.Errorf("invalid name %q, first '%c' should be alphanumeric",
 | 
						|
			name, c)
 | 
						|
	}
 | 
						|
	if len(name) > 2 {
 | 
						|
		for _, c := range name[1 : len(name)-1] {
 | 
						|
			switch {
 | 
						|
			case isAlphaNumeric(c):
 | 
						|
			case c == '_' || c == '-' || c == '.':
 | 
						|
			default:
 | 
						|
				return "", errors.Errorf("invalid name %q, invalid charcter '%c'",
 | 
						|
					name, c)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
 | 
						|
		return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric",
 | 
						|
			name, c)
 | 
						|
	}
 | 
						|
 | 
						|
	return AnnotationPrefix + name, nil
 | 
						|
}
 | 
						|
 | 
						|
// AnnotationValue returns an annotation value for the given devices.
 | 
						|
func AnnotationValue(devices []string) (string, error) {
 | 
						|
	value, sep := "", ""
 | 
						|
	for _, d := range devices {
 | 
						|
		if _, _, _, err := ParseQualifiedName(d); err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		value += sep + d
 | 
						|
		sep = ","
 | 
						|
	}
 | 
						|
 | 
						|
	return value, nil
 | 
						|
}
 |