metadata: Refactor common parts out of ec2

This commit is contained in:
Alex Crawford 2014-08-15 18:13:37 -07:00
parent e59e2f6cd5
commit a923161f4a
7 changed files with 298 additions and 220 deletions

View File

@ -1,10 +1,5 @@
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

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

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

1
test
View File

@ -18,6 +18,7 @@ declare -a TESTPKGS=(initialize
datasource datasource
datasource/configdrive datasource/configdrive
datasource/file datasource/file
datasource/metadata
datasource/metadata/cloudsigma datasource/metadata/cloudsigma
datasource/metadata/ec2 datasource/metadata/ec2
datasource/proc_cmdline datasource/proc_cmdline