Compare commits

..

25 Commits

Author SHA1 Message Date
Alex Crawford
685a38c6c8 coreos-cloudinit: bump to 0.9.5 2014-09-02 10:10:41 -07:00
Alex Crawford
9d15f2cfaf Merge pull request #213 from crawford/digitalocean
digitalocean: Add support for DigitalOcean
2014-09-01 16:55:12 -07:00
Alex Crawford
2134fce791 digitalocean: Add tests for network unit generation 2014-09-01 16:53:15 -07:00
Alex Crawford
3abd6b2225 digitalocean: Add DigitalOcean metadata service
Move debian-related processing into its own file.
2014-09-01 16:53:15 -07:00
Alex Crawford
2a8e6c9566 network: Fall back to MAC address if there is no name 2014-09-01 09:29:45 -07:00
Alex Crawford
abe43537da metadata: Merge the network config 2014-09-01 09:29:45 -07:00
Jonathan Boulle
3a550af651 Merge pull request #216 from robszumski/patch-2
docs: fix broken link to fleet docs
2014-08-29 11:22:13 -07:00
Rob Szumski
61c3a0eb2d docs: fix broken link to fleet docs 2014-08-29 11:17:05 -07:00
Brian Waldon
480176bc11 Merge pull request #214 from bcwaldon/clarify-write-files
doc: clarify docs around write_files
2014-08-28 20:24:11 -07:00
Brian Waldon
01b18eb551 squash: fix spacing 2014-08-28 13:48:58 -07:00
Brian Waldon
970ef435b6 doc: clarify docs around write_files 2014-08-28 13:33:59 -07:00
Alex Crawford
e8d0021140 Merge pull request #212 from crawford/metadata
refactor: Refactor metadata and datasources to be more testable
2014-08-26 18:45:10 -07:00
Alex Crawford
e9ec78ac6f test: Refactor interface tests a little 2014-08-26 13:18:46 -07:00
Alex Crawford
4a2e417781 network: Add support for multiple addresses and HW addresses
Logical Interfaces can be assigned a hardware address allowing them
to match on MAC address. The static config method also needs to
support specifying multiple addresses.
2014-08-26 13:07:38 -07:00
Alex Crawford
604ef7ecb4 datasource: Add FetchNetworkConfig
FetchNetworkConfig is currently only used by ConfigDrive to read the
network config file from the disk.
2014-08-26 13:04:43 -07:00
Alex Crawford
c39dd5cc67 networkd: Fix bug causing bonding to always be loaded 2014-08-26 13:04:21 -07:00
Alex Crawford
a923161f4a metadata: Refactor common parts out of ec2 2014-08-26 12:02:56 -07:00
Alex Crawford
e59e2f6cd5 Merge pull request #210 from crawford/test
test: Add gofmt to test
2014-08-25 17:04:04 -07:00
Alex Crawford
e90fe3eba8 test: Add gofmt to test 2014-08-25 12:48:52 -07:00
Alex Crawford
fb0187b197 gofmt: sort 2014-08-25 12:35:40 -07:00
Michael Marineau
6babe74716 Merge pull request #209 from marineam/go13
travis: enable testing under go 1.3
2014-08-25 12:26:23 -07:00
Michael Marineau
b1e88284ca travis: enable testing under go 1.3 2014-08-25 12:21:07 -07:00
Alex Crawford
18a65f7dac Merge pull request #208 from crawford/go
test: Fix tests for Go 1.3
2014-08-25 12:19:52 -07:00
Alex Crawford
0c212c72c9 test: Fix tests for Go 1.3 2014-08-25 12:01:27 -07:00
Alex Crawford
6a800d8cc0 coreos-cloudinit: bump to 0.9.4+git 2014-08-24 18:41:20 -07:00
30 changed files with 1284 additions and 523 deletions

View File

@@ -1,5 +1,7 @@
language: go language: go
go: 1.2 go:
- 1.3
- 1.2
install: install:
- go get code.google.com/p/go.tools/cmd/cover - go get code.google.com/p/go.tools/cmd/cover

View File

@@ -95,7 +95,7 @@ Environment="FLEET_METADATA=region=us-west"
For more information on fleet configuration, see the [fleet documentation][fleet-config]. For more information on fleet configuration, see the [fleet documentation][fleet-config].
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/configuration.md [fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md
#### update #### update
@@ -298,7 +298,8 @@ users:
### write_files ### write_files
The `write-file` parameter defines a list of files to create on the local filesystem. Each file is represented as an associative array which has the following keys: The `write_files` directive defines a set of files to create on the local filesystem.
Each item in the list may have the following keys:
- **path**: Absolute location on disk where contents should be written - **path**: Absolute location on disk where contents should be written
- **content**: Data to write at the provided `path` - **content**: Data to write at the provided `path`
@@ -311,11 +312,16 @@ The **content** field must represent exactly what should be written to disk.
```yaml ```yaml
#cloud-config #cloud-config
write_files: write_files:
- path: /etc/fleet/fleet.conf - path: /etc/resolv.conf
permissions: 0644 permissions: 0644
owner: root
content: | content: |
verbosity=1 nameserver 8.8.8.8
metadata="region=us-west,type=ssd" - path: /etc/motd
permissions: 0644
owner: root
content: |
Good news, everyone!
``` ```
### manage_etc_hosts ### manage_etc_hosts

View File

@@ -11,6 +11,7 @@ import (
"github.com/coreos/coreos-cloudinit/datasource/configdrive" "github.com/coreos/coreos-cloudinit/datasource/configdrive"
"github.com/coreos/coreos-cloudinit/datasource/file" "github.com/coreos/coreos-cloudinit/datasource/file"
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma" "github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2" "github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline" "github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
"github.com/coreos/coreos-cloudinit/datasource/url" "github.com/coreos/coreos-cloudinit/datasource/url"
@@ -20,7 +21,7 @@ import (
) )
const ( const (
version = "0.9.4" version = "0.9.5"
datasourceInterval = 100 * time.Millisecond datasourceInterval = 100 * time.Millisecond
datasourceMaxInterval = 30 * time.Second datasourceMaxInterval = 30 * time.Second
datasourceTimeout = 5 * time.Minute datasourceTimeout = 5 * time.Minute
@@ -35,6 +36,7 @@ var (
metadataService bool metadataService bool
ec2MetadataService string ec2MetadataService string
cloudSigmaMetadataService bool cloudSigmaMetadataService bool
digitalOceanMetadataService string
url string url string
procCmdLine bool procCmdLine bool
} }
@@ -49,11 +51,12 @@ func init() {
flag.StringVar(&sources.file, "from-file", "", "Read user-data from provided file") 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.StringVar(&sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service") flag.BoolVar(&sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download data from the provided metadata service") flag.StringVar(&sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context") flag.BoolVar(&sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
flag.StringVar(&sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
flag.StringVar(&sources.url, "from-url", "", "Download user-data from provided url") 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>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag)) 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>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.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(&convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
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")
flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name") flag.StringVar(&sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
} }
@@ -73,16 +76,12 @@ func main() {
os.Exit(0) os.Exit(0)
} }
if convertNetconf != "" && sources.configDrive == "" {
fmt.Println("-convert-netconf flag requires -from-configdrive")
os.Exit(1)
}
switch convertNetconf { switch convertNetconf {
case "": case "":
case "debian": case "debian":
case "digitalocean":
default: default:
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian'\n", convertNetconf) fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", convertNetconf)
os.Exit(1) os.Exit(1)
} }
@@ -132,6 +131,17 @@ func main() {
fmt.Printf("Failed to parse meta-data: %v\n", err) fmt.Printf("Failed to parse meta-data: %v\n", err)
die() die()
} }
if ccm != nil {
fmt.Printf("Fetching network config from datasource of type %q\n", ds.Type())
netconfBytes, err := ds.FetchNetworkConfig(ccm.NetworkConfigPath)
if err != nil {
fmt.Printf("Failed fetching network config from datasource: %v\n", err)
die()
}
ccm.NetworkConfig = string(netconfBytes)
}
if ud, err := initialize.ParseUserData(userdata); err != nil { if ud, err := initialize.ParseUserData(userdata); err != nil {
fmt.Printf("Failed to parse user-data: %v\n", err) fmt.Printf("Failed to parse user-data: %v\n", err)
die() die()
@@ -198,6 +208,13 @@ func mergeCloudConfig(mdcc, udcc initialize.CloudConfig) (cc initialize.CloudCon
udcc.NetworkConfigPath = mdcc.NetworkConfigPath udcc.NetworkConfigPath = mdcc.NetworkConfigPath
} }
} }
if mdcc.NetworkConfig != "" {
if udcc.NetworkConfig != "" {
fmt.Printf("Warning: user-data NetworkConfig %s overrides metadata NetworkConfig %s\n", udcc.NetworkConfig, mdcc.NetworkConfig)
} else {
udcc.NetworkConfig = mdcc.NetworkConfig
}
}
return udcc return udcc
} }
@@ -223,6 +240,9 @@ func getDatasources() []datasource.Datasource {
if sources.cloudSigmaMetadataService { if sources.cloudSigmaMetadataService {
dss = append(dss, cloudsigma.NewServerContextService()) dss = append(dss, cloudsigma.NewServerContextService())
} }
if sources.digitalOceanMetadataService != "" {
dss = append(dss, digitalocean.NewDatasource(sources.digitalOceanMetadataService))
}
if sources.procCmdLine { if sources.procCmdLine {
dss = append(dss, proc_cmdline.NewDatasource()) dss = append(dss, proc_cmdline.NewDatasource())
} }

View File

@@ -12,6 +12,7 @@ func TestMergeCloudConfig(t *testing.T) {
SSHAuthorizedKeys: []string{"abc", "def"}, SSHAuthorizedKeys: []string{"abc", "def"},
Hostname: "foobar", Hostname: "foobar",
NetworkConfigPath: "/path/somewhere", NetworkConfigPath: "/path/somewhere",
NetworkConfig: `{}`,
} }
for i, tt := range []struct { for i, tt := range []struct {
udcc initialize.CloudConfig udcc initialize.CloudConfig
@@ -36,6 +37,7 @@ func TestMergeCloudConfig(t *testing.T) {
initialize.CloudConfig{ initialize.CloudConfig{
Hostname: "meta-hostname", Hostname: "meta-hostname",
NetworkConfigPath: "/path/meta", NetworkConfigPath: "/path/meta",
NetworkConfig: `{"hostname":"test"}`,
}, },
simplecc, simplecc,
}, },
@@ -45,6 +47,7 @@ func TestMergeCloudConfig(t *testing.T) {
SSHAuthorizedKeys: []string{"abc", "def"}, SSHAuthorizedKeys: []string{"abc", "def"},
Hostname: "user-hostname", Hostname: "user-hostname",
NetworkConfigPath: "/path/somewhere", NetworkConfigPath: "/path/somewhere",
NetworkConfig: `{"hostname":"test"}`,
}, },
initialize.CloudConfig{ initialize.CloudConfig{
SSHAuthorizedKeys: []string{"woof", "qux"}, SSHAuthorizedKeys: []string{"woof", "qux"},
@@ -54,6 +57,7 @@ func TestMergeCloudConfig(t *testing.T) {
SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"}, SSHAuthorizedKeys: []string{"abc", "def", "woof", "qux"},
Hostname: "user-hostname", Hostname: "user-hostname",
NetworkConfigPath: "/path/somewhere", NetworkConfigPath: "/path/somewhere",
NetworkConfig: `{"hostname":"test"}`,
}, },
}, },
{ {
@@ -64,11 +68,13 @@ func TestMergeCloudConfig(t *testing.T) {
initialize.CloudConfig{ initialize.CloudConfig{
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"}, SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
NetworkConfigPath: "/dev/fun", NetworkConfigPath: "/dev/fun",
NetworkConfig: `{"hostname":"test"}`,
}, },
initialize.CloudConfig{ initialize.CloudConfig{
Hostname: "supercool", Hostname: "supercool",
SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"}, SSHAuthorizedKeys: []string{"zaphod", "beeblebrox"},
NetworkConfigPath: "/dev/fun", NetworkConfigPath: "/dev/fun",
NetworkConfig: `{"hostname":"test"}`,
}, },
}, },
{ {
@@ -80,11 +86,13 @@ func TestMergeCloudConfig(t *testing.T) {
initialize.CloudConfig{ initialize.CloudConfig{
Hostname: "youyouyou", Hostname: "youyouyou",
NetworkConfigPath: "meta-meta-yo", NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
}, },
initialize.CloudConfig{ initialize.CloudConfig{
Hostname: "mememe", Hostname: "mememe",
ManageEtcHosts: initialize.EtcHosts("lolz"), ManageEtcHosts: initialize.EtcHosts("lolz"),
NetworkConfigPath: "meta-meta-yo", NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
}, },
}, },
{ {
@@ -95,10 +103,12 @@ func TestMergeCloudConfig(t *testing.T) {
initialize.CloudConfig{ initialize.CloudConfig{
ManageEtcHosts: initialize.EtcHosts("lolz"), ManageEtcHosts: initialize.EtcHosts("lolz"),
NetworkConfigPath: "meta-meta-yo", NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
}, },
initialize.CloudConfig{ initialize.CloudConfig{
Hostname: "mememe", Hostname: "mememe",
NetworkConfigPath: "meta-meta-yo", NetworkConfigPath: "meta-meta-yo",
NetworkConfig: `{"hostname":"test"}`,
}, },
}, },
} { } {

View File

@@ -41,6 +41,10 @@ func (cd *configDrive) FetchUserdata() ([]byte, error) {
return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "user_data")) return cd.tryReadFile(path.Join(cd.openstackVersionRoot(), "user_data"))
} }
func (cd *configDrive) FetchNetworkConfig(filename string) ([]byte, error) {
return cd.tryReadFile(path.Join(cd.openstackRoot(), filename))
}
func (cd *configDrive) Type() string { func (cd *configDrive) Type() string {
return "cloud-drive" return "cloud-drive"
} }

