From 84e1cb3242fb44fc3499b615f3515081c9482f6b Mon Sep 17 00:00:00 2001 From: Alex Crawford Date: Fri, 10 Oct 2014 11:27:27 -0700 Subject: [PATCH] datasource/waagent: add support for WAAgent metadata --- datasource/waagent/waagent.go | 108 +++++++++++++++++++ datasource/waagent/waagent_test.go | 168 +++++++++++++++++++++++++++++ test | 1 + 3 files changed, 277 insertions(+) create mode 100644 datasource/waagent/waagent.go create mode 100644 datasource/waagent/waagent_test.go diff --git a/datasource/waagent/waagent.go b/datasource/waagent/waagent.go new file mode 100644 index 0000000..07bffe7 --- /dev/null +++ b/datasource/waagent/waagent.go @@ -0,0 +1,108 @@ +package waagent + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io/ioutil" + "net" + "os" + "path" +) + +type waagent struct { + root string + readFile func(filename string) ([]byte, error) +} + +func NewDatasource(root string) *waagent { + return &waagent{root, ioutil.ReadFile} +} + +func (a *waagent) IsAvailable() bool { + _, err := os.Stat(path.Join(a.root, "provisioned")) + return !os.IsNotExist(err) +} + +func (a *waagent) AvailabilityChanges() bool { + return true +} + +func (a *waagent) ConfigRoot() string { + return a.root +} + +func (a *waagent) FetchMetadata() ([]byte, error) { + metadataBytes, err := a.tryReadFile(path.Join(a.root, "SharedConfig.xml")) + if err != nil { + return nil, err + } + if len(metadataBytes) == 0 { + return metadataBytes, nil + } + + type Instance struct { + Id string `xml:"id,attr"` + Address string `xml:"address,attr"` + InputEndpoints struct { + Endpoints []struct { + LoadBalancedPublicAddress string `xml:"loadBalancedPublicAddress,attr"` + } `xml:"Endpoint"` + } + } + + type SharedConfig struct { + Incarnation struct { + Instance string `xml:"instance,attr"` + } + Instances struct { + Instances []Instance `xml:"Instance"` + } + } + + var metadata SharedConfig + if err := xml.Unmarshal(metadataBytes, &metadata); err != nil { + return nil, err + } + + var instance Instance + for _, i := range metadata.Instances.Instances { + if i.Id == metadata.Incarnation.Instance { + instance = i + break + } + } + + attrs := map[string]string{ + "local-ipv4": instance.Address, + } + for _, e := range instance.InputEndpoints.Endpoints { + host, _, err := net.SplitHostPort(e.LoadBalancedPublicAddress) + if err == nil { + attrs["public-ipv4"] = host + break + } + } + return json.Marshal(attrs) +} + +func (a *waagent) FetchUserdata() ([]byte, error) { + return a.tryReadFile(path.Join(a.root, "CustomData")) +} + +func (a *waagent) FetchNetworkConfig(filename string) ([]byte, error) { + return nil, nil +} + +func (a *waagent) Type() string { + return "waagent" +} + +func (a *waagent) tryReadFile(filename string) ([]byte, error) { + fmt.Printf("Attempting to read from %q\n", filename) + data, err := a.readFile(filename) + if os.IsNotExist(err) { + err = nil + } + return data, err +} diff --git a/datasource/waagent/waagent_test.go b/datasource/waagent/waagent_test.go new file mode 100644 index 0000000..2b9eee9 --- /dev/null +++ b/datasource/waagent/waagent_test.go @@ -0,0 +1,168 @@ +package waagent + +import ( + "encoding/json" + "os" + "reflect" + "testing" +) + +type mockFilesystem map[string][]byte + +func (m mockFilesystem) readFile(filename string) ([]byte, error) { + if contents := m[filename]; contents != nil { + return contents, nil + } + return nil, os.ErrNotExist +} + +func TestFetchMetadata(t *testing.T) { + for _, tt := range []struct { + root string + files mockFilesystem + metadata map[string]string + }{ + { + "/", + mockFilesystem{}, + nil, + }, + { + "/", + mockFilesystem{"/SharedConfig.xml": []byte("")}, + nil, + }, + { + "/var/lib/waagent", + mockFilesystem{"/var/lib/waagent/SharedConfig.xml": []byte("")}, + nil, + }, + { + "/var/lib/waagent", + mockFilesystem{"/var/lib/waagent/SharedConfig.xml": []byte(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`)}, + map[string]string{ + "local-ipv4": "100.73.202.64", + "public-ipv4": "191.239.39.77", + }, + }, + } { + a := waagent{tt.root, tt.files.readFile} + metadataBytes, err := a.FetchMetadata() + if err != nil { + t.Fatalf("bad error for %q: want %q, got %q", tt, nil, err) + } + var metadata map[string]string + if len(metadataBytes) > 0 { + if err := json.Unmarshal(metadataBytes, &metadata); err != nil { + panic(err) + } + } + if !reflect.DeepEqual(tt.metadata, metadata) { + t.Fatalf("bad metadata for %q: want %q, got %q", tt, tt.metadata, metadata) + } + } +} + +func TestFetchUserdata(t *testing.T) { + for _, tt := range []struct { + root string + files mockFilesystem + }{ + { + "/", + mockFilesystem{}, + }, + { + "/", + mockFilesystem{"/CustomData": []byte{}}, + }, + { + "/var/lib/waagent/", + mockFilesystem{"/var/lib/waagent/CustomData": []byte{}}, + }, + } { + a := waagent{tt.root, tt.files.readFile} + _, err := a.FetchUserdata() + if err != nil { + t.Fatalf("bad error for %q: want %q, got %q", tt, nil, err) + } + } +} + +func TestConfigRoot(t *testing.T) { + for _, tt := range []struct { + root string + configRoot string + }{ + { + "/", + "/", + }, + { + "/var/lib/waagent", + "/var/lib/waagent", + }, + } { + a := waagent{tt.root, nil} + if configRoot := a.ConfigRoot(); configRoot != tt.configRoot { + t.Fatalf("bad config root for %q: want %q, got %q", tt, tt.configRoot, configRoot) + } + } +} + +func TestNewDatasource(t *testing.T) { + for _, tt := range []struct { + root string + expectRoot string + }{ + { + root: "", + expectRoot: "", + }, + { + root: "/var/lib/waagent", + expectRoot: "/var/lib/waagent", + }, + } { + service := NewDatasource(tt.root) + if service.root != tt.expectRoot { + t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.root) + } + } +} diff --git a/test b/test index d34ff5b..aace4dc 100755 --- a/test +++ b/test @@ -24,6 +24,7 @@ declare -a TESTPKGS=( datasource/metadata/ec2 datasource/proc_cmdline datasource/url + datasource/waagent initialize network pkg