mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
Merge pull request #2847 from bensmrs/nic-attached-connected
Add `attached` and `connected` keys to NIC devices
This commit is contained in:
commit
2591dceca4
@ -2974,3 +2974,10 @@ Adds support for selecting an SR-IOV network interface by vendor ID, product ID,
|
||||
## `network_zones_dns_contact`
|
||||
|
||||
Adds a `dns.contact` configuration key to network zones.
|
||||
|
||||
## `nic_attached_connected`
|
||||
|
||||
This introduces two new properties for NICs:
|
||||
|
||||
* `attached`, behaving like the `attached` key for disk and USB devices;
|
||||
* `connected`, setting the up/down link state for the NIC (when supported).
|
||||
|
||||
@ -443,6 +443,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
<!-- config group devices-infiniband end -->
|
||||
<!-- config group devices-nic_bridged start -->
|
||||
```{config:option} attached devices-nic_bridged
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} boot.priority devices-nic_bridged
|
||||
:managed: "no"
|
||||
:shortdesc: "Boot priority for VMs (higher value boots first)"
|
||||
@ -450,6 +458,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_bridged
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} host_name devices-nic_bridged
|
||||
:default: "randomly assigned"
|
||||
:managed: "no"
|
||||
@ -668,6 +684,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
<!-- config group devices-nic_bridged end -->
|
||||
<!-- config group devices-nic_ipvlan start -->
|
||||
```{config:option} attached devices-nic_ipvlan
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} gvrp devices-nic_ipvlan
|
||||
:default: "false"
|
||||
:shortdesc: "Register VLAN using GARP VLAN Registration Protocol"
|
||||
@ -755,6 +779,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
<!-- config group devices-nic_ipvlan end -->
|
||||
<!-- config group devices-nic_macvlan start -->
|
||||
```{config:option} attached devices-nic_macvlan
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} boot.priority devices-nic_macvlan
|
||||
:managed: "no"
|
||||
:shortdesc: "Boot priority for VMs (higher value boots first)"
|
||||
@ -762,6 +794,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_macvlan
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network (VM only)"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} gvrp devices-nic_macvlan
|
||||
:default: "false"
|
||||
:managed: "no"
|
||||
@ -841,6 +881,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
```
|
||||
|
||||
```{config:option} attached devices-nic_ovn
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} boot.priority devices-nic_ovn
|
||||
:managed: "no"
|
||||
:shortdesc: "Boot priority for VMs (higher value boots first)"
|
||||
@ -848,6 +896,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_ovn
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network (container support requires setting `acceleration` to `none`)"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} host_name devices-nic_ovn
|
||||
:default: "randomly assigned"
|
||||
:managed: "no"
|
||||
@ -1035,12 +1091,28 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
<!-- config group devices-nic_ovn end -->
|
||||
<!-- config group devices-nic_p2p start -->
|
||||
```{config:option} boot.priotiry devices-nic_p2p
|
||||
```{config:option} attached devices-nic_p2p
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} boot.priority devices-nic_p2p
|
||||
:shortdesc: "Boot priority for VMs (higher value boots first)"
|
||||
:type: "integer"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_p2p
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} host_name devices-nic_p2p
|
||||
:default: "randomly assigned"
|
||||
:shortdesc: "The name of the interface on the host"
|
||||
@ -1120,6 +1192,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
<!-- config group devices-nic_p2p end -->
|
||||
<!-- config group devices-nic_physical start -->
|
||||
```{config:option} attached devices-nic_physical
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} boot.priority devices-nic_physical
|
||||
:managed: "no"
|
||||
:shortdesc: "Boot priority for VMs (higher value boots first)"
|
||||
@ -1127,6 +1207,14 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_physical
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network (VM only)"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} gvrp devices-nic_physical
|
||||
:default: "false"
|
||||
:managed: "no"
|
||||
@ -1189,6 +1277,22 @@ With VMs, this option supports mounting file system disk devices and paths withi
|
||||
|
||||
<!-- config group devices-nic_physical end -->
|
||||
<!-- config group devices-nic_routed start -->
|
||||
```{config:option} attached devices-nic_routed
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_routed
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} gvrp devices-nic_routed
|
||||
:default: "false"
|
||||
:shortdesc: "Register VLAN using GARP VLAN Registration Protocol"
|
||||
@ -1375,6 +1479,14 @@ The custom policy routing table ID to add IPv6 static routes to (in addition to
|
||||
|
||||
<!-- config group devices-nic_routed end -->
|
||||
<!-- config group devices-nic_sriov start -->
|
||||
```{config:option} attached devices-nic_sriov
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is plugged in or not"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} boot.priority devices-nic_sriov
|
||||
:managed: "no"
|
||||
:shortdesc: "Boot priority for VMs (higher value boots first)"
|
||||
@ -1382,6 +1494,14 @@ The custom policy routing table ID to add IPv6 static routes to (in addition to
|
||||
|
||||
```
|
||||
|
||||
```{config:option} connected devices-nic_sriov
|
||||
:default: "`true`"
|
||||
:required: "no"
|
||||
:shortdesc: "Whether the NIC is connected to the host network (VM only)"
|
||||
:type: "bool"
|
||||
|
||||
```
|
||||
|
||||
```{config:option} hwaddr devices-nic_sriov
|
||||
:default: "randomly assigned"
|
||||
:managed: "no"
|
||||
|
||||
@ -118,3 +118,14 @@ func (d *deviceCommon) generateHostName(prefix string, hwaddr string) (string, e
|
||||
// Handle instances.nic.host_name random mode or where no MAC address supplied.
|
||||
return network.RandomDevName(prefix), nil
|
||||
}
|
||||
|
||||
// setNICLink sets the link status (connected/disconnected) for the given NIC.
|
||||
func (d *deviceCommon) setNICLink() error {
|
||||
runConf := deviceConfig.RunConfig{}
|
||||
runConf.NetworkInterface = []deviceConfig.RunConfigItem{
|
||||
{Key: "devName", Value: d.name},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
return d.inst.DeviceEventHandler(&runConf)
|
||||
}
|
||||
|
||||
@ -611,12 +611,12 @@ func networkSetupHostVethLimits(d *deviceCommon, oldConfig deviceConfig.Device,
|
||||
|
||||
// networkClearHostVethLimits clears any network rate limits to the veth device specified in the config.
|
||||
func networkClearHostVethLimits(d *deviceCommon) error {
|
||||
err := d.state.Firewall.InstanceClearNetPrio(d.inst.Project().Name, d.inst.Name(), d.config["host_name"])
|
||||
if err != nil {
|
||||
return err
|
||||
// Detached NICs cannot be cleaned up this way.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return d.state.Firewall.InstanceClearNetPrio(d.inst.Project().Name, d.inst.Name(), d.config["host_name"])
|
||||
}
|
||||
|
||||
// networkValidGateway validates the gateway value.
|
||||
|
||||
@ -60,6 +60,8 @@ func nicValidationRules(requiredFields []string, optionalFields []string, instCo
|
||||
"vendorid": validate.Optional(validate.IsDeviceID),
|
||||
"productid": validate.Optional(validate.IsDeviceID),
|
||||
"pci": validate.IsPCIAddress,
|
||||
"attached": validate.Optional(validate.IsBool),
|
||||
"connected": validate.Optional(validate.IsBool),
|
||||
}
|
||||
|
||||
validators := map[string]func(value string) error{}
|
||||
|
||||
@ -316,6 +316,24 @@ func (d *nicBridged) validateConfig(instConf instance.ConfigReader) error {
|
||||
// managed: no
|
||||
// shortdesc: Override the bus for the device (can be `virtio` or `usb`) (VM only)
|
||||
"io.bus",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_bridged, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_bridged, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network
|
||||
"connected",
|
||||
}
|
||||
|
||||
// checkWithManagedNetwork validates the device's settings against the managed network.
|
||||
@ -693,7 +711,7 @@ func (d *nicBridged) UpdatableFields(oldDevice Type) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "ipv4.routes", "ipv6.routes", "ipv4.routes.external", "ipv6.routes.external", "ipv4.address", "ipv6.address", "security.mac_filtering", "security.ipv4_filtering", "security.ipv6_filtering", "security.acls", "security.acls.default.egress.action", "security.acls.default.egress.logged", "security.acls.default.ingress.action", "security.acls.default.ingress.logged"}
|
||||
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "ipv4.routes", "ipv6.routes", "ipv4.routes.external", "ipv6.routes.external", "ipv4.address", "ipv6.address", "security.mac_filtering", "security.ipv4_filtering", "security.ipv6_filtering", "security.acls", "security.acls.default.egress.action", "security.acls.default.egress.logged", "security.acls.default.ingress.action", "security.acls.default.ingress.logged", "connected"}
|
||||
}
|
||||
|
||||
// Add is run when a device is added to a non-snapshot instance whether or not the instance is running.
|
||||
@ -726,6 +744,11 @@ func (d *nicBridged) PreStartCheck() error {
|
||||
|
||||
// Start is run when the device is added to a running instance or instance is starting up.
|
||||
func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -916,6 +939,7 @@ func (d *nicBridged) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "flags", Value: "up"},
|
||||
{Key: "link", Value: peerName},
|
||||
{Key: "hwaddr", Value: d.config["hwaddr"]},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
if d.config["io.bus"] == "usb" {
|
||||
@ -1049,6 +1073,13 @@ func (d *nicBridged) Update(oldDevices deviceConfig.Devices, isRunning bool) err
|
||||
return err
|
||||
}
|
||||
|
||||
if isRunning {
|
||||
err = d.setNICLink()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
reverter.Success()
|
||||
|
||||
return nil
|
||||
|
||||
@ -108,6 +108,15 @@ func (d *nicIPVLAN) validateConfig(instConf instance.ConfigReader) error {
|
||||
// default: false
|
||||
// shortdesc: Register VLAN using GARP VLAN Registration Protocol
|
||||
"gvrp",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_ipvlan, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
}
|
||||
|
||||
rules := nicValidationRules(requiredFields, optionalFields, instConf)
|
||||
@ -301,6 +310,11 @@ func (d *nicIPVLAN) validateEnvironment() error {
|
||||
|
||||
// Start is run when the instance is starting up (IPVLAN doesn't support hot plugging).
|
||||
func (d *nicIPVLAN) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -128,6 +128,24 @@ func (d *nicMACVLAN) validateConfig(instConf instance.ConfigReader) error {
|
||||
// managed: no
|
||||
// shortdesc: Override the bus for the device (can be `virtio` or `usb`) (VM only)
|
||||
"io.bus",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_macvlan, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_macvlan, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network (VM only)
|
||||
"connected",
|
||||
}
|
||||
|
||||
// Check that if network proeperty is set that conflicting keys are not present.
|
||||
@ -175,6 +193,10 @@ func (d *nicMACVLAN) validateConfig(instConf instance.ConfigReader) error {
|
||||
requiredFields = append(requiredFields, "parent")
|
||||
}
|
||||
|
||||
if instConf.Type() != instancetype.VM && d.config["connected"] != "" {
|
||||
return errors.New("The \"connected\" option is only supported on virtual machines for macvlan NICs")
|
||||
}
|
||||
|
||||
err := d.config.Validate(nicValidationRules(requiredFields, optionalFields, instConf))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -213,6 +235,11 @@ func (d *nicMACVLAN) validateEnvironment() error {
|
||||
|
||||
// Start is run when the device is added to a running instance or instance is starting up.
|
||||
func (d *nicMACVLAN) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -339,6 +366,7 @@ func (d *nicMACVLAN) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "flags", Value: "up"},
|
||||
{Key: "link", Value: saveData["host_name"]},
|
||||
{Key: "hwaddr", Value: d.config["hwaddr"]},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
if d.config["io.bus"] == "usb" {
|
||||
@ -363,9 +391,12 @@ func (d *nicMACVLAN) Stop() (*deviceConfig.RunConfig, error) {
|
||||
v := d.volatileGet()
|
||||
runConf := deviceConfig.RunConfig{
|
||||
PostHooks: []func() error{d.postStop},
|
||||
NetworkInterface: []deviceConfig.RunConfigItem{
|
||||
}
|
||||
|
||||
if util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
runConf.NetworkInterface = []deviceConfig.RunConfigItem{
|
||||
{Key: "link", Value: v["host_name"]},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &runConf, nil
|
||||
@ -408,3 +439,23 @@ func (d *nicMACVLAN) postStop() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatableFields returns a list of fields that can be updated without triggering a device remove & add.
|
||||
func (d *nicMACVLAN) UpdatableFields(oldDevice Type) []string {
|
||||
// Check old and new device types match.
|
||||
_, match := oldDevice.(*nicMACVLAN)
|
||||
if !match {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"connected"}
|
||||
}
|
||||
|
||||
// Update applies configuration changes to a started device.
|
||||
func (d *nicMACVLAN) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
|
||||
if isRunning {
|
||||
return d.setNICLink()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ func (d *nicOVN) UpdatableFields(oldDevice Type) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"security.acls", "limits.ingress", "limits.egress", "limits.max", "limits.priority"}
|
||||
return []string{"security.acls", "limits.ingress", "limits.egress", "limits.max", "limits.priority", "connected"}
|
||||
}
|
||||
|
||||
// validateConfig checks the supplied config for correctness.
|
||||
@ -317,6 +317,24 @@ func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error {
|
||||
// managed: no
|
||||
// shortdesc: The priority for outgoing traffic, to be used by the kernel queuing discipline to prioritize network packets
|
||||
"limits.priority",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_ovn, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_ovn, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network (container support requires setting `acceleration` to `none`)
|
||||
"connected",
|
||||
}
|
||||
|
||||
// The NIC's network may be a non-default project, so lookup project and get network's project name.
|
||||
@ -575,6 +593,11 @@ func (d *nicOVN) validateConfig(instConf instance.ConfigReader) error {
|
||||
}
|
||||
}
|
||||
|
||||
// The connected option can only be handled properly on containers if acceleration is set to none.
|
||||
if d.config["connected"] != "" && !d.canSetLink(instConf) {
|
||||
return errors.New("The \"connected\" option requires setting acceleration=none on containers for OVN NICs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -693,6 +716,11 @@ func (d *nicOVN) init(inst instance.Instance, s *state.State, name string, conf
|
||||
|
||||
// Start is run when the device is added to a running instance or instance is starting up.
|
||||
func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -987,15 +1015,22 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "link", Value: peerName},
|
||||
}
|
||||
|
||||
if d.canSetLink(nil) {
|
||||
runConf.NetworkInterface = append(runConf.NetworkInterface, deviceConfig.RunConfigItem{Key: "connected", Value: d.config["connected"]})
|
||||
}
|
||||
|
||||
instType := d.inst.Type()
|
||||
if instType == instancetype.VM {
|
||||
runConf.NetworkInterface = append(runConf.NetworkInterface,
|
||||
[]deviceConfig.RunConfigItem{
|
||||
{Key: "devName", Value: d.name},
|
||||
{Key: "mtu", Value: fmt.Sprintf("%d", mtu)},
|
||||
}...)
|
||||
if d.config["acceleration"] == "sriov" {
|
||||
runConf.NetworkInterface = append(runConf.NetworkInterface,
|
||||
[]deviceConfig.RunConfigItem{
|
||||
{Key: "devName", Value: d.name},
|
||||
{Key: "pciSlotName", Value: vfPCIDev.SlotName},
|
||||
{Key: "pciIOMMUGroup", Value: fmt.Sprintf("%d", pciIOMMUGroup)},
|
||||
{Key: "mtu", Value: fmt.Sprintf("%d", mtu)},
|
||||
}...)
|
||||
} else if d.config["acceleration"] == "vdpa" {
|
||||
if vDPADevice == nil {
|
||||
@ -1004,20 +1039,16 @@ func (d *nicOVN) Start() (*deviceConfig.RunConfig, error) {
|
||||
|
||||
runConf.NetworkInterface = append(runConf.NetworkInterface,
|
||||
[]deviceConfig.RunConfigItem{
|
||||
{Key: "devName", Value: d.name},
|
||||
{Key: "pciSlotName", Value: vfPCIDev.SlotName},
|
||||
{Key: "pciIOMMUGroup", Value: fmt.Sprintf("%d", pciIOMMUGroup)},
|
||||
{Key: "maxVQP", Value: fmt.Sprintf("%d", vDPADevice.MaxVQs/2)},
|
||||
{Key: "vDPADevName", Value: vDPADevice.Name},
|
||||
{Key: "vhostVDPAPath", Value: vDPADevice.VhostVDPA.Path},
|
||||
{Key: "mtu", Value: fmt.Sprintf("%d", mtu)},
|
||||
}...)
|
||||
} else {
|
||||
runConf.NetworkInterface = append(runConf.NetworkInterface,
|
||||
[]deviceConfig.RunConfigItem{
|
||||
{Key: "devName", Value: d.name},
|
||||
{Key: "hwaddr", Value: d.config["hwaddr"]},
|
||||
{Key: "mtu", Value: fmt.Sprintf("%d", mtu)},
|
||||
}...)
|
||||
}
|
||||
} else if instType == instancetype.Container {
|
||||
@ -1142,6 +1173,10 @@ func (d *nicOVN) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if isRunning && d.canSetLink(nil) {
|
||||
return d.setNICLink()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1534,3 +1569,15 @@ func (d *nicOVN) setupHostNIC(hostName string, ovnPortName ovn.OVNSwitchPort) (r
|
||||
|
||||
return cleanup, err
|
||||
}
|
||||
|
||||
// canSetLink determines whether the device supports setting a link state.
|
||||
func (d *nicOVN) canSetLink(instConf instance.ConfigReader) bool {
|
||||
var instType instancetype.Type
|
||||
if instConf != nil {
|
||||
instType = instConf.Type()
|
||||
} else {
|
||||
instType = d.inst.Type()
|
||||
}
|
||||
|
||||
return instType == instancetype.VM || slices.Contains([]string{"", "none"}, d.config["acceleration"])
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ func (d *nicP2P) validateConfig(instConf instance.ConfigReader) error {
|
||||
// shortdesc: Comma-delimited list of IPv6 static routes to add on host to NIC
|
||||
"ipv6.routes",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_p2p, key=boot.priotiry)
|
||||
// gendoc:generate(entity=devices, group=nic_p2p, key=boot.priority)
|
||||
//
|
||||
// ---
|
||||
// type: integer
|
||||
@ -125,6 +125,24 @@ func (d *nicP2P) validateConfig(instConf instance.ConfigReader) error {
|
||||
// default: `virtio`
|
||||
// shortdesc: Override the bus for the device (can be `virtio` or `usb`) (VM only)
|
||||
"io.bus",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_p2p, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_p2p, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network
|
||||
"connected",
|
||||
}
|
||||
|
||||
err := d.config.Validate(nicValidationRules([]string{}, optionalFields, instConf))
|
||||
@ -152,11 +170,16 @@ func (d *nicP2P) UpdatableFields(oldDevice Type) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "ipv4.routes", "ipv6.routes"}
|
||||
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "ipv4.routes", "ipv6.routes", "connected"}
|
||||
}
|
||||
|
||||
// Start is run when the device is added to a running instance or instance is starting up.
|
||||
func (d *nicP2P) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -232,6 +255,7 @@ func (d *nicP2P) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "flags", Value: "up"},
|
||||
{Key: "link", Value: peerName},
|
||||
{Key: "hwaddr", Value: d.config["hwaddr"]},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
if d.config["io.bus"] == "usb" {
|
||||
@ -284,7 +308,7 @@ func (d *nicP2P) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return d.setNICLink()
|
||||
}
|
||||
|
||||
// Stop is run when the device is removed from the instance.
|
||||
|
||||
@ -85,6 +85,24 @@ func (d *nicPhysical) validateConfig(instConf instance.ConfigReader) error {
|
||||
// managed: no
|
||||
// shortdesc: The Maximum Transmit Unit (MTU) of the new interface
|
||||
"mtu",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_physical, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_physical, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network (VM only)
|
||||
"connected",
|
||||
}
|
||||
|
||||
if instConf.Type() == instancetype.Container || instConf.Type() == instancetype.Any {
|
||||
@ -168,6 +186,10 @@ func (d *nicPhysical) validateConfig(instConf instance.ConfigReader) error {
|
||||
requiredFields = append(requiredFields, "parent")
|
||||
}
|
||||
|
||||
if instConf.Type() != instancetype.VM && d.config["connected"] != "" {
|
||||
return errors.New("The \"connected\" option is only supported on virtual machines for physical NICs")
|
||||
}
|
||||
|
||||
err := d.config.Validate(nicValidationRules(requiredFields, optionalFields, instConf))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -196,6 +218,11 @@ func (d *nicPhysical) validateEnvironment() error {
|
||||
|
||||
// Start is run when the device is added to a running instance or instance is starting up.
|
||||
func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -332,6 +359,7 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "name", Value: d.config["name"]},
|
||||
{Key: "flags", Value: "up"},
|
||||
{Key: "link", Value: saveData["host_name"]},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
if d.inst.Type() == instancetype.VM {
|
||||
@ -443,7 +471,7 @@ func (d *nicPhysical) Stop() (*deviceConfig.RunConfig, error) {
|
||||
DeviceName: fmt.Sprintf("%s-%s-%s", d.name, v["last_state.usb.bus"], v["last_state.usb.device"]),
|
||||
HostDevicePath: fmt.Sprintf("/dev/bus/usb/%s/%s", v["last_state.usb.bus"], v["last_state.usb.device"]),
|
||||
})
|
||||
} else {
|
||||
} else if util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
// Handle all other NICs.
|
||||
runConf.NetworkInterface = []deviceConfig.RunConfigItem{
|
||||
{Key: "link", Value: v["host_name"]},
|
||||
@ -536,3 +564,23 @@ func IsPhysicalNICWithBridge(s *state.State, deviceProjectName string, d deviceC
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdatableFields returns a list of fields that can be updated without triggering a device remove & add.
|
||||
func (d *nicPhysical) UpdatableFields(oldDevice Type) []string {
|
||||
// Check old and new device types match.
|
||||
_, match := oldDevice.(*nicPhysical)
|
||||
if !match {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"connected"}
|
||||
}
|
||||
|
||||
// Update applies configuration changes to a started device.
|
||||
func (d *nicPhysical) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
|
||||
if isRunning {
|
||||
return d.setNICLink()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ func (d *nicRouted) UpdatableFields(oldDevice Type) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority"}
|
||||
return []string{"limits.ingress", "limits.egress", "limits.max", "limits.priority", "connected"}
|
||||
}
|
||||
|
||||
// validateConfig checks the supplied config for correctness.
|
||||
@ -243,6 +243,24 @@ func (d *nicRouted) validateConfig(instConf instance.ConfigReader) error {
|
||||
// default: `virtio`
|
||||
// shortdesc: Override the bus for the device (can be `virtio` or `usb`) (VM only)
|
||||
"io.bus",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_routed, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_routed, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network
|
||||
"connected",
|
||||
}
|
||||
|
||||
rules := nicValidationRules(requiredFields, optionalFields, instConf)
|
||||
@ -467,6 +485,11 @@ func (d *nicRouted) checkIPAvailability(parent string) error {
|
||||
|
||||
// Start is run when the instance is starting up (Routed mode doesn't support hot plugging).
|
||||
func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -760,6 +783,7 @@ func (d *nicRouted) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "flags", Value: "up"},
|
||||
{Key: "link", Value: peerName},
|
||||
{Key: "hwaddr", Value: d.config["hwaddr"]},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
if d.config["io.bus"] == "usb" {
|
||||
@ -856,6 +880,8 @@ func (d *nicRouted) Update(oldDevices deviceConfig.Devices, isRunning bool) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.setNICLink()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@ -133,6 +133,24 @@ func (d *nicSRIOV) validateConfig(instConf instance.ConfigReader) error {
|
||||
// required: no
|
||||
// shortdesc: The PCI address of the parent host device
|
||||
"pci",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_sriov, key=attached)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is plugged in or not
|
||||
"attached",
|
||||
|
||||
// gendoc:generate(entity=devices, group=nic_sriov, key=connected)
|
||||
//
|
||||
// ---
|
||||
// type: bool
|
||||
// default: `true`
|
||||
// required: no
|
||||
// shortdesc: Whether the NIC is connected to the host network (VM only)
|
||||
"connected",
|
||||
}
|
||||
|
||||
// Check that if network property is set that conflicting keys are not present.
|
||||
@ -180,6 +198,10 @@ func (d *nicSRIOV) validateConfig(instConf instance.ConfigReader) error {
|
||||
requiredFields = append(requiredFields, "parent")
|
||||
}
|
||||
|
||||
if instConf.Type() != instancetype.VM && d.config["connected"] != "" {
|
||||
return errors.New("The \"connected\" option is only supported on virtual machines for SR-IOV NICs")
|
||||
}
|
||||
|
||||
err := d.config.Validate(nicValidationRules(requiredFields, optionalFields, instConf))
|
||||
if err != nil {
|
||||
return err
|
||||
@ -240,6 +262,11 @@ func (d *nicSRIOV) validateEnvironment() error {
|
||||
|
||||
// Start is run when the device is added to a running instance or instance is starting up.
|
||||
func (d *nicSRIOV) Start() (*deviceConfig.RunConfig, error) {
|
||||
// Ignore detached NICs.
|
||||
if !util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := d.validateEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -310,6 +337,7 @@ func (d *nicSRIOV) Start() (*deviceConfig.RunConfig, error) {
|
||||
{Key: "flags", Value: "up"},
|
||||
{Key: "link", Value: saveData["host_name"]},
|
||||
{Key: "hwaddr", Value: d.config["hwaddr"]},
|
||||
{Key: "connected", Value: d.config["connected"]},
|
||||
}
|
||||
|
||||
if d.inst.Type() == instancetype.VM {
|
||||
@ -329,9 +357,12 @@ func (d *nicSRIOV) Stop() (*deviceConfig.RunConfig, error) {
|
||||
v := d.volatileGet()
|
||||
runConf := deviceConfig.RunConfig{
|
||||
PostHooks: []func() error{d.postStop},
|
||||
NetworkInterface: []deviceConfig.RunConfigItem{
|
||||
}
|
||||
|
||||
if util.IsTrueOrEmpty(d.config["attached"]) {
|
||||
runConf.NetworkInterface = []deviceConfig.RunConfigItem{
|
||||
{Key: "link", Value: v["host_name"]},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &runConf, nil
|
||||
@ -504,3 +535,23 @@ func nicSelected(device deviceConfig.Device, nic api.ResourcesNetworkCard) bool
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdatableFields returns a list of fields that can be updated without triggering a device remove & add.
|
||||
func (d *nicSRIOV) UpdatableFields(oldDevice Type) []string {
|
||||
// Check old and new device types match.
|
||||
_, match := oldDevice.(*nicSRIOV)
|
||||
if !match {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return []string{"connected"}
|
||||
}
|
||||
|
||||
// Update applies configuration changes to a started device.
|
||||
func (d *nicSRIOV) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
|
||||
if isRunning {
|
||||
return d.setNICLink()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@ import (
|
||||
"github.com/lxc/incus/v6/internal/server/instance"
|
||||
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
|
||||
"github.com/lxc/incus/v6/internal/server/instance/operationlock"
|
||||
"github.com/lxc/incus/v6/internal/server/ip"
|
||||
"github.com/lxc/incus/v6/internal/server/lifecycle"
|
||||
"github.com/lxc/incus/v6/internal/server/locking"
|
||||
"github.com/lxc/incus/v6/internal/server/metrics"
|
||||
@ -1493,7 +1494,7 @@ func (d *lxc) deviceStart(dev device.Device, instanceRunning bool) (*deviceConfi
|
||||
|
||||
// Attach network interface if requested.
|
||||
if len(runConf.NetworkInterface) > 0 {
|
||||
err = d.deviceAttachNIC(configCopy, runConf.NetworkInterface)
|
||||
err = d.deviceAttachNIC(dev.Name(), configCopy, runConf.NetworkInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1570,16 +1571,18 @@ func (d *lxc) deviceAddCgroupRules(cgroups []deviceConfig.RunConfigItem) error {
|
||||
}
|
||||
|
||||
// deviceAttachNIC live attaches a NIC device to a container.
|
||||
func (d *lxc) deviceAttachNIC(configCopy map[string]string, netIF []deviceConfig.RunConfigItem) error {
|
||||
devName := ""
|
||||
func (d *lxc) deviceAttachNIC(devName string, configCopy map[string]string, netIF []deviceConfig.RunConfigItem) error {
|
||||
ctDevName := ""
|
||||
connected := true
|
||||
for _, dev := range netIF {
|
||||
if dev.Key == "link" {
|
||||
devName = dev.Value
|
||||
break
|
||||
ctDevName = dev.Value
|
||||
} else if dev.Key == "connected" {
|
||||
connected = util.IsTrueOrEmpty(dev.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if devName == "" {
|
||||
if ctDevName == "" {
|
||||
return errors.New("Device didn't provide a link property to use")
|
||||
}
|
||||
|
||||
@ -1590,12 +1593,12 @@ func (d *lxc) deviceAttachNIC(configCopy map[string]string, netIF []deviceConfig
|
||||
}
|
||||
|
||||
// Add the interface to the container.
|
||||
err = cc.AttachInterface(devName, configCopy["name"])
|
||||
err = cc.AttachInterface(ctDevName, configCopy["name"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to attach interface: %s to %s: %w", devName, configCopy["name"], err)
|
||||
return fmt.Errorf("Failed to attach interface: %s to %s: %w", ctDevName, configCopy["name"], err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return d.setNICLink(devName, connected, true)
|
||||
}
|
||||
|
||||
// deviceStop loads a new device and calls its Stop() function.
|
||||
@ -1817,6 +1820,25 @@ func (d *lxc) DeviceEventHandler(runConf *deviceConfig.RunConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle NIC reconfiguration.
|
||||
var devName string
|
||||
var connected bool
|
||||
for _, dev := range runConf.NetworkInterface {
|
||||
switch dev.Key {
|
||||
case "devName":
|
||||
devName = dev.Value
|
||||
case "connected":
|
||||
connected = util.IsTrueOrEmpty(dev.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if devName != "" {
|
||||
err := d.setNICLink(devName, connected, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Run any post hooks requested.
|
||||
err := d.runHooks(runConf.PostHooks)
|
||||
if err != nil {
|
||||
@ -2302,6 +2324,15 @@ func (d *lxc) startCommon() (string, []func() error, error) {
|
||||
}
|
||||
|
||||
for _, nicItem := range runConf.NetworkInterface {
|
||||
// The connected state is not a LXC configuration key; we defer its handling to a post hook.
|
||||
if nicItem.Key == "connected" {
|
||||
runConf.PostHooks = append(runConf.PostHooks, func() error {
|
||||
return d.setNICLink(dev.Name(), util.IsTrueOrEmpty(nicItem.Value), true)
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.%s", networkKeyPrefix, nicID, nicItem.Key), nicItem.Value)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("Failed to setup device network interface %q: %w", dev.Name(), err)
|
||||
@ -4813,6 +4844,12 @@ func (d *lxc) Update(args db.InstanceArgs, userRequested bool) error {
|
||||
return []string{} // Couldn't create Device, so this cannot be an update.
|
||||
}
|
||||
|
||||
// Detached devices need to be fully recreated on update so that the update logic doesn't
|
||||
// try to access non-existing LXC devices.
|
||||
if !util.IsTrueOrEmpty(oldDevice["attached"]) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return newDevType.UpdatableFields(oldDevType)
|
||||
})
|
||||
|
||||
@ -9452,3 +9489,30 @@ func (d *lxc) CreateQcow2Snapshot(snapName string, backingFilename string) error
|
||||
func (d *lxc) DeleteQcow2Snapshot(snapshotIndex int, backingFilename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setNICLink sets the link status of the given device.
|
||||
func (d *lxc) setNICLink(devName string, connected bool, assumeUp bool) error {
|
||||
// This check is added so that devices that cannot handle link states do not fail to initialize.
|
||||
if connected && assumeUp {
|
||||
return nil
|
||||
}
|
||||
|
||||
link, err := ip.LinkByName(d.localConfig["volatile."+devName+".host_name"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to find interface %s: %w", devName, err)
|
||||
}
|
||||
|
||||
if connected {
|
||||
err = link.SetUp()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to bring %s up: %w", devName, err)
|
||||
}
|
||||
} else {
|
||||
err = link.SetDown()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to bring %s down: %w", devName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -4929,6 +4929,7 @@ func (d *qemu) addNetDevConfig(busName string, qemuDev map[string]any, bootIndex
|
||||
defer reverter.Fail()
|
||||
|
||||
var devName, nicName, devHwaddr, pciSlotName, pciIOMMUGroup, vDPADevName, vhostVDPAPath, maxVQP string
|
||||
var connected bool
|
||||
for _, nicItem := range nicConfig {
|
||||
if nicItem.Key == "devName" {
|
||||
devName = nicItem.Value
|
||||
@ -4946,6 +4947,8 @@ func (d *qemu) addNetDevConfig(busName string, qemuDev map[string]any, bootIndex
|
||||
vhostVDPAPath = nicItem.Value
|
||||
} else if nicItem.Key == "maxVQP" {
|
||||
maxVQP = nicItem.Value
|
||||
} else if nicItem.Key == "connected" {
|
||||
connected = util.IsTrueOrEmpty(nicItem.Value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5062,7 +5065,7 @@ func (d *qemu) addNetDevConfig(busName string, qemuDev map[string]any, bootIndex
|
||||
qemuDev["netdev"] = qemuNetDev["id"].(string)
|
||||
qemuDev["mac"] = devHwaddr
|
||||
|
||||
err = m.AddNIC(qemuNetDev, qemuDev)
|
||||
err = m.AddNIC(qemuNetDev, qemuDev, connected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed setting up device %q: %w", devName, err)
|
||||
}
|
||||
@ -5171,7 +5174,7 @@ func (d *qemu) addNetDevConfig(busName string, qemuDev map[string]any, bootIndex
|
||||
qemuDev["iommu_platform"] = true
|
||||
qemuDev["disable-legacy"] = true
|
||||
|
||||
err = m.AddNIC(qemuNetDev, qemuDev)
|
||||
err = m.AddNIC(qemuNetDev, qemuDev, connected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed setting up device %q: %w", devName, err)
|
||||
}
|
||||
@ -5204,7 +5207,7 @@ func (d *qemu) addNetDevConfig(busName string, qemuDev map[string]any, bootIndex
|
||||
}
|
||||
|
||||
monHook = func(m *qmp.Monitor) error {
|
||||
err := m.AddNIC(nil, qemuDev)
|
||||
err := m.AddNIC(nil, qemuDev, connected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed setting up device %q: %w", devName, err)
|
||||
}
|
||||
@ -6327,6 +6330,12 @@ func (d *qemu) Update(args db.InstanceArgs, userRequested bool) error {
|
||||
return []string{} // Couldn't create Device, so this cannot be an update.
|
||||
}
|
||||
|
||||
// Detached devices need to be fully recreated on update so that the update logic doesn't
|
||||
// try to access non-existing QEMU devices.
|
||||
if !util.IsTrueOrEmpty(oldDevice["attached"]) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return newDevType.UpdatableFields(oldDevType)
|
||||
})
|
||||
|
||||
@ -9159,6 +9168,34 @@ func (d *qemu) DeviceEventHandler(runConf *deviceConfig.RunConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle NIC reconfiguration.
|
||||
var devName string
|
||||
var connected bool
|
||||
for _, dev := range runConf.NetworkInterface {
|
||||
switch dev.Key {
|
||||
case "devName":
|
||||
devName = dev.Value
|
||||
case "connected":
|
||||
connected = util.IsTrueOrEmpty(dev.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if devName != "" {
|
||||
// Get the QMP monitor.
|
||||
m, err := d.qmpConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Figure out the QEMU device ID.
|
||||
devID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, linux.PathNameEncode(devName))
|
||||
|
||||
err = m.SetNICLink(devID, connected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed setting NIC device link status: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -867,7 +867,7 @@ func (m *Monitor) RemoveDevice(deviceID string) error {
|
||||
}
|
||||
|
||||
// AddNIC adds a NIC device.
|
||||
func (m *Monitor) AddNIC(netDev map[string]any, device map[string]any) error {
|
||||
func (m *Monitor) AddNIC(netDev map[string]any, device map[string]any, connected bool) error {
|
||||
reverter := revert.New()
|
||||
defer reverter.Fail()
|
||||
|
||||
@ -894,6 +894,16 @@ func (m *Monitor) AddNIC(netDev map[string]any, device map[string]any) error {
|
||||
return fmt.Errorf("Failed adding NIC device: %w", err)
|
||||
}
|
||||
|
||||
id, ok := device["id"].(string)
|
||||
if !ok {
|
||||
return errors.New("NIC device must have an id")
|
||||
}
|
||||
|
||||
err = m.SetNICLink(id, connected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed setting NIC device link status: %w", err)
|
||||
}
|
||||
|
||||
reverter.Success()
|
||||
|
||||
return nil
|
||||
@ -1562,3 +1572,15 @@ func (m *Monitor) DumpGuestMemory(path string, format string) error {
|
||||
|
||||
return m.Run("dump-guest-memory", args, &queryResp)
|
||||
}
|
||||
|
||||
// SetNICLink sets the link status of the given device.
|
||||
func (m *Monitor) SetNICLink(id string, connected bool) error {
|
||||
var args struct {
|
||||
Name string `json:"name"`
|
||||
Up bool `json:"up"`
|
||||
}
|
||||
|
||||
args.Name = id
|
||||
args.Up = connected
|
||||
return m.Run("set_link", args, nil)
|
||||
}
|
||||
|
||||
@ -97,11 +97,9 @@ func LinkByName(name string) (LinkInfo, error) {
|
||||
|
||||
if link.Attrs().ParentIndex != 0 {
|
||||
parentLink, err := netlink.LinkByIndex(link.Attrs().ParentIndex)
|
||||
if err != nil {
|
||||
return LinkInfo{}, err
|
||||
if err == nil {
|
||||
parent = parentLink.Attrs().Name
|
||||
}
|
||||
|
||||
parent = parentLink.Attrs().Name
|
||||
}
|
||||
|
||||
if link.Attrs().MasterIndex != 0 {
|
||||
|
||||
@ -484,6 +484,15 @@
|
||||
},
|
||||
"nic_bridged": {
|
||||
"keys": [
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"boot.priority": {
|
||||
"longdesc": "",
|
||||
@ -492,6 +501,15 @@
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"host_name": {
|
||||
"default": "randomly assigned",
|
||||
@ -741,6 +759,15 @@
|
||||
},
|
||||
"nic_ipvlan": {
|
||||
"keys": [
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"gvrp": {
|
||||
"default": "false",
|
||||
@ -843,6 +870,15 @@
|
||||
},
|
||||
"nic_macvlan": {
|
||||
"keys": [
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"boot.priority": {
|
||||
"longdesc": "",
|
||||
@ -851,6 +887,15 @@
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network (VM only)",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"gvrp": {
|
||||
"default": "false",
|
||||
@ -942,6 +987,15 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"boot.priority": {
|
||||
"longdesc": "",
|
||||
@ -950,6 +1004,15 @@
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network (container support requires setting `acceleration` to `none`)",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"host_name": {
|
||||
"default": "randomly assigned",
|
||||
@ -1165,12 +1228,30 @@
|
||||
"nic_p2p": {
|
||||
"keys": [
|
||||
{
|
||||
"boot.priotiry": {
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"boot.priority": {
|
||||
"longdesc": "",
|
||||
"shortdesc": "Boot priority for VMs (higher value boots first)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"host_name": {
|
||||
"default": "randomly assigned",
|
||||
@ -1264,6 +1345,15 @@
|
||||
},
|
||||
"nic_physical": {
|
||||
"keys": [
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"boot.priority": {
|
||||
"longdesc": "",
|
||||
@ -1272,6 +1362,15 @@
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network (VM only)",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"gvrp": {
|
||||
"default": "false",
|
||||
@ -1344,6 +1443,24 @@
|
||||
},
|
||||
"nic_routed": {
|
||||
"keys": [
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"gvrp": {
|
||||
"default": "false",
|
||||
@ -1558,6 +1675,15 @@
|
||||
},
|
||||
"nic_sriov": {
|
||||
"keys": [
|
||||
{
|
||||
"attached": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is plugged in or not",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"boot.priority": {
|
||||
"longdesc": "",
|
||||
@ -1566,6 +1692,15 @@
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"connected": {
|
||||
"default": "`true`",
|
||||
"longdesc": "",
|
||||
"required": "no",
|
||||
"shortdesc": "Whether the NIC is connected to the host network (VM only)",
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"hwaddr": {
|
||||
"default": "randomly assigned",
|
||||
|
||||
@ -516,6 +516,7 @@ var APIExtensions = []string{
|
||||
"file_delete_force",
|
||||
"nic_sriov_select_ext",
|
||||
"network_zones_dns_contact",
|
||||
"nic_attached_connected",
|
||||
}
|
||||
|
||||
// APIExtensionsCount returns the number of available API extensions.
|
||||
|
||||
@ -709,6 +709,30 @@ test_container_devices_nic_bridged() {
|
||||
! incus start foo2 || false
|
||||
incus delete -f foo foo2
|
||||
|
||||
# Test attached and connected keys.
|
||||
incus launch testimage foo
|
||||
incus config device add foo eth0 nic nictype=bridged name=eth0 parent=${brName}
|
||||
incus exec foo ip link set eth0 up
|
||||
[ "$(incus file pull foo/sys/class/net/eth0/operstate -)" = "up" ]
|
||||
incus config device set foo eth0 connected=false
|
||||
incus exec foo ip link set eth0 up
|
||||
[ "$(incus file pull foo/sys/class/net/eth0/operstate -)" = "down" ]
|
||||
incus config device set foo eth0 attached=false
|
||||
! incus file pull foo/sys/class/net/eth0/operstate - || false
|
||||
incus config device set foo eth0 connected=true
|
||||
! incus file pull foo/sys/class/net/eth0/operstate - || false
|
||||
incus config device set foo eth0 attached=true
|
||||
incus exec foo ip link set eth0 up
|
||||
[ "$(incus file pull foo/sys/class/net/eth0/operstate -)" = "up" ]
|
||||
incus config device set foo eth0 connected=false
|
||||
incus exec foo ip link set eth0 up
|
||||
[ "$(incus file pull foo/sys/class/net/eth0/operstate -)" = "down" ]
|
||||
# Check that it survives a reboot.
|
||||
incus restart foo
|
||||
incus exec foo ip link set eth0 up
|
||||
[ "$(incus file pull foo/sys/class/net/eth0/operstate -)" = "down" ]
|
||||
incus delete -f foo
|
||||
|
||||
# Check we haven't left any NICS lying around.
|
||||
endNicCount=$(find /sys/class/net | wc -l)
|
||||
if [ "$startNicCount" != "$endNicCount" ]; then
|
||||
|
||||
@ -115,6 +115,18 @@ test_container_devices_nic_macvlan() {
|
||||
incus exec "${ctName}" -- ping -c2 -W5 "192.0.2.2${ipRand}"
|
||||
incus exec "${ctName}" -- ping6 -c2 -W5 "2001:db8::2${ipRand}"
|
||||
incus config device remove "${ctName}" eth0
|
||||
|
||||
# Test attached key.
|
||||
incus config device add "${ctName}" eth0 nic network="${ctName}net" name=eth0
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 attached=false
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 attached=true
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device remove "${ctName}" eth0
|
||||
|
||||
incus network delete "${ctName}net"
|
||||
|
||||
# Check we haven't left any NICS lying around.
|
||||
|
||||
@ -358,6 +358,30 @@ test_container_devices_nic_p2p() {
|
||||
# Test hotplugging nic with new name (rather than updating existing nic).
|
||||
incus config device add "${ctName}" eth1 nic nictype=p2p
|
||||
|
||||
incus config device remove "${ctName}" eth1
|
||||
|
||||
# Test attached and connected keys.
|
||||
incus config device add "${ctName}" eth0 nic nictype=p2p name=eth0
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 connected=false
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
incus config device set "${ctName}" eth0 attached=false
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 connected=true
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 attached=true
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 connected=false
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
# Check that it survives a reboot.
|
||||
incus restart "${ctName}"
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
|
||||
incus stop -f "${ctName}"
|
||||
|
||||
# Check we haven't left any NICS lying around.
|
||||
|
||||
@ -216,6 +216,30 @@ test_container_devices_nic_physical() {
|
||||
fi
|
||||
fi
|
||||
|
||||
incus config device remove "${ctName}" eth1
|
||||
|
||||
# Test attached and connected keys.
|
||||
incus config device add "${ctName}" eth0 nic nictype=p2p name=eth0
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 connected=false
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
incus config device set "${ctName}" eth0 attached=false
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 connected=true
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 attached=true
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 connected=false
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
# Check that it survives a reboot.
|
||||
incus restart "${ctName}"
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
|
||||
incus delete "${ctName}" -f
|
||||
|
||||
incus network delete "${networkName}"
|
||||
|
||||
@ -267,6 +267,31 @@ test_container_devices_nic_routed() {
|
||||
false
|
||||
fi
|
||||
|
||||
incus config device remove "${ctName}" eth0
|
||||
incus start "${ctName}"
|
||||
|
||||
# Test attached and connected keys.
|
||||
incus config device add "${ctName}" eth0 nic network="${ctName}" name=eth0
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 connected=false
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
incus config device set "${ctName}" eth0 attached=false
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 connected=true
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 attached=true
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 connected=false
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
# Check that it survives a reboot.
|
||||
incus restart "${ctName}"
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "down" ]
|
||||
|
||||
# Cleanup routed checks
|
||||
incus delete "${ctName}" -f
|
||||
incus delete "${ctName}2" -f
|
||||
|
||||
@ -154,7 +154,17 @@ test_container_devices_nic_sriov() {
|
||||
false
|
||||
fi
|
||||
|
||||
# Test attached key.
|
||||
incus config device add "${ctName}" eth0 nic network="${ctName}" name=eth0
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device set "${ctName}" eth0 attached=false
|
||||
! incus file pull "${ctName}/sys/class/net/eth0/operstate" - || false
|
||||
incus config device set "${ctName}" eth0 attached=true
|
||||
incus exec "${ctName}" ip link set eth0 up
|
||||
[ "$(incus file pull "${ctName}/sys/class/net/eth0/operstate" -)" = "up" ]
|
||||
incus config device remove "${ctName}" eth0
|
||||
|
||||
incus network delete "${ctName}net"
|
||||
|
||||
incus delete -f "${ctName}"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user