View File

@@ -1,15 +1,11 @@
package datasource package datasource
const (
Ec2ApiVersion = "2009-04-04"
OpenstackApiVersion = "2012-08-10"
)
type Datasource interface { type Datasource interface {
IsAvailable() bool IsAvailable() bool
AvailabilityChanges() bool AvailabilityChanges() bool
ConfigRoot() string ConfigRoot() string
FetchMetadata() ([]byte, error) FetchMetadata() ([]byte, error)
FetchUserdata() ([]byte, error) FetchUserdata() ([]byte, error)
FetchNetworkConfig(string) ([]byte, error)
Type() string Type() string
} }

View File

@@ -34,6 +34,10 @@ func (f *localFile) FetchUserdata() ([]byte, error) {
return ioutil.ReadFile(f.path) return ioutil.ReadFile(f.path)
} }
func (f *localFile) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (f *localFile) Type() string { func (f *localFile) Type() string {
return "local-file" return "local-file"
} }

View File

@@ -126,6 +126,10 @@ func (scs *serverContextService) FetchUserdata() ([]byte, error) {
return []byte(userData), nil return []byte(userData), nil
} }
func (scs *serverContextService) FetchNetworkConfig(a string) ([]byte, error) {
return nil, nil
}
func isBase64Encoded(field string, userdata map[string]string) bool { func isBase64Encoded(field string, userdata map[string]string) bool {
base64Fields, ok := userdata["base64_fields"] base64Fields, ok := userdata["base64_fields"]
if !ok { if !ok {

View File

@@ -0,0 +1,107 @@
package digitalocean
import (
"encoding/json"
"strconv"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
)
const (
DefaultAddress = "http://169.254.169.254/"
apiVersion = "metadata/v1"
userdataUrl = apiVersion + "/user-data"
metadataPath = apiVersion + ".json"
)
type Address struct {
IPAddress string `json:"ip_address"`
Netmask string `json:"netmask"`
Cidr int `json:"cidr"`
Gateway string `json:"gateway"`
}
type Interface struct {
IPv4 *Address `json:"ipv4"`
IPv6 *Address `json:"ipv6"`
MAC string `json:"mac"`
Type string `json:"type"`
}
type Interfaces struct {
Public []Interface `json:"public"`
Private []Interface `json:"private"`
}
type DNS struct {
Nameservers []string `json:"nameservers"`
}
type Metadata struct {
Hostname string `json:"hostname"`
Interfaces Interfaces `json:"interfaces"`
PublicKeys []string `json:"public_keys"`
DNS DNS `json:"dns"`
}
type metadataService struct {
interfaces Interfaces
dns DNS
metadata.MetadataService
}
func NewDatasource(root string) *metadataService {
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
}
func (ms *metadataService) FetchMetadata() ([]byte, error) {
data, err := ms.FetchData(ms.MetadataUrl())
if err != nil || len(data) == 0 {
return []byte{}, err
}
var metadata Metadata
if err := json.Unmarshal(data, &metadata); err != nil {
return []byte{}, err
}
ms.interfaces = metadata.Interfaces
ms.dns = metadata.DNS
attrs := make(map[string]interface{})
if len(metadata.Interfaces.Public) > 0 {
if metadata.Interfaces.Public[0].IPv4 != nil {
attrs["public-ipv4"] = metadata.Interfaces.Public[0].IPv4.IPAddress
}
if metadata.Interfaces.Public[0].IPv6 != nil {
attrs["public-ipv6"] = metadata.Interfaces.Public[0].IPv6.IPAddress
}
}
if len(metadata.Interfaces.Private) > 0 {
if metadata.Interfaces.Private[0].IPv4 != nil {
attrs["local-ipv4"] = metadata.Interfaces.Private[0].IPv4.IPAddress
}
if metadata.Interfaces.Private[0].IPv6 != nil {
attrs["local-ipv6"] = metadata.Interfaces.Private[0].IPv6.IPAddress
}
}
attrs["hostname"] = metadata.Hostname
keys := make(map[string]string)
for i, key := range metadata.PublicKeys {
keys[strconv.Itoa(i)] = key
}
attrs["public_keys"] = keys
return json.Marshal(attrs)
}
func (ms metadataService) FetchNetworkConfig(filename string) ([]byte, error) {
return json.Marshal(Metadata{
Interfaces: ms.interfaces,
DNS: ms.dns,
})
}
func (ms metadataService) Type() string {
return "digitalocean-metadata-service"
}

View File

@@ -0,0 +1,99 @@
package digitalocean
import (
"bytes"
"fmt"
"testing"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
"github.com/coreos/coreos-cloudinit/pkg"
)
func TestType(t *testing.T) {
want := "digitalocean-metadata-service"
if kind := (metadataService{}).Type(); kind != want {
t.Fatalf("bad type: want %q, got %q", want, kind)
}
}
func TestFetchMetadata(t *testing.T) {
for _, tt := range []struct {
root string
metadataPath string
resources map[string]string
expect []byte
clientErr error
expectErr error
}{
{
root: "/",
metadataPath: "v1.json",
resources: map[string]string{
"/v1.json": "bad",
},
expectErr: fmt.Errorf("invalid character 'b' looking for beginning of value"),
},
{
root: "/",
metadataPath: "v1.json",
resources: map[string]string{
"/v1.json": `{
"droplet_id": 1,
"user_data": "hello",
"vendor_data": "hello",
"public_keys": [
"publickey1",
"publickey2"
],
"region": "nyc2",
"interfaces": {
"public": [
{
"ipv4": {
"ip_address": "192.168.1.2",
"netmask": "255.255.255.0",
"gateway": "192.168.1.1"
},
"ipv6": {
"ip_address": "fe00::",
"cidr": 126,
"gateway": "fe00::"
},
"mac": "ab:cd:ef:gh:ij",
"type": "public"
}
]
}
}`,
},
expect: []byte(`{"hostname":"","public-ipv4":"192.168.1.2","public-ipv6":"fe00::","public_keys":{"0":"publickey1","1":"publickey2"}}`),
},
{
clientErr: pkg.ErrTimeout{fmt.Errorf("test error")},
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
},
} {
service := &metadataService{
MetadataService: metadata.MetadataService{
Root: tt.root,
Client: &test.HttpClient{tt.resources, tt.clientErr},
MetadataPath: tt.metadataPath,
},
}
metadata, err := service.FetchMetadata()
if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
}
if !bytes.Equal(metadata, tt.expect) {
t.Fatalf("bad fetch (%q): want %q, got %q", tt.resources, tt.expect, metadata)
}
}
}
func Error(err error) string {
if err != nil {
return err.Error()
}
return ""
}

View File

@@ -7,44 +7,28 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
) )
const ( const (
DefaultAddress = "http://169.254.169.254/" DefaultAddress = "http://169.254.169.254/"
apiVersion = "2009-04-04" apiVersion = "2009-04-04"
userdataUrl = apiVersion + "/user-data" userdataPath = apiVersion + "/user-data"
metadataUrl = apiVersion + "/meta-data" metadataPath = apiVersion + "/meta-data"
) )
type metadataService struct { type metadataService struct {
root string metadata.MetadataService
client pkg.Getter
} }
func NewDatasource(root string) *metadataService { func NewDatasource(root string) *metadataService {
if !strings.HasSuffix(root, "/") { return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)}
root += "/"
}
return &metadataService{root, pkg.NewHttpClient()}
}
func (ms metadataService) IsAvailable() bool {
_, err := ms.client.Get(ms.root + apiVersion)
return (err == nil)
}
func (ms metadataService) AvailabilityChanges() bool {
return true
}
func (ms metadataService) ConfigRoot() string {
return ms.root
} }
func (ms metadataService) FetchMetadata() ([]byte, error) { func (ms metadataService) FetchMetadata() ([]byte, error) {
attrs := make(map[string]interface{}) attrs := make(map[string]interface{})
if keynames, err := fetchAttributes(ms.client, fmt.Sprintf("%s/public-keys", ms.metadataUrl())); err == nil { if keynames, err := ms.fetchAttributes(fmt.Sprintf("%s/public-keys", ms.MetadataUrl())); err == nil {
keyIDs := make(map[string]string) keyIDs := make(map[string]string)
for _, keyname := range keynames { for _, keyname := range keynames {
tokens := strings.SplitN(keyname, "=", 2) tokens := strings.SplitN(keyname, "=", 2)
@@ -56,7 +40,7 @@ func (ms metadataService) FetchMetadata() ([]byte, error) {
keys := make(map[string]string) keys := make(map[string]string)
for name, id := range keyIDs { for name, id := range keyIDs {
sshkey, err := fetchAttribute(ms.client, fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.metadataUrl(), id)) sshkey, err := ms.fetchAttribute(fmt.Sprintf("%s/public-keys/%s/openssh-key", ms.MetadataUrl(), id))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -68,25 +52,25 @@ func (ms metadataService) FetchMetadata() ([]byte, error) {
return nil, err return nil, err
} }
if hostname, err := fetchAttribute(ms.client, fmt.Sprintf("%s/hostname", ms.metadataUrl())); err == nil { if hostname, err := ms.fetchAttribute(fmt.Sprintf("%s/hostname", ms.MetadataUrl())); err == nil {
attrs["hostname"] = hostname attrs["hostname"] = hostname
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return nil, err
} }
if localAddr, err := fetchAttribute(ms.client, fmt.Sprintf("%s/local-ipv4", ms.metadataUrl())); err == nil { if localAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/local-ipv4", ms.MetadataUrl())); err == nil {
attrs["local-ipv4"] = localAddr attrs["local-ipv4"] = localAddr
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return nil, err
} }
if publicAddr, err := fetchAttribute(ms.client, fmt.Sprintf("%s/public-ipv4", ms.metadataUrl())); err == nil { if publicAddr, err := ms.fetchAttribute(fmt.Sprintf("%s/public-ipv4", ms.MetadataUrl())); err == nil {
attrs["public-ipv4"] = publicAddr attrs["public-ipv4"] = publicAddr
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return nil, err return nil, err
} }
if content_path, err := fetchAttribute(ms.client, fmt.Sprintf("%s/network_config/content_path", ms.metadataUrl())); err == nil { if content_path, err := ms.fetchAttribute(fmt.Sprintf("%s/network_config/content_path", ms.MetadataUrl())); err == nil {
attrs["network_config"] = map[string]string{ attrs["network_config"] = map[string]string{
"content_path": content_path, "content_path": content_path,
} }
@@ -97,30 +81,12 @@ func (ms metadataService) FetchMetadata() ([]byte, error) {
return json.Marshal(attrs) return json.Marshal(attrs)
} }
func (ms metadataService) FetchUserdata() ([]byte, error) {
if data, err := ms.client.GetRetry(ms.userdataUrl()); err == nil {
return data, err
} else if _, ok := err.(pkg.ErrNotFound); ok {
return []byte{}, nil
} else {
return data, err
}
}
func (ms metadataService) Type() string { func (ms metadataService) Type() string {
return "ec2-metadata-service" return "ec2-metadata-service"
} }
func (ms metadataService) metadataUrl() string { func (ms metadataService) fetchAttributes(url string) ([]string, error) {
return (ms.root + metadataUrl) resp, err := ms.FetchData(url)
}
func (ms metadataService) userdataUrl() string {
return (ms.root + userdataUrl)
}
func fetchAttributes(client pkg.Getter, url string) ([]string, error) {
resp, err := client.GetRetry(url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -132,8 +98,8 @@ func fetchAttributes(client pkg.Getter, url string) ([]string, error) {
return data, scanner.Err() return data, scanner.Err()
} }
func fetchAttribute(client pkg.Getter, url string) (string, error) { func (ms metadataService) fetchAttribute(url string) (string, error) {
if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 { if attrs, err := ms.fetchAttributes(url); err == nil && len(attrs) > 0 {
return attrs[0], nil return attrs[0], nil
} else { } else {
return "", err return "", err

View File

@@ -6,36 +6,11 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
"github.com/coreos/coreos-cloudinit/pkg" "github.com/coreos/coreos-cloudinit/pkg"
) )
type testHttpClient struct {
resources 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.resources[url]; ok {
return []byte(val), nil
} else {
return nil, pkg.ErrNotFound{fmt.Errorf("not found: %q", url)}
}
}
func (t *testHttpClient) Get(url string) ([]byte, error) {
return t.GetRetry(url)
}
func TestAvailabilityChanges(t *testing.T) {
want := true
if ac := (metadataService{}).AvailabilityChanges(); ac != want {
t.Fatalf("bad AvailabilityChanges: want %q, got %q", want, ac)
}
}
func TestType(t *testing.T) { func TestType(t *testing.T) {
want := "ec2-metadata-service" want := "ec2-metadata-service"
if kind := (metadataService{}).Type(); kind != want { if kind := (metadataService{}).Type(); kind != want {
@@ -43,102 +18,6 @@ func TestType(t *testing.T) {
} }
} }
func TestIsAvailable(t *testing.T) {
for _, tt := range []struct {
root string
resources map[string]string
expect bool
}{
{
root: "/",
resources: map[string]string{
"/2009-04-04": "",
},
expect: true,
},
{
root: "/",
resources: map[string]string{},
expect: false,
},
} {
service := &metadataService{tt.root, &testHttpClient{tt.resources, nil}}
if a := service.IsAvailable(); a != tt.expect {
t.Fatalf("bad isAvailable (%q): want %q, got %q", tt.resources, tt.expect, a)
}
}
}
func TestFetchUserdata(t *testing.T) {
for _, tt := range []struct {
root string
resources map[string]string
userdata []byte
clientErr error
expectErr error
}{
{
root: "/",
resources: map[string]string{
"/2009-04-04/user-data": "hello",
},
userdata: []byte("hello"),
},
{
root: "/",
clientErr: pkg.ErrNotFound{fmt.Errorf("test not found error")},
userdata: []byte{},
},
{
root: "/",
clientErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
expectErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
},
} {
service := &metadataService{tt.root, &testHttpClient{tt.resources, tt.clientErr}}
data, err := service.FetchUserdata()
if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
}
if !bytes.Equal(data, tt.userdata) {
t.Fatalf("bad userdata (%q): want %q, got %q", tt.resources, tt.userdata, data)
}
}
}
func TestUrls(t *testing.T) {
for _, tt := range []struct {
root string
expectRoot string
userdata string
metadata string
}{
{
root: "/",
expectRoot: "/",
userdata: "/2009-04-04/user-data",
metadata: "/2009-04-04/meta-data",
},
{
root: "http://169.254.169.254/",
expectRoot: "http://169.254.169.254/",
userdata: "http://169.254.169.254/2009-04-04/user-data",
metadata: "http://169.254.169.254/2009-04-04/meta-data",
},
} {
service := &metadataService{tt.root, nil}
if url := service.userdataUrl(); url != tt.userdata {
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.userdata, url)
}
if url := service.metadataUrl(); url != tt.metadata {
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.metadata, url)
}
if url := service.ConfigRoot(); url != tt.expectRoot {
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.expectRoot, url)
}
}
}
func TestFetchAttributes(t *testing.T) { func TestFetchAttributes(t *testing.T) {
for _, s := range []struct { for _, s := range []struct {
resources map[string]string resources map[string]string
@@ -169,7 +48,7 @@ func TestFetchAttributes(t *testing.T) {
}, },
}, },
{ {
err: pkg.ErrNotFound{fmt.Errorf("test error")}, err: fmt.Errorf("test error"),
tests: []struct { tests: []struct {
path string path string
val []string val []string
@@ -178,9 +57,11 @@ func TestFetchAttributes(t *testing.T) {
}, },
}, },
} { } {
client := &testHttpClient{s.resources, s.err} service := metadataService{metadata.MetadataService{
Client: &test.HttpClient{s.resources, s.err},
}}
for _, tt := range s.tests { for _, tt := range s.tests {
attrs, err := fetchAttributes(client, tt.path) attrs, err := service.fetchAttributes(tt.path)
if err != s.err { if err != s.err {
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err) t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err)
} }
@@ -221,7 +102,7 @@ func TestFetchAttribute(t *testing.T) {
}, },
}, },
{ {
err: pkg.ErrNotFound{fmt.Errorf("test error")}, err: fmt.Errorf("test error"),
tests: []struct { tests: []struct {
path string path string
val string val string
@@ -230,9 +111,11 @@ func TestFetchAttribute(t *testing.T) {
}, },
}, },
} { } {
client := &testHttpClient{s.resources, s.err} service := metadataService{metadata.MetadataService{
Client: &test.HttpClient{s.resources, s.err},
}}
for _, tt := range s.tests { for _, tt := range s.tests {
attr, err := fetchAttribute(client, tt.path) attr, err := service.fetchAttribute(tt.path)
if err != s.err { if err != s.err {
t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err) t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err)
} }
@@ -246,6 +129,7 @@ func TestFetchAttribute(t *testing.T) {
func TestFetchMetadata(t *testing.T) { func TestFetchMetadata(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
root string root string
metadataPath string
resources map[string]string resources map[string]string
expect []byte expect []byte
clientErr error clientErr error
@@ -253,6 +137,7 @@ func TestFetchMetadata(t *testing.T) {
}{ }{
{ {
root: "/", root: "/",
metadataPath: "2009-04-04/meta-data",
resources: map[string]string{ resources: map[string]string{
"/2009-04-04/meta-data/public-keys": "bad\n", "/2009-04-04/meta-data/public-keys": "bad\n",
}, },
@@ -260,6 +145,7 @@ func TestFetchMetadata(t *testing.T) {
}, },
{ {
root: "/", root: "/",
metadataPath: "2009-04-04/meta-data",
resources: map[string]string{ resources: map[string]string{
"/2009-04-04/meta-data/hostname": "host", "/2009-04-04/meta-data/hostname": "host",
"/2009-04-04/meta-data/local-ipv4": "1.2.3.4", "/2009-04-04/meta-data/local-ipv4": "1.2.3.4",
@@ -276,7 +162,11 @@ func TestFetchMetadata(t *testing.T) {
expectErr: pkg.ErrTimeout{fmt.Errorf("test error")}, expectErr: pkg.ErrTimeout{fmt.Errorf("test error")},
}, },
} { } {
service := &metadataService{tt.root, &testHttpClient{tt.resources, tt.clientErr}} service := &metadataService{metadata.MetadataService{
Root: tt.root,
Client: &test.HttpClient{tt.resources, tt.clientErr},
MetadataPath: tt.metadataPath,
}}
metadata, err := service.FetchMetadata() metadata, err := service.FetchMetadata()
if Error(err) != Error(tt.expectErr) { if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err) t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
@@ -287,35 +177,6 @@ func TestFetchMetadata(t *testing.T) {
} }
} }
func TestNewDatasource(t *testing.T) {
for _, tt := range []struct {
root string
expectRoot string
}{
{
root: "",
expectRoot: "/",
},
{
root: "/",
expectRoot: "/",
},
{
root: "http://169.254.169.254",
expectRoot: "http://169.254.169.254/",
},
{
root: "http://169.254.169.254/",
expectRoot: "http://169.254.169.254/",
},
} {
service := NewDatasource(tt.root)
if service.root != tt.expectRoot {
t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.root)
}
}
}
func Error(err error) string { func Error(err error) string {
if err != nil { if err != nil {
return err.Error() return err.Error()

View File

@@ -0,0 +1,61 @@
package metadata
import (
"strings"
"github.com/coreos/coreos-cloudinit/pkg"
)
type MetadataService struct {
Root string
Client pkg.Getter
ApiVersion string
UserdataPath string
MetadataPath string
}
func NewDatasource(root, apiVersion, userdataPath, metadataPath string) MetadataService {
if !strings.HasSuffix(root, "/") {
root += "/"
}
return MetadataService{root, pkg.NewHttpClient(), apiVersion, userdataPath, metadataPath}
}
func (ms MetadataService) IsAvailable() bool {
_, err := ms.Client.Get(ms.Root + ms.ApiVersion)
return (err == nil)
}
func (ms MetadataService) AvailabilityChanges() bool {
return true
}
func (ms MetadataService) ConfigRoot() string {
return ms.Root
}
func (ms MetadataService) FetchUserdata() ([]byte, error) {
return ms.FetchData(ms.UserdataUrl())
}
func (ms MetadataService) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (ms MetadataService) FetchData(url string) ([]byte, error) {
if data, err := ms.Client.GetRetry(url); err == nil {
return data, err
} else if _, ok := err.(pkg.ErrNotFound); ok {
return []byte{}, nil
} else {
return data, err
}
}
func (ms MetadataService) MetadataUrl() string {
return (ms.Root + ms.MetadataPath)
}
func (ms MetadataService) UserdataUrl() string {
return (ms.Root + ms.UserdataPath)
}

View File

@@ -0,0 +1,171 @@
package metadata
import (
"bytes"
"fmt"
"testing"
"github.com/coreos/coreos-cloudinit/datasource/metadata/test"
"github.com/coreos/coreos-cloudinit/pkg"
)
func TestAvailabilityChanges(t *testing.T) {
want := true
if ac := (MetadataService{}).AvailabilityChanges(); ac != want {
t.Fatalf("bad AvailabilityChanges: want %q, got %q", want, ac)
}
}
func TestIsAvailable(t *testing.T) {
for _, tt := range []struct {
root string
apiVersion string
resources map[string]string
expect bool
}{
{
root: "/",
apiVersion: "2009-04-04",
resources: map[string]string{
"/2009-04-04": "",
},
expect: true,
},
{
root: "/",
resources: map[string]string{},
expect: false,
},
} {
service := &MetadataService{
Root: tt.root,
Client: &test.HttpClient{tt.resources, nil},
ApiVersion: tt.apiVersion,
}
if a := service.IsAvailable(); a != tt.expect {
t.Fatalf("bad isAvailable (%q): want %q, got %q", tt.resources, tt.expect, a)
}
}
}
func TestFetchUserdata(t *testing.T) {
for _, tt := range []struct {
root string
userdataPath string
resources map[string]string
userdata []byte
clientErr error
expectErr error
}{
{
root: "/",
userdataPath: "2009-04-04/user-data",
resources: map[string]string{
"/2009-04-04/user-data": "hello",
},
userdata: []byte("hello"),
},
{
root: "/",
clientErr: pkg.ErrNotFound{fmt.Errorf("test not found error")},
userdata: []byte{},
},
{
root: "/",
clientErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
expectErr: pkg.ErrTimeout{fmt.Errorf("test timeout error")},
},
} {
service := &MetadataService{
Root: tt.root,
Client: &test.HttpClient{tt.resources, tt.clientErr},
UserdataPath: tt.userdataPath,
}
data, err := service.FetchUserdata()
if Error(err) != Error(tt.expectErr) {
t.Fatalf("bad error (%q): want %q, got %q", tt.resources, tt.expectErr, err)
}
if !bytes.Equal(data, tt.userdata) {
t.Fatalf("bad userdata (%q): want %q, got %q", tt.resources, tt.userdata, data)
}
}
}
func TestUrls(t *testing.T) {
for _, tt := range []struct {
root string
userdataPath string
metadataPath string
expectRoot string
userdata string
metadata string
}{
{
root: "/",
userdataPath: "2009-04-04/user-data",
metadataPath: "2009-04-04/meta-data",
expectRoot: "/",
userdata: "/2009-04-04/user-data",
metadata: "/2009-04-04/meta-data",
},
{
root: "http://169.254.169.254/",
userdataPath: "2009-04-04/user-data",
metadataPath: "2009-04-04/meta-data",
expectRoot: "http://169.254.169.254/",
userdata: "http://169.254.169.254/2009-04-04/user-data",
metadata: "http://169.254.169.254/2009-04-04/meta-data",
},
} {
service := &MetadataService{
Root: tt.root,
UserdataPath: tt.userdataPath,
MetadataPath: tt.metadataPath,
}
if url := service.UserdataUrl(); url != tt.userdata {
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.userdata, url)
}
if url := service.MetadataUrl(); url != tt.metadata {
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.metadata, url)
}
if url := service.ConfigRoot(); url != tt.expectRoot {
t.Fatalf("bad url (%q): want %q, got %q", tt.root, tt.expectRoot, url)
}
}
}
func TestNewDatasource(t *testing.T) {
for _, tt := range []struct {
root string
expectRoot string
}{
{
root: "",
expectRoot: "/",
},
{
root: "/",
expectRoot: "/",
},
{
root: "http://169.254.169.254",
expectRoot: "http://169.254.169.254/",
},
{
root: "http://169.254.169.254/",
expectRoot: "http://169.254.169.254/",
},
} {
service := NewDatasource(tt.root, "", "", "")
if service.Root != tt.expectRoot {
t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.Root)
}
}
}
func Error(err error) string {
if err != nil {
return err.Error()
}
return ""
}

View File

@@ -0,0 +1,27 @@
package test
import (
"fmt"
"github.com/coreos/coreos-cloudinit/pkg"
)
type HttpClient struct {
Resources map[string]string
Err error
}
func (t *HttpClient) GetRetry(url string) ([]byte, error) {
if t.Err != nil {
return nil, t.Err
}
if val, ok := t.Resources[url]; ok {
return []byte(val), nil
} else {
return nil, pkg.ErrNotFound{fmt.Errorf("not found: %q", url)}
}
}
func (t *HttpClient) Get(url string) ([]byte, error) {
return t.GetRetry(url)
}

View File

@@ -66,6 +66,10 @@ func (c *procCmdline) FetchUserdata() ([]byte, error) {
return cfg, nil return cfg, nil
} }
func (c *procCmdline) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (c *procCmdline) Type() string { func (c *procCmdline) Type() string {
return "proc-cmdline" return "proc-cmdline"
} }

View File

@@ -1,6 +1,8 @@
package url package url
import "github.com/coreos/coreos-cloudinit/pkg" import (
"github.com/coreos/coreos-cloudinit/pkg"
)
type remoteFile struct { type remoteFile struct {
url string url string
@@ -33,6 +35,10 @@ func (f *remoteFile) FetchUserdata() ([]byte, error) {
return client.GetRetry(f.url) return client.GetRetry(f.url)
} }
func (f *remoteFile) FetchNetworkConfig(filename string) ([]byte, error) {
return nil, nil
}
func (f *remoteFile) Type() string { func (f *remoteFile) Type() string {
return "url" return "url"
} }

View File

@@ -3,7 +3,6 @@ package initialize
import ( import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"path" "path"
@@ -42,6 +41,7 @@ type CloudConfig struct {
Users []system.User Users []system.User
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"` ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
NetworkConfigPath string NetworkConfigPath string
NetworkConfig string
} }
type warner func(format string, v ...interface{}) type warner func(format string, v ...interface{})
@@ -258,17 +258,13 @@ func Apply(cfg CloudConfig, env *Environment) error {
} }
if env.NetconfType() != "" { if env.NetconfType() != "" {
filename := path.Join(env.ConfigRoot(), cfg.NetworkConfigPath)
log.Printf("Attempting to read config from %q\n", filename)
netconfBytes, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
var interfaces []network.InterfaceGenerator var interfaces []network.InterfaceGenerator
var err error
switch env.NetconfType() { switch env.NetconfType() {
case "debian": case "debian":
interfaces, err = network.ProcessDebianNetconf(string(netconfBytes)) interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig)
case "digitalocean":
interfaces, err = network.ProcessDigitalOceanNetconf(cfg.NetworkConfig)
default: default:
return fmt.Errorf("Unsupported network config format %q", env.NetconfType()) return fmt.Errorf("Unsupported network config format %q", env.NetconfType())
} }

View File

@@ -1,6 +1,9 @@
package initialize package initialize
import "encoding/json" import (
"encoding/json"
"sort"
)
// ParseMetaData parses a JSON blob in the OpenStack metadata service format, and // ParseMetaData parses a JSON blob in the OpenStack metadata service format, and
// converts it to a partially hydrated CloudConfig // converts it to a partially hydrated CloudConfig
@@ -22,8 +25,8 @@ func ParseMetaData(contents string) (*CloudConfig, error) {
var cfg CloudConfig var cfg CloudConfig
if len(metadata.SSHAuthorizedKeyMap) > 0 { if len(metadata.SSHAuthorizedKeyMap) > 0 {
cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap)) cfg.SSHAuthorizedKeys = make([]string, 0, len(metadata.SSHAuthorizedKeyMap))
for _, key := range metadata.SSHAuthorizedKeyMap { for _, name := range sortedKeys(metadata.SSHAuthorizedKeyMap) {
cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, key) cfg.SSHAuthorizedKeys = append(cfg.SSHAuthorizedKeys, metadata.SSHAuthorizedKeyMap[name])
} }
} }
cfg.Hostname = metadata.Hostname cfg.Hostname = metadata.Hostname
@@ -50,3 +53,11 @@ func ExtractIPsFromMetadata(contents []byte) (map[string]string, error) {
} }
return m, nil return m, nil
} }
func sortedKeys(m map[string]string) (keys []string) {
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return
}

View File

@@ -14,7 +14,7 @@ func TestParseMetadata(t *testing.T) {
{`{"foo": "bar"}`, &CloudConfig{}, false}, {`{"foo": "bar"}`, &CloudConfig{}, false},
{`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false}, {`{"network_config": {"content_path": "asdf"}}`, &CloudConfig{NetworkConfigPath: "asdf"}, false},
{`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false}, {`{"hostname": "turkleton"}`, &CloudConfig{Hostname: "turkleton"}, false},
{`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"jill", "alice"}}, false}, {`{"public_keys": {"jack": "jill", "bob": "alice"}}`, &CloudConfig{SSHAuthorizedKeys: []string{"alice", "jill"}}, 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}, {`{"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) got, err := ParseMetaData(tt.in)

View File

@@ -1,10 +1,12 @@
package network package network
import ( import (
"log"
"strings" "strings"
) )
func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) { func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
log.Println("Processing Debian network config")
lines := formatConfig(config) lines := formatConfig(config)
stanzas, err := parseStanzas(lines) stanzas, err := parseStanzas(lines)
if err != nil { if err != nil {
@@ -18,7 +20,9 @@ func ProcessDebianNetconf(config string) ([]InterfaceGenerator, error) {
interfaces = append(interfaces, s) interfaces = append(interfaces, s)
} }
} }
log.Printf("Parsed %d network interfaces\n", len(interfaces))
log.Println("Processed Debian network config")
return buildInterfaces(interfaces), nil return buildInterfaces(interfaces), nil
} }

142
network/digitalocean.go Normal file
View File

@@ -0,0 +1,142 @@
package network
import (
"encoding/json"
"fmt"
"log"
"net"
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
)
func ProcessDigitalOceanNetconf(config string) ([]InterfaceGenerator, error) {
log.Println("Processing DigitalOcean network config")
if config == "" {
return nil, nil
}
var cfg digitalocean.Metadata
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
return nil, err
}
log.Println("Parsing nameservers")
nameservers, err := parseNameservers(cfg.DNS)
if err != nil {
return nil, err
}
log.Printf("Parsed %d nameservers\n", len(nameservers))
log.Println("Parsing interfaces")
generators, err := parseInterfaces(cfg.Interfaces, nameservers)
if err != nil {
return nil, err
}
log.Printf("Parsed %d network interfaces\n", len(generators))
log.Println("Processed DigitalOcean network config")
return generators, nil
}
func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) {
nameservers := make([]net.IP, 0, len(cfg.Nameservers))
for _, ns := range cfg.Nameservers {
if ip := net.ParseIP(ns); ip == nil {
return nil, fmt.Errorf("could not parse %q as nameserver IP address", ns)
} else {
nameservers = append(nameservers, ip)
}
}
return nameservers, nil
}
func parseInterfaces(cfg digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) {
generators := make([]InterfaceGenerator, 0, len(cfg.Public)+len(cfg.Private))
for _, iface := range cfg.Public {
if generator, err := parseInterface(iface, nameservers, true); err == nil {
generators = append(generators, &physicalInterface{*generator})
} else {
return nil, err
}
}
for _, iface := range cfg.Private {
if generator, err := parseInterface(iface, []net.IP{}, false); err == nil {
generators = append(generators, &physicalInterface{*generator})
} else {
return nil, err
}
}
return generators, nil
}
func parseInterface(iface digitalocean.Interface, nameservers []net.IP, useRoute bool) (*logicalInterface, error) {
routes := make([]route, 0)
addresses := make([]net.IPNet, 0)
if iface.IPv4 != nil {
var ip, mask, gateway net.IP
if ip = net.ParseIP(iface.IPv4.IPAddress); ip == nil {
return nil, fmt.Errorf("could not parse %q as IPv4 address", iface.IPv4.IPAddress)
}
if mask = net.ParseIP(iface.IPv4.Netmask); mask == nil {
return nil, fmt.Errorf("could not parse %q as IPv4 mask", iface.IPv4.Netmask)
}
addresses = append(addresses, net.IPNet{
IP: ip,
Mask: net.IPMask(mask),
})
if useRoute {
if gateway = net.ParseIP(iface.IPv4.Gateway); gateway == nil {
return nil, fmt.Errorf("could not parse %q as IPv4 gateway", iface.IPv4.Gateway)
}
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv4zero,
Mask: net.IPMask(net.IPv4zero),
},
gateway: gateway,
})
}
}
if iface.IPv6 != nil {
var ip, gateway net.IP
if ip = net.ParseIP(iface.IPv6.IPAddress); ip == nil {
return nil, fmt.Errorf("could not parse %q as IPv6 address", iface.IPv6.IPAddress)
}
addresses = append(addresses, net.IPNet{
IP: ip,
Mask: net.CIDRMask(iface.IPv6.Cidr, net.IPv6len*8),
})
if useRoute {
if gateway = net.ParseIP(iface.IPv6.Gateway); gateway == nil {
return nil, fmt.Errorf("could not parse %q as IPv6 gateway", iface.IPv6.Gateway)
}
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv6zero,
Mask: net.IPMask(net.IPv6zero),
},
gateway: gateway,
})
}
}
hwaddr, err := net.ParseMAC(iface.MAC)
if err != nil {
return nil, err
}
if nameservers == nil {
nameservers = []net.IP{}
}
return &logicalInterface{
hwaddr: hwaddr,
config: configMethodStatic{
addresses: addresses,
nameservers: nameservers,
routes: routes,
},
}, nil
}

