From a4035cffeae497b72f2b51e0d794e2ff0ffaf2e5 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 May 2014 13:48:26 -0700 Subject: [PATCH 1/7] feat(config-drive): Add support for reading user-data from config-drive The -config-drive flag tells cloudinit to read the user-data from within the config-drive (./openstack/latest/user-data). --- coreos-cloudinit.go | 7 ++++++- datasource/configdrive.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 datasource/configdrive.go diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 2c75e75..a86630e 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -28,6 +28,9 @@ func main() { var file string flag.StringVar(&file, "from-file", "", "Read user-data from provided file") + var configdrive string + flag.StringVar(&configdrive, "from-configdrive", "", "Read user-data from provided cloud-drive directory") + var url string flag.StringVar(&url, "from-url", "", "Download user-data from provided url") @@ -52,10 +55,12 @@ func main() { ds = datasource.NewLocalFile(file) } else if url != "" { ds = datasource.NewMetadataService(url) + } else if configdrive != "" { + ds = datasource.NewConfigDrive(configdrive) } else if useProcCmdline { ds = datasource.NewProcCmdline() } else { - fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline") + fmt.Println("Provide one of --from-file, --from-configdrive, --from-url or --from-proc-cmdline") os.Exit(1) } diff --git a/datasource/configdrive.go b/datasource/configdrive.go new file mode 100644 index 0000000..7459471 --- /dev/null +++ b/datasource/configdrive.go @@ -0,0 +1,27 @@ +package datasource + +import ( + "io/ioutil" + "os" + "path" +) + +type configDrive struct { + path string +} + +func NewConfigDrive(path string) *configDrive { + return &configDrive{path} +} + +func (self *configDrive) Fetch() ([]byte, error) { + data, err := ioutil.ReadFile(path.Join(self.path, "openstack", "latest", "user_data")) + if os.IsNotExist(err) { + err = nil + } + return data, err +} + +func (self *configDrive) Type() string { + return "cloud-drive" +} From f8a823cf7e59dcf4fd85d66564eb1ba79b2115d5 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 May 2014 14:02:10 -0700 Subject: [PATCH 2/7] refactor(userdata): Move userdata processing into a function --- coreos-cloudinit.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index a86630e..6b44342 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -75,24 +75,26 @@ func main() { } } - if len(userdataBytes) == 0 { - log.Printf("No user data to handle, exiting.") - os.Exit(0) - } - env := initialize.NewEnvironment("/", workspace) + if len(userdataBytes) > 0 { + if err := processUserdata(string(userdataBytes), env); err != nil { + log.Fatalf("Failed resolving user-data: %v", err) + if !ignoreFailure { + os.Exit(1) + } + } + } else { + log.Printf("No user data to handle.") + } +} - userdata := string(userdataBytes) +func processUserdata(userdata string, env *initialize.Environment) error { userdata = env.Apply(userdata) parsed, err := initialize.ParseUserData(userdata) if err != nil { log.Printf("Failed parsing user-data: %v", err) - if ignoreFailure { - os.Exit(0) - } else { - os.Exit(1) - } + return err } err = initialize.PrepWorkspace(env.Workspace()) @@ -109,11 +111,9 @@ func main() { if err == nil { var name string name, err = system.ExecuteScript(path) - initialize.PersistUnitNameInWorkspace(name, workspace) + initialize.PersistUnitNameInWorkspace(name, env.Workspace()) } } - if err != nil { - log.Fatalf("Failed resolving user-data: %v", err) - } + return err } From 38321fedce7cbc5cd710fc6d459ed4101fe22b1c Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 May 2014 13:42:09 -0700 Subject: [PATCH 3/7] feat(interfaces): Add support for interfaces file This adds the ability for cloudinit to parse a debian interfaces file and generate the coresponding networkd configs. --- network/interface.go | 193 ++++++++++++++++++++++++++++ network/network.go | 86 +++++++++++++ network/stanza.go | 295 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 network/interface.go create mode 100644 network/network.go create mode 100644 network/stanza.go diff --git a/network/interface.go b/network/interface.go new file mode 100644 index 0000000..a139a67 --- /dev/null +++ b/network/interface.go @@ -0,0 +1,193 @@ +package network + +import ( + "fmt" + "strconv" +) + +type InterfaceGenerator interface { + Name() string + Netdev() string + Link() string + Network() string +} + +type logicalInterface struct { + name string + config configMethod + children []InterfaceGenerator +} + +func (i *logicalInterface) Network() string { + config := fmt.Sprintf("[Match]\nName=%s\n\n[Network]\n", i.name) + + for _, child := range i.children { + switch iface := child.(type) { + case *vlanInterface: + config += fmt.Sprintf("VLAN=%s\n", iface.name) + case *bondInterface: + config += fmt.Sprintf("Bond=%s\n", iface.name) + } + } + + switch conf := i.config.(type) { + case configMethodStatic: + for _, nameserver := range conf.nameservers { + config += fmt.Sprintf("DNS=%s\n", nameserver) + } + if conf.address.IP != nil { + config += fmt.Sprintf("\n[Address]\nAddress=%s\n", conf.address.String()) + } + for _, route := range conf.routes { + config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway) + } + case configMethodDHCP: + config += "DHCP=true\n" + } + + return config +} + +type physicalInterface struct { + logicalInterface +} + +func (p *physicalInterface) Name() string { + return p.name +} + +func (p *physicalInterface) Netdev() string { + return "" +} + +func (p *physicalInterface) Link() string { + return "" +} + +type bondInterface struct { + logicalInterface + slaves []string +} + +func (b *bondInterface) Name() string { + return b.name +} + +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 + rawDevice string +} + +func (v *vlanInterface) Name() string { + return v.name +} + +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 "" +} + +func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { + bondStanzas := make(map[string]*stanzaInterface) + physicalStanzas := make(map[string]*stanzaInterface) + vlanStanzas := make(map[string]*stanzaInterface) + for _, iface := range stanzas { + switch iface.kind { + case interfaceBond: + bondStanzas[iface.name] = iface + case interfacePhysical: + physicalStanzas[iface.name] = iface + 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) + } + if pBond, ok := bonds[slave]; ok { + pBond.children = append(pBond.children, bond) + } + } + } + + interfaces := make([]InterfaceGenerator, 0, len(physicals)+len(bonds)+len(vlans)) + for _, physical := range physicals { + interfaces = append(interfaces, physical) + } + for _, bond := range bonds { + interfaces = append(interfaces, bond) + } + for _, vlan := range vlans { + interfaces = append(interfaces, vlan) + } + + return interfaces +} diff --git a/network/network.go b/network/network.go new file mode 100644 index 0000000..2f23098 --- /dev/null +++ b/network/network.go @@ -0,0 +1,86 @@ +package network + +import ( + "fmt" + "io" + "os" + "path" + "strings" +) + +func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) { + lines := formatConfig(config) + stanzas, err := parseStanzas(lines) + if err != nil { + return nil, err + } + + interfaces := make([]*stanzaInterface, 0, len(stanzas)) + for _, stanza := range stanzas { + switch s := stanza.(type) { + case *stanzaInterface: + interfaces = append(interfaces, s) + } + } + + return buildInterfaces(interfaces), nil +} + +func WriteConfigs(configPath string, interfaces []InterfaceGenerator) error { + if err := os.MkdirAll(configPath, os.ModePerm+os.ModeDir); err != nil { + fmt.Println(err) + os.Exit(1) + } + + for _, iface := range interfaces { + filename := path.Join(configPath, fmt.Sprintf("%s.netdev", iface.Name())) + if err := writeConfig(filename, iface.GenerateNetdevConfig()); err != nil { + return err + } + filename = path.Join(configPath, fmt.Sprintf("%s.link", iface.Name())) + if err := writeConfig(filename, iface.GenerateLinkConfig()); err != nil { + return err + } + filename = path.Join(configPath, fmt.Sprintf("%s.network", iface.Name())) + if err := writeConfig(filename, iface.GenerateNetworkConfig()); err != nil { + return err + } + } + return nil +} + +func writeConfig(filename string, config string) error { + if config == "" { + return nil + } + + if file, err := os.Create(filename); err == nil { + io.WriteString(file, config) + file.Close() + return nil + } else { + return err + } +} + +func formatConfig(config string) []string { + lines := []string{} + config = strings.Replace(config, "\\\n", "", -1) + for config != "" { + split := strings.SplitN(config, "\n", 2) + line := strings.TrimSpace(split[0]) + + if len(split) == 2 { + config = split[1] + } else { + config = "" + } + + if strings.HasPrefix(line, "#") || line == "" { + continue + } + + lines = append(lines, line) + } + return lines +} diff --git a/network/stanza.go b/network/stanza.go new file mode 100644 index 0000000..dca59aa --- /dev/null +++ b/network/stanza.go @@ -0,0 +1,295 @@ +package network + +import ( + "fmt" + "net" + "strconv" + "strings" +) + +type stanza interface{} + +type stanzaAuto struct { + interfaces []string +} + +type stanzaInterface struct { + name string + kind interfaceKind + auto bool + configMethod configMethod + options map[string][]string +} + +type interfaceKind int + +const ( + interfaceBond = interfaceKind(iota) + interfacePhysical + interfaceVLAN +) + +type route struct { + destination net.IPNet + gateway net.IP +} + +type configMethod interface{} + +type configMethodStatic struct { + address net.IPNet + nameservers []net.IP + routes []route +} + +type configMethodLoopback struct{} + +type configMethodManual struct{} + +type configMethodDHCP struct{} + +func parseStanzas(lines []string) (stanzas []stanza, err error) { + rawStanzas, err := splitStanzas(lines) + if err != nil { + return nil, err + } + + stanzas = make([]stanza, 0, len(rawStanzas)) + for _, rawStanza := range rawStanzas { + if stanza, err := parseStanza(rawStanza); err == nil { + stanzas = append(stanzas, stanza) + } else { + return nil, err + } + } + + autos := make([]string, 0) + interfaceMap := make(map[string]*stanzaInterface) + for _, stanza := range stanzas { + switch c := stanza.(type) { + case *stanzaAuto: + autos = append(autos, c.interfaces...) + case *stanzaInterface: + interfaceMap[c.name] = c + } + } + + // Apply the auto attribute + for _, auto := range autos { + if iface, ok := interfaceMap[auto]; ok { + iface.auto = true + } + } + + return stanzas, nil +} + +func splitStanzas(lines []string) ([][]string, error) { + var curStanza []string + stanzas := make([][]string, 0) + for _, line := range lines { + if isStanzaStart(line) { + if curStanza != nil { + stanzas = append(stanzas, curStanza) + } + curStanza = []string{line} + } else if curStanza != nil { + curStanza = append(curStanza, line) + } else { + return nil, fmt.Errorf("missing stanza start '%s'", line) + } + } + + if curStanza != nil { + stanzas = append(stanzas, curStanza) + } + + return stanzas, nil +} + +func isStanzaStart(line string) bool { + switch strings.Split(line, " ")[0] { + case "auto": + fallthrough + case "iface": + fallthrough + case "mapping": + return true + } + + if strings.HasPrefix(line, "allow-") { + return true + } + + return false +} + +func parseStanza(rawStanza []string) (stanza, error) { + if len(rawStanza) == 0 { + panic("empty stanza") + } + tokens := strings.Fields(rawStanza[0]) + if len(tokens) < 2 { + return nil, fmt.Errorf("malformed stanza start %q", rawStanza[0]) + } + + kind := tokens[0] + attributes := tokens[1:] + + switch kind { + case "auto": + return parseAutoStanza(attributes, rawStanza[1:]) + case "iface": + return parseInterfaceStanza(attributes, rawStanza[1:]) + default: + return nil, fmt.Errorf("unknown stanza '%s'", kind) + } +} + +func parseAutoStanza(attributes []string, options []string) (*stanzaAuto, error) { + return &stanzaAuto{interfaces: attributes}, nil +} + +func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterface, error) { + if len(attributes) != 3 { + return nil, fmt.Errorf("incorrect number of attributes") + } + + iface := attributes[0] + confMethod := attributes[2] + + optionMap := make(map[string][]string, 0) + for _, option := range options { + if strings.HasPrefix(option, "post-up") { + tokens := strings.SplitAfterN(option, " ", 2) + if len(tokens) != 2 { + continue + } + if v, ok := optionMap["post-up"]; ok { + optionMap["post-up"] = append(v, tokens[1]) + } else { + optionMap["post-up"] = []string{tokens[1]} + } + } else if strings.HasPrefix(option, "pre-down") { + tokens := strings.SplitAfterN(option, " ", 2) + if len(tokens) != 2 { + continue + } + if v, ok := optionMap["pre-down"]; ok { + optionMap["pre-down"] = append(v, tokens[1]) + } else { + optionMap["pre-down"] = []string{tokens[1]} + } + } else { + tokens := strings.Fields(option) + optionMap[tokens[0]] = tokens[1:] + } + } + + var conf configMethod + switch confMethod { + case "static": + config := configMethodStatic{ + routes: make([]route, 0), + nameservers: make([]net.IP, 0), + } + if addresses, ok := optionMap["address"]; ok { + if len(addresses) == 1 { + config.address.IP = net.ParseIP(addresses[0]) + } + } + if netmasks, ok := optionMap["netmask"]; ok { + if len(netmasks) == 1 { + config.address.Mask = net.IPMask(net.ParseIP(netmasks[0]).To4()) + } + } + if config.address.IP == nil || config.address.Mask == nil { + return nil, fmt.Errorf("malformed static network config for '%s'", iface) + } + if gateways, ok := optionMap["gateway"]; ok { + if len(gateways) == 1 { + config.routes = append(config.routes, route{ + destination: net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.IPv4Mask(0, 0, 0, 0), + }, + gateway: net.ParseIP(gateways[0]), + }) + } + } + for _, nameserver := range optionMap["dns-nameservers"] { + config.nameservers = append(config.nameservers, net.ParseIP(nameserver)) + } + for _, postup := range optionMap["post-up"] { + if strings.HasPrefix(postup, "route add") { + route := route{} + fields := strings.Fields(postup) + for i, field := range fields[:len(fields)-1] { + switch field { + case "-net": + route.destination.IP = net.ParseIP(fields[i+1]) + case "netmask": + route.destination.Mask = net.IPMask(net.ParseIP(fields[i+1]).To4()) + case "gw": + route.gateway = net.ParseIP(fields[i+1]) + } + } + if route.destination.IP != nil && route.destination.Mask != nil && route.gateway != nil { + config.routes = append(config.routes, route) + } + } + } + conf = config + case "loopback": + conf = configMethodLoopback{} + case "manual": + conf = configMethodManual{} + case "dhcp": + conf = configMethodDHCP{} + default: + return nil, fmt.Errorf("invalid config method '%s'", confMethod) + } + + if _, ok := optionMap["vlan_raw_device"]; ok { + return parseVLANStanza(iface, conf, attributes, optionMap) + } + + if strings.Contains(iface, ".") { + return parseVLANStanza(iface, conf, attributes, optionMap) + } + + if _, ok := optionMap["bond-slaves"]; ok { + return parseBondStanza(iface, conf, attributes, optionMap) + } + + return parsePhysicalStanza(iface, conf, attributes, optionMap) +} + +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 +} + +func parsePhysicalStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) { + return &stanzaInterface{name: iface, kind: interfacePhysical, configMethod: conf, options: options}, nil +} + +func parseVLANStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) { + var id string + if strings.Contains(iface, ".") { + tokens := strings.Split(iface, ".") + id = tokens[len(tokens)-1] + } else if strings.HasPrefix(iface, "vlan") { + id = strings.TrimPrefix(iface, "vlan") + } else { + return nil, fmt.Errorf("malformed vlan name %s", iface) + } + + if _, err := strconv.Atoi(id); err != nil { + return nil, fmt.Errorf("malformed vlan name %s", iface) + } + options["id"] = []string{id} + options["raw_device"] = options["vlan_raw_device"] + + return &stanzaInterface{name: iface, kind: interfaceVLAN, configMethod: conf, options: options}, nil +} From 856061b445aa446aa2c3c72545926303f58354b2 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Fri, 23 May 2014 11:58:38 -0700 Subject: [PATCH 4/7] test(interfaces): Add tests for network conversion These tests should be an exhaustive set of tests for the parsing of Debian interface files and generation of equivilent networkd config files. --- network/interface_test.go | 321 ++++++++++++++++++++++++ network/network_test.go | 42 ++++ network/stanza_test.go | 502 ++++++++++++++++++++++++++++++++++++++ test | 2 +- 4 files changed, 866 insertions(+), 1 deletion(-) create mode 100644 network/interface_test.go create mode 100644 network/network_test.go create mode 100644 network/stanza_test.go diff --git a/network/interface_test.go b/network/interface_test.go new file mode 100644 index 0000000..7ebe80e --- /dev/null +++ b/network/interface_test.go @@ -0,0 +1,321 @@ +package network + +import ( + "net" + "reflect" + "testing" +) + +func TestPhysicalInterfaceName(t *testing.T) { + p := physicalInterface{logicalInterface{name: "testname"}} + if p.Name() != "testname" { + t.FailNow() + } +} + +func TestPhysicalInterfaceNetdev(t *testing.T) { + p := physicalInterface{} + if p.Netdev() != "" { + t.FailNow() + } +} + +func TestPhysicalInterfaceLink(t *testing.T) { + p := physicalInterface{} + if p.Link() != "" { + t.FailNow() + } +} + +func TestPhysicalInterfaceNetwork(t *testing.T) { + p := physicalInterface{logicalInterface{ + name: "testname", + children: []InterfaceGenerator{ + &bondInterface{ + logicalInterface{ + name: "testbond1", + }, + nil, + }, + &vlanInterface{ + logicalInterface{ + name: "testvlan1", + }, + 1, + "", + }, + &vlanInterface{ + logicalInterface{ + name: "testvlan2", + }, + 1, + "", + }, + }, + }} + network := `[Match] +Name=testname + +[Network] +Bond=testbond1 +VLAN=testvlan1 +VLAN=testvlan2 +` + if p.Network() != network { + t.FailNow() + } +} + +func TestBondInterfaceName(t *testing.T) { + b := bondInterface{logicalInterface{name: "testname"}, nil} + if b.Name() != "testname" { + t.FailNow() + } +} + +func TestBondInterfaceNetdev(t *testing.T) { + b := bondInterface{logicalInterface{name: "testname"}, nil} + netdev := `[NetDev] +Kind=bond +Name=testname +` + if b.Netdev() != netdev { + t.FailNow() + } +} + +func TestBondInterfaceLink(t *testing.T) { + b := bondInterface{} + if b.Link() != "" { + t.FailNow() + } +} + +func TestBondInterfaceNetwork(t *testing.T) { + b := bondInterface{ + logicalInterface{ + name: "testname", + config: configMethodDHCP{}, + children: []InterfaceGenerator{ + &bondInterface{ + logicalInterface{ + name: "testbond1", + }, + nil, + }, + &vlanInterface{ + logicalInterface{ + name: "testvlan1", + }, + 1, + "", + }, + &vlanInterface{ + logicalInterface{ + name: "testvlan2", + }, + 1, + "", + }, + }, + }, + nil, + } + network := `[Match] +Name=testname + +[Network] +Bond=testbond1 +VLAN=testvlan1 +VLAN=testvlan2 +DHCP=true +` + if b.Network() != network { + t.FailNow() + } +} + +func TestVLANInterfaceName(t *testing.T) { + v := vlanInterface{logicalInterface{name: "testname"}, 1, ""} + if v.Name() != "testname" { + t.FailNow() + } +} + +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() + } +} + +func TestVLANInterfaceLink(t *testing.T) { + v := vlanInterface{} + if v.Link() != "" { + t.FailNow() + } +} + +func TestVLANInterfaceNetwork(t *testing.T) { + v := vlanInterface{ + logicalInterface{ + name: "testname", + config: configMethodStatic{ + address: net.IPNet{ + IP: []byte{192, 168, 1, 100}, + Mask: []byte{255, 255, 255, 0}, + }, + nameservers: []net.IP{ + []byte{8, 8, 8, 8}, + }, + routes: []route{ + route{ + destination: net.IPNet{ + IP: []byte{0, 0, 0, 0}, + Mask: []byte{0, 0, 0, 0}, + }, + gateway: []byte{1, 2, 3, 4}, + }, + }, + }, + }, + 0, + "", + } + network := `[Match] +Name=testname + +[Network] +DNS=8.8.8.8 + +[Address] +Address=192.168.1.100/24 + +[Route] +Destination=0.0.0.0/0 +Gateway=1.2.3.4 +` + if v.Network() != network { + t.Log(v.Network()) + t.FailNow() + } +} + +func TestBuildInterfacesLo(t *testing.T) { + stanzas := []*stanzaInterface{ + &stanzaInterface{ + name: "lo", + kind: interfacePhysical, + auto: false, + configMethod: configMethodLoopback{}, + options: map[string][]string{}, + }, + } + interfaces := buildInterfaces(stanzas) + if len(interfaces) != 0 { + t.FailNow() + } +} + +func TestBuildInterfaces(t *testing.T) { + stanzas := []*stanzaInterface{ + &stanzaInterface{ + name: "eth0", + kind: interfacePhysical, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{}, + }, + &stanzaInterface{ + name: "bond0", + kind: interfaceBond, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{ + "slaves": []string{"eth0"}, + }, + }, + &stanzaInterface{ + name: "bond1", + kind: interfaceBond, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{ + "slaves": []string{"bond0"}, + }, + }, + &stanzaInterface{ + name: "vlan0", + kind: interfaceVLAN, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{ + "id": []string{"0"}, + "raw_device": []string{"eth0"}, + }, + }, + &stanzaInterface{ + name: "vlan1", + kind: interfaceVLAN, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{ + "id": []string{"1"}, + "raw_device": []string{"bond0"}, + }, + }, + } + interfaces := buildInterfaces(stanzas) + vlan1 := &vlanInterface{ + logicalInterface{ + name: "vlan1", + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + 1, + "bond0", + } + vlan0 := &vlanInterface{ + logicalInterface{ + name: "vlan0", + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + 0, + "eth0", + } + bond1 := &bondInterface{ + logicalInterface{ + name: "bond1", + config: configMethodManual{}, + children: []InterfaceGenerator{}, + }, + []string{"bond0"}, + } + bond0 := &bondInterface{ + logicalInterface{ + name: "bond0", + config: configMethodManual{}, + children: []InterfaceGenerator{vlan1, bond1}, + }, + []string{"eth0"}, + } + eth0 := &physicalInterface{ + logicalInterface{ + name: "eth0", + config: configMethodManual{}, + children: []InterfaceGenerator{vlan0, bond0}, + }, + } + expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1} + if !reflect.DeepEqual(interfaces, expect) { + t.FailNow() + } +} diff --git a/network/network_test.go b/network/network_test.go new file mode 100644 index 0000000..558710d --- /dev/null +++ b/network/network_test.go @@ -0,0 +1,42 @@ +package network + +import ( + "testing" +) + +func TestFormatConfigs(t *testing.T) { + for in, n := range map[string]int{ + "": 0, + "line1\\\nis long": 1, + "#comment": 0, + "#comment\\\ncomment": 0, + " #comment \\\n comment\nline 1\nline 2\\\n is long": 2, + } { + lines := formatConfig(in) + if len(lines) != n { + t.Fatalf("bad number of lines for config %q: got %d, want %d", in, len(lines), n) + } + } +} + +func TestProcessDebianNetconf(t *testing.T) { + for _, tt := range []struct { + in string + fail bool + n int + }{ + {"", false, 0}, + {"iface", true, -1}, + {"auto eth1\nauto eth2", false, 0}, + {"iface eth1 inet manual", false, 1}, + } { + interfaces, err := ProcessDebianNetconf(tt.in) + failed := err != nil + if tt.fail != failed { + t.Fatalf("bad failure state for %q: got %b, want %b", failed, tt.fail) + } + if tt.n != -1 && tt.n != len(interfaces) { + t.Fatalf("bad number of interfaces for %q: got %d, want %q", tt.in, len(interfaces), tt.n) + } + } +} diff --git a/network/stanza_test.go b/network/stanza_test.go new file mode 100644 index 0000000..bddd292 --- /dev/null +++ b/network/stanza_test.go @@ -0,0 +1,502 @@ +package network + +import ( + "net" + "reflect" + "strings" + "testing" +) + +func TestSplitStanzasNoParent(t *testing.T) { + in := []string{"test"} + e := "missing stanza start" + _, err := splitStanzas(in) + if err == nil || !strings.HasPrefix(err.Error(), e) { + t.Fatalf("bad error for splitStanzas(%q): got %q, want %q", in, err, e) + } +} + +func TestBadParseStanzas(t *testing.T) { + for in, e := range map[string]string{ + "": "missing stanza start", + "iface": "malformed stanza start", + "allow-?? unknown": "unknown stanza", + } { + _, err := parseStanzas([]string{in}) + if err == nil || !strings.HasPrefix(err.Error(), e) { + t.Fatalf("bad error for parseStanzas(%q): got %q, want %q", in, err, e) + } + + } +} + +func TestBadParseInterfaceStanza(t *testing.T) { + for _, tt := range []struct { + in []string + opts []string + e string + }{ + {[]string{}, nil, "incorrect number of attributes"}, + {[]string{"eth", "inet", "invalid"}, nil, "invalid config method"}, + {[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100"}, "malformed static network config"}, + {[]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"}, + } { + _, err := parseInterfaceStanza(tt.in, tt.opts) + if err == nil || !strings.HasPrefix(err.Error(), tt.e) { + t.Fatalf("bad error parsing interface stanza %q: got %q, want %q", tt.in, err.Error(), tt.e) + } + } +} + +func TestBadParseVLANStanzas(t *testing.T) { + conf := configMethodManual{} + options := map[string][]string{} + for _, in := range []string{"myvlan", "eth.vlan"} { + _, err := parseVLANStanza(in, conf, nil, options) + if err == nil || !strings.HasPrefix(err.Error(), "malformed vlan name") { + t.Fatalf("did not error on bad vlan %q", in) + } + } +} + +func TestSplitStanzas(t *testing.T) { + expect := [][]string{ + {"auto lo"}, + {"iface eth1", "option: 1"}, + {"mapping"}, + {"allow-"}, + } + lines := make([]string, 0, 5) + for _, stanza := range expect { + for _, line := range stanza { + lines = append(lines, line) + } + } + + stanzas, err := splitStanzas(lines) + if err != nil { + t.FailNow() + } + for i, stanza := range stanzas { + if len(stanza) != len(expect[i]) { + t.FailNow() + } + for j, line := range stanza { + if line != expect[i][j] { + t.FailNow() + } + } + } +} + +func TestParseStanzaNil(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("parseStanza(nil) did not panic") + } + }() + parseStanza(nil) +} + +func TestParseStanzaSuccess(t *testing.T) { + for _, in := range []string{ + "auto a", + "iface a inet manual", + } { + if _, err := parseStanza([]string{in}); err != nil { + t.Fatalf("unexpected error parsing stanza %q: %s", in, err) + } + } +} + +func TestParseAutoStanza(t *testing.T) { + interfaces := []string{"test", "attribute"} + stanza, err := parseAutoStanza(interfaces, nil) + if err != nil { + t.Fatalf("unexpected error parsing auto stanza %q: %s", interfaces, err) + } + if !reflect.DeepEqual(stanza.interfaces, interfaces) { + t.FailNow() + } +} + +func TestParseBondStanzaNoSlaves(t *testing.T) { + bond, err := parseBondStanza("", nil, nil, map[string][]string{}) + if err != nil { + t.FailNow() + } + if bond.options["slaves"] != nil { + t.FailNow() + } +} + +func TestParseBondStanza(t *testing.T) { + conf := configMethodManual{} + options := map[string][]string{ + "bond-slaves": []string{"1", "2"}, + } + bond, err := parseBondStanza("test", conf, nil, options) + if err != nil { + t.FailNow() + } + if bond.name != "test" { + t.FailNow() + } + if bond.kind != interfaceBond { + t.FailNow() + } + if bond.configMethod != conf { + t.FailNow() + } + if !reflect.DeepEqual(bond.options["slaves"], options["bond-slaves"]) { + t.FailNow() + } +} + +func TestParsePhysicalStanza(t *testing.T) { + conf := configMethodManual{} + options := map[string][]string{ + "a": []string{"1", "2"}, + "b": []string{"1"}, + } + physical, err := parsePhysicalStanza("test", conf, nil, options) + if err != nil { + t.FailNow() + } + if physical.name != "test" { + t.FailNow() + } + if physical.kind != interfacePhysical { + t.FailNow() + } + if physical.configMethod != conf { + t.FailNow() + } + if !reflect.DeepEqual(physical.options, options) { + t.FailNow() + } +} + +func TestParseVLANStanzas(t *testing.T) { + conf := configMethodManual{} + options := map[string][]string{} + for _, in := range []string{"vlan25", "eth.25"} { + vlan, err := parseVLANStanza(in, conf, nil, options) + if err != nil { + t.Fatalf("unexpected error from parseVLANStanza(%q): %s", in, err) + } + if !reflect.DeepEqual(vlan.options["id"], []string{"25"}) { + t.FailNow() + } + } +} + +func TestParseInterfaceStanzaStaticAddress(t *testing.T) { + options := []string{"address 192.168.1.100", "netmask 255.255.255.0"} + expect := net.IPNet{ + IP: net.IPv4(192, 168, 1, 100), + Mask: net.IPv4Mask(255, 255, 255, 0), + } + + iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) + if err != nil { + t.FailNow() + } + static, ok := iface.configMethod.(configMethodStatic) + if !ok { + t.FailNow() + } + if !reflect.DeepEqual(static.address, expect) { + t.FailNow() + } +} + +func TestParseInterfaceStanzaStaticGateway(t *testing.T) { + options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "gateway 192.168.1.1"} + expect := []route{ + { + destination: net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.IPv4Mask(0, 0, 0, 0), + }, + gateway: net.IPv4(192, 168, 1, 1), + }, + } + + iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) + if err != nil { + t.FailNow() + } + static, ok := iface.configMethod.(configMethodStatic) + if !ok { + t.FailNow() + } + if !reflect.DeepEqual(static.routes, expect) { + t.FailNow() + } +} + +func TestParseInterfaceStanzaStaticDNS(t *testing.T) { + options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "dns-nameservers 192.168.1.10 192.168.1.11 192.168.1.12"} + expect := []net.IP{ + net.IPv4(192, 168, 1, 10), + net.IPv4(192, 168, 1, 11), + net.IPv4(192, 168, 1, 12), + } + iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) + if err != nil { + t.FailNow() + } + static, ok := iface.configMethod.(configMethodStatic) + if !ok { + t.FailNow() + } + if !reflect.DeepEqual(static.nameservers, expect) { + t.FailNow() + } +} + +func TestBadParseInterfaceStanzasStaticPostUp(t *testing.T) { + for _, in := range []string{ + "post-up invalid", + "post-up route add", + "post-up route add -net", + "post-up route add gw", + "post-up route add netmask", + "gateway", + "gateway 192.168.1.1 192.168.1.2", + } { + options := []string{"address 192.168.1.100", "netmask 255.255.255.0", in} + iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) + if err != nil { + t.Fatalf("parseInterfaceStanza with options %s got unexpected error", options) + } + static, ok := iface.configMethod.(configMethodStatic) + if !ok { + t.Fatalf("parseInterfaceStanza with options %s did not return configMethodStatic", options) + } + if len(static.routes) != 0 { + t.Fatalf("parseInterfaceStanza with options %s did not return zero-length static routes", options) + } + } +} + +func TestParseInterfaceStanzaStaticPostUp(t *testing.T) { + options := []string{ + "address 192.168.1.100", + "netmask 255.255.255.0", + "post-up route add gw 192.168.1.1 -net 192.168.1.0 netmask 255.255.255.0", + } + expect := []route{ + { + destination: net.IPNet{ + IP: net.IPv4(192, 168, 1, 0), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + gateway: net.IPv4(192, 168, 1, 1), + }, + } + + iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) + if err != nil { + t.FailNow() + } + static, ok := iface.configMethod.(configMethodStatic) + if !ok { + t.FailNow() + } + if !reflect.DeepEqual(static.routes, expect) { + t.FailNow() + } +} + +func TestParseInterfaceStanzaLoopback(t *testing.T) { + iface, err := parseInterfaceStanza([]string{"eth", "inet", "loopback"}, nil) + if err != nil { + t.FailNow() + } + if _, ok := iface.configMethod.(configMethodLoopback); !ok { + t.FailNow() + } +} + +func TestParseInterfaceStanzaManual(t *testing.T) { + iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, nil) + if err != nil { + t.FailNow() + } + if _, ok := iface.configMethod.(configMethodManual); !ok { + t.FailNow() + } +} + +func TestParseInterfaceStanzaDHCP(t *testing.T) { + iface, err := parseInterfaceStanza([]string{"eth", "inet", "dhcp"}, nil) + if err != nil { + t.FailNow() + } + if _, ok := iface.configMethod.(configMethodDHCP); !ok { + t.FailNow() + } +} + +func TestParseInterfaceStanzaPostUpOption(t *testing.T) { + options := []string{ + "post-up", + "post-up 1 2", + "post-up 3 4", + } + iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options) + if err != nil { + t.FailNow() + } + if !reflect.DeepEqual(iface.options["post-up"], []string{"1 2", "3 4"}) { + t.Log(iface.options["post-up"]) + t.FailNow() + } +} + +func TestParseInterfaceStanzaPreDownOption(t *testing.T) { + options := []string{ + "pre-down", + "pre-down 3", + "pre-down 4", + } + iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options) + if err != nil { + t.FailNow() + } + if !reflect.DeepEqual(iface.options["pre-down"], []string{"3", "4"}) { + t.Log(iface.options["pre-down"]) + t.FailNow() + } +} + +func TestParseInterfaceStanzaEmptyOption(t *testing.T) { + options := []string{ + "test", + } + iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options) + if err != nil { + t.FailNow() + } + if !reflect.DeepEqual(iface.options["test"], []string{}) { + t.FailNow() + } +} + +func TestParseInterfaceStanzaOptions(t *testing.T) { + options := []string{ + "test1 1", + "test2 2 3", + "test1 5 6", + } + iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options) + if err != nil { + t.FailNow() + } + if !reflect.DeepEqual(iface.options["test1"], []string{"5", "6"}) { + t.Log(iface.options["test1"]) + t.FailNow() + } + if !reflect.DeepEqual(iface.options["test2"], []string{"2", "3"}) { + t.Log(iface.options["test2"]) + t.FailNow() + } +} + +func TestParseInterfaceStazaBond(t *testing.T) { + iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"}) + if err != nil { + t.FailNow() + } + if iface.kind != interfaceBond { + t.FailNow() + } +} + +func TestParseInterfaceStazaVLANName(t *testing.T) { + iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil) + if err != nil { + t.FailNow() + } + if iface.kind != interfaceVLAN { + t.FailNow() + } +} + +func TestParseInterfaceStazaVLANOption(t *testing.T) { + iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"}) + if err != nil { + t.FailNow() + } + if iface.kind != interfaceVLAN { + t.FailNow() + } +} + +func TestParseStanzasNone(t *testing.T) { + stanzas, err := parseStanzas(nil) + if err != nil { + t.FailNow() + } + if len(stanzas) != 0 { + t.FailNow() + } +} + +func TestParseStanzas(t *testing.T) { + lines := []string{ + "auto lo", + "iface lo inet loopback", + "iface eth1 inet manual", + "iface eth2 inet manual", + "iface eth3 inet manual", + "auto eth1 eth3", + } + expect := []stanza{ + &stanzaAuto{ + interfaces: []string{"lo"}, + }, + &stanzaInterface{ + name: "lo", + kind: interfacePhysical, + auto: true, + configMethod: configMethodLoopback{}, + options: map[string][]string{}, + }, + &stanzaInterface{ + name: "eth1", + kind: interfacePhysical, + auto: true, + configMethod: configMethodManual{}, + options: map[string][]string{}, + }, + &stanzaInterface{ + name: "eth2", + kind: interfacePhysical, + auto: false, + configMethod: configMethodManual{}, + options: map[string][]string{}, + }, + &stanzaInterface{ + name: "eth3", + kind: interfacePhysical, + auto: true, + configMethod: configMethodManual{}, + options: map[string][]string{}, + }, + &stanzaAuto{ + interfaces: []string{"eth1", "eth3"}, + }, + } + stanzas, err := parseStanzas(lines) + if err != err { + t.FailNow() + } + if !reflect.DeepEqual(stanzas, expect) { + t.FailNow() + } +} diff --git a/test b/test index 801c9f8..24e556b 100755 --- a/test +++ b/test @@ -13,7 +13,7 @@ COVER=${COVER:-"-cover"} source ./build -declare -a TESTPKGS=(initialize system datasource pkg) +declare -a TESTPKGS=(initialize system datasource pkg network) if [ -z "$PKG" ]; then GOFMTPATH="$TESTPKGS coreos-cloudinit.go" From 79a40a38d819238bcf3b2f0dabc0bff98c1fe361 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 May 2014 17:22:30 -0700 Subject: [PATCH 5/7] add(netlink): import dotcloud/docker/pkg/netlink --- .../dotcloud/docker/pkg/netlink/MAINTAINERS | 2 + .../dotcloud/docker/pkg/netlink/netlink.go | 23 + .../docker/pkg/netlink/netlink_linux.go | 891 ++++++++++++++++++ .../docker/pkg/netlink/netlink_unsupported.go | 69 ++ 4 files changed, 985 insertions(+) create mode 100644 third_party/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS create mode 100644 third_party/github.com/dotcloud/docker/pkg/netlink/netlink.go create mode 100644 third_party/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go create mode 100644 third_party/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go diff --git a/third_party/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS b/third_party/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS new file mode 100644 index 0000000..1cb5513 --- /dev/null +++ b/third_party/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Guillaume J. Charmes (@creack) diff --git a/third_party/github.com/dotcloud/docker/pkg/netlink/netlink.go b/third_party/github.com/dotcloud/docker/pkg/netlink/netlink.go new file mode 100644 index 0000000..5cc7562 --- /dev/null +++ b/third_party/github.com/dotcloud/docker/pkg/netlink/netlink.go @@ -0,0 +1,23 @@ +// Packet netlink provide access to low level Netlink sockets and messages. +// +// Actual implementations are in: +// netlink_linux.go +// netlink_darwin.go +package netlink + +import ( + "errors" + "net" +) + +var ( + ErrWrongSockType = errors.New("Wrong socket type") + ErrShortResponse = errors.New("Got short response from netlink") +) + +// A Route is a subnet associated with the interface to reach it. +type Route struct { + *net.IPNet + Iface *net.Interface + Default bool +} diff --git a/third_party/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go b/third_party/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go new file mode 100644 index 0000000..6de293d --- /dev/null +++ b/third_party/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go @@ -0,0 +1,891 @@ +// +build amd64 + +package netlink + +import ( + "encoding/binary" + "fmt" + "math/rand" + "net" + "syscall" + "unsafe" +) + +const ( + IFNAMSIZ = 16 + DEFAULT_CHANGE = 0xFFFFFFFF + IFLA_INFO_KIND = 1 + IFLA_INFO_DATA = 2 + VETH_INFO_PEER = 1 + IFLA_NET_NS_FD = 28 + SIOC_BRADDBR = 0x89a0 + SIOC_BRADDIF = 0x89a2 +) + +var nextSeqNr int + +type ifreqHwaddr struct { + IfrnName [16]byte + IfruHwaddr syscall.RawSockaddr +} + +type ifreqIndex struct { + IfrnName [16]byte + IfruIndex int32 +} + +func nativeEndian() binary.ByteOrder { + var x uint32 = 0x01020304 + if *(*byte)(unsafe.Pointer(&x)) == 0x01 { + return binary.BigEndian + } + return binary.LittleEndian +} + +func getSeq() int { + nextSeqNr = nextSeqNr + 1 + return nextSeqNr +} + +func getIpFamily(ip net.IP) int { + if len(ip) <= net.IPv4len { + return syscall.AF_INET + } + if ip.To4() != nil { + return syscall.AF_INET + } + return syscall.AF_INET6 +} + +type NetlinkRequestData interface { + Len() int + ToWireFormat() []byte +} + +type IfInfomsg struct { + syscall.IfInfomsg +} + +func newIfInfomsg(family int) *IfInfomsg { + return &IfInfomsg{ + IfInfomsg: syscall.IfInfomsg{ + Family: uint8(family), + }, + } +} + +func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg { + msg := newIfInfomsg(family) + parent.children = append(parent.children, msg) + return msg +} + +func (msg *IfInfomsg) ToWireFormat() []byte { + native := nativeEndian() + + length := syscall.SizeofIfInfomsg + b := make([]byte, length) + b[0] = msg.Family + b[1] = 0 + native.PutUint16(b[2:4], msg.Type) + native.PutUint32(b[4:8], uint32(msg.Index)) + native.PutUint32(b[8:12], msg.Flags) + native.PutUint32(b[12:16], msg.Change) + return b +} + +func (msg *IfInfomsg) Len() int { + return syscall.SizeofIfInfomsg +} + +type IfAddrmsg struct { + syscall.IfAddrmsg +} + +func newIfAddrmsg(family int) *IfAddrmsg { + return &IfAddrmsg{ + IfAddrmsg: syscall.IfAddrmsg{ + Family: uint8(family), + }, + } +} + +func (msg *IfAddrmsg) ToWireFormat() []byte { + native := nativeEndian() + + length := syscall.SizeofIfAddrmsg + b := make([]byte, length) + b[0] = msg.Family + b[1] = msg.Prefixlen + b[2] = msg.Flags + b[3] = msg.Scope + native.PutUint32(b[4:8], msg.Index) + return b +} + +func (msg *IfAddrmsg) Len() int { + return syscall.SizeofIfAddrmsg +} + +type RtMsg struct { + syscall.RtMsg +} + +func newRtMsg(family int) *RtMsg { + return &RtMsg{ + RtMsg: syscall.RtMsg{ + Family: uint8(family), + Table: syscall.RT_TABLE_MAIN, + Scope: syscall.RT_SCOPE_UNIVERSE, + Protocol: syscall.RTPROT_BOOT, + Type: syscall.RTN_UNICAST, + }, + } +} + +func (msg *RtMsg) ToWireFormat() []byte { + native := nativeEndian() + + length := syscall.SizeofRtMsg + b := make([]byte, length) + b[0] = msg.Family + b[1] = msg.Dst_len + b[2] = msg.Src_len + b[3] = msg.Tos + b[4] = msg.Table + b[5] = msg.Protocol + b[6] = msg.Scope + b[7] = msg.Type + native.PutUint32(b[8:12], msg.Flags) + return b +} + +func (msg *RtMsg) Len() int { + return syscall.SizeofRtMsg +} + +func rtaAlignOf(attrlen int) int { + return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1) +} + +type RtAttr struct { + syscall.RtAttr + Data []byte + children []NetlinkRequestData +} + +func newRtAttr(attrType int, data []byte) *RtAttr { + return &RtAttr{ + RtAttr: syscall.RtAttr{ + Type: uint16(attrType), + }, + children: []NetlinkRequestData{}, + Data: data, + } +} + +func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr { + attr := newRtAttr(attrType, data) + parent.children = append(parent.children, attr) + return attr +} + +func (a *RtAttr) Len() int { + l := 0 + for _, child := range a.children { + l += child.Len() + syscall.SizeofRtAttr + } + if l == 0 { + l++ + } + return rtaAlignOf(l + len(a.Data)) +} + +func (a *RtAttr) ToWireFormat() []byte { + native := nativeEndian() + + length := a.Len() + buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr)) + + if a.Data != nil { + copy(buf[4:], a.Data) + } else { + next := 4 + for _, child := range a.children { + childBuf := child.ToWireFormat() + copy(buf[next:], childBuf) + next += rtaAlignOf(len(childBuf)) + } + } + + if l := uint16(rtaAlignOf(length)); l != 0 { + native.PutUint16(buf[0:2], l+1) + } + native.PutUint16(buf[2:4], a.Type) + + return buf +} + +type NetlinkRequest struct { + syscall.NlMsghdr + Data []NetlinkRequestData +} + +func (rr *NetlinkRequest) ToWireFormat() []byte { + native := nativeEndian() + + length := rr.Len + dataBytes := make([][]byte, len(rr.Data)) + for i, data := range rr.Data { + dataBytes[i] = data.ToWireFormat() + length += uint32(len(dataBytes[i])) + } + b := make([]byte, length) + native.PutUint32(b[0:4], length) + native.PutUint16(b[4:6], rr.Type) + native.PutUint16(b[6:8], rr.Flags) + native.PutUint32(b[8:12], rr.Seq) + native.PutUint32(b[12:16], rr.Pid) + + next := 16 + for _, data := range dataBytes { + copy(b[next:], data) + next += len(data) + } + return b +} + +func (rr *NetlinkRequest) AddData(data NetlinkRequestData) { + if data != nil { + rr.Data = append(rr.Data, data) + } +} + +func newNetlinkRequest(proto, flags int) *NetlinkRequest { + return &NetlinkRequest{ + NlMsghdr: syscall.NlMsghdr{ + Len: uint32(syscall.NLMSG_HDRLEN), + Type: uint16(proto), + Flags: syscall.NLM_F_REQUEST | uint16(flags), + Seq: uint32(getSeq()), + }, + } +} + +type NetlinkSocket struct { + fd int + lsa syscall.SockaddrNetlink +} + +func getNetlinkSocket() (*NetlinkSocket, error) { + fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE) + if err != nil { + return nil, err + } + s := &NetlinkSocket{ + fd: fd, + } + s.lsa.Family = syscall.AF_NETLINK + if err := syscall.Bind(fd, &s.lsa); err != nil { + syscall.Close(fd) + return nil, err + } + + return s, nil +} + +func (s *NetlinkSocket) Close() { + syscall.Close(s.fd) +} + +func (s *NetlinkSocket) Send(request *NetlinkRequest) error { + if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil { + return err + } + return nil +} + +func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) { + rb := make([]byte, syscall.Getpagesize()) + nr, _, err := syscall.Recvfrom(s.fd, rb, 0) + if err != nil { + return nil, err + } + if nr < syscall.NLMSG_HDRLEN { + return nil, ErrShortResponse + } + rb = rb[:nr] + return syscall.ParseNetlinkMessage(rb) +} + +func (s *NetlinkSocket) GetPid() (uint32, error) { + lsa, err := syscall.Getsockname(s.fd) + if err != nil { + return 0, err + } + switch v := lsa.(type) { + case *syscall.SockaddrNetlink: + return v.Pid, nil + } + return 0, ErrWrongSockType +} + +func (s *NetlinkSocket) HandleAck(seq uint32) error { + native := nativeEndian() + + pid, err := s.GetPid() + if err != nil { + return err + } + +done: + for { + msgs, err := s.Receive() + if err != nil { + return err + } + for _, m := range msgs { + if m.Header.Seq != seq { + return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq) + } + if m.Header.Pid != pid { + return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid) + } + if m.Header.Type == syscall.NLMSG_DONE { + break done + } + if m.Header.Type == syscall.NLMSG_ERROR { + error := int32(native.Uint32(m.Data[0:4])) + if error == 0 { + break done + } + return syscall.Errno(-error) + } + } + } + + return nil +} + +// Add a new default gateway. Identical to: +// ip route add default via $ip +func AddDefaultGw(ip net.IP) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + family := getIpFamily(ip) + + wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newRtMsg(family) + wb.AddData(msg) + + var ipData []byte + if family == syscall.AF_INET { + ipData = ip.To4() + } else { + ipData = ip.To16() + } + + gateway := newRtAttr(syscall.RTA_GATEWAY, ipData) + + wb.AddData(gateway) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +// Bring up a particular network interface +func NetworkLinkUp(iface *net.Interface) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Change = syscall.IFF_UP + msg.Flags = syscall.IFF_UP + msg.Index = int32(iface.Index) + wb.AddData(msg) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +func NetworkLinkDown(iface *net.Interface) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Change = syscall.IFF_UP + msg.Flags = 0 & ^syscall.IFF_UP + msg.Index = int32(iface.Index) + wb.AddData(msg) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +func NetworkSetMTU(iface *net.Interface, mtu int) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = DEFAULT_CHANGE + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(mtu)) + + data := newRtAttr(syscall.IFLA_MTU, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + return s.HandleAck(wb.Seq) +} + +// same as ip link set $name master $master +func NetworkSetMaster(iface, master *net.Interface) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = DEFAULT_CHANGE + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(master.Index)) + + data := newRtAttr(syscall.IFLA_MASTER, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +func NetworkSetNsPid(iface *net.Interface, nspid int) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = DEFAULT_CHANGE + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(nspid)) + + data := newRtAttr(syscall.IFLA_NET_NS_PID, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +func NetworkSetNsFd(iface *net.Interface, fd int) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = DEFAULT_CHANGE + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(fd)) + + data := newRtAttr(IFLA_NET_NS_FD, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +// Add an Ip address to an interface. This is identical to: +// ip addr add $ip/$ipNet dev $iface +func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + family := getIpFamily(ip) + + wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newIfAddrmsg(family) + msg.Index = uint32(iface.Index) + prefixLen, _ := ipNet.Mask.Size() + msg.Prefixlen = uint8(prefixLen) + wb.AddData(msg) + + var ipData []byte + if family == syscall.AF_INET { + ipData = ip.To4() + } else { + ipData = ip.To16() + } + + localData := newRtAttr(syscall.IFA_LOCAL, ipData) + wb.AddData(localData) + + addrData := newRtAttr(syscall.IFA_ADDRESS, ipData) + wb.AddData(addrData) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +func zeroTerminated(s string) []byte { + return []byte(s + "\000") +} + +func nonZeroTerminated(s string) []byte { + return []byte(s) +} + +// Add a new network link of a specified type. This is identical to +// running: ip add link $name type $linkType +func NetworkLinkAdd(name string, linkType string) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + wb.AddData(msg) + + if name != "" { + nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) + wb.AddData(nameData) + } + + kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType)) + + infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) + wb.AddData(infoData) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +// Returns an array of IPNet for all the currently routed subnets on ipv4 +// This is similar to the first column of "ip route" output +func NetworkGetRoutes() ([]Route, error) { + native := nativeEndian() + + s, err := getNetlinkSocket() + if err != nil { + return nil, err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + wb.AddData(msg) + + if err := s.Send(wb); err != nil { + return nil, err + } + + pid, err := s.GetPid() + if err != nil { + return nil, err + } + + res := make([]Route, 0) + +done: + for { + msgs, err := s.Receive() + if err != nil { + return nil, err + } + for _, m := range msgs { + if m.Header.Seq != wb.Seq { + return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq) + } + if m.Header.Pid != pid { + return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid) + } + if m.Header.Type == syscall.NLMSG_DONE { + break done + } + if m.Header.Type == syscall.NLMSG_ERROR { + error := int32(native.Uint32(m.Data[0:4])) + if error == 0 { + break done + } + return nil, syscall.Errno(-error) + } + if m.Header.Type != syscall.RTM_NEWROUTE { + continue + } + + var r Route + + msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0])) + + if msg.Flags&syscall.RTM_F_CLONED != 0 { + // Ignore cloned routes + continue + } + + if msg.Table != syscall.RT_TABLE_MAIN { + // Ignore non-main tables + continue + } + + if msg.Family != syscall.AF_INET { + // Ignore non-ipv4 routes + continue + } + + if msg.Dst_len == 0 { + // Default routes + r.Default = true + } + + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, err + } + for _, attr := range attrs { + switch attr.Attr.Type { + case syscall.RTA_DST: + ip := attr.Value + r.IPNet = &net.IPNet{ + IP: ip, + Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)), + } + case syscall.RTA_OIF: + index := int(native.Uint32(attr.Value[0:4])) + r.Iface, _ = net.InterfaceByIndex(index) + } + } + if r.Default || r.IPNet != nil { + res = append(res, r) + } + } + } + + return res, nil +} + +func getIfSocket() (fd int, err error) { + for _, socket := range []int{ + syscall.AF_INET, + syscall.AF_PACKET, + syscall.AF_INET6, + } { + if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil { + break + } + } + if err == nil { + return fd, nil + } + return -1, err +} + +func NetworkChangeName(iface *net.Interface, newName string) error { + fd, err := getIfSocket() + if err != nil { + return err + } + defer syscall.Close(fd) + + data := [IFNAMSIZ * 2]byte{} + // the "-1"s here are very important for ensuring we get proper null + // termination of our new C strings + copy(data[:IFNAMSIZ-1], iface.Name) + copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 { + return errno + } + return nil +} + +func NetworkCreateVethPair(name1, name2 string) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + wb.AddData(msg) + + nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1)) + wb.AddData(nameData) + + nest1 := newRtAttr(syscall.IFLA_LINKINFO, nil) + newRtAttrChild(nest1, IFLA_INFO_KIND, zeroTerminated("veth")) + nest2 := newRtAttrChild(nest1, IFLA_INFO_DATA, nil) + nest3 := newRtAttrChild(nest2, VETH_INFO_PEER, nil) + + newIfInfomsgChild(nest3, syscall.AF_UNSPEC) + newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2)) + + wb.AddData(nest1) + + if err := s.Send(wb); err != nil { + return err + } + return s.HandleAck(wb.Seq) +} + +// Create the actual bridge device. This is more backward-compatible than +// netlink.NetworkLinkAdd and works on RHEL 6. +func CreateBridge(name string, setMacAddr bool) error { + s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + // ipv6 issue, creating with ipv4 + s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + return err + } + } + defer syscall.Close(s) + + nameBytePtr, err := syscall.BytePtrFromString(name) + if err != nil { + return err + } + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 { + return err + } + if setMacAddr { + return setBridgeMacAddress(s, name) + } + return nil +} + +// Add a slave to abridge device. This is more backward-compatible than +// netlink.NetworkSetMaster and works on RHEL 6. +func AddToBridge(iface, master *net.Interface) error { + s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + // ipv6 issue, creating with ipv4 + s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + return err + } + } + defer syscall.Close(s) + + ifr := ifreqIndex{} + copy(ifr.IfrnName[:], master.Name) + ifr.IfruIndex = int32(iface.Index) + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDIF, uintptr(unsafe.Pointer(&ifr))); err != 0 { + return err + } + + return nil +} + +func setBridgeMacAddress(s int, name string) error { + ifr := ifreqHwaddr{} + ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER + copy(ifr.IfrnName[:], name) + + for i := 0; i < 6; i++ { + ifr.IfruHwaddr.Data[i] = int8(rand.Intn(255)) + } + + ifr.IfruHwaddr.Data[0] &^= 0x1 // clear multicast bit + ifr.IfruHwaddr.Data[0] |= 0x2 // set local assignment bit (IEEE802) + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 { + return err + } + return nil +} diff --git a/third_party/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go b/third_party/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go new file mode 100644 index 0000000..8a5531b --- /dev/null +++ b/third_party/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go @@ -0,0 +1,69 @@ +// +build !linux !amd64 + +package netlink + +import ( + "errors" + "net" +) + +var ( + ErrNotImplemented = errors.New("not implemented") +) + +func NetworkGetRoutes() ([]Route, error) { + return nil, ErrNotImplemented +} + +func NetworkLinkAdd(name string, linkType string) error { + return ErrNotImplemented +} + +func NetworkLinkUp(iface *net.Interface) error { + return ErrNotImplemented +} + +func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { + return ErrNotImplemented +} + +func AddDefaultGw(ip net.IP) error { + return ErrNotImplemented + +} + +func NetworkSetMTU(iface *net.Interface, mtu int) error { + return ErrNotImplemented +} + +func NetworkCreateVethPair(name1, name2 string) error { + return ErrNotImplemented +} + +func NetworkChangeName(iface *net.Interface, newName string) error { + return ErrNotImplemented +} + +func NetworkSetNsFd(iface *net.Interface, fd int) error { + return ErrNotImplemented +} + +func NetworkSetNsPid(iface *net.Interface, nspid int) error { + return ErrNotImplemented +} + +func NetworkSetMaster(iface, master *net.Interface) error { + return ErrNotImplemented +} + +func NetworkLinkDown(iface *net.Interface) error { + return ErrNotImplemented +} + +func CreateBridge(name string, setMacAddr bool) error { + return ErrNotImplemented +} + +func AddToBridge(iface, master *net.Interface) error { + return ErrNotImplemented +} From 48df1be79314bed3bf0bbf69a9e43a45e6b0aba1 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 May 2014 15:00:41 -0700 Subject: [PATCH 6/7] feat(convertNetconf): Add support for network config conversion Adding the flag -convertNetconf which is used to specify the config format to convert from (right now, only 'debian' is supported). Once the network configs are generated, they are written to systemd's runtime network directory and the network is restarted. --- coreos-cloudinit.go | 93 +++++++++++++++++++++++++++++++++++++++------ network/network.go | 41 -------------------- system/networkd.go | 89 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 53 deletions(-) create mode 100644 system/networkd.go diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 6b44342..93cebc6 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -1,23 +1,21 @@ package main import ( + "encoding/json" "flag" "fmt" - "log" + "io/ioutil" "os" + "path" "github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/initialize" + "github.com/coreos/coreos-cloudinit/network" "github.com/coreos/coreos-cloudinit/system" ) const version = "0.7.1+git" -func init() { - //Removes timestamp since it is displayed already during booting - log.SetFlags(0) -} - func main() { var printVersion bool flag.BoolVar(&printVersion, "version", false, "Print the version and exit") @@ -37,6 +35,9 @@ func main() { var useProcCmdline bool flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=', using the cloud-config served by an HTTP GET to ", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag)) + var convertNetconf string + flag.StringVar(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files (requires the -from-configdrive flag)") + var workspace string flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data") @@ -64,10 +65,23 @@ func main() { os.Exit(1) } - log.Printf("Fetching user-data from datasource of type %q", ds.Type()) + if convertNetconf != "" && configdrive == "" { + fmt.Println("-convert-netconf flag requires -from-configdrive") + os.Exit(1) + } + + switch convertNetconf { + case "": + case "debian": + default: + fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf) + os.Exit(1) + } + + fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type()) userdataBytes, err := ds.Fetch() if err != nil { - log.Printf("Failed fetching user-data from datasource: %v", err) + fmt.Printf("Failed fetching user-data from datasource: %v\n", err) if ignoreFailure { os.Exit(0) } else { @@ -78,13 +92,22 @@ func main() { env := initialize.NewEnvironment("/", workspace) if len(userdataBytes) > 0 { if err := processUserdata(string(userdataBytes), env); err != nil { - log.Fatalf("Failed resolving user-data: %v", err) + fmt.Printf("Failed resolving user-data: %v\n", err) if !ignoreFailure { os.Exit(1) } } } else { - log.Printf("No user data to handle.") + fmt.Println("No user data to handle.") + } + + if convertNetconf != "" { + if err := processNetconf(convertNetconf, configdrive); err != nil { + fmt.Printf("Failed to process network config: %v\n", err) + if !ignoreFailure { + os.Exit(1) + } + } } } @@ -93,13 +116,14 @@ func processUserdata(userdata string, env *initialize.Environment) error { parsed, err := initialize.ParseUserData(userdata) if err != nil { - log.Printf("Failed parsing user-data: %v", err) + fmt.Printf("Failed parsing user-data: %v\n", err) return err } err = initialize.PrepWorkspace(env.Workspace()) if err != nil { - log.Fatalf("Failed preparing workspace: %v", err) + fmt.Printf("Failed preparing workspace: %v\n", err) + return err } switch t := parsed.(type) { @@ -117,3 +141,48 @@ func processUserdata(userdata string, env *initialize.Environment) error { return err } + +func processNetconf(convertNetconf, configdrive string) error { + openstackRoot := path.Join(configdrive, "openstack") + metadataFilename := path.Join(openstackRoot, "latest", "meta_data.json") + metadataBytes, err := ioutil.ReadFile(metadataFilename) + if err != nil { + return err + } + + var metadata struct { + NetworkConfig struct { + ContentPath string `json:"content_path"` + } `json:"network_config"` + } + if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + return err + } + configPath := metadata.NetworkConfig.ContentPath + if configPath == "" { + fmt.Printf("No network config specified in %q.\n", metadataFilename) + return nil + } + + netconfBytes, err := ioutil.ReadFile(path.Join(openstackRoot, configPath)) + if err != nil { + return err + } + + var interfaces []network.InterfaceGenerator + switch convertNetconf { + case "debian": + interfaces, err = network.ProcessDebianNetconf(string(netconfBytes)) + default: + return fmt.Errorf("Unsupported network config format %q", convertNetconf) + } + + if err != nil { + return err + } + + if err := system.WriteNetworkdConfigs(interfaces); err != nil { + return err + } + return system.RestartNetwork(interfaces) +} diff --git a/network/network.go b/network/network.go index 2f23098..2af5f39 100644 --- a/network/network.go +++ b/network/network.go @@ -1,10 +1,6 @@ package network import ( - "fmt" - "io" - "os" - "path" "strings" ) @@ -26,43 +22,6 @@ func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) { return buildInterfaces(interfaces), nil } -func WriteConfigs(configPath string, interfaces []InterfaceGenerator) error { - if err := os.MkdirAll(configPath, os.ModePerm+os.ModeDir); err != nil { - fmt.Println(err) - os.Exit(1) - } - - for _, iface := range interfaces { - filename := path.Join(configPath, fmt.Sprintf("%s.netdev", iface.Name())) - if err := writeConfig(filename, iface.GenerateNetdevConfig()); err != nil { - return err - } - filename = path.Join(configPath, fmt.Sprintf("%s.link", iface.Name())) - if err := writeConfig(filename, iface.GenerateLinkConfig()); err != nil { - return err - } - filename = path.Join(configPath, fmt.Sprintf("%s.network", iface.Name())) - if err := writeConfig(filename, iface.GenerateNetworkConfig()); err != nil { - return err - } - } - return nil -} - -func writeConfig(filename string, config string) error { - if config == "" { - return nil - } - - if file, err := os.Create(filename); err == nil { - io.WriteString(file, config) - file.Close() - return nil - } else { - return err - } -} - func formatConfig(config string) []string { lines := []string{} config = strings.Replace(config, "\\\n", "", -1) diff --git a/system/networkd.go b/system/networkd.go new file mode 100644 index 0000000..c795563 --- /dev/null +++ b/system/networkd.go @@ -0,0 +1,89 @@ +package system + +import ( + "fmt" + "io/ioutil" + "net" + "os/exec" + "path" + + "github.com/coreos/coreos-cloudinit/network" + "github.com/coreos/coreos-cloudinit/third_party/github.com/dotcloud/docker/pkg/netlink" +) + +const ( + runtimeNetworkPath = "/run/systemd/network" +) + +func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) { + defer func() { + if e := restartNetworkd(); e != nil { + err = e + } + }() + + if err = downNetworkInterfaces(interfaces); err != nil { + return + } + + if err = probe8012q(); err != nil { + return + } + return +} + +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 + } + } else { + return err + } + + for _, iface := range interfaces { + if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok { + if err := netlink.NetworkLinkDown(systemInterface); err != nil { + fmt.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err) + } + } + } + + return nil +} + +func probe8012q() error { + return exec.Command("modprobe", "8021q").Run() +} + +func restartNetworkd() error { + _, err := RunUnitCommand("restart", "systemd-networkd.service") + return err +} + +func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error { + for _, iface := range interfaces { + filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Name())) + if err := writeConfig(filename, iface.Netdev()); err != nil { + return err + } + filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Name())) + if err := writeConfig(filename, iface.Link()); err != nil { + return err + } + filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Name())) + if err := writeConfig(filename, iface.Network()); err != nil { + return err + } + } + return nil +} + +func writeConfig(filename string, config string) error { + if config == "" { + return nil + } + + return ioutil.WriteFile(filename, []byte(config), 0444) +} From 69240a7e394fd200ba25401565bb69058be127fe Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Thu, 22 May 2014 15:06:10 -0700 Subject: [PATCH 7/7] feat(systemd): Update the systemd unit files to use configdrive This makes it so that /media/configdrive can be used for user-data and network configs. --- units/user-config.target | 4 ++-- units/user-configdrive.path | 5 +++++ ...ack-latest-user_data.service => user-configdrive.service} | 5 ++--- 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 units/user-configdrive.path rename units/{user-cloudinit@media-configdrive-openstack-latest-user_data.service => user-configdrive.service} (87%) diff --git a/units/user-config.target b/units/user-config.target index dae7d05..d7cb909 100644 --- a/units/user-config.target +++ b/units/user-config.target @@ -4,8 +4,8 @@ Requires=system-config.target After=system-config.target # Watch for configs at a couple common paths -Requires=user-cloudinit@media-configdrive-openstack-latest-user_data.path -After=user-cloudinit@media-configdrive-openstack-latest-user_data.path +Requires=user-configdrive.path +After=user-configdrive.path Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.path diff --git a/units/user-configdrive.path b/units/user-configdrive.path new file mode 100644 index 0000000..77a615a --- /dev/null +++ b/units/user-configdrive.path @@ -0,0 +1,5 @@ +[Unit] +Description=Watch for a cloud-config at /media/configdrive + +[Path] +DirectoryNotEmpty=/media/configdrive diff --git a/units/user-cloudinit@media-configdrive-openstack-latest-user_data.service b/units/user-configdrive.service similarity index 87% rename from units/user-cloudinit@media-configdrive-openstack-latest-user_data.service rename to units/user-configdrive.service index 6251821..762cfd2 100644 --- a/units/user-cloudinit@media-configdrive-openstack-latest-user_data.service +++ b/units/user-configdrive.service @@ -1,9 +1,8 @@ [Unit] -Description=Load cloud-config from %f +Description=Load cloud-config from /media/configdrive Requires=coreos-setup-environment.service After=coreos-setup-environment.service Before=user-config.target -ConditionFileNotEmpty=%f # HACK: work around ordering between config drive and ec2 metadata It is # possible for OpenStack style systems to provide both the metadata service @@ -21,4 +20,4 @@ After=ec2-cloudinit.service Type=oneshot RemainAfterExit=yes EnvironmentFile=-/etc/environment -ExecStart=/usr/bin/coreos-cloudinit --from-file=%f +ExecStart=/usr/bin/coreos-cloudinit --from-configdrive=/media/configdrive