From a923161f4acfd4091994ff0b4765e7fe6647ab60 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Fri, 15 Aug 2014 18:13:37 -0700 Subject: [PATCH 1/5] metadata: Refactor common parts out of ec2 --- datasource/datasource.go | 5 - datasource/metadata/ec2/metadata.go | 64 ++------ datasource/metadata/ec2/metadata_test.go | 193 ++++------------------- datasource/metadata/metadata.go | 57 +++++++ datasource/metadata/metadata_test.go | 171 ++++++++++++++++++++ datasource/metadata/test/test.go | 27 ++++ test | 1 + 7 files changed, 298 insertions(+), 220 deletions(-) create mode 100644 datasource/metadata/metadata.go create mode 100644 datasource/metadata/metadata_test.go create mode 100644 datasource/metadata/test/test.go diff --git a/datasource/datasource.go b/datasource/datasource.go index 7de7f23..7146925 100644 --- a/datasource/datasource.go +++ b/datasource/datasource.go @@ -1,10 +1,5 @@ package datasource -const ( - Ec2ApiVersion = "2009-04-04" - OpenstackApiVersion = "2012-08-10" -) - type Datasource interface { IsAvailable() bool AvailabilityChanges() bool diff --git a/datasource/metadata/ec2/metadata.go b/datasource/metadata/ec2/metadata.go index 9079543..9e284e6 100644 --- a/datasource/metadata/ec2/metadata.go +++ b/datasource/metadata/ec2/metadata.go @@ -7,44 +7,28 @@ import ( "fmt" "strings" + "github.com/coreos/coreos-cloudinit/datasource/metadata" "github.com/coreos/coreos-cloudinit/pkg" ) const ( DefaultAddress = "http://169.254.169.254/" apiVersion = "2009-04-04" - userdataUrl = apiVersion + "/user-data" - metadataUrl = apiVersion + "/meta-data" + userdataPath = apiVersion + "/user-data" + metadataPath = apiVersion + "/meta-data" ) type metadataService struct { - root string - client pkg.Getter + metadata.MetadataService } func NewDatasource(root string) *metadataService { - if !strings.HasSuffix(root, "/") { - 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 + return &metadataService{metadata.NewDatasource(root, apiVersion, userdataPath, metadataPath)} } func (ms metadataService) FetchMetadata() ([]byte, error) { 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) for _, keyname := range keynames { tokens := strings.SplitN(keyname, "=", 2) @@ -56,7 +40,7 @@ func (ms metadataService) FetchMetadata() ([]byte, error) { keys := make(map[string]string) 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 { return nil, err } @@ -68,25 +52,25 @@ func (ms metadataService) FetchMetadata() ([]byte, error) { 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 } else if _, ok := err.(pkg.ErrNotFound); !ok { 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 } else if _, ok := err.(pkg.ErrNotFound); !ok { 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 } else if _, ok := err.(pkg.ErrNotFound); !ok { 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{ "content_path": content_path, } @@ -97,30 +81,12 @@ func (ms metadataService) FetchMetadata() ([]byte, error) { 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 { return "ec2-metadata-service" } -func (ms metadataService) metadataUrl() string { - return (ms.root + metadataUrl) -} - -func (ms metadataService) userdataUrl() string { - return (ms.root + userdataUrl) -} - -func fetchAttributes(client pkg.Getter, url string) ([]string, error) { - resp, err := client.GetRetry(url) +func (ms metadataService) fetchAttributes(url string) ([]string, error) { + resp, err := ms.FetchData(url) if err != nil { return nil, err } @@ -132,8 +98,8 @@ func fetchAttributes(client pkg.Getter, url string) ([]string, error) { return data, scanner.Err() } -func fetchAttribute(client pkg.Getter, url string) (string, error) { - if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 { +func (ms metadataService) fetchAttribute(url string) (string, error) { + if attrs, err := ms.fetchAttributes(url); err == nil && len(attrs) > 0 { return attrs[0], nil } else { return "", err diff --git a/datasource/metadata/ec2/metadata_test.go b/datasource/metadata/ec2/metadata_test.go index 6a1cc10..993ec19 100644 --- a/datasource/metadata/ec2/metadata_test.go +++ b/datasource/metadata/ec2/metadata_test.go @@ -6,36 +6,11 @@ import ( "reflect" "testing" + "github.com/coreos/coreos-cloudinit/datasource/metadata" + "github.com/coreos/coreos-cloudinit/datasource/metadata/test" "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) { want := "ec2-metadata-service" 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) { for _, s := range []struct { 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 { path 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 { - attrs, err := fetchAttributes(client, tt.path) + attrs, err := service.fetchAttributes(tt.path) if err != s.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 { path 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 { - attr, err := fetchAttribute(client, tt.path) + attr, err := service.fetchAttribute(tt.path) if err != s.err { t.Fatalf("bad error for %q (%q): want %q, got %q", tt.path, s.resources, s.err, err) } @@ -245,21 +128,24 @@ func TestFetchAttribute(t *testing.T) { func TestFetchMetadata(t *testing.T) { for _, tt := range []struct { - root string - resources map[string]string - expect []byte - clientErr error - expectErr error + root string + metadataPath string + resources map[string]string + expect []byte + clientErr error + expectErr error }{ { - root: "/", + root: "/", + metadataPath: "2009-04-04/meta-data", resources: map[string]string{ "/2009-04-04/meta-data/public-keys": "bad\n", }, expectErr: fmt.Errorf("malformed public key: \"bad\""), }, { - root: "/", + root: "/", + metadataPath: "2009-04-04/meta-data", resources: map[string]string{ "/2009-04-04/meta-data/hostname": "host", "/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")}, }, } { - 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() if Error(err) != Error(tt.expectErr) { 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 { if err != nil { return err.Error() diff --git a/datasource/metadata/metadata.go b/datasource/metadata/metadata.go new file mode 100644 index 0000000..9f58bdb --- /dev/null +++ b/datasource/metadata/metadata.go @@ -0,0 +1,57 @@ +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) 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) +} diff --git a/datasource/metadata/metadata_test.go b/datasource/metadata/metadata_test.go new file mode 100644 index 0000000..55291d3 --- /dev/null +++ b/datasource/metadata/metadata_test.go @@ -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 "" +} diff --git a/datasource/metadata/test/test.go b/datasource/metadata/test/test.go new file mode 100644 index 0000000..2fc408d --- /dev/null +++ b/datasource/metadata/test/test.go @@ -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) +} diff --git a/test b/test index 0d0a7a9..74812c4 100755 --- a/test +++ b/test @@ -18,6 +18,7 @@ declare -a TESTPKGS=(initialize datasource datasource/configdrive datasource/file + datasource/metadata datasource/metadata/cloudsigma datasource/metadata/ec2 datasource/proc_cmdline From c39dd5cc6745cab87516bed14e8c52e53b17142c Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Sat, 16 Aug 2014 10:44:29 -0700 Subject: [PATCH 2/5] networkd: Fix bug causing bonding to always be loaded --- system/networkd.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/system/networkd.go b/system/networkd.go index 0b0ffa7..0749c9c 100644 --- a/system/networkd.go +++ b/system/networkd.go @@ -74,15 +74,14 @@ func maybeProbe8012q(interfaces []network.InterfaceGenerator) error { } func maybeProbeBonding(interfaces []network.InterfaceGenerator) error { - args := []string{"bonding"} for _, iface := range interfaces { if iface.Type() == "bond" { - args = append(args, strings.Split(iface.ModprobeParams(), " ")...) - break + args := append([]string{"bonding"}, strings.Split(iface.ModprobeParams(), " ")...) + log.Printf("Probing LKM %q (%q)\n", "bonding", args) + return exec.Command("modprobe", args...).Run() } } - log.Printf("Probing LKM %q (%q)\n", "bonding", args) - return exec.Command("modprobe", args...).Run() + return nil } func restartNetworkd() error { From 604ef7ecb44decd606f68909f3a609c6d741788c Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Mon, 18 Aug 2014 12:20:25 -0700 Subject: [PATCH 3/5] datasource: Add FetchNetworkConfig FetchNetworkConfig is currently only used by ConfigDrive to read the network config file from the disk. --- coreos-cloudinit.go | 11 +++++++++++ datasource/configdrive/configdrive.go | 4 ++++ datasource/datasource.go | 1 + datasource/file/file.go | 4 ++++ datasource/metadata/cloudsigma/server_context.go | 4 ++++ datasource/metadata/metadata.go | 4 ++++ datasource/proc_cmdline/proc_cmdline.go | 4 ++++ datasource/url/url.go | 8 +++++++- initialize/config.go | 12 +++--------- 9 files changed, 42 insertions(+), 10 deletions(-) diff --git a/coreos-cloudinit.go b/coreos-cloudinit.go index 2296833..0668924 100644 --- a/coreos-cloudinit.go +++ b/coreos-cloudinit.go @@ -132,6 +132,17 @@ func main() { fmt.Printf("Failed to parse meta-data: %v\n", err) 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 { fmt.Printf("Failed to parse user-data: %v\n", err) die() diff --git a/datasource/configdrive/configdrive.go b/datasource/configdrive/configdrive.go index 1baebf1..81d401e 100644 --- a/datasource/configdrive/configdrive.go +++ b/datasource/configdrive/configdrive.go @@ -41,6 +41,10 @@ func (cd *configDrive) FetchUserdata() ([]byte, error) { 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 { return "cloud-drive" } diff --git a/datasource/datasource.go b/datasource/datasource.go index 7146925..ec30cca 100644 --- a/datasource/datasource.go +++ b/datasource/datasource.go @@ -6,5 +6,6 @@ type Datasource interface { ConfigRoot() string FetchMetadata() ([]byte, error) FetchUserdata() ([]byte, error) + FetchNetworkConfig(string) ([]byte, error) Type() string } diff --git a/datasource/file/file.go b/datasource/file/file.go index b629641..6af928c 100644 --- a/datasource/file/file.go +++ b/datasource/file/file.go @@ -34,6 +34,10 @@ func (f *localFile) FetchUserdata() ([]byte, error) { return ioutil.ReadFile(f.path) } +func (f *localFile) FetchNetworkConfig(filename string) ([]byte, error) { + return nil, nil +} + func (f *localFile) Type() string { return "local-file" } diff --git a/datasource/metadata/cloudsigma/server_context.go b/datasource/metadata/cloudsigma/server_context.go index 4bd9d0b..005c762 100644 --- a/datasource/metadata/cloudsigma/server_context.go +++ b/datasource/metadata/cloudsigma/server_context.go @@ -126,6 +126,10 @@ func (scs *serverContextService) FetchUserdata() ([]byte, error) { return []byte(userData), nil } +func (scs *serverContextService) FetchNetworkConfig(a string) ([]byte, error) { + return nil, nil +} + func isBase64Encoded(field string, userdata map[string]string) bool { base64Fields, ok := userdata["base64_fields"] if !ok { diff --git a/datasource/metadata/metadata.go b/datasource/metadata/metadata.go index 9f58bdb..9309b73 100644 --- a/datasource/metadata/metadata.go +++ b/datasource/metadata/metadata.go @@ -38,6 +38,10 @@ 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 diff --git a/datasource/proc_cmdline/proc_cmdline.go b/datasource/proc_cmdline/proc_cmdline.go index 02205c3..4b113f6 100644 --- a/datasource/proc_cmdline/proc_cmdline.go +++ b/datasource/proc_cmdline/proc_cmdline.go @@ -66,6 +66,10 @@ func (c *procCmdline) FetchUserdata() ([]byte, error) { return cfg, nil } +func (c *procCmdline) FetchNetworkConfig(filename string) ([]byte, error) { + return nil, nil +} + func (c *procCmdline) Type() string { return "proc-cmdline" } diff --git a/datasource/url/url.go b/datasource/url/url.go index ed73ba4..9a31056 100644 --- a/datasource/url/url.go +++ b/datasource/url/url.go @@ -1,6 +1,8 @@ package url -import "github.com/coreos/coreos-cloudinit/pkg" +import ( + "github.com/coreos/coreos-cloudinit/pkg" +) type remoteFile struct { url string @@ -33,6 +35,10 @@ func (f *remoteFile) FetchUserdata() ([]byte, error) { return client.GetRetry(f.url) } +func (f *remoteFile) FetchNetworkConfig(filename string) ([]byte, error) { + return nil, nil +} + func (f *remoteFile) Type() string { return "url" } diff --git a/initialize/config.go b/initialize/config.go index bf7a2da..3d07328 100644 --- a/initialize/config.go +++ b/initialize/config.go @@ -3,7 +3,6 @@ package initialize import ( "errors" "fmt" - "io/ioutil" "log" "path" @@ -42,6 +41,7 @@ type CloudConfig struct { Users []system.User ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"` NetworkConfigPath string + NetworkConfig string } type warner func(format string, v ...interface{}) @@ -258,17 +258,11 @@ func Apply(cfg CloudConfig, env *Environment) error { } 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 err error switch env.NetconfType() { case "debian": - interfaces, err = network.ProcessDebianNetconf(string(netconfBytes)) + interfaces, err = network.ProcessDebianNetconf(cfg.NetworkConfig) default: return fmt.Errorf("Unsupported network config format %q", env.NetconfType()) } From 4a2e4177813429b9378409c65334eeb314786f88 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Mon, 18 Aug 2014 12:26:27 -0700 Subject: [PATCH 4/5] 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. --- network/interface.go | 15 ++++++++++++--- network/interface_test.go | 8 +++++--- network/stanza.go | 9 +++++---- network/stanza_test.go | 10 ++++++---- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/network/interface.go b/network/interface.go index d3a6f9d..1f63a9c 100644 --- a/network/interface.go +++ b/network/interface.go @@ -2,6 +2,7 @@ package network import ( "fmt" + "net" "sort" "strconv" "strings" @@ -25,13 +26,21 @@ type networkInterface interface { type logicalInterface struct { name string + hwaddr net.HardwareAddr config configMethod children []networkInterface configDepth int } 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 { switch iface := child.(type) { @@ -47,8 +56,8 @@ func (i *logicalInterface) Network() string { for _, nameserver := range conf.nameservers { config += fmt.Sprintf("DNS=%s\n", nameserver) } - if conf.address.IP != nil { - config += fmt.Sprintf("\n[Address]\nAddress=%s\n", conf.address.String()) + for _, addr := range conf.addresses { + config += fmt.Sprintf("\n[Address]\nAddress=%s\n", addr.String()) } for _, route := range conf.routes { config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway) diff --git a/network/interface_test.go b/network/interface_test.go index daa7b99..24ecc7d 100644 --- a/network/interface_test.go +++ b/network/interface_test.go @@ -181,9 +181,11 @@ func TestVLANInterfaceNetwork(t *testing.T) { logicalInterface{ name: "testname", config: configMethodStatic{ - address: net.IPNet{ - IP: []byte{192, 168, 1, 100}, - Mask: []byte{255, 255, 255, 0}, + addresses: []net.IPNet{ + { + IP: []byte{192, 168, 1, 100}, + Mask: []byte{255, 255, 255, 0}, + }, }, nameservers: []net.IP{ []byte{8, 8, 8, 8}, diff --git a/network/stanza.go b/network/stanza.go index 3319ce7..3c7334e 100644 --- a/network/stanza.go +++ b/network/stanza.go @@ -37,7 +37,7 @@ type route struct { type configMethod interface{} type configMethodStatic struct { - address net.IPNet + addresses []net.IPNet nameservers []net.IP routes []route hwaddress net.HardwareAddr @@ -193,20 +193,21 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa switch confMethod { case "static": config := configMethodStatic{ + addresses: make([]net.IPNet, 1), routes: make([]route, 0), nameservers: make([]net.IP, 0), } if addresses, ok := optionMap["address"]; ok { if len(addresses) == 1 { - config.address.IP = net.ParseIP(addresses[0]) + config.addresses[0].IP = net.ParseIP(addresses[0]) } } if netmasks, ok := optionMap["netmask"]; ok { if len(netmasks) == 1 { - config.address.Mask = net.IPMask(net.ParseIP(netmasks[0]).To4()) + 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) } if gateways, ok := optionMap["gateway"]; ok { diff --git a/network/stanza_test.go b/network/stanza_test.go index 02cc006..b54e95c 100644 --- a/network/stanza_test.go +++ b/network/stanza_test.go @@ -194,9 +194,11 @@ func TestParseVLANStanzas(t *testing.T) { func TestParseInterfaceStanzaStaticAddress(t *testing.T) { options := []string{"address 192.168.1.100", "netmask 255.255.255.0"} - expect := net.IPNet{ - IP: net.IPv4(192, 168, 1, 100), - Mask: net.IPv4Mask(255, 255, 255, 0), + expect := []net.IPNet{ + { + IP: net.IPv4(192, 168, 1, 100), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, } iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options) @@ -207,7 +209,7 @@ func TestParseInterfaceStanzaStaticAddress(t *testing.T) { if !ok { t.FailNow() } - if !reflect.DeepEqual(static.address, expect) { + if !reflect.DeepEqual(static.addresses, expect) { t.FailNow() } } From e9ec78ac6faa986eb7cfa4dded44cda5f661fc73 Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Mon, 18 Aug 2014 15:58:26 -0700 Subject: [PATCH 5/5] test: Refactor interface tests a little --- network/interface.go | 24 +-- network/interface_test.go | 312 +++++++++++--------------------------- 2 files changed, 93 insertions(+), 243 deletions(-) diff --git a/network/interface.go b/network/interface.go index 1f63a9c..6bad7a2 100644 --- a/network/interface.go +++ b/network/interface.go @@ -32,6 +32,10 @@ type logicalInterface struct { configDepth int } +func (i *logicalInterface) Name() string { + return i.name +} + func (i *logicalInterface) Network() string { config := fmt.Sprintln("[Match]") if i.name != "" { @@ -73,6 +77,10 @@ func (i *logicalInterface) Link() string { return "" } +func (i *logicalInterface) Netdev() string { + return "" +} + func (i *logicalInterface) Filename() string { return fmt.Sprintf("%02x-%s", i.configDepth, i.name) } @@ -93,14 +101,6 @@ type physicalInterface struct { logicalInterface } -func (p *physicalInterface) Name() string { - return p.name -} - -func (p *physicalInterface) Netdev() string { - return "" -} - func (p *physicalInterface) Type() string { return "physical" } @@ -111,10 +111,6 @@ type bondInterface struct { options map[string]string } -func (b *bondInterface) Name() string { - return b.name -} - func (b *bondInterface) Netdev() string { return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name) } @@ -138,10 +134,6 @@ type vlanInterface struct { rawDevice string } -func (v *vlanInterface) Name() string { - return v.name -} - func (v *vlanInterface) Netdev() string { config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name) switch c := v.config.(type) { diff --git a/network/interface_test.go b/network/interface_test.go index 24ecc7d..51d9458 100644 --- a/network/interface_test.go +++ b/network/interface_test.go @@ -6,243 +6,101 @@ import ( "testing" ) -func TestPhysicalInterfaceName(t *testing.T) { - p := physicalInterface{logicalInterface{name: "testname"}} - if p.Name() != "testname" { - t.FailNow() - } -} - -func TestPhysicalInterfaceNetdev(t *testing.T) { - p := physicalInterface{} - if p.Netdev() != "" { - t.FailNow() - } -} - -func TestPhysicalInterfaceLink(t *testing.T) { - p := physicalInterface{} - if p.Link() != "" { - t.FailNow() - } -} - -func TestPhysicalInterfaceNetwork(t *testing.T) { - p := physicalInterface{logicalInterface{ - name: "testname", - children: []networkInterface{ - &bondInterface{ - logicalInterface{ - name: "testbond1", - }, - nil, - nil, - }, - &vlanInterface{ - logicalInterface{ - name: "testvlan1", - }, - 1, - "", - }, - &vlanInterface{ - logicalInterface{ - name: "testvlan2", - }, - 1, - "", - }, - }, - }} - network := `[Match] -Name=testname - -[Network] -Bond=testbond1 -VLAN=testvlan1 -VLAN=testvlan2 -` - if p.Network() != network { - t.FailNow() - } -} - -func TestBondInterfaceName(t *testing.T) { - b := bondInterface{logicalInterface{name: "testname"}, nil, 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", - config: configMethodDHCP{}, - children: []networkInterface{ - &bondInterface{ - logicalInterface{ - name: "testbond1", - }, - 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) { +func TestInterfaceGenerators(t *testing.T) { for _, tt := range []struct { - i vlanInterface - l string + name string + netdev string + link string + network string + kind string + iface InterfaceGenerator }{ { - vlanInterface{logicalInterface{name: "testname"}, 1, ""}, - "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n", + name: "", + network: "[Match]\nMACAddress=00:01:02:03:04:05\n\n[Network]\n", + kind: "physical", + iface: &physicalInterface{logicalInterface{ + hwaddr: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}), + }}, }, { - 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", + name: "testname", + network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\n", + kind: "physical", + iface: &physicalInterface{logicalInterface{ + name: "testname", + children: []networkInterface{ + &bondInterface{logicalInterface: logicalInterface{name: "testbond1"}}, + &vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1}, + &vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1}, + }, + }}, }, { - 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", + name: "testname", + netdev: "[NetDev]\nKind=bond\nName=testname\n", + network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\nDHCP=true\n", + kind: "bond", + iface: &bondInterface{logicalInterface: logicalInterface{ + name: "testname", + config: configMethodDHCP{}, + children: []networkInterface{ + &bondInterface{logicalInterface: logicalInterface{name: "testbond1"}}, + &vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1}, + &vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1}, + }, + }}, + }, + { + name: "testname", + 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, ""}, + }, + { + name: "testname", + 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, ""}, + }, + { + name: "testname", + 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, ""}, + }, + { + name: "testname", + netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=0\n", + 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{ + name: "testname", + config: configMethodStatic{ + addresses: []net.IPNet{{IP: []byte{192, 168, 1, 100}, Mask: []byte{255, 255, 255, 0}}}, + nameservers: []net.IP{[]byte{8, 8, 8, 8}}, + routes: []route{route{destination: net.IPNet{IP: []byte{0, 0, 0, 0}, Mask: []byte{0, 0, 0, 0}}, gateway: []byte{1, 2, 3, 4}}}, + }, + }}, }, } { - if tt.i.Netdev() != tt.l { - t.Fatalf("bad netdev config (%q): got %q, want %q", tt.i, tt.i.Netdev(), tt.l) + if name := tt.iface.Name(); name != tt.name { + t.Fatalf("bad name (%q): want %q, got %q", tt.iface, tt.name, name) } - } -} - -func TestVLANInterfaceLink(t *testing.T) { - v := vlanInterface{} - if v.Link() != "" { - t.FailNow() - } -} - -func TestVLANInterfaceNetwork(t *testing.T) { - v := vlanInterface{ - logicalInterface{ - name: "testname", - config: configMethodStatic{ - addresses: []net.IPNet{ - { - IP: []byte{192, 168, 1, 100}, - Mask: []byte{255, 255, 255, 0}, - }, - }, - nameservers: []net.IP{ - []byte{8, 8, 8, 8}, - }, - routes: []route{ - route{ - destination: net.IPNet{ - IP: []byte{0, 0, 0, 0}, - Mask: []byte{0, 0, 0, 0}, - }, - gateway: []byte{1, 2, 3, 4}, - }, - }, - }, - }, - 0, - "", - } - network := `[Match] -Name=testname - -[Network] -DNS=8.8.8.8 - -[Address] -Address=192.168.1.100/24 - -[Route] -Destination=0.0.0.0/0 -Gateway=1.2.3.4 -` - if v.Network() != network { - t.Log(v.Network()) - t.FailNow() - } -} - -func 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 { - t.Fatalf("bad type (%q): got %s, want %s", tt.i, tp, tt.t) + 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) } } }