View File

@@ -0,0 +1,367 @@
package network
import (
"errors"
"net"
"reflect"
"testing"
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
)
func TestParseNameservers(t *testing.T) {
for _, tt := range []struct {
dns digitalocean.DNS
nss []net.IP
err error
}{
{
dns: digitalocean.DNS{},
nss: []net.IP{},
},
{
dns: digitalocean.DNS{[]string{"1.2.3.4"}},
nss: []net.IP{net.ParseIP("1.2.3.4")},
},
{
dns: digitalocean.DNS{[]string{"bad"}},
err: errors.New("could not parse \"bad\" as nameserver IP address"),
},
} {
nss, err := parseNameservers(tt.dns)
if !errorsEqual(tt.err, err) {
t.Fatalf("bad error (%+v): want %q, got %q", tt.dns, tt.err, err)
}
if !reflect.DeepEqual(tt.nss, nss) {
t.Fatalf("bad nameservers (%+v): want %#v, got %#v", tt.dns, tt.nss, nss)
}
}
}
func TestParseInterface(t *testing.T) {
for _, tt := range []struct {
cfg digitalocean.Interface
nss []net.IP
useRoute bool
iface *logicalInterface
err error
}{
{
cfg: digitalocean.Interface{
MAC: "bad",
},
err: errors.New("invalid MAC address: bad"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
},
nss: []net.IP{},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{},
nameservers: []net.IP{},
routes: []route{},
},
},
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
},
useRoute: true,
nss: []net.IP{net.ParseIP("1.2.3.4")},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{},
nameservers: []net.IP{net.ParseIP("1.2.3.4")},
routes: []route{},
},
},
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv4: &digitalocean.Address{
IPAddress: "bad",
Netmask: "255.255.0.0",
},
},
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as IPv4 address"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "bad",
},
},
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as IPv4 mask"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "255.255.0.0",
Gateway: "ignoreme",
},
},
nss: []net.IP{},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{net.ParseIP("1.2.3.4"), net.IPMask(net.ParseIP("255.255.0.0"))}},
nameservers: []net.IP{},
routes: []route{},
},
},
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "255.255.0.0",
Gateway: "bad",
},
},
useRoute: true,
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as IPv4 gateway"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "255.255.0.0",
Gateway: "5.6.7.8",
},
},
useRoute: true,
nss: []net.IP{},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{net.ParseIP("1.2.3.4"), net.IPMask(net.ParseIP("255.255.0.0"))}},
nameservers: []net.IP{},
routes: []route{route{net.IPNet{net.IPv4zero, net.IPMask(net.IPv4zero)}, net.ParseIP("5.6.7.8")}},
},
},
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv6: &digitalocean.Address{
IPAddress: "bad",
Cidr: 16,
},
},
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as IPv6 address"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv6: &digitalocean.Address{
IPAddress: "fe00::",
Cidr: 16,
Gateway: "ignoreme",
},
},
nss: []net.IP{},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{net.ParseIP("fe00::"), net.IPMask(net.ParseIP("ffff::"))}},
nameservers: []net.IP{},
routes: []route{},
},
},
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv6: &digitalocean.Address{
IPAddress: "fe00::",
Cidr: 16,
Gateway: "bad",
},
},
useRoute: true,
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as IPv6 gateway"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv6: &digitalocean.Address{
IPAddress: "fe00::",
Cidr: 16,
Gateway: "fe00:1234::",
},
},
useRoute: true,
nss: []net.IP{},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{net.ParseIP("fe00::"), net.IPMask(net.ParseIP("ffff::"))}},
nameservers: []net.IP{},
routes: []route{route{net.IPNet{net.IPv6zero, net.IPMask(net.IPv6zero)}, net.ParseIP("fe00:1234::")}},
},
},
},
} {
iface, err := parseInterface(tt.cfg, tt.nss, tt.useRoute)
if !errorsEqual(tt.err, err) {
t.Fatalf("bad error (%+v): want %q, got %q", tt.cfg, tt.err, err)
}
if !reflect.DeepEqual(tt.iface, iface) {
t.Fatalf("bad interface (%+v): want %#v, got %#v", tt.cfg, tt.iface, iface)
}
}
}
func TestParseInterfaces(t *testing.T) {
for _, tt := range []struct {
cfg digitalocean.Interfaces
nss []net.IP
ifaces []InterfaceGenerator
err error
}{
{
ifaces: []InterfaceGenerator{},
},
{
cfg: digitalocean.Interfaces{
Public: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
},
ifaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{},
nameservers: []net.IP{},
routes: []route{},
},
}},
},
},
{
cfg: digitalocean.Interfaces{
Private: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
},
ifaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{},
nameservers: []net.IP{},
routes: []route{},
},
}},
},
},
{
cfg: digitalocean.Interfaces{
Public: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
},
nss: []net.IP{net.ParseIP("1.2.3.4")},
ifaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{},
nameservers: []net.IP{net.ParseIP("1.2.3.4")},
routes: []route{},
},
}},
},
},
{
cfg: digitalocean.Interfaces{
Private: []digitalocean.Interface{{MAC: "01:23:45:67:89:AB"}},
},
nss: []net.IP{net.ParseIP("1.2.3.4")},
ifaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{},
nameservers: []net.IP{},
routes: []route{},
},
}},
},
},
{
cfg: digitalocean.Interfaces{
Public: []digitalocean.Interface{{MAC: "bad"}},
},
err: errors.New("invalid MAC address: bad"),
},
{
cfg: digitalocean.Interfaces{
Private: []digitalocean.Interface{{MAC: "bad"}},
},
err: errors.New("invalid MAC address: bad"),
},
} {
ifaces, err := parseInterfaces(tt.cfg, tt.nss)
if !errorsEqual(tt.err, err) {
t.Fatalf("bad error (%+v): want %q, got %q", tt.cfg, tt.err, err)
}
if !reflect.DeepEqual(tt.ifaces, ifaces) {
t.Fatalf("bad interfaces (%+v): want %#v, got %#v", tt.cfg, tt.ifaces, ifaces)
}
}
}
func TestProcessDigitalOceanNetconf(t *testing.T) {
for _, tt := range []struct {
cfg string
ifaces []InterfaceGenerator
err error
}{
{
cfg: ``,
},
{
cfg: `{"dns":{"nameservers":["bad"]}}`,
err: errors.New("could not parse \"bad\" as nameserver IP address"),
},
{
cfg: `{"interfaces":{"public":[{"ipv4":{"ip_address":"bad"}}]}}`,
err: errors.New("could not parse \"bad\" as IPv4 address"),
},
{
cfg: `{}`,
ifaces: []InterfaceGenerator{},
},
} {
ifaces, err := ProcessDigitalOceanNetconf(tt.cfg)
if !errorsEqual(tt.err, err) {
t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err)
}
if !reflect.DeepEqual(tt.ifaces, ifaces) {
t.Fatalf("bad interfaces (%q): want %#v, got %#v", tt.cfg, tt.ifaces, ifaces)
}
}
}
func errorsEqual(a, b error) bool {
if a == nil && b == nil {
return true
}
if (a != nil && b == nil) || (a == nil && b != nil) {
return false
}
return (a.Error() == b.Error())
}

