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.
This commit is contained in:
Alex Crawford 2014-05-22 15:00:41 -07:00
parent 79a40a38d8
commit 48df1be793
3 changed files with 170 additions and 53 deletions

View File

@ -1,23 +1,21 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"log" "io/ioutil"
"os" "os"
"path"
"github.com/coreos/coreos-cloudinit/datasource" "github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/initialize" "github.com/coreos/coreos-cloudinit/initialize"
"github.com/coreos/coreos-cloudinit/network"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
) )
const version = "0.7.1+git" const version = "0.7.1+git"
func init() {
//Removes timestamp since it is displayed already during booting
log.SetFlags(0)
}
func main() { func main() {
var printVersion bool var printVersion bool
flag.BoolVar(&printVersion, "version", false, "Print the version and exit") flag.BoolVar(&printVersion, "version", false, "Print the version and exit")
@ -37,6 +35,9 @@ func main() {
var useProcCmdline bool var useProcCmdline bool
flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag)) flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", 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 var workspace string
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data") 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) 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() userdataBytes, err := ds.Fetch()
if err != nil { 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 { if ignoreFailure {
os.Exit(0) os.Exit(0)
} else { } else {
@ -78,13 +92,22 @@ func main() {
env := initialize.NewEnvironment("/", workspace) env := initialize.NewEnvironment("/", workspace)
if len(userdataBytes) > 0 { if len(userdataBytes) > 0 {
if err := processUserdata(string(userdataBytes), env); err != nil { 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 { if !ignoreFailure {
os.Exit(1) os.Exit(1)
} }
} }
} else { } 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) parsed, err := initialize.ParseUserData(userdata)
if err != nil { if err != nil {
log.Printf("Failed parsing user-data: %v", err) fmt.Printf("Failed parsing user-data: %v\n", err)
return err return err
} }
err = initialize.PrepWorkspace(env.Workspace()) err = initialize.PrepWorkspace(env.Workspace())
if err != nil { if err != nil {
log.Fatalf("Failed preparing workspace: %v", err) fmt.Printf("Failed preparing workspace: %v\n", err)
return err
} }
switch t := parsed.(type) { switch t := parsed.(type) {
@ -117,3 +141,48 @@ func processUserdata(userdata string, env *initialize.Environment) error {
return 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)
}

View File

@ -1,10 +1,6 @@
package network package network
import ( import (
"fmt"
"io"
"os"
"path"
"strings" "strings"
) )
@ -26,43 +22,6 @@ func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
return buildInterfaces(interfaces), nil 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 { func formatConfig(config string) []string {
lines := []string{} lines := []string{}
config = strings.Replace(config, "\\\n", "", -1) config = strings.Replace(config, "\\\n", "", -1)

89
system/networkd.go Normal file
View File

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