From 159f4a2c7c650706bc6f2c0a49acea8fbd3ab7fe Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Wed, 11 Jun 2014 22:07:33 -0700 Subject: [PATCH 1/5] feat(network): Add support for blind interfaces It is valid for an interface to reference another, otherwise undeclared, interface (i.e. a bond enslaves eth0 without eth0 having its own iface stanza). In order to associate the two interfaces, the undeclared interface needs to be implicitly created so that it can be referenced by the other. This adds the capability to forward-declare interfaces in addition to cleaning up the process a little bit. --- network/interface.go | 157 ++++++++++++++++++++------------------ network/interface_test.go | 74 +++++++++++++++++- 2 files changed, 153 insertions(+), 78 deletions(-) diff --git a/network/interface.go b/network/interface.go index a139a67..81d3803 100644 --- a/network/interface.go +++ b/network/interface.go @@ -100,93 +100,98 @@ func (v *vlanInterface) Link() string { } func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { - bondStanzas := make(map[string]*stanzaInterface) - physicalStanzas := make(map[string]*stanzaInterface) - vlanStanzas := make(map[string]*stanzaInterface) + interfaceMap := make(map[string]InterfaceGenerator) + for _, iface := range stanzas { switch iface.kind { case interfaceBond: - bondStanzas[iface.name] = iface + interfaceMap[iface.name] = &bondInterface{ + logicalInterface{ + name: iface.name, + config: iface.configMethod, + children: []InterfaceGenerator{}, + }, + iface.options["slaves"], + } + for _, slave := range iface.options["slaves"] { + if _, ok := interfaceMap[slave]; !ok { + interfaceMap[slave] = &physicalInterface{ + logicalInterface{ + name: slave, + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + } + } + } + case interfacePhysical: - physicalStanzas[iface.name] = iface + if _, ok := iface.configMethod.(configMethodLoopback); ok { + continue + } + interfaceMap[iface.name] = &physicalInterface{ + logicalInterface{ + name: iface.name, + config: iface.configMethod, + children: []InterfaceGenerator{}, + }, + } + case interfaceVLAN: - vlanStanzas[iface.name] = iface - } - } - - physicals := make(map[string]*physicalInterface) - for _, p := range physicalStanzas { - if _, ok := p.configMethod.(configMethodLoopback); ok { - continue - } - physicals[p.name] = &physicalInterface{ - logicalInterface{ - name: p.name, - config: p.configMethod, - children: []InterfaceGenerator{}, - }, - } - } - - bonds := make(map[string]*bondInterface) - for _, b := range bondStanzas { - bonds[b.name] = &bondInterface{ - logicalInterface{ - name: b.name, - config: b.configMethod, - children: []InterfaceGenerator{}, - }, - b.options["slaves"], - } - } - - vlans := make(map[string]*vlanInterface) - for _, v := range vlanStanzas { - var rawDevice string - id, _ := strconv.Atoi(v.options["id"][0]) - if device := v.options["raw_device"]; len(device) == 1 { - rawDevice = device[0] - } - vlans[v.name] = &vlanInterface{ - logicalInterface{ - name: v.name, - config: v.configMethod, - children: []InterfaceGenerator{}, - }, - id, - rawDevice, - } - } - - for _, vlan := range vlans { - if physical, ok := physicals[vlan.rawDevice]; ok { - physical.children = append(physical.children, vlan) - } - if bond, ok := bonds[vlan.rawDevice]; ok { - bond.children = append(bond.children, vlan) - } - } - - for _, bond := range bonds { - for _, slave := range bond.slaves { - if physical, ok := physicals[slave]; ok { - physical.children = append(physical.children, bond) + var rawDevice string + id, _ := strconv.Atoi(iface.options["id"][0]) + if device := iface.options["raw_device"]; len(device) == 1 { + rawDevice = device[0] + if _, ok := interfaceMap[rawDevice]; !ok { + interfaceMap[rawDevice] = &physicalInterface{ + logicalInterface{ + name: rawDevice, + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + } + } } - if pBond, ok := bonds[slave]; ok { - pBond.children = append(pBond.children, bond) + interfaceMap[iface.name] = &vlanInterface{ + logicalInterface{ + name: iface.name, + config: iface.configMethod, + children: []InterfaceGenerator{}, + }, + id, + rawDevice, } } } - interfaces := make([]InterfaceGenerator, 0, len(physicals)+len(bonds)+len(vlans)) - for _, physical := range physicals { - interfaces = append(interfaces, physical) + for _, iface := range interfaceMap { + switch i := iface.(type) { + case *vlanInterface: + if parent, ok := interfaceMap[i.rawDevice]; ok { + switch p := parent.(type) { + case *physicalInterface: + p.children = append(p.children, iface) + case *bondInterface: + p.children = append(p.children, iface) + } + } + case *bondInterface: + for _, slave := range i.slaves { + if parent, ok := interfaceMap[slave]; ok { + switch p := parent.(type) { + case *physicalInterface: + p.children = append(p.children, iface) + case *bondInterface: + p.children = append(p.children, iface) + } + } + } + } } - for _, bond := range bonds { - interfaces = append(interfaces, bond) - } - for _, vlan := range vlans { - interfaces = append(interfaces, vlan) + + interfaces := make([]InterfaceGenerator, 0, len(interfaceMap)) + for _, iface := range interfaceMap { + interfaces = append(interfaces, iface) } return interfaces diff --git a/network/interface_test.go b/network/interface_test.go index 7ebe80e..b986656 100644 --- a/network/interface_test.go +++ b/network/interface_test.go @@ -224,6 +224,76 @@ func TestBuildInterfacesLo(t *testing.T) { } } +func TestBuildInterfacesBlindBond(t *testing.T) { + stanzas := []*stanzaInterface{ + { + name: "bond0", + kind: interfaceBond, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{ + "slaves": []string{"eth0"}, + }, + }, + } + interfaces := buildInterfaces(stanzas) + bond0 := &bondInterface{ + logicalInterface{ + name: "bond0", + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + []string{"eth0"}, + } + eth0 := &physicalInterface{ + logicalInterface{ + name: "eth0", + config: configMethodManual{}, + children: []InterfaceGenerator{bond0}, + }, + } + expect := []InterfaceGenerator{bond0, eth0} + if !reflect.DeepEqual(interfaces, expect) { + t.FailNow() + } +} + +func TestBuildInterfacesBlindVLAN(t *testing.T) { + stanzas := []*stanzaInterface{ + { + name: "vlan0", + kind: interfaceVLAN, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{ + "id": []string{"0"}, + "raw_device": []string{"eth0"}, + }, + }, + } + interfaces := buildInterfaces(stanzas) + vlan0 := &vlanInterface{ + logicalInterface{ + name: "vlan0", + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + 0, + "eth0", + } + eth0 := &physicalInterface{ + logicalInterface{ + name: "eth0", + config: configMethodManual{}, + children: []InterfaceGenerator{vlan0}, + }, + } + expect := []InterfaceGenerator{eth0, vlan0} + if !reflect.DeepEqual(interfaces, expect) { + t.FailNow() + } +} + func TestBuildInterfaces(t *testing.T) { stanzas := []*stanzaInterface{ &stanzaInterface{ @@ -303,7 +373,7 @@ func TestBuildInterfaces(t *testing.T) { logicalInterface{ name: "bond0", config: configMethodManual{}, - children: []InterfaceGenerator{vlan1, bond1}, + children: []InterfaceGenerator{bond1, vlan1}, }, []string{"eth0"}, } @@ -311,7 +381,7 @@ func TestBuildInterfaces(t *testing.T) { logicalInterface{ name: "eth0", config: configMethodManual{}, - children: []InterfaceGenerator{vlan0, bond0}, + children: []InterfaceGenerator{bond0, vlan0}, }, } expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1} From 16d7e8af48513db4e4154076243e434579a1fd47 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Wed, 11 Jun 2014 23:20:59 -0700 Subject: [PATCH 2/5] fix(network): Take down all interfaces properly The map of interfaces wasn't being populated correctly. Also, clean up some prints. --- network/stanza.go | 12 ++++++------ system/networkd.go | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/network/stanza.go b/network/stanza.go index dca59aa..a6db8c4 100644 --- a/network/stanza.go +++ b/network/stanza.go @@ -96,7 +96,7 @@ func splitStanzas(lines []string) ([][]string, error) { } else if curStanza != nil { curStanza = append(curStanza, line) } else { - return nil, fmt.Errorf("missing stanza start '%s'", line) + return nil, fmt.Errorf("missing stanza start %q", line) } } @@ -142,7 +142,7 @@ func parseStanza(rawStanza []string) (stanza, error) { case "iface": return parseInterfaceStanza(attributes, rawStanza[1:]) default: - return nil, fmt.Errorf("unknown stanza '%s'", kind) + return nil, fmt.Errorf("unknown stanza %q", kind) } } @@ -204,7 +204,7 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa } } if config.address.IP == nil || config.address.Mask == nil { - return nil, fmt.Errorf("malformed static network config for '%s'", iface) + return nil, fmt.Errorf("malformed static network config for %q", iface) } if gateways, ok := optionMap["gateway"]; ok { if len(gateways) == 1 { @@ -247,7 +247,7 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa case "dhcp": conf = configMethodDHCP{} default: - return nil, fmt.Errorf("invalid config method '%s'", confMethod) + return nil, fmt.Errorf("invalid config method %q", confMethod) } if _, ok := optionMap["vlan_raw_device"]; ok { @@ -282,11 +282,11 @@ func parseVLANStanza(iface string, conf configMethod, attributes []string, optio } else if strings.HasPrefix(iface, "vlan") { id = strings.TrimPrefix(iface, "vlan") } else { - return nil, fmt.Errorf("malformed vlan name %s", iface) + return nil, fmt.Errorf("malformed vlan name %q", iface) } if _, err := strconv.Atoi(id); err != nil { - return nil, fmt.Errorf("malformed vlan name %s", iface) + return nil, fmt.Errorf("malformed vlan name %q", iface) } options["id"] = []string{id} options["raw_device"] = options["vlan_raw_device"] diff --git a/system/networkd.go b/system/networkd.go index c795563..f49a15d 100644 --- a/system/networkd.go +++ b/system/networkd.go @@ -36,7 +36,9 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error { sysInterfaceMap := make(map[string]*net.Interface) if systemInterfaces, err := net.Interfaces(); err == nil { for _, iface := range systemInterfaces { - sysInterfaceMap[iface.Name] = &iface + // Need a copy of the interface so we can take the address + temp := iface + sysInterfaceMap[temp.Name] = &temp } } else { return err From 033c8d352ffcb4860e9d9f978f63cc3d73b0212d Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Wed, 11 Jun 2014 23:21:35 -0700 Subject: [PATCH 3/5] feat(network): Add support for hwaddress Currently only supports the ether mode of hwaddress. No immediate plans to support ax25, ARCnet, or netrom. --- network/interface.go | 30 ++++++++++++++----------- network/interface_test.go | 30 ++++++++++++++++--------- network/stanza.go | 31 ++++++++++++++++++++++++-- network/stanza_test.go | 47 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 110 insertions(+), 28 deletions(-) diff --git a/network/interface.go b/network/interface.go index 81d3803..070c4ea 100644 --- a/network/interface.go +++ b/network/interface.go @@ -48,6 +48,10 @@ func (i *logicalInterface) Network() string { return config } +func (i *logicalInterface) Link() string { + return "" +} + type physicalInterface struct { logicalInterface } @@ -60,10 +64,6 @@ func (p *physicalInterface) Netdev() string { return "" } -func (p *physicalInterface) Link() string { - return "" -} - type bondInterface struct { logicalInterface slaves []string @@ -77,10 +77,6 @@ func (b *bondInterface) Netdev() string { return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name) } -func (b *bondInterface) Link() string { - return "" -} - type vlanInterface struct { logicalInterface id int @@ -92,11 +88,19 @@ func (v *vlanInterface) Name() string { } func (v *vlanInterface) Netdev() string { - return fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n\n[VLAN]\nId=%d\n", v.name, v.id) -} - -func (v *vlanInterface) Link() string { - return "" + config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name) + switch c := v.config.(type) { + case configMethodStatic: + if c.hwaddress != nil { + config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress) + } + case configMethodDHCP: + if c.hwaddress != nil { + config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress) + } + } + config += fmt.Sprintf("\n[VLAN]\nId=%d\n", v.id) + return config } func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { diff --git a/network/interface_test.go b/network/interface_test.go index b986656..25b4c3b 100644 --- a/network/interface_test.go +++ b/network/interface_test.go @@ -143,16 +143,26 @@ func TestVLANInterfaceName(t *testing.T) { } func TestVLANInterfaceNetdev(t *testing.T) { - v := vlanInterface{logicalInterface{name: "testname"}, 1, ""} - netdev := `[NetDev] -Kind=vlan -Name=testname - -[VLAN] -Id=1 -` - if v.Netdev() != netdev { - t.FailNow() + for _, tt := range []struct { + i vlanInterface + l string + }{ + { + vlanInterface{logicalInterface{name: "testname"}, 1, ""}, + "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n", + }, + { + vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""}, + "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n", + }, + { + vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""}, + "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n", + }, + } { + if tt.i.Netdev() != tt.l { + t.Fatalf("bad netdev config (%q): got %q, want %q", tt.i, tt.i.Netdev(), tt.l) + } } } diff --git a/network/stanza.go b/network/stanza.go index a6db8c4..005231b 100644 --- a/network/stanza.go +++ b/network/stanza.go @@ -40,13 +40,16 @@ type configMethodStatic struct { address net.IPNet nameservers []net.IP routes []route + hwaddress net.HardwareAddr } type configMethodLoopback struct{} type configMethodManual struct{} -type configMethodDHCP struct{} +type configMethodDHCP struct { + hwaddress net.HardwareAddr +} func parseStanzas(lines []string) (stanzas []stanza, err error) { rawStanzas, err := splitStanzas(lines) @@ -217,6 +220,11 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa }) } } + if hwaddress, err := parseHwaddress(optionMap, iface); err == nil { + config.hwaddress = hwaddress + } else { + return nil, err + } for _, nameserver := range optionMap["dns-nameservers"] { config.nameservers = append(config.nameservers, net.ParseIP(nameserver)) } @@ -245,7 +253,13 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa case "manual": conf = configMethodManual{} case "dhcp": - conf = configMethodDHCP{} + config := configMethodDHCP{} + if hwaddress, err := parseHwaddress(optionMap, iface); err == nil { + config.hwaddress = hwaddress + } else { + return nil, err + } + conf = config default: return nil, fmt.Errorf("invalid config method %q", confMethod) } @@ -265,6 +279,19 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa return parsePhysicalStanza(iface, conf, attributes, optionMap) } +func parseHwaddress(options map[string][]string, iface string) (net.HardwareAddr, error) { + if hwaddress, ok := options["hwaddress"]; ok && len(hwaddress) == 2 { + switch hwaddress[0] { + case "ether": + if address, err := net.ParseMAC(hwaddress[1]); err == nil { + return address, nil + } + return nil, fmt.Errorf("malformed hwaddress option for %q", iface) + } + } + return nil, nil +} + 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 diff --git a/network/stanza_test.go b/network/stanza_test.go index bddd292..440dcf8 100644 --- a/network/stanza_test.go +++ b/network/stanza_test.go @@ -42,6 +42,8 @@ func TestBadParseInterfaceStanza(t *testing.T) { {[]string{"eth", "inet", "static"}, []string{"netmask 255.255.255.0"}, "malformed static network config"}, {[]string{"eth", "inet", "static"}, []string{"address invalid", "netmask 255.255.255.0"}, "malformed static network config"}, {[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask invalid"}, "malformed static network config"}, + {[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask 255.255.255.0", "hwaddress ether NotAnAddress"}, "malformed hwaddress option"}, + {[]string{"eth", "inet", "dhcp"}, []string{"hwaddress ether NotAnAddress"}, "malformed hwaddress option"}, } { _, err := parseInterfaceStanza(tt.in, tt.opts) if err == nil || !strings.HasPrefix(err.Error(), tt.e) { @@ -407,7 +409,46 @@ func TestParseInterfaceStanzaOptions(t *testing.T) { } } -func TestParseInterfaceStazaBond(t *testing.T) { +func TestParseInterfaceStanzaHwaddress(t *testing.T) { + for _, tt := range []struct { + attr []string + opt []string + hw net.HardwareAddr + }{ + { + []string{"mybond", "inet", "dhcp"}, + []string{}, + nil, + }, + { + []string{"mybond", "inet", "dhcp"}, + []string{"hwaddress ether 00:01:02:03:04:05"}, + net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}), + }, + { + []string{"mybond", "inet", "static"}, + []string{"hwaddress ether 00:01:02:03:04:05", "address 192.168.1.100", "netmask 255.255.255.0"}, + net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}), + }, + } { + iface, err := parseInterfaceStanza(tt.attr, tt.opt) + if err != nil { + t.Fatalf("error in parseInterfaceStanza (%q, %q): %q", tt.attr, tt.opt, err) + } + switch c := iface.configMethod.(type) { + case configMethodStatic: + if !reflect.DeepEqual(c.hwaddress, tt.hw) { + t.Fatalf("bad hwaddress (%q, %q): got %q, want %q", tt.attr, tt.opt, c.hwaddress, tt.hw) + } + case configMethodDHCP: + if !reflect.DeepEqual(c.hwaddress, tt.hw) { + t.Fatalf("bad hwaddress (%q, %q): got %q, want %q", tt.attr, tt.opt, c.hwaddress, tt.hw) + } + } + } +} + +func TestParseInterfaceStanzaBond(t *testing.T) { iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"}) if err != nil { t.FailNow() @@ -417,7 +458,7 @@ func TestParseInterfaceStazaBond(t *testing.T) { } } -func TestParseInterfaceStazaVLANName(t *testing.T) { +func TestParseInterfaceStanzaVLANName(t *testing.T) { iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil) if err != nil { t.FailNow() @@ -427,7 +468,7 @@ func TestParseInterfaceStazaVLANName(t *testing.T) { } } -func TestParseInterfaceStazaVLANOption(t *testing.T) { +func TestParseInterfaceStanzaVLANOption(t *testing.T) { iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"}) if err != nil { t.FailNow() From d6a0d0908cf954e0e2a93e61b2a6c07e67c1a931 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 12 Jun 2014 18:13:19 -0500 Subject: [PATCH 4/5] fix(network): Generate prefixes to ensure proper lexicographical ordering In order for networkd to properly configure the network interfaces, the configs must be prefixed to ensure that they load in the correct order (parent interfaces have a lower prefix than their children). --- network/interface.go | 77 ++++++++++++++++++++++++++++++------ network/interface_test.go | 83 +++++++++++++++++++++++++-------------- system/networkd.go | 6 +-- 3 files changed, 121 insertions(+), 45 deletions(-) diff --git a/network/interface.go b/network/interface.go index 070c4ea..be0b04b 100644 --- a/network/interface.go +++ b/network/interface.go @@ -7,15 +7,23 @@ import ( type InterfaceGenerator interface { Name() string + Filename() string Netdev() string Link() string Network() string } +type networkInterface interface { + InterfaceGenerator + Children() []networkInterface + setConfigDepth(int) +} + type logicalInterface struct { - name string - config configMethod - children []InterfaceGenerator + name string + config configMethod + children []networkInterface + configDepth int } func (i *logicalInterface) Network() string { @@ -52,6 +60,18 @@ func (i *logicalInterface) Link() string { return "" } +func (i *logicalInterface) Filename() string { + return fmt.Sprintf("%02x-%s", i.configDepth, i.name) +} + +func (i *logicalInterface) Children() []networkInterface { + return i.children +} + +func (i *logicalInterface) setConfigDepth(depth int) { + i.configDepth = depth +} + type physicalInterface struct { logicalInterface } @@ -104,8 +124,20 @@ func (v *vlanInterface) Netdev() string { } func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { - interfaceMap := make(map[string]InterfaceGenerator) + interfaceMap := createInterfaces(stanzas) + linkAncestors(interfaceMap) + markConfigDepths(interfaceMap) + interfaces := make([]InterfaceGenerator, 0, len(interfaceMap)) + for _, iface := range interfaceMap { + interfaces = append(interfaces, iface) + } + + return interfaces +} + +func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface { + interfaceMap := make(map[string]networkInterface) for _, iface := range stanzas { switch iface.kind { case interfaceBond: @@ -113,7 +145,7 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { logicalInterface{ name: iface.name, config: iface.configMethod, - children: []InterfaceGenerator{}, + children: []networkInterface{}, }, iface.options["slaves"], } @@ -123,7 +155,7 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { logicalInterface{ name: slave, config: configMethodManual{}, - children: []InterfaceGenerator{}, + children: []networkInterface{}, }, } } @@ -137,7 +169,7 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { logicalInterface{ name: iface.name, config: iface.configMethod, - children: []InterfaceGenerator{}, + children: []networkInterface{}, }, } @@ -151,7 +183,7 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { logicalInterface{ name: rawDevice, config: configMethodManual{}, - children: []InterfaceGenerator{}, + children: []networkInterface{}, }, } } @@ -160,14 +192,17 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { logicalInterface{ name: iface.name, config: iface.configMethod, - children: []InterfaceGenerator{}, + children: []networkInterface{}, }, id, rawDevice, } } } + return interfaceMap +} +func linkAncestors(interfaceMap map[string]networkInterface) { for _, iface := range interfaceMap { switch i := iface.(type) { case *vlanInterface: @@ -192,11 +227,27 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { } } } +} - interfaces := make([]InterfaceGenerator, 0, len(interfaceMap)) - for _, iface := range interfaceMap { - interfaces = append(interfaces, iface) +func markConfigDepths(interfaceMap map[string]networkInterface) { + rootInterfaceMap := make(map[string]networkInterface) + for k, v := range interfaceMap { + rootInterfaceMap[k] = v } - return interfaces + for _, iface := range interfaceMap { + for _, child := range iface.Children() { + delete(rootInterfaceMap, child.Name()) + } + } + for _, iface := range rootInterfaceMap { + setDepth(iface, 0) + } +} + +func setDepth(iface networkInterface, depth int) { + iface.setConfigDepth(depth) + for _, child := range iface.Children() { + setDepth(child, depth+1) + } } diff --git a/network/interface_test.go b/network/interface_test.go index 25b4c3b..c98bf60 100644 --- a/network/interface_test.go +++ b/network/interface_test.go @@ -30,7 +30,7 @@ func TestPhysicalInterfaceLink(t *testing.T) { func TestPhysicalInterfaceNetwork(t *testing.T) { p := physicalInterface{logicalInterface{ name: "testname", - children: []InterfaceGenerator{ + children: []networkInterface{ &bondInterface{ logicalInterface{ name: "testbond1", @@ -96,7 +96,7 @@ func TestBondInterfaceNetwork(t *testing.T) { logicalInterface{ name: "testname", config: configMethodDHCP{}, - children: []InterfaceGenerator{ + children: []networkInterface{ &bondInterface{ logicalInterface{ name: "testbond1", @@ -249,17 +249,19 @@ func TestBuildInterfacesBlindBond(t *testing.T) { interfaces := buildInterfaces(stanzas) bond0 := &bondInterface{ logicalInterface{ - name: "bond0", - config: configMethodManual{}, - children: []InterfaceGenerator{}, + name: "bond0", + config: configMethodManual{}, + children: []networkInterface{}, + configDepth: 1, }, []string{"eth0"}, } eth0 := &physicalInterface{ logicalInterface{ - name: "eth0", - config: configMethodManual{}, - children: []InterfaceGenerator{bond0}, + name: "eth0", + config: configMethodManual{}, + children: []networkInterface{bond0}, + configDepth: 0, }, } expect := []InterfaceGenerator{bond0, eth0} @@ -284,18 +286,20 @@ func TestBuildInterfacesBlindVLAN(t *testing.T) { interfaces := buildInterfaces(stanzas) vlan0 := &vlanInterface{ logicalInterface{ - name: "vlan0", - config: configMethodManual{}, - children: []InterfaceGenerator{}, + name: "vlan0", + config: configMethodManual{}, + children: []networkInterface{}, + configDepth: 1, }, 0, "eth0", } eth0 := &physicalInterface{ logicalInterface{ - name: "eth0", - config: configMethodManual{}, - children: []InterfaceGenerator{vlan0}, + name: "eth0", + config: configMethodManual{}, + children: []networkInterface{vlan0}, + configDepth: 0, }, } expect := []InterfaceGenerator{eth0, vlan0} @@ -355,43 +359,48 @@ func TestBuildInterfaces(t *testing.T) { interfaces := buildInterfaces(stanzas) vlan1 := &vlanInterface{ logicalInterface{ - name: "vlan1", - config: configMethodManual{}, - children: []InterfaceGenerator{}, + name: "vlan1", + config: configMethodManual{}, + children: []networkInterface{}, + configDepth: 2, }, 1, "bond0", } vlan0 := &vlanInterface{ logicalInterface{ - name: "vlan0", - config: configMethodManual{}, - children: []InterfaceGenerator{}, + name: "vlan0", + config: configMethodManual{}, + children: []networkInterface{}, + configDepth: 1, }, 0, "eth0", } bond1 := &bondInterface{ logicalInterface{ - name: "bond1", - config: configMethodManual{}, - children: []InterfaceGenerator{}, + name: "bond1", + config: configMethodManual{}, + children: []networkInterface{}, + configDepth: 2, }, []string{"bond0"}, } bond0 := &bondInterface{ logicalInterface{ - name: "bond0", - config: configMethodManual{}, - children: []InterfaceGenerator{bond1, vlan1}, + name: "bond0", + config: configMethodManual{}, + children: []networkInterface{bond1, vlan1}, + configDepth: 1, }, []string{"eth0"}, } eth0 := &physicalInterface{ logicalInterface{ - name: "eth0", - config: configMethodManual{}, - children: []InterfaceGenerator{bond0, vlan0}, + name: "eth0", + config: configMethodManual{}, + children: []networkInterface{bond0, vlan0}, + configDepth: 0, }, } expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1} @@ -399,3 +408,19 @@ func TestBuildInterfaces(t *testing.T) { t.FailNow() } } + +func TestFilename(t *testing.T) { + for _, tt := range []struct { + i logicalInterface + f string + }{ + {logicalInterface{name: "iface", configDepth: 0}, "00-iface"}, + {logicalInterface{name: "iface", configDepth: 9}, "09-iface"}, + {logicalInterface{name: "iface", configDepth: 10}, "0a-iface"}, + {logicalInterface{name: "iface", configDepth: 53}, "35-iface"}, + } { + if tt.i.Filename() != tt.f { + t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f) + } + } +} diff --git a/system/networkd.go b/system/networkd.go index f49a15d..a982869 100644 --- a/system/networkd.go +++ b/system/networkd.go @@ -66,15 +66,15 @@ func restartNetworkd() error { func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error { for _, iface := range interfaces { - filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Name())) + filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Filename())) if err := writeConfig(filename, iface.Netdev()); err != nil { return err } - filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Name())) + filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Filename())) if err := writeConfig(filename, iface.Link()); err != nil { return err } - filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Name())) + filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Filename())) if err := writeConfig(filename, iface.Network()); err != nil { return err } From f7c25a1b83a21819ea8a89c8534c98f5a2e0d470 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Fri, 20 Jun 2014 16:34:52 -0700 Subject: [PATCH 5/5] doc(debian-interfaces): Add basic docs for convert-netconf --- Documentation/debian-interfaces.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Documentation/debian-interfaces.md diff --git a/Documentation/debian-interfaces.md b/Documentation/debian-interfaces.md new file mode 100644 index 0000000..d3c64a1 --- /dev/null +++ b/Documentation/debian-interfaces.md @@ -0,0 +1,27 @@ +#Debian Interfaces# +**WARNING**: This option is EXPERIMENTAL and may change or be removed at any +point. +There is basic support for converting from a Debian network configuration to +networkd unit files. The -convert-netconf=debian option is used to activate +this feature. + +#convert-netconf# +Default: "" +Read the network config provided in cloud-drive and translate it from the +specified format into networkd unit files (requires the -from-configdrive +flag). Currently only supports "debian" which provides support for a small +subset of the [Debian network configuration] +(https://wiki.debian.org/NetworkConfiguration). These options include: + +- interface config methods + - static + - address/netmask + - gateway + - hwaddress + - dns-nameservers + - dhcp + - hwaddress + - manual + - loopback +- vlan_raw_device +- bond-slaves