View File

@@ -2,6 +2,8 @@ package network
import ( import (
"fmt" "fmt"
"net"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@@ -24,13 +26,25 @@ type networkInterface interface {
type logicalInterface struct { type logicalInterface struct {
name string name string
hwaddr net.HardwareAddr
config configMethod config configMethod
children []networkInterface children []networkInterface
configDepth int configDepth int
} }
func (i *logicalInterface) Name() string {
return i.name
}
func (i *logicalInterface) Network() string { func (i *logicalInterface) Network() string {
config := fmt.Sprintf("[Match]\nName=%s\n\n[Network]\n", i.name) config := fmt.Sprintln("[Match]")
if i.name != "" {
config += fmt.Sprintf("Name=%s\n", i.name)
}
if i.hwaddr != nil {
config += fmt.Sprintf("MACAddress=%s\n", i.hwaddr)
}
config += "\n[Network]\n"
for _, child := range i.children { for _, child := range i.children {
switch iface := child.(type) { switch iface := child.(type) {
@@ -46,8 +60,8 @@ func (i *logicalInterface) Network() string {
for _, nameserver := range conf.nameservers { for _, nameserver := range conf.nameservers {
config += fmt.Sprintf("DNS=%s\n", nameserver) config += fmt.Sprintf("DNS=%s\n", nameserver)
} }
if conf.address.IP != nil { for _, addr := range conf.addresses {
config += fmt.Sprintf("\n[Address]\nAddress=%s\n", conf.address.String()) config += fmt.Sprintf("\n[Address]\nAddress=%s\n", addr.String())
} }
for _, route := range conf.routes { for _, route := range conf.routes {
config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway) config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
@@ -63,8 +77,16 @@ func (i *logicalInterface) Link() string {
return "" return ""
} }
func (i *logicalInterface) Netdev() string {
return ""
}
func (i *logicalInterface) Filename() string { func (i *logicalInterface) Filename() string {
return fmt.Sprintf("%02x-%s", i.configDepth, i.name) name := i.name
if name == "" {
name = i.hwaddr.String()
}
return fmt.Sprintf("%02x-%s", i.configDepth, name)
} }
func (i *logicalInterface) Children() []networkInterface { func (i *logicalInterface) Children() []networkInterface {
@@ -83,14 +105,6 @@ type physicalInterface struct {
logicalInterface logicalInterface
} }
func (p *physicalInterface) Name() string {
return p.name
}
func (p *physicalInterface) Netdev() string {
return ""
}
func (p *physicalInterface) Type() string { func (p *physicalInterface) Type() string {
return "physical" return "physical"
} }
@@ -101,10 +115,6 @@ type bondInterface struct {
options map[string]string options map[string]string
} }
func (b *bondInterface) Name() string {
return b.name
}
func (b *bondInterface) Netdev() string { func (b *bondInterface) Netdev() string {
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name) return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
} }
@@ -115,8 +125,8 @@ func (b *bondInterface) Type() string {
func (b *bondInterface) ModprobeParams() string { func (b *bondInterface) ModprobeParams() string {
params := "" params := ""
for name, val := range b.options { for _, name := range sortedKeys(b.options) {
params += fmt.Sprintf("%s=%s ", name, val) params += fmt.Sprintf("%s=%s ", name, b.options[name])
} }
params = strings.TrimSuffix(params, " ") params = strings.TrimSuffix(params, " ")
return params return params
@@ -128,10 +138,6 @@ type vlanInterface struct {
rawDevice string rawDevice string
} }
func (v *vlanInterface) Name() string {
return v.name
}
func (v *vlanInterface) Netdev() string { func (v *vlanInterface) Netdev() string {
config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name) config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
switch c := v.config.(type) { switch c := v.config.(type) {
@@ -158,8 +164,8 @@ func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
markConfigDepths(interfaceMap) markConfigDepths(interfaceMap)
interfaces := make([]InterfaceGenerator, 0, len(interfaceMap)) interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
for _, iface := range interfaceMap { for _, name := range sortedInterfaces(interfaceMap) {
interfaces = append(interfaces, iface) interfaces = append(interfaces, interfaceMap[name])
} }
return interfaces return interfaces
@@ -239,7 +245,8 @@ func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
} }
func linkAncestors(interfaceMap map[string]networkInterface) { func linkAncestors(interfaceMap map[string]networkInterface) {
for _, iface := range interfaceMap { for _, name := range sortedInterfaces(interfaceMap) {
iface := interfaceMap[name]
switch i := iface.(type) { switch i := iface.(type) {
case *vlanInterface: case *vlanInterface:
if parent, ok := interfaceMap[i.rawDevice]; ok { if parent, ok := interfaceMap[i.rawDevice]; ok {
@@ -291,3 +298,19 @@ func setDepth(iface networkInterface) int {
iface.setConfigDepth(maxDepth) iface.setConfigDepth(maxDepth)
return (maxDepth + 1) return (maxDepth + 1)
} }
func sortedKeys(m map[string]string) (keys []string) {
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return
}
func sortedInterfaces(m map[string]networkInterface) (keys []string) {
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return
}

View File

@@ -6,241 +6,101 @@ import (
"testing" "testing"
) )
func TestPhysicalInterfaceName(t *testing.T) { func TestInterfaceGenerators(t *testing.T) {
p := physicalInterface{logicalInterface{name: "testname"}} for _, tt := range []struct {
if p.Name() != "testname" { name string
t.FailNow() netdev string
} link string
} network string
kind string
func TestPhysicalInterfaceNetdev(t *testing.T) { iface InterfaceGenerator
p := physicalInterface{} }{
if p.Netdev() != "" { {
t.FailNow() name: "",
} network: "[Match]\nMACAddress=00:01:02:03:04:05\n\n[Network]\n",
} kind: "physical",
iface: &physicalInterface{logicalInterface{
func TestPhysicalInterfaceLink(t *testing.T) { hwaddr: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
p := physicalInterface{} }},
if p.Link() != "" { },
t.FailNow() {
} name: "testname",
} network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\n",
kind: "physical",
func TestPhysicalInterfaceNetwork(t *testing.T) { iface: &physicalInterface{logicalInterface{
p := physicalInterface{logicalInterface{
name: "testname", name: "testname",
children: []networkInterface{ children: []networkInterface{
&bondInterface{ &bondInterface{logicalInterface: logicalInterface{name: "testbond1"}},
logicalInterface{ &vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1},
name: "testbond1", &vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1},
}, },
nil, }},
nil,
}, },
&vlanInterface{ {
logicalInterface{ name: "testname",
name: "testvlan1", netdev: "[NetDev]\nKind=bond\nName=testname\n",
}, network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\nDHCP=true\n",
1, kind: "bond",
"", iface: &bondInterface{logicalInterface: logicalInterface{
},
&vlanInterface{
logicalInterface{
name: "testvlan2",
},
1,
"",
},
},
}}
network := `[Match]
Name=testname
[Network]
Bond=testbond1
VLAN=testvlan1
VLAN=testvlan2
`
if p.Network() != network {
t.FailNow()
}
}
func TestBondInterfaceName(t *testing.T) {
b := bondInterface{logicalInterface{name: "testname"}, nil, nil}
if b.Name() != "testname" {
t.FailNow()
}
}
func TestBondInterfaceNetdev(t *testing.T) {
b := bondInterface{logicalInterface{name: "testname"}, nil, nil}
netdev := `[NetDev]
Kind=bond
Name=testname
`
if b.Netdev() != netdev {
t.FailNow()
}
}
func TestBondInterfaceLink(t *testing.T) {
b := bondInterface{}
if b.Link() != "" {
t.FailNow()
}
}
func TestBondInterfaceNetwork(t *testing.T) {
b := bondInterface{
logicalInterface{
name: "testname", name: "testname",
config: configMethodDHCP{}, config: configMethodDHCP{},
children: []networkInterface{ children: []networkInterface{
&bondInterface{ &bondInterface{logicalInterface: logicalInterface{name: "testbond1"}},
logicalInterface{ &vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1},
name: "testbond1", &vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1},
}, },
nil, }},
nil,
},
&vlanInterface{
logicalInterface{
name: "testvlan1",
},
1,
"",
},
&vlanInterface{
logicalInterface{
name: "testvlan2",
},
1,
"",
},
},
},
nil,
nil,
}
network := `[Match]
Name=testname
[Network]
Bond=testbond1
VLAN=testvlan1
VLAN=testvlan2
DHCP=true
`
if b.Network() != network {
t.FailNow()
}
}
func TestVLANInterfaceName(t *testing.T) {
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
if v.Name() != "testname" {
t.FailNow()
}
}
func TestVLANInterfaceNetdev(t *testing.T) {
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, ""}, name: "testname",
"[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n", netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n",
network: "[Match]\nName=testname\n\n[Network]\n",
kind: "vlan",
iface: &vlanInterface{logicalInterface{name: "testname"}, 1, ""},
}, },
{ {
vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""}, name: "testname",
"[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n", netdev: "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
network: "[Match]\nName=testname\n\n[Network]\n",
kind: "vlan",
iface: &vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
}, },
} { {
if tt.i.Netdev() != tt.l { name: "testname",
t.Fatalf("bad netdev config (%q): got %q, want %q", tt.i, tt.i.Netdev(), tt.l) netdev: "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
} network: "[Match]\nName=testname\n\n[Network]\nDHCP=true\n",
} kind: "vlan",
} iface: &vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
},
func TestVLANInterfaceLink(t *testing.T) { {
v := vlanInterface{} name: "testname",
if v.Link() != "" { netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=0\n",
t.FailNow() network: "[Match]\nName=testname\n\n[Network]\nDNS=8.8.8.8\n\n[Address]\nAddress=192.168.1.100/24\n\n[Route]\nDestination=0.0.0.0/0\nGateway=1.2.3.4\n",
} kind: "vlan",
} iface: &vlanInterface{logicalInterface: logicalInterface{
func TestVLANInterfaceNetwork(t *testing.T) {
v := vlanInterface{
logicalInterface{
name: "testname", name: "testname",
config: configMethodStatic{ config: configMethodStatic{
address: net.IPNet{ addresses: []net.IPNet{{IP: []byte{192, 168, 1, 100}, Mask: []byte{255, 255, 255, 0}}},
IP: []byte{192, 168, 1, 100}, nameservers: []net.IP{[]byte{8, 8, 8, 8}},
Mask: []byte{255, 255, 255, 0}, routes: []route{route{destination: net.IPNet{IP: []byte{0, 0, 0, 0}, Mask: []byte{0, 0, 0, 0}}, gateway: []byte{1, 2, 3, 4}}},
}, },
nameservers: []net.IP{ }},
[]byte{8, 8, 8, 8},
},
routes: []route{
route{
destination: net.IPNet{
IP: []byte{0, 0, 0, 0},
Mask: []byte{0, 0, 0, 0},
},
gateway: []byte{1, 2, 3, 4},
},
},
},
},
0,
"",
}
network := `[Match]
Name=testname
[Network]
DNS=8.8.8.8
[Address]
Address=192.168.1.100/24
[Route]
Destination=0.0.0.0/0
Gateway=1.2.3.4
`
if v.Network() != network {
t.Log(v.Network())
t.FailNow()
}
}
func TestType(t *testing.T) {
for _, tt := range []struct {
i InterfaceGenerator
t string
}{
{
i: &physicalInterface{},
t: "physical",
},
{
i: &vlanInterface{},
t: "vlan",
},
{
i: &bondInterface{},
t: "bond",
}, },
} { } {
if tp := tt.i.Type(); tp != tt.t { if name := tt.iface.Name(); name != tt.name {
t.Fatalf("bad type (%q): got %s, want %s", tt.i, tp, tt.t) t.Fatalf("bad name (%q): want %q, got %q", tt.iface, tt.name, name)
}
if netdev := tt.iface.Netdev(); netdev != tt.netdev {
t.Fatalf("bad netdev (%q): want %q, got %q", tt.iface, tt.netdev, netdev)
}
if link := tt.iface.Link(); link != tt.link {
t.Fatalf("bad link (%q): want %q, got %q", tt.iface, tt.link, link)
}
if network := tt.iface.Network(); network != tt.network {
t.Fatalf("bad network (%q): want %q, got %q", tt.iface, tt.network, network)
}
if kind := tt.iface.Type(); kind != tt.kind {
t.Fatalf("bad type (%q): want %q, got %q", tt.iface, tt.kind, kind)
} }
} }
} }
@@ -469,7 +329,7 @@ func TestBuildInterfaces(t *testing.T) {
configDepth: 2, configDepth: 2,
}, },
} }
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1} expect := []InterfaceGenerator{bond0, bond1, eth0, vlan0, vlan1}
if !reflect.DeepEqual(interfaces, expect) { if !reflect.DeepEqual(interfaces, expect) {
t.FailNow() t.FailNow()
} }
@@ -484,6 +344,8 @@ func TestFilename(t *testing.T) {
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"}, {logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"}, {logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"}, {logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
{logicalInterface{hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-01:23:45:67:89:ab"},
{logicalInterface{name: "iface", hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-iface"},
} { } {
if tt.i.Filename() != tt.f { if tt.i.Filename() != tt.f {
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f) t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)

View File

@@ -37,7 +37,7 @@ type route struct {
type configMethod interface{} type configMethod interface{}
type configMethodStatic struct { type configMethodStatic struct {
address net.IPNet addresses []net.IPNet
nameservers []net.IP nameservers []net.IP
routes []route routes []route
hwaddress net.HardwareAddr hwaddress net.HardwareAddr
@@ -193,20 +193,21 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
switch confMethod { switch confMethod {
case "static": case "static":
config := configMethodStatic{ config := configMethodStatic{
addresses: make([]net.IPNet, 1),
routes: make([]route, 0), routes: make([]route, 0),
nameservers: make([]net.IP, 0), nameservers: make([]net.IP, 0),
} }
if addresses, ok := optionMap["address"]; ok { if addresses, ok := optionMap["address"]; ok {
if len(addresses) == 1 { if len(addresses) == 1 {
config.address.IP = net.ParseIP(addresses[0]) config.addresses[0].IP = net.ParseIP(addresses[0])
} }
} }
if netmasks, ok := optionMap["netmask"]; ok { if netmasks, ok := optionMap["netmask"]; ok {
if len(netmasks) == 1 { if len(netmasks) == 1 {
config.address.Mask = net.IPMask(net.ParseIP(netmasks[0]).To4()) config.addresses[0].Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
} }
} }
if config.address.IP == nil || config.address.Mask == nil { if config.addresses[0].IP == nil || config.addresses[0].Mask == nil {
return nil, fmt.Errorf("malformed static network config for %q", iface) return nil, fmt.Errorf("malformed static network config for %q", iface)
} }
if gateways, ok := optionMap["gateway"]; ok { if gateways, ok := optionMap["gateway"]; ok {

View File

@@ -194,9 +194,11 @@ func TestParseVLANStanzas(t *testing.T) {
func TestParseInterfaceStanzaStaticAddress(t *testing.T) { func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
options := []string{"address 192.168.1.100", "netmask 255.255.255.0"} options := []string{"address 192.168.1.100", "netmask 255.255.255.0"}
expect := net.IPNet{ expect := []net.IPNet{
{
IP: net.IPv4(192, 168, 1, 100), IP: net.IPv4(192, 168, 1, 100),
Mask: net.IPv4Mask(255, 255, 255, 0), Mask: net.IPv4Mask(255, 255, 255, 0),
},
} }
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
@@ -207,7 +209,7 @@ func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
if !ok { if !ok {
t.FailNow() t.FailNow()
} }
if !reflect.DeepEqual(static.address, expect) { if !reflect.DeepEqual(static.addresses, expect) {
t.FailNow() t.FailNow()
} }
} }

View File

@@ -74,15 +74,14 @@ func maybeProbe8012q(interfaces []network.InterfaceGenerator) error {
} }
func maybeProbeBonding(interfaces []network.InterfaceGenerator) error { func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
args := []string{"bonding"}
for _, iface := range interfaces { for _, iface := range interfaces {
if iface.Type() == "bond" { if iface.Type() == "bond" {
args = append(args, strings.Split(iface.ModprobeParams(), " ")...) args := append([]string{"bonding"}, strings.Split(iface.ModprobeParams(), " ")...)
break
}
}
log.Printf("Probing LKM %q (%q)\n", "bonding", args) log.Printf("Probing LKM %q (%q)\n", "bonding", args)
return exec.Command("modprobe", args...).Run() return exec.Command("modprobe", args...).Run()
}
}
return nil
} }
func restartNetworkd() error { func restartNetworkd() error {

10
test
View File

@@ -18,7 +18,9 @@ declare -a TESTPKGS=(initialize
datasource datasource
datasource/configdrive datasource/configdrive
datasource/file datasource/file
datasource/metadata
datasource/metadata/cloudsigma datasource/metadata/cloudsigma
datasource/metadata/digitalocean
datasource/metadata/ec2 datasource/metadata/ec2
datasource/proc_cmdline datasource/proc_cmdline
datasource/url datasource/url
@@ -26,9 +28,9 @@ declare -a TESTPKGS=(initialize
network) network)
if [ -z "$PKG" ]; then if [ -z "$PKG" ]; then
GOFMTPATH="$TESTPKGS coreos-cloudinit.go" GOFMTPATH="${TESTPKGS[*]} coreos-cloudinit.go"
# prepend repo path to each package # prepend repo path to each package
TESTPKGS="${TESTPKGS[@]/#/${REPO_PATH}/} ./" TESTPKGS="${TESTPKGS[*]/#/${REPO_PATH}/} ./"
else else
GOFMTPATH="$TESTPKGS" GOFMTPATH="$TESTPKGS"
# strip out slashes and dots from PKG=./foo/ # strip out slashes and dots from PKG=./foo/
@@ -43,5 +45,9 @@ go test ${COVER} $@ ${TESTPKGS}
echo "Checking gofmt..." echo "Checking gofmt..."
fmtRes=$(gofmt -l $GOFMTPATH) fmtRes=$(gofmt -l $GOFMTPATH)
if [ -n "$fmtRes" ]; then
echo "$fmtRes"
exit 1
fi
echo "Success" echo "Success"