Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2d91369596 | ||
|
d8d3928978 | ||
|
7fcc540154 | ||
|
cb7fbd4668 | ||
|
d4e048a1f4 | ||
|
231c0fa20b | ||
|
1aabacc769 | ||
|
6a2927d701 | ||
|
126188510b | ||
|
4627ccb444 | ||
|
aff372111a | ||
|
c7081b9918 | ||
|
9ba3b18b59 | ||
|
099de62e9a | ||
|
c089216cb5 | ||
|
68dc902ed1 | ||
|
ad66b1c92f | ||
|
fbdece2762 | ||
|
f85eafb7ca | ||
|
f0dba2294e | ||
|
bda3948382 | ||
|
fae81c78f3 | ||
|
a5dec7d7bd | ||
|
e1222c9885 | ||
|
ded3bcf122 | ||
|
80d00cde94 | ||
|
2805d70ece | ||
|
439b7e8b98 | ||
|
ba1c1e97d0 | ||
|
8a50fd8595 | ||
|
465bcce72c | ||
|
361edeebc6 | ||
|
29a7b0e34f | ||
|
8496ffb53a | ||
|
2c717a6cd1 | ||
|
13a91c9181 | ||
|
338e1b64ab | ||
|
8eb0636034 | ||
|
f7c25a1b83 | ||
|
d6a0d0908c | ||
|
5c89afc18a | ||
|
376cc4bcac | ||
|
e6cf83a2e5 | ||
|
840c208b60 | ||
|
29ed6b38bd | ||
|
259c7e1fe2 | ||
|
033c8d352f | ||
|
16d7e8af48 | ||
|
159f4a2c7c |
27
Documentation/debian-interfaces.md
Normal file
27
Documentation/debian-interfaces.md
Normal file
@@ -0,0 +1,27 @@
|
||||
#Debian Interfaces#
|
||||
**WARNING**: This option is EXPERIMENTAL and may change or be removed at any
|
||||
point.
|
||||
There is basic support for converting from a Debian network configuration to
|
||||
networkd unit files. The -convert-netconf=debian option is used to activate
|
||||
this feature.
|
||||
|
||||
#convert-netconf#
|
||||
Default: ""
|
||||
Read the network config provided in cloud-drive and translate it from the
|
||||
specified format into networkd unit files (requires the -from-configdrive
|
||||
flag). Currently only supports "debian" which provides support for a small
|
||||
subset of the [Debian network configuration]
|
||||
(https://wiki.debian.org/NetworkConfiguration). These options include:
|
||||
|
||||
- interface config methods
|
||||
- static
|
||||
- address/netmask
|
||||
- gateway
|
||||
- hwaddress
|
||||
- dns-nameservers
|
||||
- dhcp
|
||||
- hwaddress
|
||||
- manual
|
||||
- loopback
|
||||
- vlan_raw_device
|
||||
- bond-slaves
|
@@ -1,71 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/datasource"
|
||||
"github.com/coreos/coreos-cloudinit/initialize"
|
||||
"github.com/coreos/coreos-cloudinit/network"
|
||||
"github.com/coreos/coreos-cloudinit/pkg"
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
const version = "0.7.7"
|
||||
const (
|
||||
version = "0.8.2"
|
||||
datasourceInterval = 100 * time.Millisecond
|
||||
datasourceMaxInterval = 30 * time.Second
|
||||
datasourceTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
printVersion bool
|
||||
ignoreFailure bool
|
||||
sources struct {
|
||||
file string
|
||||
configDrive string
|
||||
metadataService bool
|
||||
url string
|
||||
procCmdLine bool
|
||||
}
|
||||
convertNetconf string
|
||||
workspace string
|
||||
sshKeyName string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&printVersion, "version", false, "Print the version and exit")
|
||||
flag.BoolVar(&ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")
|
||||
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file")
|
||||
flag.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
|
||||
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "Download data from metadata service")
|
||||
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url")
|
||||
flag.BoolVar(&sources.procCmdLine, "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.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)")
|
||||
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
||||
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var printVersion bool
|
||||
flag.BoolVar(&printVersion, "version", false, "Print the version and exit")
|
||||
|
||||
var ignoreFailure bool
|
||||
flag.BoolVar(&ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")
|
||||
|
||||
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=<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
|
||||
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
||||
|
||||
var sshKeyName string
|
||||
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
die := func() {
|
||||
if ignoreFailure {
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if printVersion == true {
|
||||
fmt.Printf("coreos-cloudinit version %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var ds datasource.Datasource
|
||||
if file != "" {
|
||||
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-configdrive, --from-url or --from-proc-cmdline")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if convertNetconf != "" && configdrive == "" {
|
||||
if convertNetconf != "" && sources.configDrive == "" {
|
||||
fmt.Println("-convert-netconf flag requires -from-configdrive")
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -78,111 +76,204 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dss := getDatasources()
|
||||
if len(dss) == 0 {
|
||||
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-metadata-service, --from-url or --from-proc-cmdline")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ds := selectDatasource(dss)
|
||||
if ds == nil {
|
||||
fmt.Println("No datasources available in time")
|
||||
die()
|
||||
}
|
||||
|
||||
fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
|
||||
userdataBytes, err := ds.Fetch()
|
||||
userdataBytes, err := ds.FetchUserdata()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed fetching user-data from datasource: %v\n", err)
|
||||
if ignoreFailure {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
os.Exit(1)
|
||||
die()
|
||||
}
|
||||
|
||||
fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
|
||||
metadataBytes, err := ds.FetchMetadata()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed fetching meta-data from datasource: %v\n", err)
|
||||
die()
|
||||
}
|
||||
|
||||
// Extract IPv4 addresses from metadata if possible
|
||||
var subs map[string]string
|
||||
if len(metadataBytes) > 0 {
|
||||
subs, err = initialize.ExtractIPsFromMetadata(metadataBytes)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed extracting IPs from meta-data: %v\n", err)
|
||||
die()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
env := initialize.NewEnvironment("/", ds.ConfigRoot(), workspace, convertNetconf, sshKeyName, subs)
|
||||
|
||||
var ccm, ccu *initialize.CloudConfig
|
||||
var script *system.Script
|
||||
if ccm, err = initialize.ParseMetaData(string(metadataBytes)); err != nil {
|
||||
fmt.Printf("Failed to parse meta-data: %v\n", err)
|
||||
die()
|
||||
}
|
||||
if ud, err := initialize.ParseUserData(string(userdataBytes)); err != nil {
|
||||
fmt.Printf("Failed to parse user-data: %v\n", err)
|
||||
die()
|
||||
} else {
|
||||
fmt.Println("No user data to handle.")
|
||||
switch t := ud.(type) {
|
||||
case *initialize.CloudConfig:
|
||||
ccu = t
|
||||
case system.Script:
|
||||
script = &t
|
||||
}
|
||||
}
|
||||
|
||||
if convertNetconf != "" {
|
||||
if err := processNetconf(convertNetconf, configdrive); err != nil {
|
||||
fmt.Printf("Failed to process network config: %v\n", err)
|
||||
if !ignoreFailure {
|
||||
os.Exit(1)
|
||||
var cc *initialize.CloudConfig
|
||||
if ccm != nil && ccu != nil {
|
||||
fmt.Println("Merging cloud-config from meta-data and user-data")
|
||||
merged := mergeCloudConfig(*ccu, *ccm)
|
||||
cc = &merged
|
||||
} else if ccm != nil && ccu == nil {
|
||||
fmt.Println("Processing cloud-config from meta-data")
|
||||
cc = ccm
|
||||
} else if ccm == nil && ccu != nil {
|
||||
fmt.Println("Processing cloud-config from user-data")
|
||||
cc = ccu
|
||||
} else {
|
||||
fmt.Println("No cloud-config data to handle.")
|
||||
}
|
||||
|
||||
if cc != nil {
|
||||
if err = initialize.Apply(*cc, env); err != nil {
|
||||
fmt.Printf("Failed to apply cloud-config: %v\n", err)
|
||||
die()
|
||||
}
|
||||
}
|
||||
|
||||
if script != nil {
|
||||
if err = runScript(*script, env); err != nil {
|
||||
fmt.Printf("Failed to run script: %v\n", err)
|
||||
die()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processUserdata(userdata string, env *initialize.Environment) error {
|
||||
userdata = env.Apply(userdata)
|
||||
|
||||
parsed, err := initialize.ParseUserData(userdata)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed parsing user-data: %v\n", err)
|
||||
return err
|
||||
// mergeCloudConfig merges certain options from mdcc (a CloudConfig derived from
|
||||
// meta-data) onto udcc (a CloudConfig derived from user-data), if they are
|
||||
// not already set on udcc (i.e. user-data always takes precedence)
|
||||
// NB: This needs to be kept in sync with ParseMetadata so that it tracks all
|
||||
// elements of a CloudConfig which that function can populate.
|
||||
func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudConfig) {
|
||||
if mdcc.Hostname != "" {
|
||||
if udcc.Hostname != "" {
|
||||
fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)", udcc.Hostname, mdcc.Hostname)
|
||||
} else {
|
||||
udcc.Hostname = mdcc.Hostname
|
||||
}
|
||||
|
||||
err = initialize.PrepWorkspace(env.Workspace())
|
||||
}
|
||||
for _, key := range mdcc.SSHAuthorizedKeys {
|
||||
udcc.SSHAuthorizedKeys = append(udcc.SSHAuthorizedKeys, key)
|
||||
}
|
||||
if mdcc.NetworkConfigPath != "" {
|
||||
if udcc.NetworkConfigPath != "" {
|
||||
fmt.Printf("Warning: user-data NetworkConfigPath %s overrides metadata NetworkConfigPath %s", udcc.NetworkConfigPath, mdcc.NetworkConfigPath)
|
||||
} else {
|
||||
udcc.NetworkConfigPath = mdcc.NetworkConfigPath
|
||||
}
|
||||
}
|
||||
return udcc
|
||||
}
|
||||
|
||||
// getDatasources creates a slice of possible Datasources for cloudinit based
|
||||
// on the different source command-line flags.
|
||||
func getDatasources() []datasource.Datasource {
|
||||
dss := make([]datasource.Datasource, 0, 5)
|
||||
if sources.file != "" {
|
||||
dss = append(dss, datasource.NewLocalFile(sources.file))
|
||||
}
|
||||
if sources.url != "" {
|
||||
dss = append(dss, datasource.NewRemoteFile(sources.url))
|
||||
}
|
||||
if sources.configDrive != "" {
|
||||
dss = append(dss, datasource.NewConfigDrive(sources.configDrive))
|
||||
}
|
||||
if sources.metadataService {
|
||||
dss = append(dss, datasource.NewMetadataService())
|
||||
}
|
||||
if sources.procCmdLine {
|
||||
dss = append(dss, datasource.NewProcCmdline())
|
||||
}
|
||||
return dss
|
||||
}
|
||||
|
||||
// selectDatasource attempts to choose a valid Datasource to use based on its
|
||||
// current availability. The first Datasource to report to be available is
|
||||
// returned. Datasources will be retried if possible if they are not
|
||||
// immediately available. If all Datasources are permanently unavailable or
|
||||
// datasourceTimeout is reached before one becomes available, nil is returned.
|
||||
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
||||
ds := make(chan datasource.Datasource)
|
||||
stop := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, s := range sources {
|
||||
wg.Add(1)
|
||||
go func(s datasource.Datasource) {
|
||||
defer wg.Done()
|
||||
|
||||
duration := datasourceInterval
|
||||
for {
|
||||
fmt.Printf("Checking availability of %q\n", s.Type())
|
||||
if s.IsAvailable() {
|
||||
ds <- s
|
||||
return
|
||||
} else if !s.AvailabilityChanges() {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-time.Tick(duration):
|
||||
duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
var s datasource.Datasource
|
||||
select {
|
||||
case s = <-ds:
|
||||
case <-done:
|
||||
case <-time.Tick(datasourceTimeout):
|
||||
}
|
||||
|
||||
close(stop)
|
||||
return s
|
||||
}
|
||||
|
||||
// TODO(jonboulle): this should probably be refactored and moved into a different module
|
||||
func runScript(script system.Script, env *initialize.Environment) error {
|
||||
err := initialize.PrepWorkspace(env.Workspace())
|
||||
if err != nil {
|
||||
fmt.Printf("Failed preparing workspace: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch t := parsed.(type) {
|
||||
case initialize.CloudConfig:
|
||||
err = initialize.Apply(t, env)
|
||||
case system.Script:
|
||||
var path string
|
||||
path, err = initialize.PersistScriptInWorkspace(t, env.Workspace())
|
||||
path, err := initialize.PersistScriptInWorkspace(script, env.Workspace())
|
||||
if err == nil {
|
||||
var name string
|
||||
name, err = system.ExecuteScript(path)
|
||||
initialize.PersistUnitNameInWorkspace(name, env.Workspace())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
110
coreos-cloudinit_test.go
Normal file
110
coreos-cloudinit_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/initialize"
|
||||
)
|
||||
|
||||
func TestMergeCloudConfig(t *testing.T) {
|
||||
simplecc := initialize.CloudConfig{
|
||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||
Hostname: "foobar",
|
||||
NetworkConfigPath: "/path/somewhere",
|
||||
}
|
||||
for i, tt := range []struct {
|
||||
udcc initialize.CloudConfig
|
||||
mdcc initialize.CloudConfig
|
||||
want initialize.CloudConfig
|
||||
}{
|
||||
{
|
||||
// If mdcc is empty, udcc should be returned unchanged
|
||||
simplecc,
|
||||
initialize.CloudConfig{},
|
||||
simplecc,
|
||||
},
|
||||
{
|
||||
// If udcc is empty, mdcc should be returned unchanged(overridden)
|
||||
initialize.CloudConfig{},
|
||||
simplecc,
|
||||
simplecc,
|
||||
},
|
||||
{
|
||||
// user-data should override completely in the case of conflicts
|
||||
simplecc,
|
||||
initialize.CloudConfig{
|
||||
Hostname: "meta-hostname",
|
||||
NetworkConfigPath: "/path/meta",
|
||||
},
|
||||
simplecc,
|
||||
},
|
||||
{
|
||||
// Mixed merge should succeed
|
||||
initialize.CloudConfig{
|
||||
SSHAuthorizedKeys: []string{"abc", "def"},
|
||||
Hostname: "user-hostname",
|
||||
NetworkConfigPath: "/path/somewhere",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
SSHAuthorizedKeys: []string{"woof", "qux"},
|
||||
Hostname: "meta-hostname",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
|
||||
Hostname: "user-hostname",
|
||||
NetworkConfigPath: "/path/somewhere",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Completely non-conflicting merge should be fine
|
||||
initialize.CloudConfig{
|
||||
Hostname: "supercool",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||
NetworkConfigPath: "/dev/fun",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
Hostname: "supercool",
|
||||
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
|
||||
NetworkConfigPath: "/dev/fun",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Non-mergeable settings in user-data should not be affected
|
||||
initialize.CloudConfig{
|
||||
Hostname: "mememe",
|
||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
Hostname: "youyouyou",
|
||||
NetworkConfigPath: "meta-meta-yo",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
Hostname: "mememe",
|
||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||
NetworkConfigPath: "meta-meta-yo",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Non-mergeable (unexpected) settings in meta-data are ignored
|
||||
initialize.CloudConfig{
|
||||
Hostname: "mememe",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
ManageEtcHosts: initialize.EtcHosts("lolz"),
|
||||
NetworkConfigPath: "meta-meta-yo",
|
||||
},
|
||||
initialize.CloudConfig{
|
||||
Hostname: "mememe",
|
||||
NetworkConfigPath: "meta-meta-yo",
|
||||
},
|
||||
},
|
||||
} {
|
||||
got := mergeCloudConfig(tt.mdcc, tt.udcc)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("case #%d: mergeCloudConfig mutated CloudConfig unexpectedly:\ngot:\n%s\nwant:\n%s", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,21 +7,42 @@ import (
|
||||
)
|
||||
|
||||
type configDrive struct {
|
||||
path string
|
||||
root string
|
||||
}
|
||||
|
||||
func NewConfigDrive(path string) *configDrive {
|
||||
return &configDrive{path}
|
||||
func NewConfigDrive(root string) *configDrive {
|
||||
return &configDrive{path.Join(root, "openstack")}
|
||||
}
|
||||
|
||||
func (self *configDrive) Fetch() ([]byte, error) {
|
||||
data, err := ioutil.ReadFile(path.Join(self.path, "openstack", "latest", "user_data"))
|
||||
func (cd *configDrive) IsAvailable() bool {
|
||||
_, err := os.Stat(cd.root)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func (cd *configDrive) AvailabilityChanges() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cd *configDrive) ConfigRoot() string {
|
||||
return cd.root
|
||||
}
|
||||
|
||||
func (cd *configDrive) FetchMetadata() ([]byte, error) {
|
||||
return cd.readFile("meta_data.json")
|
||||
}
|
||||
|
||||
func (cd *configDrive) FetchUserdata() ([]byte, error) {
|
||||
return cd.readFile("user_data")
|
||||
}
|
||||
|
||||
func (cd *configDrive) Type() string {
|
||||
return "cloud-drive"
|
||||
}
|
||||
|
||||
func (cd *configDrive) readFile(filename string) ([]byte, error) {
|
||||
data, err := ioutil.ReadFile(path.Join(cd.root, "latest", filename))
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (self *configDrive) Type() string {
|
||||
return "cloud-drive"
|
||||
}
|
||||
|
@@ -1,6 +1,10 @@
|
||||
package datasource
|
||||
|
||||
type Datasource interface {
|
||||
Fetch() ([]byte, error)
|
||||
IsAvailable() bool
|
||||
AvailabilityChanges() bool
|
||||
ConfigRoot() string
|
||||
FetchMetadata() ([]byte, error)
|
||||
FetchUserdata() ([]byte, error)
|
||||
Type() string
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package datasource
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type localFile struct {
|
||||
@@ -12,10 +13,27 @@ func NewLocalFile(path string) *localFile {
|
||||
return &localFile{path}
|
||||
}
|
||||
|
||||
func (self *localFile) Fetch() ([]byte, error) {
|
||||
return ioutil.ReadFile(self.path)
|
||||
func (f *localFile) IsAvailable() bool {
|
||||
_, err := os.Stat(f.path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func (self *localFile) Type() string {
|
||||
func (f *localFile) AvailabilityChanges() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *localFile) ConfigRoot() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *localFile) FetchMetadata() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (f *localFile) FetchUserdata() ([]byte, error) {
|
||||
return ioutil.ReadFile(f.path)
|
||||
}
|
||||
|
||||
func (f *localFile) Type() string {
|
||||
return "local-file"
|
||||
}
|
||||
|
@@ -1,20 +1,143 @@
|
||||
package datasource
|
||||
|
||||
import "github.com/coreos/coreos-cloudinit/pkg"
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
type metadataService struct {
|
||||
url string
|
||||
"github.com/coreos/coreos-cloudinit/pkg"
|
||||
)
|
||||
|
||||
// metadataService retrieves metadata from either an OpenStack[1] (2012-08-10)
|
||||
// or EC2[2] (2009-04-04) compatible endpoint. It will first attempt to
|
||||
// directly retrieve a JSON blob from the OpenStack endpoint. If that fails
|
||||
// with a 404, it then attempts to retrieve metadata bit-by-bit from the EC2
|
||||
// endpoint, and populates that into an equivalent JSON blob. metadataService
|
||||
// also checks for userdata from EC2 and, if that fails with a 404, OpenStack.
|
||||
//
|
||||
// [1] http://docs.openstack.org/grizzly/openstack-compute/admin/content/metadata-service.html
|
||||
// [2] http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html#instancedata-data-categories
|
||||
|
||||
const (
|
||||
BaseUrl = "http://169.254.169.254/"
|
||||
Ec2ApiVersion = "2009-04-04"
|
||||
Ec2UserdataUrl = BaseUrl + Ec2ApiVersion + "/user-data"
|
||||
Ec2MetadataUrl = BaseUrl + Ec2ApiVersion + "/meta-data"
|
||||
OpenstackApiVersion = "openstack/2012-08-10"
|
||||
OpenstackUserdataUrl = BaseUrl + OpenstackApiVersion + "/user_data"
|
||||
OpenstackMetadataUrl = BaseUrl + OpenstackApiVersion + "/meta_data.json"
|
||||
)
|
||||
|
||||
type metadataService struct{}
|
||||
|
||||
type getter interface {
|
||||
GetRetry(string) ([]byte, error)
|
||||
}
|
||||
|
||||
func NewMetadataService(url string) *metadataService {
|
||||
return &metadataService{url}
|
||||
func NewMetadataService() *metadataService {
|
||||
return &metadataService{}
|
||||
}
|
||||
|
||||
func (ms *metadataService) Fetch() ([]byte, error) {
|
||||
func (ms *metadataService) IsAvailable() bool {
|
||||
client := pkg.NewHttpClient()
|
||||
return client.Get(ms.url)
|
||||
_, err := client.Get(BaseUrl)
|
||||
return (err == nil)
|
||||
}
|
||||
|
||||
func (ms *metadataService) AvailabilityChanges() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ms *metadataService) ConfigRoot() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ms *metadataService) FetchMetadata() ([]byte, error) {
|
||||
return fetchMetadata(pkg.NewHttpClient())
|
||||
}
|
||||
|
||||
func (ms *metadataService) FetchUserdata() ([]byte, error) {
|
||||
client := pkg.NewHttpClient()
|
||||
if data, err := client.GetRetry(Ec2UserdataUrl); err == nil {
|
||||
return data, err
|
||||
} else if _, ok := err.(pkg.ErrTimeout); ok {
|
||||
return data, err
|
||||
}
|
||||
return client.GetRetry(OpenstackUserdataUrl)
|
||||
}
|
||||
|
||||
func (ms *metadataService) Type() string {
|
||||
return "metadata-service"
|
||||
}
|
||||
|
||||
func fetchMetadata(client getter) ([]byte, error) {
|
||||
if metadata, err := client.GetRetry(OpenstackMetadataUrl); err == nil {
|
||||
return metadata, nil
|
||||
} else if _, ok := err.(pkg.ErrTimeout); ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs := make(map[string]interface{})
|
||||
if keynames, err := fetchAttributes(client, fmt.Sprintf("%s/public-keys", Ec2MetadataUrl)); err == nil {
|
||||
keyIDs := make(map[string]string)
|
||||
for _, keyname := range keynames {
|
||||
tokens := strings.SplitN(keyname, "=", 2)
|
||||
if len(tokens) != 2 {
|
||||
return nil, fmt.Errorf("malformed public key: %q\n", keyname)
|
||||
}
|
||||
keyIDs[tokens[1]] = tokens[0]
|
||||
}
|
||||
|
||||
keys := make(map[string]string)
|
||||
for name, id := range keyIDs {
|
||||
sshkey, err := fetchAttribute(client, fmt.Sprintf("%s/public-keys/%s/openssh-key", Ec2MetadataUrl, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[name] = sshkey
|
||||
fmt.Printf("Found SSH key for %q\n", name)
|
||||
}
|
||||
attrs["public_keys"] = keys
|
||||
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hostname, err := fetchAttribute(client, fmt.Sprintf("%s/hostname", Ec2MetadataUrl)); err == nil {
|
||||
attrs["hostname"] = hostname
|
||||
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if content_path, err := fetchAttribute(client, fmt.Sprintf("%s/network_config/content_path", Ec2MetadataUrl)); err == nil {
|
||||
attrs["network_config"] = map[string]string{
|
||||
"content_path": content_path,
|
||||
}
|
||||
} else if _, ok := err.(pkg.ErrNotFound); !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(attrs)
|
||||
}
|
||||
|
||||
func fetchAttributes(client getter, url string) ([]string, error) {
|
||||
resp, err := client.GetRetry(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(resp))
|
||||
data := make([]string, 0)
|
||||
for scanner.Scan() {
|
||||
data = append(data, scanner.Text())
|
||||
}
|
||||
return data, scanner.Err()
|
||||
}
|
||||
|
||||
func fetchAttribute(client getter, url string) (string, error) {
|
||||
if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 {
|
||||
return attrs[0], nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
165
datasource/metadata_service_test.go
Normal file
165
datasource/metadata_service_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/pkg"
|
||||
)
|
||||
|
||||
type TestHttpClient struct {
|
||||
metadata map[string]string
|
||||
err error
|
||||
}
|
||||
|
||||
func (t *TestHttpClient) GetRetry(url string) ([]byte, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
if val, ok := t.metadata[url]; ok {
|
||||
return []byte(val), nil
|
||||
} else {
|
||||
return nil, pkg.ErrNotFound{fmt.Errorf("not found: %q", url)}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchAttributes(t *testing.T) {
|
||||
for _, s := range []struct {
|
||||
metadata map[string]string
|
||||
err error
|
||||
tests []struct {
|
||||
path string
|
||||
val []string
|
||||
}
|
||||
}{
|
||||
{
|
||||
metadata: map[string]string{
|
||||
"/": "a\nb\nc/",
|
||||
"/c/": "d\ne/",
|
||||
"/c/e/": "f",
|
||||
"/a": "1",
|
||||
"/b": "2",
|
||||
"/c/d": "3",
|
||||
"/c/e/f": "4",
|
||||
},
|
||||
tests: []struct {
|
||||
path string
|
||||
val []string
|
||||
}{
|
||||
{"/", []string{"a", "b", "c/"}},
|
||||
{"/b", []string{"2"}},
|
||||
{"/c/d", []string{"3"}},
|
||||
{"/c/e/", []string{"f"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
err: pkg.ErrNotFound{fmt.Errorf("test error")},
|
||||
tests: []struct {
|
||||
path string
|
||||
val []string
|
||||
}{
|
||||
{"", nil},
|
||||
},
|
||||
},
|
||||
} {
|
||||
client := &TestHttpClient{s.metadata, s.err}
|
||||
for _, tt := range s.tests {
|
||||
attrs, err := fetchAttributes(client, tt.path)
|
||||
if err != s.err {
|
||||
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.metadata, s.err, err)
|
||||
}
|
||||
if !reflect.DeepEqual(attrs, tt.val) {
|
||||
t.Fatalf("bad fetch for %q (%q): want %q, got %q", tt.path, s.metadata, tt.val, attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchAttribute(t *testing.T) {
|
||||
for _, s := range []struct {
|
||||
metadata map[string]string
|
||||
err error
|
||||
tests []struct {
|
||||
path string
|
||||
val string
|
||||
}
|
||||
}{
|
||||
{
|
||||
metadata: map[string]string{
|
||||
"/": "a\nb\nc/",
|
||||
"/c/": "d\ne/",
|
||||
"/c/e/": "f",
|
||||
"/a": "1",
|
||||
"/b": "2",
|
||||
"/c/d": "3",
|
||||
"/c/e/f": "4",
|
||||
},
|
||||
tests: []struct {
|
||||
path string
|
||||
val string
|
||||
}{
|
||||
{"/a", "1"},
|
||||
{"/b", "2"},
|
||||
{"/c/d", "3"},
|
||||
{"/c/e/f", "4"},
|
||||
},
|
||||
},
|
||||
{
|
||||
err: pkg.ErrNotFound{fmt.Errorf("test error")},
|
||||
tests: []struct {
|
||||
path string
|
||||
val string
|
||||
}{
|
||||
{"", ""},
|
||||
},
|
||||
},
|
||||
} {
|
||||
client := &TestHttpClient{s.metadata, s.err}
|
||||
for _, tt := range s.tests {
|
||||
attr, err := fetchAttribute(client, tt.path)
|
||||
if err != s.err {
|
||||
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.metadata, s.err, err)
|
||||
}
|
||||
if attr != tt.val {
|
||||
t.Fatalf("bad fetch for %q (%q): want %q, got %q", tt.path, s.metadata, tt.val, attr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchMetadata(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
metadata map[string]string
|
||||
err error
|
||||
expect []byte
|
||||
}{
|
||||
{
|
||||
metadata: map[string]string{
|
||||
"http://169.254.169.254/2009-04-04/meta-data/hostname": "host",
|
||||
"http://169.254.169.254/2009-04-04/meta-data/public-keys": "0=test1\n",
|
||||
"http://169.254.169.254/2009-04-04/meta-data/public-keys/0": "openssh-key",
|
||||
"http://169.254.169.254/2009-04-04/meta-data/public-keys/0/openssh-key": "key",
|
||||
"http://169.254.169.254/2009-04-04/meta-data/network_config/content_path": "path",
|
||||
},
|
||||
expect: []byte(`{"hostname":"host","network_config":{"content_path":"path"},"public_keys":{"test1":"key"}}`),
|
||||
},
|
||||
{
|
||||
metadata: map[string]string{
|
||||
"http://169.254.169.254/openstack/2012-08-10/meta_data.json": "test",
|
||||
},
|
||||
expect: []byte("test"),
|
||||
},
|
||||
{err: pkg.ErrTimeout{fmt.Errorf("test error")}},
|
||||
} {
|
||||
client := &TestHttpClient{tt.metadata, tt.err}
|
||||
metadata, err := fetchMetadata(client)
|
||||
if err != tt.err {
|
||||
t.Fatalf("bad error (%q): want %q, got %q", tt.metadata, tt.err, err)
|
||||
}
|
||||
if !bytes.Equal(metadata, tt.expect) {
|
||||
t.Fatalf("bad fetch (%q): want %q, got %q", tt.metadata, tt.expect, metadata)
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@ const (
|
||||
ProcCmdlineCloudConfigFlag = "cloud-config-url"
|
||||
)
|
||||
|
||||
type procCmdline struct{
|
||||
type procCmdline struct {
|
||||
Location string
|
||||
}
|
||||
|
||||
@@ -22,8 +22,31 @@ func NewProcCmdline() *procCmdline {
|
||||
return &procCmdline{Location: ProcCmdlineLocation}
|
||||
}
|
||||
|
||||
func (self *procCmdline) Fetch() ([]byte, error) {
|
||||
contents, err := ioutil.ReadFile(self.Location)
|
||||
func (c *procCmdline) IsAvailable() bool {
|
||||
contents, err := ioutil.ReadFile(c.Location)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cmdline := strings.TrimSpace(string(contents))
|
||||
_, err = findCloudConfigURL(cmdline)
|
||||
return (err == nil)
|
||||
}
|
||||
|
||||
func (c *procCmdline) AvailabilityChanges() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *procCmdline) ConfigRoot() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *procCmdline) FetchMetadata() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (c *procCmdline) FetchUserdata() ([]byte, error) {
|
||||
contents, err := ioutil.ReadFile(c.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,7 +58,7 @@ func (self *procCmdline) Fetch() ([]byte, error) {
|
||||
}
|
||||
|
||||
client := pkg.NewHttpClient()
|
||||
cfg, err := client.Get(url)
|
||||
cfg, err := client.GetRetry(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -43,7 +66,7 @@ func (self *procCmdline) Fetch() ([]byte, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (self *procCmdline) Type() string {
|
||||
func (c *procCmdline) Type() string {
|
||||
return "proc-cmdline"
|
||||
}
|
||||
|
||||
|
@@ -77,7 +77,7 @@ func TestProcCmdlineAndFetchConfig(t *testing.T) {
|
||||
|
||||
p := NewProcCmdline()
|
||||
p.Location = file.Name()
|
||||
cfg, err := p.Fetch()
|
||||
cfg, err := p.FetchUserdata()
|
||||
if err != nil {
|
||||
t.Errorf("Test produced error: %v", err)
|
||||
}
|
||||
|
38
datasource/url.go
Normal file
38
datasource/url.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package datasource
|
||||
|
||||
import "github.com/coreos/coreos-cloudinit/pkg"
|
||||
|
||||
type remoteFile struct {
|
||||
url string
|
||||
}
|
||||
|
||||
func NewRemoteFile(url string) *remoteFile {
|
||||
return &remoteFile{url}
|
||||
}
|
||||
|
||||
func (f *remoteFile) IsAvailable() bool {
|
||||
client := pkg.NewHttpClient()
|
||||
_, err := client.Get(f.url)
|
||||
return (err == nil)
|
||||
}
|
||||
|
||||
func (f *remoteFile) AvailabilityChanges() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *remoteFile) ConfigRoot() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *remoteFile) FetchMetadata() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (f *remoteFile) FetchUserdata() ([]byte, error) {
|
||||
client := pkg.NewHttpClient()
|
||||
return client.GetRetry(f.url)
|
||||
}
|
||||
|
||||
func (f *remoteFile) Type() string {
|
||||
return "url"
|
||||
}
|
@@ -3,10 +3,13 @@ package initialize
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/network"
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
@@ -38,6 +41,7 @@ type CloudConfig struct {
|
||||
Hostname string
|
||||
Users []system.User
|
||||
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
|
||||
NetworkConfigPath string
|
||||
}
|
||||
|
||||
type warner func(format string, v ...interface{})
|
||||
@@ -227,6 +231,32 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
||||
log.Printf("Wrote file %s to filesystem", path)
|
||||
}
|
||||
|
||||
if env.NetconfType() != "" {
|
||||
netconfBytes, err := ioutil.ReadFile(path.Join(env.ConfigRoot(), cfg.NetworkConfigPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var interfaces []network.InterfaceGenerator
|
||||
switch env.NetconfType() {
|
||||
case "debian":
|
||||
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes))
|
||||
default:
|
||||
return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := system.WriteNetworkdConfigs(interfaces); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := system.RestartNetwork(interfaces); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
commands := make(map[string]string, 0)
|
||||
reload := false
|
||||
for _, unit := range cfg.Coreos.Units {
|
||||
|
@@ -10,37 +10,56 @@ const DefaultSSHKeyName = "coreos-cloudinit"
|
||||
|
||||
type Environment struct {
|
||||
root string
|
||||
configRoot string
|
||||
workspace string
|
||||
netconfType string
|
||||
sshKeyName string
|
||||
substitutions map[string]string
|
||||
}
|
||||
|
||||
func NewEnvironment(root, workspace string) *Environment {
|
||||
substitutions := map[string]string{
|
||||
// TODO(jonboulle): this is getting unwieldy, should be able to simplify the interface somehow
|
||||
func NewEnvironment(root, configRoot, workspace, netconfType, sshKeyName string, substitutions map[string]string) *Environment {
|
||||
if substitutions == nil {
|
||||
substitutions = make(map[string]string)
|
||||
}
|
||||
// If certain values are not in the supplied substitution, fall back to retrieving them from the environment
|
||||
for k, v := range map[string]string{
|
||||
"$public_ipv4": os.Getenv("COREOS_PUBLIC_IPV4"),
|
||||
"$private_ipv4": os.Getenv("COREOS_PRIVATE_IPV4"),
|
||||
} {
|
||||
if _, ok := substitutions[k]; !ok {
|
||||
substitutions[k] = v
|
||||
}
|
||||
return &Environment{root, workspace, DefaultSSHKeyName, substitutions}
|
||||
}
|
||||
return &Environment{root, configRoot, workspace, netconfType, sshKeyName, substitutions}
|
||||
}
|
||||
|
||||
func (self *Environment) Workspace() string {
|
||||
return path.Join(self.root, self.workspace)
|
||||
func (e *Environment) Workspace() string {
|
||||
return path.Join(e.root, e.workspace)
|
||||
}
|
||||
|
||||
func (self *Environment) Root() string {
|
||||
return self.root
|
||||
func (e *Environment) Root() string {
|
||||
return e.root
|
||||
}
|
||||
|
||||
func (self *Environment) SSHKeyName() string {
|
||||
return self.sshKeyName
|
||||
func (e *Environment) ConfigRoot() string {
|
||||
return e.configRoot
|
||||
}
|
||||
|
||||
func (self *Environment) SetSSHKeyName(name string) {
|
||||
self.sshKeyName = name
|
||||
func (e *Environment) NetconfType() string {
|
||||
return e.netconfType
|
||||
}
|
||||
|
||||
func (self *Environment) Apply(data string) string {
|
||||
for key, val := range self.substitutions {
|
||||
func (e *Environment) SSHKeyName() string {
|
||||
return e.sshKeyName
|
||||
}
|
||||
|
||||
func (e *Environment) SetSSHKeyName(name string) {
|
||||
e.sshKeyName = name
|
||||
}
|
||||
|
||||
func (e *Environment) Apply(data string) string {
|
||||
for key, val := range e.substitutions {
|
||||
data = strings.Replace(data, key, val, -1)
|
||||
}
|
||||
return data
|
||||
|
@@ -6,22 +6,53 @@ import (
|
||||
)
|
||||
|
||||
func TestEnvironmentApply(t *testing.T) {
|
||||
os.Setenv("COREOS_PUBLIC_IPV4", "192.0.2.3")
|
||||
os.Setenv("COREOS_PRIVATE_IPV4", "192.0.2.203")
|
||||
env := NewEnvironment("./", "./")
|
||||
input := `[Service]
|
||||
os.Setenv("COREOS_PUBLIC_IPV4", "1.2.3.4")
|
||||
os.Setenv("COREOS_PRIVATE_IPV4", "5.6.7.8")
|
||||
for _, tt := range []struct {
|
||||
subs map[string]string
|
||||
input string
|
||||
out string
|
||||
}{
|
||||
{
|
||||
// Substituting both values directly should always take precedence
|
||||
// over environment variables
|
||||
map[string]string{
|
||||
"$public_ipv4": "192.0.2.3",
|
||||
"$private_ipv4": "192.0.2.203",
|
||||
},
|
||||
`[Service]
|
||||
ExecStart=/usr/bin/echo "$public_ipv4"
|
||||
ExecStop=/usr/bin/echo $private_ipv4
|
||||
ExecStop=/usr/bin/echo $unknown
|
||||
`
|
||||
expected := `[Service]
|
||||
ExecStop=/usr/bin/echo $unknown`,
|
||||
`[Service]
|
||||
ExecStart=/usr/bin/echo "192.0.2.3"
|
||||
ExecStop=/usr/bin/echo 192.0.2.203
|
||||
ExecStop=/usr/bin/echo $unknown
|
||||
`
|
||||
ExecStop=/usr/bin/echo $unknown`,
|
||||
},
|
||||
{
|
||||
// Substituting one value directly while falling back with the other
|
||||
map[string]string{"$private_ipv4": "127.0.0.1"},
|
||||
"$private_ipv4\n$public_ipv4",
|
||||
"127.0.0.1\n1.2.3.4",
|
||||
},
|
||||
{
|
||||
// Falling back to environment variables for both values
|
||||
map[string]string{"foo": "bar"},
|
||||
"$private_ipv4\n$public_ipv4",
|
||||
"5.6.7.8\n1.2.3.4",
|
||||
},
|
||||
{
|
||||
// No substitutions
|
||||
nil,
|
||||
"$private_ipv4\nfoobar",
|
||||
"5.6.7.8\nfoobar",
|
||||
},
|
||||
} {
|
||||
|
||||
output := env.Apply(input)
|
||||
if output != expected {
|
||||
t.Fatalf("Environment incorrectly applied.\nOutput:\n%s\nExpected:\n%s", output, expected)
|
||||
env := NewEnvironment("./", "./", "./", "", "", tt.subs)
|
||||
got := env.Apply(tt.input)
|
||||
if got != tt.out {
|
||||
t.Fatalf("Environment incorrectly applied.\ngot:\n%s\nwant:\n%s", got, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package initialize
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
@@ -19,9 +20,16 @@ func (ee EtcdEnvironment) String() (out string) {
|
||||
}
|
||||
}
|
||||
|
||||
var sorted sort.StringSlice
|
||||
for k, _ := range norm {
|
||||
sorted = append(sorted, k)
|
||||
}
|
||||
sorted.Sort()
|
||||
|
||||
out += "[Service]\n"
|
||||
|
||||
for key, val := range norm {
|
||||
for _, key := range sorted {
|
||||
val := norm[key]
|
||||
out += fmt.Sprintf("Environment=\"ETCD_%s=%s\"\n", key, val)
|
||||
}
|
||||
|
||||
|
@@ -102,8 +102,8 @@ func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
|
||||
}
|
||||
|
||||
expect := `[Service]
|
||||
Environment="ETCD_NAME=node001"
|
||||
Environment="ETCD_DISCOVERY=http://disco.example.com/foobar"
|
||||
Environment="ETCD_NAME=node001"
|
||||
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
||||
`
|
||||
if string(contents) != expect {
|
||||
|
52
initialize/meta_data.go
Normal file
52
initialize/meta_data.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package initialize
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and
|
||||
// converts it to a partially hydrated CloudConfig
|
||||
func ParseMetaData(contents string) (*CloudConfig, error) {
|
||||
if len(contents) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var metadata struct {
|
||||
SSHAuthorizedKeyMap map[string]string `json:"public_keys"`
|
||||
Hostname string `json:"hostname"`
|
||||
NetworkConfig struct {
|
||||
ContentPath string `json:"content_path"`
|
||||
} `json:"network_config"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(contents), &metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg CloudConfig
|
||||
if len(metadata.SSHAuthorizedKeyMap) > 0 {
|
||||
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
|
||||
for _, key := range metadata.SSHAuthorizedKeyMap {
|
||||
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, key)
|
||||
}
|
||||
}
|
||||
cfg.Hostname = metadata.Hostname
|
||||
cfg.NetworkConfigPath = metadata.NetworkConfig.ContentPath
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// ExtractIPsFromMetaData parses a JSON blob in the OpenStack metadata service format,
|
||||
// and returns a substitution map possibly containing private_ipv4 and public_ipv4 addresses
|
||||
func ExtractIPsFromMetadata(contents []byte) (map[string]string, error) {
|
||||
var ips struct {
|
||||
Public string `json:"public-ipv4"`
|
||||
Private string `json:"local-ipv4"`
|
||||
}
|
||||
if err := json.Unmarshal(contents, &ips); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]string)
|
||||
if ips.Private != "" {
|
||||
m["$private_ipv4"] = ips.Private
|
||||
}
|
||||
if ips.Public != "" {
|
||||
m["$public_ipv4"] = ips.Public
|
||||
}
|
||||
return m, nil
|
||||
}
|
69
initialize/meta_data_test.go
Normal file
69
initialize/meta_data_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package initialize
|
||||
|
||||
import "reflect"
|
||||
import "testing"
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
in string
|
||||
want *CloudConfig
|
||||
err bool
|
||||
}{
|
||||
{"", nil, false},
|
||||
{`garbage, invalid json`, nil, true},
|
||||
{`{"foo": "bar"}`, &CloudConfig{}, false},
|
||||
{`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false},
|
||||
{`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false},
|
||||
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"jill", "alice"}}, false},
|
||||
{`{"unknown": "thing", "hostname": "my_host", "public_keys": {"do": "re", "mi": "fa"}, "network_config": {"content_path": "/root", "blah": "zzz"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"re", "fa"}, Hostname: "my_host", NetworkConfigPath: "/root"}, false},
|
||||
} {
|
||||
got, err := ParseMetaData(tt.in)
|
||||
if tt.err != (err != nil) {
|
||||
t.Errorf("case #%d: bad error state: got %t, want %t (err=%v)", i, (err != nil), tt.err, err)
|
||||
}
|
||||
if got == nil {
|
||||
if tt.want != nil {
|
||||
t.Errorf("case #%d: unexpected nil output", i)
|
||||
}
|
||||
} else if tt.want == nil {
|
||||
t.Errorf("case #%d: unexpected non-nil output", i)
|
||||
} else {
|
||||
if !reflect.DeepEqual(*got, *tt.want) {
|
||||
t.Errorf("case #%d: bad output:\ngot\n%v\nwant\n%v", i, *got, *tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestExtractIPsFromMetadata(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
in []byte
|
||||
err bool
|
||||
out map[string]string
|
||||
}{
|
||||
{
|
||||
[]byte(`{"public-ipv4": "12.34.56.78", "local-ipv4": "1.2.3.4"}`),
|
||||
false,
|
||||
map[string]string{"$public_ipv4": "12.34.56.78", "$private_ipv4": "1.2.3.4"},
|
||||
},
|
||||
{
|
||||
[]byte(`{"local-ipv4": "127.0.0.1", "something_else": "don't care"}`),
|
||||
false,
|
||||
map[string]string{"$private_ipv4": "127.0.0.1"},
|
||||
},
|
||||
{
|
||||
[]byte(`garbage`),
|
||||
true,
|
||||
nil,
|
||||
},
|
||||
} {
|
||||
got, err := ExtractIPsFromMetadata(tt.in)
|
||||
if (err != nil) != tt.err {
|
||||
t.Errorf("bad error state (got %t, want %t)", err != nil, tt.err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.out) {
|
||||
t.Errorf("case %d: got %s, want %s", i, got, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,7 +25,7 @@ func SSHImportKeysFromURL(system_user string, url string) error {
|
||||
|
||||
func fetchUserKeys(url string) ([]string, error) {
|
||||
client := pkg.NewHttpClient()
|
||||
data, err := client.Get(url)
|
||||
data, err := client.GetRetry(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -9,6 +9,9 @@ import (
|
||||
)
|
||||
|
||||
func ParseUserData(contents string) (interface{}, error) {
|
||||
if len(contents) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
header := strings.SplitN(contents, "\n", 2)[0]
|
||||
|
||||
// Explicitly trim the header so we can handle user-data from
|
||||
@@ -19,14 +22,9 @@ func ParseUserData(contents string) (interface{}, error) {
|
||||
if strings.HasPrefix(header, "#!") {
|
||||
log.Printf("Parsing user-data as script")
|
||||
return system.Script(contents), nil
|
||||
|
||||
} else if header == "#cloud-config" {
|
||||
log.Printf("Parsing user-data as cloud-config")
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
return *cfg, nil
|
||||
return NewCloudConfig(contents)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
|
||||
}
|
||||
|
@@ -37,7 +37,7 @@ func TestParseConfigCRLF(t *testing.T) {
|
||||
t.Fatalf("Failed parsing config: %v", err)
|
||||
}
|
||||
|
||||
cfg := ud.(CloudConfig)
|
||||
cfg := ud.(*CloudConfig)
|
||||
|
||||
if cfg.Hostname != "foo" {
|
||||
t.Error("Failed parsing hostname from config")
|
||||
@@ -47,3 +47,12 @@ func TestParseConfigCRLF(t *testing.T) {
|
||||
t.Error("Parsed incorrect number of SSH keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigEmpty(t *testing.T) {
|
||||
i, e := ParseUserData(``)
|
||||
if i != nil {
|
||||
t.Error("ParseUserData of empty string returned non-nil unexpectedly")
|
||||
} else if e != nil {
|
||||
t.Error("ParseUserData of empty string returned error unexpectedly")
|
||||
}
|
||||
}
|
||||
|
@@ -7,15 +7,23 @@ import (
|
||||
|
||||
type InterfaceGenerator interface {
|
||||
Name() string
|
||||
Filename() string
|
||||
Netdev() string
|
||||
Link() string
|
||||
Network() string
|
||||
}
|
||||
|
||||
type networkInterface interface {
|
||||
InterfaceGenerator
|
||||
Children() []networkInterface
|
||||
setConfigDepth(int)
|
||||
}
|
||||
|
||||
type logicalInterface struct {
|
||||
name string
|
||||
config configMethod
|
||||
children []InterfaceGenerator
|
||||
children []networkInterface
|
||||
configDepth int
|
||||
}
|
||||
|
||||
func (i *logicalInterface) Network() string {
|
||||
@@ -48,6 +56,22 @@ func (i *logicalInterface) Network() string {
|
||||
return config
|
||||
}
|
||||
|
||||
func (i *logicalInterface) Link() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *logicalInterface) Filename() string {
|
||||
return fmt.Sprintf("%02x-%s", i.configDepth, i.name)
|
||||
}
|
||||
|
||||
func (i *logicalInterface) Children() []networkInterface {
|
||||
return i.children
|
||||
}
|
||||
|
||||
func (i *logicalInterface) setConfigDepth(depth int) {
|
||||
i.configDepth = depth
|
||||
}
|
||||
|
||||
type physicalInterface struct {
|
||||
logicalInterface
|
||||
}
|
||||
@@ -60,10 +84,6 @@ func (p *physicalInterface) Netdev() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *physicalInterface) Link() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type bondInterface struct {
|
||||
logicalInterface
|
||||
slaves []string
|
||||
@@ -77,10 +97,6 @@ func (b *bondInterface) Netdev() string {
|
||||
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
||||
}
|
||||
|
||||
func (b *bondInterface) Link() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type vlanInterface struct {
|
||||
logicalInterface
|
||||
id int
|
||||
@@ -92,102 +108,146 @@ func (v *vlanInterface) Name() string {
|
||||
}
|
||||
|
||||
func (v *vlanInterface) Netdev() string {
|
||||
return fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n\n[VLAN]\nId=%d\n", v.name, v.id)
|
||||
}
|
||||
|
||||
func (v *vlanInterface) Link() string {
|
||||
return ""
|
||||
config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
|
||||
switch c := v.config.(type) {
|
||||
case configMethodStatic:
|
||||
if c.hwaddress != nil {
|
||||
config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress)
|
||||
}
|
||||
case configMethodDHCP:
|
||||
if c.hwaddress != nil {
|
||||
config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress)
|
||||
}
|
||||
}
|
||||
config += fmt.Sprintf("\n[VLAN]\nId=%d\n", v.id)
|
||||
return config
|
||||
}
|
||||
|
||||
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
||||
bondStanzas := make(map[string]*stanzaInterface)
|
||||
physicalStanzas := make(map[string]*stanzaInterface)
|
||||
vlanStanzas := make(map[string]*stanzaInterface)
|
||||
interfaceMap := createInterfaces(stanzas)
|
||||
linkAncestors(interfaceMap)
|
||||
markConfigDepths(interfaceMap)
|
||||
|
||||
interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
|
||||
for _, iface := range interfaceMap {
|
||||
interfaces = append(interfaces, iface)
|
||||
}
|
||||
|
||||
return interfaces
|
||||
}
|
||||
|
||||
func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
|
||||
interfaceMap := make(map[string]networkInterface)
|
||||
for _, iface := range stanzas {
|
||||
switch iface.kind {
|
||||
case interfaceBond:
|
||||
bondStanzas[iface.name] = iface
|
||||
case interfacePhysical:
|
||||
physicalStanzas[iface.name] = iface
|
||||
case interfaceVLAN:
|
||||
vlanStanzas[iface.name] = iface
|
||||
interfaceMap[iface.name] = &bondInterface{
|
||||
logicalInterface{
|
||||
name: iface.name,
|
||||
config: iface.configMethod,
|
||||
children: []networkInterface{},
|
||||
},
|
||||
iface.options["slaves"],
|
||||
}
|
||||
for _, slave := range iface.options["slaves"] {
|
||||
if _, ok := interfaceMap[slave]; !ok {
|
||||
interfaceMap[slave] = &physicalInterface{
|
||||
logicalInterface{
|
||||
name: slave,
|
||||
config: configMethodManual{},
|
||||
children: []networkInterface{},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
physicals := make(map[string]*physicalInterface)
|
||||
for _, p := range physicalStanzas {
|
||||
if _, ok := p.configMethod.(configMethodLoopback); ok {
|
||||
case interfacePhysical:
|
||||
if _, ok := iface.configMethod.(configMethodLoopback); ok {
|
||||
continue
|
||||
}
|
||||
physicals[p.name] = &physicalInterface{
|
||||
interfaceMap[iface.name] = &physicalInterface{
|
||||
logicalInterface{
|
||||
name: p.name,
|
||||
config: p.configMethod,
|
||||
children: []InterfaceGenerator{},
|
||||
name: iface.name,
|
||||
config: iface.configMethod,
|
||||
children: []networkInterface{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
case interfaceVLAN:
|
||||
var rawDevice string
|
||||
id, _ := strconv.Atoi(v.options["id"][0])
|
||||
if device := v.options["raw_device"]; len(device) == 1 {
|
||||
id, _ := strconv.Atoi(iface.options["id"][0])
|
||||
if device := iface.options["raw_device"]; len(device) == 1 {
|
||||
rawDevice = device[0]
|
||||
}
|
||||
vlans[v.name] = &vlanInterface{
|
||||
if _, ok := interfaceMap[rawDevice]; !ok {
|
||||
interfaceMap[rawDevice] = &physicalInterface{
|
||||
logicalInterface{
|
||||
name: v.name,
|
||||
config: v.configMethod,
|
||||
children: []InterfaceGenerator{},
|
||||
name: rawDevice,
|
||||
config: configMethodManual{},
|
||||
children: []networkInterface{},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
interfaceMap[iface.name] = &vlanInterface{
|
||||
logicalInterface{
|
||||
name: iface.name,
|
||||
config: iface.configMethod,
|
||||
children: []networkInterface{},
|
||||
},
|
||||
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
|
||||
return interfaceMap
|
||||
}
|
||||
|
||||
func linkAncestors(interfaceMap map[string]networkInterface) {
|
||||
for _, iface := range interfaceMap {
|
||||
switch i := iface.(type) {
|
||||
case *vlanInterface:
|
||||
if parent, ok := interfaceMap[i.rawDevice]; ok {
|
||||
switch p := parent.(type) {
|
||||
case *physicalInterface:
|
||||
p.children = append(p.children, iface)
|
||||
case *bondInterface:
|
||||
p.children = append(p.children, iface)
|
||||
}
|
||||
}
|
||||
case *bondInterface:
|
||||
for _, slave := range i.slaves {
|
||||
if parent, ok := interfaceMap[slave]; ok {
|
||||
switch p := parent.(type) {
|
||||
case *physicalInterface:
|
||||
p.children = append(p.children, iface)
|
||||
case *bondInterface:
|
||||
p.children = append(p.children, iface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func markConfigDepths(interfaceMap map[string]networkInterface) {
|
||||
rootInterfaceMap := make(map[string]networkInterface)
|
||||
for k, v := range interfaceMap {
|
||||
rootInterfaceMap[k] = v
|
||||
}
|
||||
|
||||
for _, iface := range interfaceMap {
|
||||
for _, child := range iface.Children() {
|
||||
delete(rootInterfaceMap, child.Name())
|
||||
}
|
||||
}
|
||||
for _, iface := range rootInterfaceMap {
|
||||
setDepth(iface, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func setDepth(iface networkInterface, depth int) {
|
||||
iface.setConfigDepth(depth)
|
||||
for _, child := range iface.Children() {
|
||||
setDepth(child, depth+1)
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ func TestPhysicalInterfaceLink(t *testing.T) {
|
||||
func TestPhysicalInterfaceNetwork(t *testing.T) {
|
||||
p := physicalInterface{logicalInterface{
|
||||
name: "testname",
|
||||
children: []InterfaceGenerator{
|
||||
children: []networkInterface{
|
||||
&bondInterface{
|
||||
logicalInterface{
|
||||
name: "testbond1",
|
||||
@@ -96,7 +96,7 @@ func TestBondInterfaceNetwork(t *testing.T) {
|
||||
logicalInterface{
|
||||
name: "testname",
|
||||
config: configMethodDHCP{},
|
||||
children: []InterfaceGenerator{
|
||||
children: []networkInterface{
|
||||
&bondInterface{
|
||||
logicalInterface{
|
||||
name: "testbond1",
|
||||
@@ -143,16 +143,26 @@ func TestVLANInterfaceName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVLANInterfaceNetdev(t *testing.T) {
|
||||
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
|
||||
netdev := `[NetDev]
|
||||
Kind=vlan
|
||||
Name=testname
|
||||
|
||||
[VLAN]
|
||||
Id=1
|
||||
`
|
||||
if v.Netdev() != netdev {
|
||||
t.FailNow()
|
||||
for _, tt := range []struct {
|
||||
i vlanInterface
|
||||
l string
|
||||
}{
|
||||
{
|
||||
vlanInterface{logicalInterface{name: "testname"}, 1, ""},
|
||||
"[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n",
|
||||
},
|
||||
{
|
||||
vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
|
||||
"[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
|
||||
},
|
||||
{
|
||||
vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
|
||||
"[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
|
||||
},
|
||||
} {
|
||||
if tt.i.Netdev() != tt.l {
|
||||
t.Fatalf("bad netdev config (%q): got %q, want %q", tt.i, tt.i.Netdev(), tt.l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +234,80 @@ func TestBuildInterfacesLo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInterfacesBlindBond(t *testing.T) {
|
||||
stanzas := []*stanzaInterface{
|
||||
{
|
||||
name: "bond0",
|
||||
kind: interfaceBond,
|
||||
auto: false,
|
||||
configMethod: configMethodManual{},
|
||||
options: map[string][]string{
|
||||
"slaves": []string{"eth0"},
|
||||
},
|
||||
},
|
||||
}
|
||||
interfaces := buildInterfaces(stanzas)
|
||||
bond0 := &bondInterface{
|
||||
logicalInterface{
|
||||
name: "bond0",
|
||||
config: configMethodManual{},
|
||||
children: []networkInterface{},
|
||||
configDepth: 1,
|
||||
},
|
||||
[]string{"eth0"},
|
||||
}
|
||||
eth0 := &physicalInterface{
|
||||
logicalInterface{
|
||||
name: "eth0",
|
||||
config: configMethodManual{},
|
||||
children: []networkInterface{bond0},
|
||||
configDepth: 0,
|
||||
},
|
||||
}
|
||||
expect := []InterfaceGenerator{bond0, eth0}
|
||||
if !reflect.DeepEqual(interfaces, expect) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInterfacesBlindVLAN(t *testing.T) {
|
||||
stanzas := []*stanzaInterface{
|
||||
{
|
||||
name: "vlan0",
|
||||
kind: interfaceVLAN,
|
||||
auto: false,
|
||||
configMethod: configMethodManual{},
|
||||
options: map[string][]string{
|
||||
"id": []string{"0"},
|
||||
"raw_device": []string{"eth0"},
|
||||
},
|
||||
},
|
||||
}
|
||||
interfaces := buildInterfaces(stanzas)
|
||||
vlan0 := &vlanInterface{
|
||||
logicalInterface{
|
||||
name: "vlan0",
|
||||
config: configMethodManual{},
|
||||
children: []networkInterface{},
|
||||
configDepth: 1,
|
||||
},
|
||||
0,
|
||||
"eth0",
|
||||
}
|
||||
eth0 := &physicalInterface{
|
||||
logicalInterface{
|
||||
name: "eth0",
|
||||
config: configMethodManual{},
|
||||
children: []networkInterface{vlan0},
|
||||
configDepth: 0,
|
||||
},
|
||||
}
|
||||
expect := []InterfaceGenerator{eth0, vlan0}
|
||||
if !reflect.DeepEqual(interfaces, expect) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInterfaces(t *testing.T) {
|
||||
stanzas := []*stanzaInterface{
|
||||
&stanzaInterface{
|
||||
@@ -277,7 +361,8 @@ func TestBuildInterfaces(t *testing.T) {
|
||||
logicalInterface{
|
||||
name: "vlan1",
|
||||
config: configMethodManual{},
|
||||
children: []InterfaceGenerator{},
|
||||
children: []networkInterface{},
|
||||
configDepth: 2,
|
||||
},
|
||||
1,
|
||||
"bond0",
|
||||
@@ -286,7 +371,8 @@ func TestBuildInterfaces(t *testing.T) {
|
||||
logicalInterface{
|
||||
name: "vlan0",
|
||||
config: configMethodManual{},
|
||||
children: []InterfaceGenerator{},
|
||||
children: []networkInterface{},
|
||||
configDepth: 1,
|
||||
},
|
||||
0,
|
||||
"eth0",
|
||||
@@ -295,7 +381,8 @@ func TestBuildInterfaces(t *testing.T) {
|
||||
logicalInterface{
|
||||
name: "bond1",
|
||||
config: configMethodManual{},
|
||||
children: []InterfaceGenerator{},
|
||||
children: []networkInterface{},
|
||||
configDepth: 2,
|
||||
},
|
||||
[]string{"bond0"},
|
||||
}
|
||||
@@ -303,7 +390,8 @@ func TestBuildInterfaces(t *testing.T) {
|
||||
logicalInterface{
|
||||
name: "bond0",
|
||||
config: configMethodManual{},
|
||||
children: []InterfaceGenerator{vlan1, bond1},
|
||||
children: []networkInterface{bond1, vlan1},
|
||||
configDepth: 1,
|
||||
},
|
||||
[]string{"eth0"},
|
||||
}
|
||||
@@ -311,7 +399,8 @@ func TestBuildInterfaces(t *testing.T) {
|
||||
logicalInterface{
|
||||
name: "eth0",
|
||||
config: configMethodManual{},
|
||||
children: []InterfaceGenerator{vlan0, bond0},
|
||||
children: []networkInterface{bond0, vlan0},
|
||||
configDepth: 0,
|
||||
},
|
||||
}
|
||||
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
||||
@@ -319,3 +408,19 @@ func TestBuildInterfaces(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilename(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
i logicalInterface
|
||||
f string
|
||||
}{
|
||||
{logicalInterface{name: "iface", configDepth: 0}, "00-iface"},
|
||||
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
|
||||
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
|
||||
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
|
||||
} {
|
||||
if tt.i.Filename() != tt.f {
|
||||
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -40,13 +40,16 @@ type configMethodStatic struct {
|
||||
address net.IPNet
|
||||
nameservers []net.IP
|
||||
routes []route
|
||||
hwaddress net.HardwareAddr
|
||||
}
|
||||
|
||||
type configMethodLoopback struct{}
|
||||
|
||||
type configMethodManual struct{}
|
||||
|
||||
type configMethodDHCP struct{}
|
||||
type configMethodDHCP struct {
|
||||
hwaddress net.HardwareAddr
|
||||
}
|
||||
|
||||
func parseStanzas(lines []string) (stanzas []stanza, err error) {
|
||||
rawStanzas, err := splitStanzas(lines)
|
||||
@@ -96,7 +99,7 @@ func splitStanzas(lines []string) ([][]string, error) {
|
||||
} else if curStanza != nil {
|
||||
curStanza = append(curStanza, line)
|
||||
} else {
|
||||
return nil, fmt.Errorf("missing stanza start '%s'", line)
|
||||
return nil, fmt.Errorf("missing stanza start %q", line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +145,7 @@ func parseStanza(rawStanza []string) (stanza, error) {
|
||||
case "iface":
|
||||
return parseInterfaceStanza(attributes, rawStanza[1:])
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown stanza '%s'", kind)
|
||||
return nil, fmt.Errorf("unknown stanza %q", kind)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +207,7 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
||||
}
|
||||
}
|
||||
if config.address.IP == nil || config.address.Mask == nil {
|
||||
return nil, fmt.Errorf("malformed static network config for '%s'", iface)
|
||||
return nil, fmt.Errorf("malformed static network config for %q", iface)
|
||||
}
|
||||
if gateways, ok := optionMap["gateway"]; ok {
|
||||
if len(gateways) == 1 {
|
||||
@@ -217,6 +220,11 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
||||
})
|
||||
}
|
||||
}
|
||||
if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
|
||||
config.hwaddress = hwaddress
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
for _, nameserver := range optionMap["dns-nameservers"] {
|
||||
config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
|
||||
}
|
||||
@@ -245,9 +253,15 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
||||
case "manual":
|
||||
conf = configMethodManual{}
|
||||
case "dhcp":
|
||||
conf = configMethodDHCP{}
|
||||
config := configMethodDHCP{}
|
||||
if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
|
||||
config.hwaddress = hwaddress
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
conf = config
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid config method '%s'", confMethod)
|
||||
return nil, fmt.Errorf("invalid config method %q", confMethod)
|
||||
}
|
||||
|
||||
if _, ok := optionMap["vlan_raw_device"]; ok {
|
||||
@@ -265,6 +279,19 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
||||
return parsePhysicalStanza(iface, conf, attributes, optionMap)
|
||||
}
|
||||
|
||||
func parseHwaddress(options map[string][]string, iface string) (net.HardwareAddr, error) {
|
||||
if hwaddress, ok := options["hwaddress"]; ok && len(hwaddress) == 2 {
|
||||
switch hwaddress[0] {
|
||||
case "ether":
|
||||
if address, err := net.ParseMAC(hwaddress[1]); err == nil {
|
||||
return address, nil
|
||||
}
|
||||
return nil, fmt.Errorf("malformed hwaddress option for %q", iface)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
||||
options["slaves"] = options["bond-slaves"]
|
||||
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
||||
@@ -282,11 +309,11 @@ func parseVLANStanza(iface string, conf configMethod, attributes []string, optio
|
||||
} else if strings.HasPrefix(iface, "vlan") {
|
||||
id = strings.TrimPrefix(iface, "vlan")
|
||||
} else {
|
||||
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
||||
return nil, fmt.Errorf("malformed vlan name %q", iface)
|
||||
}
|
||||
|
||||
if _, err := strconv.Atoi(id); err != nil {
|
||||
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
||||
return nil, fmt.Errorf("malformed vlan name %q", iface)
|
||||
}
|
||||
options["id"] = []string{id}
|
||||
options["raw_device"] = options["vlan_raw_device"]
|
||||
|
@@ -42,6 +42,8 @@ func TestBadParseInterfaceStanza(t *testing.T) {
|
||||
{[]string{"eth", "inet", "static"}, []string{"netmask 255.255.255.0"}, "malformed static network config"},
|
||||
{[]string{"eth", "inet", "static"}, []string{"address invalid", "netmask 255.255.255.0"}, "malformed static network config"},
|
||||
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask invalid"}, "malformed static network config"},
|
||||
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask 255.255.255.0", "hwaddress ether NotAnAddress"}, "malformed hwaddress option"},
|
||||
{[]string{"eth", "inet", "dhcp"}, []string{"hwaddress ether NotAnAddress"}, "malformed hwaddress option"},
|
||||
} {
|
||||
_, err := parseInterfaceStanza(tt.in, tt.opts)
|
||||
if err == nil || !strings.HasPrefix(err.Error(), tt.e) {
|
||||
@@ -407,7 +409,46 @@ func TestParseInterfaceStanzaOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInterfaceStazaBond(t *testing.T) {
|
||||
func TestParseInterfaceStanzaHwaddress(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
attr []string
|
||||
opt []string
|
||||
hw net.HardwareAddr
|
||||
}{
|
||||
{
|
||||
[]string{"mybond", "inet", "dhcp"},
|
||||
[]string{},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{"mybond", "inet", "dhcp"},
|
||||
[]string{"hwaddress ether 00:01:02:03:04:05"},
|
||||
net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
|
||||
},
|
||||
{
|
||||
[]string{"mybond", "inet", "static"},
|
||||
[]string{"hwaddress ether 00:01:02:03:04:05", "address 192.168.1.100", "netmask 255.255.255.0"},
|
||||
net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
|
||||
},
|
||||
} {
|
||||
iface, err := parseInterfaceStanza(tt.attr, tt.opt)
|
||||
if err != nil {
|
||||
t.Fatalf("error in parseInterfaceStanza (%q, %q): %q", tt.attr, tt.opt, err)
|
||||
}
|
||||
switch c := iface.configMethod.(type) {
|
||||
case configMethodStatic:
|
||||
if !reflect.DeepEqual(c.hwaddress, tt.hw) {
|
||||
t.Fatalf("bad hwaddress (%q, %q): got %q, want %q", tt.attr, tt.opt, c.hwaddress, tt.hw)
|
||||
}
|
||||
case configMethodDHCP:
|
||||
if !reflect.DeepEqual(c.hwaddress, tt.hw) {
|
||||
t.Fatalf("bad hwaddress (%q, %q): got %q, want %q", tt.attr, tt.opt, c.hwaddress, tt.hw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInterfaceStanzaBond(t *testing.T) {
|
||||
iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"})
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
@@ -417,7 +458,7 @@ func TestParseInterfaceStazaBond(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInterfaceStazaVLANName(t *testing.T) {
|
||||
func TestParseInterfaceStanzaVLANName(t *testing.T) {
|
||||
iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
@@ -427,7 +468,7 @@ func TestParseInterfaceStazaVLANName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseInterfaceStazaVLANOption(t *testing.T) {
|
||||
func TestParseInterfaceStanzaVLANOption(t *testing.T) {
|
||||
iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"})
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
|
@@ -18,6 +18,28 @@ const (
|
||||
HTTP_4xx = 4
|
||||
)
|
||||
|
||||
type Err error
|
||||
|
||||
type ErrTimeout struct {
|
||||
Err
|
||||
}
|
||||
|
||||
type ErrNotFound struct {
|
||||
Err
|
||||
}
|
||||
|
||||
type ErrInvalid struct {
|
||||
Err
|
||||
}
|
||||
|
||||
type ErrServer struct {
|
||||
Err
|
||||
}
|
||||
|
||||
type ErrNetwork struct {
|
||||
Err
|
||||
}
|
||||
|
||||
type HttpClient struct {
|
||||
// Maximum exp backoff duration. Defaults to 5 seconds
|
||||
MaxBackoff time.Duration
|
||||
@@ -31,18 +53,42 @@ type HttpClient struct {
|
||||
|
||||
// Whether or not to skip TLS verification. Defaults to false
|
||||
SkipTLS bool
|
||||
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewHttpClient() *HttpClient {
|
||||
return &HttpClient{
|
||||
hc := &HttpClient{
|
||||
MaxBackoff: time.Second * 5,
|
||||
MaxRetries: 15,
|
||||
Timeout: time.Duration(2) * time.Second,
|
||||
SkipTLS: false,
|
||||
}
|
||||
|
||||
// We need to create our own client in order to add timeout support.
|
||||
// TODO(c4milo) Replace it once Go 1.3 is officially used by CoreOS
|
||||
// More info: https://code.google.com/p/go/source/detail?r=ada6f2d5f99f
|
||||
hc.client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: hc.SkipTLS,
|
||||
},
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
deadline := time.Now().Add(hc.Timeout)
|
||||
c, err := net.DialTimeout(network, addr, hc.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.SetDeadline(deadline)
|
||||
return c, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return hc
|
||||
}
|
||||
|
||||
func expBackoff(interval, max time.Duration) time.Duration {
|
||||
func ExpBackoff(interval, max time.Duration) time.Duration {
|
||||
interval = interval * 2
|
||||
if interval > max {
|
||||
interval = max
|
||||
@@ -50,74 +96,61 @@ func expBackoff(interval, max time.Duration) time.Duration {
|
||||
return interval
|
||||
}
|
||||
|
||||
// Fetches a given URL with support for exponential backoff and maximum retries
|
||||
func (h *HttpClient) Get(rawurl string) ([]byte, error) {
|
||||
// GetRetry fetches a given URL with support for exponential backoff and maximum retries
|
||||
func (h *HttpClient) GetRetry(rawurl string) ([]byte, error) {
|
||||
if rawurl == "" {
|
||||
return nil, errors.New("URL is empty. Skipping.")
|
||||
return nil, ErrInvalid{errors.New("URL is empty. Skipping.")}
|
||||
}
|
||||
|
||||
url, err := neturl.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, ErrInvalid{err}
|
||||
}
|
||||
|
||||
// Unfortunately, url.Parse is too generic to throw errors if a URL does not
|
||||
// have a valid HTTP scheme. So, we have to do this extra validation
|
||||
if !strings.HasPrefix(url.Scheme, "http") {
|
||||
return nil, fmt.Errorf("URL %s does not have a valid HTTP scheme. Skipping.", rawurl)
|
||||
return nil, ErrInvalid{fmt.Errorf("URL %s does not have a valid HTTP scheme. Skipping.", rawurl)}
|
||||
}
|
||||
|
||||
dataURL := url.String()
|
||||
|
||||
// We need to create our own client in order to add timeout support.
|
||||
// TODO(c4milo) Replace it once Go 1.3 is officially used by CoreOS
|
||||
// More info: https://code.google.com/p/go/source/detail?r=ada6f2d5f99f
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: h.SkipTLS,
|
||||
},
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
deadline := time.Now().Add(h.Timeout)
|
||||
c, err := net.DialTimeout(network, addr, h.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.SetDeadline(deadline)
|
||||
return c, nil
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
duration := 50 * time.Millisecond
|
||||
for retry := 1; retry <= h.MaxRetries; retry++ {
|
||||
log.Printf("Fetching data from %s. Attempt #%d", dataURL, retry)
|
||||
|
||||
resp, err := client.Get(dataURL)
|
||||
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
status := resp.StatusCode / 100
|
||||
|
||||
if status == HTTP_2xx {
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
data, err := h.Get(dataURL)
|
||||
switch err.(type) {
|
||||
case ErrNetwork:
|
||||
log.Printf(err.Error())
|
||||
case ErrServer:
|
||||
log.Printf(err.Error())
|
||||
case ErrNotFound:
|
||||
return data, err
|
||||
default:
|
||||
return data, err
|
||||
}
|
||||
|
||||
if status == HTTP_4xx {
|
||||
return nil, fmt.Errorf("Not found. HTTP status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
log.Printf("Server error. HTTP status code: %d", resp.StatusCode)
|
||||
} else {
|
||||
log.Printf("Unable to fetch data: %s", err.Error())
|
||||
}
|
||||
|
||||
duration = expBackoff(duration, h.MaxBackoff)
|
||||
duration = ExpBackoff(duration, h.MaxBackoff)
|
||||
log.Printf("Sleeping for %v...", duration)
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unable to fetch data. Maximum retries reached: %d", h.MaxRetries)
|
||||
return nil, ErrTimeout{fmt.Errorf("Unable to fetch data. Maximum retries reached: %d", h.MaxRetries)}
|
||||
}
|
||||
|
||||
func (h *HttpClient) Get(dataURL string) ([]byte, error) {
|
||||
if resp, err := h.client.Get(dataURL); err == nil {
|
||||
defer resp.Body.Close()
|
||||
switch resp.StatusCode / 100 {
|
||||
case HTTP_2xx:
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
case HTTP_4xx:
|
||||
return nil, ErrNotFound{fmt.Errorf("Not found. HTTP status code: %d", resp.StatusCode)}
|
||||
default:
|
||||
return nil, ErrServer{fmt.Errorf("Server error. HTTP status code: %d", resp.StatusCode)}
|
||||
}
|
||||
} else {
|
||||
return nil, ErrNetwork{fmt.Errorf("Unable to fetch data: %s", err.Error())}
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ func TestExpBackoff(t *testing.T) {
|
||||
duration := time.Millisecond
|
||||
max := time.Hour
|
||||
for i := 0; i < math.MaxUint16; i++ {
|
||||
duration = expBackoff(duration, max)
|
||||
duration = ExpBackoff(duration, max)
|
||||
if duration < 0 {
|
||||
t.Fatalf("duration too small: %v %v", duration, i)
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func TestGetURLExpBackOff(t *testing.T) {
|
||||
ts := httptest.NewServer(mux)
|
||||
defer ts.Close()
|
||||
|
||||
data, err := client.Get(ts.URL)
|
||||
data, err := client.GetRetry(ts.URL)
|
||||
if err != nil {
|
||||
t.Errorf("Test case %d produced error: %v", i, err)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func TestGetURL4xx(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
_, err := client.Get(ts.URL)
|
||||
_, err := client.GetRetry(ts.URL)
|
||||
if err == nil {
|
||||
t.Errorf("Incorrect result\ngot: %s\nwant: %s", err.Error(), "Not found. HTTP status code: 404")
|
||||
}
|
||||
@@ -107,7 +107,7 @@ coreos:
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
data, err := client.Get(ts.URL)
|
||||
data, err := client.GetRetry(ts.URL)
|
||||
if err != nil {
|
||||
t.Errorf("Incorrect result\ngot: %v\nwant: %v", err, nil)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func TestGetMalformedURL(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := client.Get(test.url)
|
||||
_, err := client.GetRetry(test.url)
|
||||
if err == nil || err.Error() != test.want {
|
||||
t.Errorf("Incorrect result\ngot: %v\nwant: %v", err, test.want)
|
||||
}
|
||||
|
@@ -36,7 +36,9 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
||||
sysInterfaceMap := make(map[string]*net.Interface)
|
||||
if systemInterfaces, err := net.Interfaces(); err == nil {
|
||||
for _, iface := range systemInterfaces {
|
||||
sysInterfaceMap[iface.Name] = &iface
|
||||
// Need a copy of the interface so we can take the address
|
||||
temp := iface
|
||||
sysInterfaceMap[temp.Name] = &temp
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
@@ -64,15 +66,15 @@ func restartNetworkd() error {
|
||||
|
||||
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
||||
for _, iface := range interfaces {
|
||||
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Name()))
|
||||
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Filename()))
|
||||
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
||||
return err
|
||||
}
|
||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Name()))
|
||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Filename()))
|
||||
if err := writeConfig(filename, iface.Link()); err != nil {
|
||||
return err
|
||||
}
|
||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Name()))
|
||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Filename()))
|
||||
if err := writeConfig(filename, iface.Network()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
2
test
2
test
@@ -18,7 +18,7 @@ declare -a TESTPKGS=(initialize system datasource pkg network)
|
||||
if [ -z "$PKG" ]; then
|
||||
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
|
||||
# prepend repo path to each package
|
||||
TESTPKGS=${TESTPKGS[@]/#/${REPO_PATH}/}
|
||||
TESTPKGS="${TESTPKGS[@]/#/${REPO_PATH}/} ./"
|
||||
else
|
||||
GOFMTPATH="$TESTPKGS"
|
||||
# strip out slashes and dots from PKG=./foo/
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[Unit]
|
||||
Description=Load cloud-config from /media/configdrive
|
||||
Requires=coreos-setup-environment.service
|
||||
After=coreos-setup-environment.service
|
||||
After=coreos-setup-environment.service system-config.target
|
||||
Before=user-config.target
|
||||
|
||||
# HACK: work around ordering between config drive and ec2 metadata It is
|
||||
@@ -13,7 +13,6 @@ Before=user-config.target
|
||||
# systemd knows about the ordering as early as possible.
|
||||
# coreos-cloudinit could implement a simple lock but that cannot be used
|
||||
# until after the systemd dbus calls are made non-blocking.
|
||||
After=system-cloudinit@usr-share-oem-cloud\x2dconfig.yml.service
|
||||
After=ec2-cloudinit.service
|
||||
|
||||
[Service]
|
||||
|
Reference in New Issue
Block a user