The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6420

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From 1212321a20284c6bce1529dc2fd03191a08c1957 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Thu, 7 Nov 2019 11:03:39 -0500
Subject: [PATCH 1/2] lxd/device/nic: Fix race in vlan creation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/device/device_utils_network.go | 4 ++++
 lxd/device/nic_ipvlan.go           | 4 ++++
 lxd/device/nic_macvlan.go          | 4 ++++
 lxd/device/nic_physical.go         | 4 ++++
 4 files changed, 16 insertions(+)

diff --git a/lxd/device/device_utils_network.go 
b/lxd/device/device_utils_network.go
index e2eeed970b..6462d4c493 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -11,6 +11,7 @@ import (
        "regexp"
        "strconv"
        "strings"
+       "sync"
 
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
        "github.com/lxc/lxd/shared"
@@ -18,6 +19,9 @@ import (
        "github.com/lxc/lxd/shared/units"
 )
 
+// Instances can be started in parallel, so lock the creation of VLANs.
+var networkCreateSharedDeviceLock sync.Mutex
+
 // NetworkSysctlGet retrieves the value of a sysctl file in /proc/sys/net.
 func NetworkSysctlGet(path string) (string, error) {
        // Read the current content
diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go
index 226d8aa7d3..82f698bfe7 100644
--- a/lxd/device/nic_ipvlan.go
+++ b/lxd/device/nic_ipvlan.go
@@ -113,6 +113,10 @@ func (d *nicIPVLAN) Start() (*RunConfig, error) {
                return nil, err
        }
 
+       // Lock to avoid issues with containers starting in parallel.
+       networkCreateSharedDeviceLock.Lock()
+       defer networkCreateSharedDeviceLock.Unlock()
+
        saveData := make(map[string]string)
 
        // Decide which parent we should use based on VLAN setting.
diff --git a/lxd/device/nic_macvlan.go b/lxd/device/nic_macvlan.go
index e5f92565aa..0b8fe8077a 100644
--- a/lxd/device/nic_macvlan.go
+++ b/lxd/device/nic_macvlan.go
@@ -47,6 +47,10 @@ func (d *nicMACVLAN) Start() (*RunConfig, error) {
                return nil, err
        }
 
+       // Lock to avoid issues with containers starting in parallel.
+       networkCreateSharedDeviceLock.Lock()
+       defer networkCreateSharedDeviceLock.Unlock()
+
        saveData := make(map[string]string)
 
        // Decide which parent we should use based on VLAN setting.
diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go
index 1a935518cc..7989049ea5 100644
--- a/lxd/device/nic_physical.go
+++ b/lxd/device/nic_physical.go
@@ -54,6 +54,10 @@ func (d *nicPhysical) Start() (*RunConfig, error) {
                return nil, err
        }
 
+       // Lock to avoid issues with containers starting in parallel.
+       networkCreateSharedDeviceLock.Lock()
+       defer networkCreateSharedDeviceLock.Unlock()
+
        saveData := make(map[string]string)
 
        // Record the host_name device used for restoration later.

From f59a5d927890357fec9feaf5a6345be63ac10d62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Thu, 7 Nov 2019 15:05:13 -0500
Subject: [PATCH 2/2] lxd/device/nic: Fix handling of shared vlans
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #6416

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/device/device_utils_network.go | 62 ++++++++++++++++++++++++++----
 lxd/device/nic_ipvlan.go           |  8 ++--
 lxd/device/nic_macvlan.go          | 12 +++---
 lxd/device/nic_physical.go         |  8 ++--
 4 files changed, 70 insertions(+), 20 deletions(-)

diff --git a/lxd/device/device_utils_network.go 
b/lxd/device/device_utils_network.go
index 6462d4c493..67cd1fbf42 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -14,6 +14,7 @@ import (
        "sync"
 
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/logger"
        "github.com/lxc/lxd/shared/units"
@@ -155,31 +156,78 @@ func NetworkRemoveInterface(nic string) error {
        return err
 }
 
+// NetworkRemoveInterface removes a network interface by name but only if no 
other instance is using it.
+func NetworkRemoveInterfaceIfNeeded(state *state.State, nic string, current 
Instance, parent string, vlanID string) error {
+       // Check if it's used by another instance.
+       instances, err := InstanceLoadNodeAll(state)
+       if err != nil {
+               return err
+       }
+
+       for _, inst := range instances {
+               if inst.Name() == current.Name() && inst.Project() == 
current.Project() {
+                       continue
+               }
+
+               for devName, dev := range inst.ExpandedDevices() {
+                       if dev["type"] != "nic" || dev["vlan"] != vlanID || 
dev["parent"] != parent {
+                               continue
+                       }
+
+                       // Check if another running instance created the 
device, if so, don't touch it.
+                       if 
shared.IsTrue(inst.ExpandedConfig()[fmt.Sprintf("volatile.%s.last_state.created",
 devName)]) {
+                               return nil
+                       }
+               }
+       }
+
+       return NetworkRemoveInterface(nic)
+}
+
 // NetworkCreateVlanDeviceIfNeeded creates a VLAN device if doesn't already 
exist.
-func NetworkCreateVlanDeviceIfNeeded(parent string, vlanDevice string, vlanID 
string) (bool, error) {
+func NetworkCreateVlanDeviceIfNeeded(state *state.State, parent string, 
vlanDevice string, vlanID string) (string, error) {
        if vlanID != "" {
                if !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", 
vlanDevice)) {
                        // Bring the parent interface up so we can add a vlan 
to it.
                        _, err := shared.RunCommand("ip", "link", "set", "dev", 
parent, "up")
                        if err != nil {
-                               return false, fmt.Errorf("Failed to bring up 
parent %s: %v", parent, err)
+                               return "", fmt.Errorf("Failed to bring up 
parent %s: %v", parent, err)
                        }
 
                        // Add VLAN interface on top of parent.
                        _, err = shared.RunCommand("ip", "link", "add", "link", 
parent, "name", vlanDevice, "up", "type", "vlan", "id", vlanID)
                        if err != nil {
-                               return false, err
+                               return "", err
                        }
 
-                       // Attempt to disable IPv6 router advertisement 
acceptance
+                       // Attempt to disable IPv6 router advertisement 
acceptance.
                        NetworkSysctlSet(fmt.Sprintf("ipv6/conf/%s/accept_ra", 
vlanDevice), "0")
 
-                       // We created a new vlan interface, return true
-                       return true, nil
+                       // We created a new vlan interface, return true.
+                       return "created", nil
+               } else {
+                       // Check if it was created for another running instance.
+                       instances, err := InstanceLoadNodeAll(state)
+                       if err != nil {
+                               return "", err
+                       }
+
+                       for _, inst := range instances {
+                               for devName, dev := range 
inst.ExpandedDevices() {
+                                       if dev["type"] != "nic" || dev["vlan"] 
!= vlanID || dev["parent"] != parent {
+                                               continue
+                                       }
+
+                                       // Check if another running instance 
created the device, if so, mark it as created.
+                                       if 
shared.IsTrue(inst.ExpandedConfig()[fmt.Sprintf("volatile.%s.last_state.created",
 devName)]) {
+                                               return "reused", nil
+                                       }
+                               }
+                       }
                }
        }
 
-       return false, nil
+       return "existing", nil
 }
 
 // networkSnapshotPhysicalNic records properties of the NIC to volatile so 
they can be restored later.
diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go
index 82f698bfe7..0308e90c5e 100644
--- a/lxd/device/nic_ipvlan.go
+++ b/lxd/device/nic_ipvlan.go
@@ -122,16 +122,16 @@ func (d *nicIPVLAN) Start() (*RunConfig, error) {
        // Decide which parent we should use based on VLAN setting.
        parentName := NetworkGetHostDevice(d.config["parent"], d.config["vlan"])
 
-       createdDev, err := NetworkCreateVlanDeviceIfNeeded(d.config["parent"], 
parentName, d.config["vlan"])
+       statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, 
d.config["parent"], parentName, d.config["vlan"])
        if err != nil {
                return nil, err
        }
 
        // Record whether we created this device or not so it can be removed on 
stop.
-       saveData["last_state.created"] = fmt.Sprintf("%t", createdDev)
+       saveData["last_state.created"] = fmt.Sprintf("%t", statusDev != 
"existing")
 
        // If we created a VLAN interface, we need to setup the sysctls on that 
interface.
-       if createdDev {
+       if statusDev == "created" {
                err := d.setupParentSysctls(parentName)
                if err != nil {
                        return nil, err
@@ -231,7 +231,7 @@ func (d *nicIPVLAN) postStop() error {
        // This will delete the parent interface if we created it for VLAN 
parent.
        if shared.IsTrue(v["last_state.created"]) {
                parentName := NetworkGetHostDevice(d.config["parent"], 
d.config["vlan"])
-               err := NetworkRemoveInterface(parentName)
+               err := NetworkRemoveInterfaceIfNeeded(d.state, parentName, 
d.instance, d.config["parent"], d.config["vlan"])
                if err != nil {
                        return err
                }
diff --git a/lxd/device/nic_macvlan.go b/lxd/device/nic_macvlan.go
index 0b8fe8077a..f02ee4a38f 100644
--- a/lxd/device/nic_macvlan.go
+++ b/lxd/device/nic_macvlan.go
@@ -60,13 +60,13 @@ func (d *nicMACVLAN) Start() (*RunConfig, error) {
        saveData["host_name"] = NetworkRandomDevName("mac")
 
        // Create VLAN parent device if needed.
-       createdDev, err := NetworkCreateVlanDeviceIfNeeded(d.config["parent"], 
parentName, d.config["vlan"])
+       statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, 
d.config["parent"], parentName, d.config["vlan"])
        if err != nil {
                return nil, err
        }
 
        // Record whether we created the parent device or not so it can be 
removed on stop.
-       saveData["last_state.created"] = fmt.Sprintf("%t", createdDev)
+       saveData["last_state.created"] = fmt.Sprintf("%t", statusDev != 
"existing")
 
        // Create MACVLAN interface.
        _, err = shared.RunCommand("ip", "link", "add", "dev", 
saveData["host_name"], "link", parentName, "type", "macvlan", "mode", "bridge")
@@ -78,9 +78,10 @@ func (d *nicMACVLAN) Start() (*RunConfig, error) {
        if d.config["hwaddr"] != "" {
                _, err := shared.RunCommand("ip", "link", "set", "dev", 
saveData["host_name"], "address", d.config["hwaddr"])
                if err != nil {
-                       if createdDev {
+                       if statusDev == "created" {
                                NetworkRemoveInterface(saveData["host_name"])
                        }
+
                        return nil, fmt.Errorf("Failed to set the MAC address: 
%s", err)
                }
        }
@@ -89,9 +90,10 @@ func (d *nicMACVLAN) Start() (*RunConfig, error) {
        if d.config["mtu"] != "" {
                _, err := shared.RunCommand("ip", "link", "set", "dev", 
saveData["host_name"], "mtu", d.config["mtu"])
                if err != nil {
-                       if createdDev {
+                       if statusDev == "created" {
                                NetworkRemoveInterface(saveData["host_name"])
                        }
+
                        return nil, fmt.Errorf("Failed to set the MTU: %s", err)
                }
        }
@@ -148,7 +150,7 @@ func (d *nicMACVLAN) postStop() error {
        // This will delete the parent interface if we created it for VLAN 
parent.
        if shared.IsTrue(v["last_state.created"]) {
                parentName := NetworkGetHostDevice(d.config["parent"], 
d.config["vlan"])
-               err := NetworkRemoveInterface(parentName)
+               err := NetworkRemoveInterfaceIfNeeded(d.state, parentName, 
d.instance, d.config["parent"], d.config["vlan"])
                if err != nil {
                        errs = append(errs, err)
                }
diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go
index 7989049ea5..13c0653dfc 100644
--- a/lxd/device/nic_physical.go
+++ b/lxd/device/nic_physical.go
@@ -62,24 +62,24 @@ func (d *nicPhysical) Start() (*RunConfig, error) {
 
        // Record the host_name device used for restoration later.
        saveData["host_name"] = NetworkGetHostDevice(d.config["parent"], 
d.config["vlan"])
-       createdDev, err := NetworkCreateVlanDeviceIfNeeded(d.config["parent"], 
saveData["host_name"], d.config["vlan"])
+       statusDev, err := NetworkCreateVlanDeviceIfNeeded(d.state, 
d.config["parent"], saveData["host_name"], d.config["vlan"])
        if err != nil {
                return nil, err
        }
 
        // Record whether we created this device or not so it can be removed on 
stop.
-       saveData["last_state.created"] = fmt.Sprintf("%t", createdDev)
+       saveData["last_state.created"] = fmt.Sprintf("%t", statusDev != 
"existing")
 
        // If we return from this function with an error, ensure we clean up 
created device.
        defer func() {
-               if err != nil && createdDev {
+               if err != nil && statusDev == "created" {
                        NetworkRemoveInterface(saveData["host_name"])
                }
        }()
 
        // If we didn't create the device we should track various properties so 
we can
        // restore them when the instance is stopped or the device is detached.
-       if createdDev == false {
+       if statusDev == "existing" {
                err = networkSnapshotPhysicalNic(saveData["host_name"], 
saveData)
                if err != nil {
                        return nil, err
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to