diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index de384c7..e40832f 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.3+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") @@ -28,12 +26,18 @@ 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") 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") @@ -52,17 +56,32 @@ 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) } - 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 { @@ -70,29 +89,41 @@ 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 { + fmt.Printf("Failed resolving user-data: %v\n", err) + if !ignoreFailure { + os.Exit(1) + } + } + } else { + fmt.Println("No user data to handle.") } - env := initialize.NewEnvironment("/", workspace) + if convertNetconf != "" { + if err := processNetconf(convertNetconf, configdrive); err != nil { + fmt.Printf("Failed to process network config: %v\n", err) + if !ignoreFailure { + os.Exit(1) + } + } + } +} - 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) - } + 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) { @@ -104,11 +135,54 @@ 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 +} + +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/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" +} 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/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.go b/network/network.go new file mode 100644 index 0000000..2af5f39 --- /dev/null +++ b/network/network.go @@ -0,0 +1,45 @@ +package network + +import ( + "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 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/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.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 +} 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/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) +} 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" 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 +} 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