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) +}