From 6e72b26e3ddce6029906aa5f90611a062a9b9ff8 Mon Sep 17 00:00:00 2001 From: "Steven E. Harris" Date: Tue, 10 Jan 2017 16:40:23 -0500 Subject: [PATCH] Accommodate JSON data center metadata translation When the Eureka server encodes an instance as JSON, it will sometimes write the data center metadata values that look like numbers as JSON numbers, rather than writing all metadata values as strings. Demanding that all metadata values arrive as JSON strings precludes proper decoding of instances, so be more flexible by first decoding the metadata values as any JSON type, and then projecting the values that are not strings back to strings. Note that we only project decoded Go types bool, float64, and nil; while it's possible that Go can decode a value of type []interface{} or map[string]interface{}, we make no effort to project those values. --- marshal.go | 40 ++++++++++++++++++++++++++++++++++------ metadata.go | 2 +- tests/marshal_test.go | 10 ++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/marshal.go b/marshal.go index 7355954..7a634be 100644 --- a/marshal.go +++ b/marshal.go @@ -377,7 +377,7 @@ func populateAmazonMetadata(dst *AmazonMetadataType, src map[string]string) { bindValue(&dst.InstanceType, src, "instance-type") } -func adaptDataCenterInfo(dst *DataCenterInfo, src preliminaryDataCenterInfo) { +func adaptDataCenterInfo(dst *DataCenterInfo, src *preliminaryDataCenterInfo) { dst.Name = src.Name dst.Class = src.Class if src.Name == Amazon { @@ -396,7 +396,7 @@ func (i *DataCenterInfo) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er if err := d.DecodeElement(&p, &start); err != nil { return err } - adaptDataCenterInfo(i, p) + adaptDataCenterInfo(i, &p) return nil } @@ -429,15 +429,43 @@ func (i *DataCenterInfo) MarshalJSON() ([]byte, error) { }) } +func jsonValueAsString(i interface{}) string { + switch v := i.(type) { + case string: + return v + case float64: + return fmt.Sprintf("%.f", v) + case bool: + return strconv.FormatBool(v) + case []interface{}, map[string]interface{}: + // Don't bother trying to decode these. + return "" + case nil: + return "" + default: + panic("type of unexpected value") + } +} + // UnmarshalJSON is a custom JSON unmarshaler for DataCenterInfo, populating either Metadata or AlternateMetadata // depending on the type of data center indicated by the Name. func (i *DataCenterInfo) UnmarshalJSON(b []byte) error { - p := preliminaryDataCenterInfo{ - Metadata: make(map[string]string, 11), + // The Eureka server will mistakenly convert metadata values that look like numbers to JSON numbers. + // Convert them back to strings. + aux := struct { + *preliminaryDataCenterInfo + PreliminaryMetadata map[string]interface{} `json:"metadata"` + }{ + PreliminaryMetadata: make(map[string]interface{}, 11), } - if err := json.Unmarshal(b, &p); err != nil { + if err := json.Unmarshal(b, &aux); err != nil { return err } - adaptDataCenterInfo(i, p) + metadata := make(map[string]string, len(aux.PreliminaryMetadata)) + for k, v := range aux.PreliminaryMetadata { + metadata[k] = jsonValueAsString(v) + } + aux.Metadata = metadata + adaptDataCenterInfo(i, aux.preliminaryDataCenterInfo) return nil } diff --git a/metadata.go b/metadata.go index f1af0be..2efec06 100644 --- a/metadata.go +++ b/metadata.go @@ -5,6 +5,7 @@ package fargo import ( "encoding/json" "fmt" + "github.com/clbanning/x2j" ) @@ -32,7 +33,6 @@ func (ins *Instance) SetMetadataString(key, value string) { func (im *InstanceMetadata) parse() error { if len(im.Raw) == 0 { im.parsed = make(map[string]interface{}) - log.Debug("len(Metadata)==0. Quitting parsing.") return nil } metadataLog.Debugf("InstanceMetadata.parse: %s", im.Raw) diff --git a/tests/marshal_test.go b/tests/marshal_test.go index cc20056..28896d5 100644 --- a/tests/marshal_test.go +++ b/tests/marshal_test.go @@ -310,6 +310,16 @@ func TestDataCenterInfoMarshal(t *testing.T) { So(err, ShouldBeNil) So(d, ShouldResemble, ins.DataCenterInfo) + + Convey("Even if the server translates strings to other types", func() { + translated := bytes.Replace(b, []byte(`"123"`), []byte("123"), 1) + + d := fargo.DataCenterInfo{} + err := json.Unmarshal(translated, &d) + + So(err, ShouldBeNil) + So(d, ShouldResemble, ins.DataCenterInfo) + }) }) }) })