Merge pull request #174 from crawford/teeth

networkd: Fix issues with bonding and VLANs
This commit is contained in:
Alex Crawford 2014-07-11 15:48:02 -07:00
commit a974e85103
5 changed files with 144 additions and 18 deletions

View File

@ -3,6 +3,7 @@ package network
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
) )
type InterfaceGenerator interface { type InterfaceGenerator interface {
@ -11,6 +12,8 @@ type InterfaceGenerator interface {
Netdev() string Netdev() string
Link() string Link() string
Network() string Network() string
Type() string
ModprobeParams() string
} }
type networkInterface interface { type networkInterface interface {
@ -68,6 +71,10 @@ func (i *logicalInterface) Children() []networkInterface {
return i.children return i.children
} }
func (i *logicalInterface) ModprobeParams() string {
return ""
}
func (i *logicalInterface) setConfigDepth(depth int) { func (i *logicalInterface) setConfigDepth(depth int) {
i.configDepth = depth i.configDepth = depth
} }
@ -84,9 +91,14 @@ func (p *physicalInterface) Netdev() string {
return "" return ""
} }
func (p *physicalInterface) Type() string {
return "physical"
}
type bondInterface struct { type bondInterface struct {
logicalInterface logicalInterface
slaves []string slaves []string
options map[string]string
} }
func (b *bondInterface) Name() string { func (b *bondInterface) Name() string {
@ -97,6 +109,19 @@ func (b *bondInterface) Netdev() string {
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name) return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
} }
func (b *bondInterface) Type() string {
return "bond"
}
func (b *bondInterface) ModprobeParams() string {
params := ""
for name, val := range b.options {
params += fmt.Sprintf("%s=%s ", name, val)
}
params = strings.TrimSuffix(params, " ")
return params
}
type vlanInterface struct { type vlanInterface struct {
logicalInterface logicalInterface
id int id int
@ -123,6 +148,10 @@ func (v *vlanInterface) Netdev() string {
return config return config
} }
func (v *vlanInterface) Type() string {
return "vlan"
}
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
interfaceMap := createInterfaces(stanzas) interfaceMap := createInterfaces(stanzas)
linkAncestors(interfaceMap) linkAncestors(interfaceMap)
@ -141,15 +170,22 @@ func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
for _, iface := range stanzas { for _, iface := range stanzas {
switch iface.kind { switch iface.kind {
case interfaceBond: case interfaceBond:
bondOptions := make(map[string]string)
for _, k := range []string{"mode", "miimon", "lacp-rate"} {
if v, ok := iface.options["bond-"+k]; ok && len(v) > 0 {
bondOptions[k] = v[0]
}
}
interfaceMap[iface.name] = &bondInterface{ interfaceMap[iface.name] = &bondInterface{
logicalInterface{ logicalInterface{
name: iface.name, name: iface.name,
config: iface.configMethod, config: iface.configMethod,
children: []networkInterface{}, children: []networkInterface{},
}, },
iface.options["slaves"], iface.options["bond-slaves"],
bondOptions,
} }
for _, slave := range iface.options["slaves"] { for _, slave := range iface.options["bond-slaves"] {
if _, ok := interfaceMap[slave]; !ok { if _, ok := interfaceMap[slave]; !ok {
interfaceMap[slave] = &physicalInterface{ interfaceMap[slave] = &physicalInterface{
logicalInterface{ logicalInterface{

View File

@ -36,6 +36,7 @@ func TestPhysicalInterfaceNetwork(t *testing.T) {
name: "testbond1", name: "testbond1",
}, },
nil, nil,
nil,
}, },
&vlanInterface{ &vlanInterface{
logicalInterface{ logicalInterface{
@ -67,14 +68,14 @@ VLAN=testvlan2
} }
func TestBondInterfaceName(t *testing.T) { func TestBondInterfaceName(t *testing.T) {
b := bondInterface{logicalInterface{name: "testname"}, nil} b := bondInterface{logicalInterface{name: "testname"}, nil, nil}
if b.Name() != "testname" { if b.Name() != "testname" {
t.FailNow() t.FailNow()
} }
} }
func TestBondInterfaceNetdev(t *testing.T) { func TestBondInterfaceNetdev(t *testing.T) {
b := bondInterface{logicalInterface{name: "testname"}, nil} b := bondInterface{logicalInterface{name: "testname"}, nil, nil}
netdev := `[NetDev] netdev := `[NetDev]
Kind=bond Kind=bond
Name=testname Name=testname
@ -102,6 +103,7 @@ func TestBondInterfaceNetwork(t *testing.T) {
name: "testbond1", name: "testbond1",
}, },
nil, nil,
nil,
}, },
&vlanInterface{ &vlanInterface{
logicalInterface{ logicalInterface{
@ -120,6 +122,7 @@ func TestBondInterfaceNetwork(t *testing.T) {
}, },
}, },
nil, nil,
nil,
} }
network := `[Match] network := `[Match]
Name=testname Name=testname
@ -218,6 +221,61 @@ Gateway=1.2.3.4
} }
} }
func TestType(t *testing.T) {
for _, tt := range []struct {
i InterfaceGenerator
t string
}{
{
i: &physicalInterface{},
t: "physical",
},
{
i: &vlanInterface{},
t: "vlan",
},
{
i: &bondInterface{},
t: "bond",
},
} {
if tp := tt.i.Type(); tp != tt.t {
t.Fatalf("bad type (%q): got %s, want %s", tt.i, tp, tt.t)
}
}
}
func TestModprobeParams(t *testing.T) {
for _, tt := range []struct {
i InterfaceGenerator
p string
}{
{
i: &physicalInterface{},
p: "",
},
{
i: &vlanInterface{},
p: "",
},
{
i: &bondInterface{
logicalInterface{},
nil,
map[string]string{
"a": "1",
"b": "2",
},
},
p: "a=1 b=2",
},
} {
if p := tt.i.ModprobeParams(); p != tt.p {
t.Fatalf("bad params (%q): got %s, want %s", tt.i, p, tt.p)
}
}
}
func TestBuildInterfacesLo(t *testing.T) { func TestBuildInterfacesLo(t *testing.T) {
stanzas := []*stanzaInterface{ stanzas := []*stanzaInterface{
&stanzaInterface{ &stanzaInterface{
@ -242,7 +300,7 @@ func TestBuildInterfacesBlindBond(t *testing.T) {
auto: false, auto: false,
configMethod: configMethodManual{}, configMethod: configMethodManual{},
options: map[string][]string{ options: map[string][]string{
"slaves": []string{"eth0"}, "bond-slaves": []string{"eth0"},
}, },
}, },
} }
@ -255,6 +313,7 @@ func TestBuildInterfacesBlindBond(t *testing.T) {
configDepth: 1, configDepth: 1,
}, },
[]string{"eth0"}, []string{"eth0"},
map[string]string{},
} }
eth0 := &physicalInterface{ eth0 := &physicalInterface{
logicalInterface{ logicalInterface{
@ -323,7 +382,9 @@ func TestBuildInterfaces(t *testing.T) {
auto: false, auto: false,
configMethod: configMethodManual{}, configMethod: configMethodManual{},
options: map[string][]string{ options: map[string][]string{
"slaves": []string{"eth0"}, "bond-slaves": []string{"eth0"},
"bond-mode": []string{"4"},
"bond-miimon": []string{"100"},
}, },
}, },
&stanzaInterface{ &stanzaInterface{
@ -332,7 +393,7 @@ func TestBuildInterfaces(t *testing.T) {
auto: false, auto: false,
configMethod: configMethodManual{}, configMethod: configMethodManual{},
options: map[string][]string{ options: map[string][]string{
"slaves": []string{"bond0"}, "bond-slaves": []string{"bond0"},
}, },
}, },
&stanzaInterface{ &stanzaInterface{
@ -385,6 +446,7 @@ func TestBuildInterfaces(t *testing.T) {
configDepth: 2, configDepth: 2,
}, },
[]string{"bond0"}, []string{"bond0"},
map[string]string{},
} }
bond0 := &bondInterface{ bond0 := &bondInterface{
logicalInterface{ logicalInterface{
@ -394,6 +456,10 @@ func TestBuildInterfaces(t *testing.T) {
configDepth: 1, configDepth: 1,
}, },
[]string{"eth0"}, []string{"eth0"},
map[string]string{
"mode": "4",
"miimon": "100",
},
} }
eth0 := &physicalInterface{ eth0 := &physicalInterface{
logicalInterface{ logicalInterface{

View File

@ -293,7 +293,6 @@ func parseHwaddress(options map[string][]string, iface string) (net.HardwareAddr
} }
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) { func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
options["slaves"] = options["bond-slaves"]
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
} }

View File

@ -129,7 +129,7 @@ func TestParseBondStanzaNoSlaves(t *testing.T) {
if err != nil { if err != nil {
t.FailNow() t.FailNow()
} }
if bond.options["slaves"] != nil { if bond.options["bond-slaves"] != nil {
t.FailNow() t.FailNow()
} }
} }
@ -152,9 +152,6 @@ func TestParseBondStanza(t *testing.T) {
if bond.configMethod != conf { if bond.configMethod != conf {
t.FailNow() t.FailNow()
} }
if !reflect.DeepEqual(bond.options["slaves"], options["bond-slaves"]) {
t.FailNow()
}
} }
func TestParsePhysicalStanza(t *testing.T) { func TestParsePhysicalStanza(t *testing.T) {

View File

@ -4,8 +4,11 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"os"
"os/exec" "os/exec"
"path" "path"
"strings"
"time"
"github.com/coreos/coreos-cloudinit/network" "github.com/coreos/coreos-cloudinit/network"
"github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink" "github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink"
@ -17,6 +20,13 @@ const (
func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) { func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
defer func() { defer func() {
if e := restartNetworkd(); e != nil {
err = e
return
}
// TODO(crawford): Get rid of this once networkd fixes the race
// https://bugs.freedesktop.org/show_bug.cgi?id=76077
time.Sleep(5 * time.Second)
if e := restartNetworkd(); e != nil { if e := restartNetworkd(); e != nil {
err = e err = e
} }
@ -26,10 +36,10 @@ func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
return return
} }
if err = probe8012q(); err != nil { if err = maybeProbe8012q(interfaces); err != nil {
return return
} }
return return maybeProbeBonding(interfaces)
} }
func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error { func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
@ -55,9 +65,25 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
return nil return nil
} }
func probe8012q() error { func maybeProbe8012q(interfaces []network.InterfaceGenerator) error {
for _, iface := range interfaces {
if iface.Type() == "vlan" {
return exec.Command("modprobe", "8021q").Run() return exec.Command("modprobe", "8021q").Run()
} }
}
return nil
}
func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
args := []string{"bonding"}
for _, iface := range interfaces {
if iface.Type() == "bond" {
args = append(args, strings.Split(iface.ModprobeParams(), " ")...)
break
}
}
return exec.Command("modprobe", args...).Run()
}
func restartNetworkd() error { func restartNetworkd() error {
_, err := RunUnitCommand("restart", "systemd-networkd.service") _, err := RunUnitCommand("restart", "systemd-networkd.service")
@ -86,6 +112,8 @@ func writeConfig(filename string, config string) error {
if config == "" { if config == "" {
return nil return nil
} }
if err := os.MkdirAll(path.Dir(filename), 0755); err != nil {
return err
}
return ioutil.WriteFile(filename, []byte(config), 0444) return ioutil.WriteFile(filename, []byte(config), 0444)
} }