1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
package device
import (
"errors"
"fmt"
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
"github.com/lxc/incus/v6/internal/server/device/nictype"
"github.com/lxc/incus/v6/internal/server/instance"
"github.com/lxc/incus/v6/internal/server/state"
"github.com/lxc/incus/v6/shared/validate"
)
// newByType returns a new uninitialized device based of the type indicated by the project and device config.
func newByType(state *state.State, projectName string, conf deviceConfig.Device) (device, error) {
if conf["type"] == "" {
return nil, errors.New("Missing device type in config")
}
// NIC type is required to lookup network devices.
nicType, err := nictype.NICType(state, projectName, conf)
if err != nil {
return nil, err
}
// Lookup device type implementation.
var dev device
switch conf["type"] {
case "nic":
switch nicType {
case "physical":
dev = &nicPhysical{}
case "ipvlan":
dev = &nicIPVLAN{}
case "p2p":
dev = &nicP2P{}
case "bridged":
dev = &nicBridged{}
case "routed":
dev = &nicRouted{}
case "macvlan":
dev = &nicMACVLAN{}
case "sriov":
dev = &nicSRIOV{}
case "ovn":
dev = &nicOVN{}
}
case "infiniband":
// gendoc:generate(entity=devices, group=infiniband, key=nictype)
//
// ---
// type: string
// required: yes
// shortdesc: The device type (one of `physical` or `sriov`)
switch nicType {
case "physical":
dev = &infinibandPhysical{}
case "sriov":
dev = &infinibandSRIOV{}
}
case "gpu":
switch conf["gputype"] {
case "mig":
dev = &gpuMIG{}
case "mdev":
dev = &gpuMdev{}
case "sriov":
dev = &gpuSRIOV{}
default:
dev = &gpuPhysical{}
}
case "proxy":
dev = &proxy{}
case "usb":
dev = &usb{}
case "unix-char", "unix-block":
dev = &unixCommon{}
case "unix-hotplug":
dev = &unixHotplug{}
case "disk":
dev = &disk{}
case "none":
dev = &none{}
case "tpm":
dev = &tpm{}
case "pci":
dev = &pci{}
}
// Check a valid device type has been found.
if dev == nil {
return nil, ErrUnsupportedDevType
}
return dev, nil
}
// load instantiates a device and initializes its internal state. It does not validate the config supplied.
func load(inst instance.Instance, state *state.State, projectName string, name string, conf deviceConfig.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) (device, error) {
// Warning: When validating a profile, inst is expected to be provided as nil.
dev, err := newByType(state, projectName, conf)
if err != nil {
return nil, fmt.Errorf("Failed loading device %q: %w", name, err)
}
// Setup the device's internal variables.
err = dev.init(inst, state, name, conf, volatileGet, volatileSet)
if err != nil {
return nil, fmt.Errorf("Failed loading device %q: %w", name, err)
}
return dev, nil
}
// New instantiates a new device struct, validates the supplied config and sets it into the device.
// If the device type is valid, but the other config validation fails then an instantiated device
// is still returned with the validation error. If an unknown device is requested or the device is
// not compatible with the instance type then an ErrUnsupportedDevType error is returned.
// Note: The supplied config may be modified during validation to enrich. If this is not desired, supply a copy.
func New(inst instance.Instance, state *state.State, name string, conf deviceConfig.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) (Device, error) {
dev, err := load(inst, state, inst.Project().Name, name, conf, volatileGet, volatileSet)
if err != nil {
return nil, err
}
// We still return the instantiated device here, as in some scenarios the caller
// may still want to use the device (such as when stopping or removing) even if
// the config validation has failed.
err = validate.IsDeviceName(name)
if err != nil {
return dev, err
}
err = dev.validateConfig(inst)
if err != nil {
return dev, err
}
return dev, nil
}
// Validate checks a device's config is valid. This only requires an instance.ConfigReader rather than an full
// blown instance to allow profile devices to be validated too.
// Note: The supplied config may be modified during validation to enrich. If this is not desired, supply a copy.
func Validate(instConfig instance.ConfigReader, state *state.State, name string, conf deviceConfig.Device) error {
err := validate.IsDeviceName(name)
if err != nil {
return err
}
dev, err := load(nil, state, instConfig.Project().Name, name, conf, nil, nil)
if err != nil {
return err
}
return dev.validateConfig(instConfig)
}
// Register performs a lightweight load of the device, bypassing most
// validation to very quickly register the device on server startup.
func Register(inst instance.Instance, s *state.State, name string, conf deviceConfig.Device) error {
dev, err := load(inst, s, inst.Project().Name, name, conf, nil, nil)
if err != nil {
return err
}
return dev.Register()
}
// LoadByType loads a device by type based on its project and config.
// It does not validate config beyond the type fields.
func LoadByType(state *state.State, projectName string, conf deviceConfig.Device) (Type, error) {
dev, err := newByType(state, projectName, conf)
if err != nil {
return nil, fmt.Errorf("Failed loading device type: %w", err)
}
return dev, nil
}
|