-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accommodate non-Amazon data center info metadata #44
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ package fargo | |
import ( | ||
"encoding/json" | ||
"encoding/xml" | ||
"io" | ||
"strconv" | ||
) | ||
|
||
|
@@ -122,17 +123,22 @@ func (i *InstanceMetadata) MarshalJSON() ([]byte, error) { | |
return i.Raw, nil | ||
} | ||
|
||
// startLocalName creates a start-tag of an XML element with the given local name and no namespace name. | ||
func startLocalName(local string) xml.StartElement { | ||
return xml.StartElement{Name: xml.Name{Space: "", Local: local}} | ||
} | ||
|
||
// MarshalXML is a custom XML marshaler for InstanceMetadata. | ||
func (i InstanceMetadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error { | ||
tokens := []xml.Token{start} | ||
|
||
if i.parsed != nil { | ||
for key, value := range i.parsed { | ||
t := xml.StartElement{Name: xml.Name{"", key}} | ||
tokens = append(tokens, t, xml.CharData(value.(string)), xml.EndElement{t.Name}) | ||
t := startLocalName(key) | ||
tokens = append(tokens, t, xml.CharData(value.(string)), xml.EndElement{Name: t.Name}) | ||
} | ||
} | ||
tokens = append(tokens, xml.EndElement{start.Name}) | ||
tokens = append(tokens, xml.EndElement{Name: start.Name}) | ||
|
||
for _, t := range tokens { | ||
err := e.EncodeToken(t) | ||
|
@@ -144,3 +150,152 @@ func (i InstanceMetadata) MarshalXML(e *xml.Encoder, start xml.StartElement) err | |
// flush to ensure tokens are written | ||
return e.Flush() | ||
} | ||
|
||
type metadataMap map[string]string | ||
|
||
// MarshalXML is a custom XML marshaler for metadataMap, mapping each metadata name/value pair to a | ||
// correspondingly named XML element with the pair's value as character data content. | ||
func (m metadataMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { | ||
if err := e.EncodeToken(start); err != nil { | ||
return err | ||
} | ||
|
||
for k, v := range m { | ||
if err := e.EncodeElement(v, startLocalName(k)); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return e.EncodeToken(start.End()) | ||
} | ||
|
||
// UnmarshalXML is a custom XML unmarshaler for metadataMap, mapping each XML element's name and | ||
// character data content to a corresponding metadata name/value pair. | ||
func (m metadataMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { | ||
var v string | ||
for { | ||
t, err := d.Token() | ||
if err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return err | ||
} | ||
if k, ok := t.(xml.StartElement); ok { | ||
if err := d.DecodeElement(&v, &k); err != nil { | ||
return err | ||
} | ||
m[k.Name.Local] = v | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func metadataValue(i DataCenterInfo) interface{} { | ||
if i.Name == Amazon { | ||
return i.Metadata | ||
} | ||
return metadataMap(i.AlternateMetadata) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My use of "alternate" here is apparently a North American meaning, which is nearly synonymous with "alternative". I did some reading, and found this explanation ("Alternate' in American English):
Another source has this to say:
Here, our first choice is to use Amazon-related metadata. Failing that, we fall back to using the alternate metadata. To my ear, "alternative" makes it sound like we could choose either of them, but they should never both be available or used at the same time. It sounds like my choice might confuse our readers in UK, but their preferred form sounds strange to my North American ear. What to do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Giving that we use either one or another and not both at a time, I'm happy with |
||
} | ||
|
||
var ( | ||
startName = startLocalName("name") | ||
startMetadata = startLocalName("metadata") | ||
) | ||
|
||
// MarshalXML is a custom XML marshaler for DataCenterInfo, writing either Metadata or AlternateMetadata | ||
// depending on the type of data center indicated by the Name. | ||
func (i DataCenterInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error { | ||
if err := e.EncodeToken(start); err != nil { | ||
return err | ||
} | ||
|
||
if err := e.EncodeElement(i.Name, startName); err != nil { | ||
return err | ||
} | ||
if err := e.EncodeElement(metadataValue(i), startMetadata); err != nil { | ||
return err | ||
} | ||
|
||
return e.EncodeToken(start.End()) | ||
} | ||
|
||
type preliminaryDataCenterInfo struct { | ||
Name string `xml:"name" json:"name"` | ||
Metadata metadataMap `xml:"metadata" json:"metadata"` | ||
} | ||
|
||
func bindValue(dst *string, src map[string]string, k string) bool { | ||
if v, ok := src[k]; ok { | ||
*dst = v | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func populateAmazonMetadata(dst *AmazonMetadataType, src map[string]string) { | ||
bindValue(&dst.AmiLaunchIndex, src, "ami-launch-index") | ||
bindValue(&dst.LocalHostname, src, "local-hostname") | ||
bindValue(&dst.AvailabilityZone, src, "availability-zone") | ||
bindValue(&dst.InstanceID, src, "instance-id") | ||
bindValue(&dst.PublicIpv4, src, "public-ipv4") | ||
bindValue(&dst.PublicHostname, src, "public-hostname") | ||
bindValue(&dst.AmiManifestPath, src, "ami-manifest-path") | ||
bindValue(&dst.LocalIpv4, src, "local-ipv4") | ||
bindValue(&dst.HostName, src, "hostname") | ||
bindValue(&dst.AmiID, src, "ami-id") | ||
bindValue(&dst.InstanceType, src, "instance-type") | ||
} | ||
|
||
func adaptDataCenterInfo(dst *DataCenterInfo, src preliminaryDataCenterInfo) { | ||
dst.Name = src.Name | ||
if src.Name == Amazon { | ||
populateAmazonMetadata(&dst.Metadata, src.Metadata) | ||
} else { | ||
dst.AlternateMetadata = src.Metadata | ||
} | ||
} | ||
|
||
// UnmarshalXML is a custom XML unmarshaler for DataCenterInfo, populating either Metadata or AlternateMetadata | ||
// depending on the type of data center indicated by the Name. | ||
func (i *DataCenterInfo) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { | ||
p := preliminaryDataCenterInfo{ | ||
Metadata: make(map[string]string, 11), | ||
} | ||
if err := d.DecodeElement(&p, &start); err != nil { | ||
return err | ||
} | ||
adaptDataCenterInfo(i, p) | ||
return nil | ||
} | ||
|
||
// MarshalJSON is a custom JSON marshaler for DataCenterInfo, writing either Metadata or AlternateMetadata | ||
// depending on the type of data center indicated by the Name. | ||
func (i DataCenterInfo) MarshalJSON() ([]byte, error) { | ||
type named struct { | ||
Name string `json:"name"` | ||
} | ||
if i.Name == Amazon { | ||
return json.Marshal(struct { | ||
named | ||
Metadata AmazonMetadataType `json:"metadata"` | ||
}{named{i.Name}, i.Metadata}) | ||
} | ||
return json.Marshal(struct { | ||
named | ||
Metadata map[string]string `json:"metadata"` | ||
}{named{i.Name}, i.AlternateMetadata}) | ||
} | ||
|
||
// 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), | ||
} | ||
if err := json.Unmarshal(b, &p); err != nil { | ||
return err | ||
} | ||
adaptDataCenterInfo(i, p) | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,13 @@ import ( | |
"time" | ||
) | ||
|
||
// EurekaUrlSlugs is a map of resource names -> eureka URLs | ||
// EurekaUrlSlugs is a map of resource names->Eureka URLs. | ||
var EurekaURLSlugs = map[string]string{ | ||
"Apps": "apps", | ||
"Instances": "instances", | ||
} | ||
|
||
// EurekaConnection is the settings required to make eureka requests | ||
// EurekaConnection is the settings required to make Eureka requests. | ||
type EurekaConnection struct { | ||
ServiceUrls []string | ||
ServicePort int | ||
|
@@ -26,30 +26,30 @@ type EurekaConnection struct { | |
UseJson bool | ||
} | ||
|
||
// GetAppsResponseJson lets us deserialize the eureka/v2/apps response JSON--a wrapped GetAppsResponse | ||
// GetAppsResponseJson lets us deserialize the eureka/v2/apps response JSON—a wrapped GetAppsResponse. | ||
type GetAppsResponseJson struct { | ||
Response *GetAppsResponse `json:"applications"` | ||
} | ||
|
||
// GetAppsResponse lets us deserialize the eureka/v2/apps response XML | ||
// GetAppsResponse lets us deserialize the eureka/v2/apps response XML. | ||
type GetAppsResponse struct { | ||
Applications []*Application `xml:"application" json:"application"` | ||
AppsHashcode string `xml:"apps__hashcode" json:"apps__hashcode"` | ||
VersionsDelta int `xml:"versions__delta" json:"versions__delta"` | ||
} | ||
|
||
// Application deserializeable from Eureka JSON | ||
// Application deserializeable from Eureka JSON. | ||
type GetAppResponseJson struct { | ||
Application Application `json:"application"` | ||
} | ||
|
||
// Application deserializeable from Eureka XML | ||
// Application deserializeable from Eureka XML. | ||
type Application struct { | ||
Name string `xml:"name" json:"name"` | ||
Instances []*Instance `xml:"instance" json:"instance"` | ||
} | ||
|
||
// StatusType is an enum of the different statuses allowed by Eureka | ||
// StatusType is an enum of the different statuses allowed by Eureka. | ||
type StatusType string | ||
|
||
// Supported statuses | ||
|
@@ -67,12 +67,12 @@ const ( | |
MyOwn = "MyOwn" | ||
) | ||
|
||
// RegisterInstanceJson lets us serialize the eureka/v2/apps/<ins> request JSON--a wrapped Instance | ||
// RegisterInstanceJson lets us serialize the eureka/v2/apps/<ins> request JSON—a wrapped Instance. | ||
type RegisterInstanceJson struct { | ||
Instance *Instance `json:"instance"` | ||
} | ||
|
||
// Instance [de]serializeable [to|from] Eureka XML | ||
// Instance [de]serializeable [to|from] Eureka XML. | ||
type Instance struct { | ||
XMLName struct{} `xml:"instance" json:"-"` | ||
HostName string `xml:"hostName" json:"hostName"` | ||
|
@@ -102,21 +102,22 @@ type Instance struct { | |
UniqueID func(i Instance) string `xml:"-" json:"-"` | ||
} | ||
|
||
// Port struct used for JSON [un]marshaling only | ||
// looks like: "port":{"@enabled":"true", "$":"7101"}, | ||
// Port struct used for JSON [un]marshaling only. | ||
// An example: | ||
// "port":{"@enabled":"true", "$":"7101"} | ||
type Port struct { | ||
Number string `json:"$"` | ||
Enabled string `json:"@enabled"` | ||
} | ||
|
||
// InstanceMetadata represents the eureka metadata, which is arbitrary XML. See | ||
// metadata.go for more info. | ||
// InstanceMetadata represents the eureka metadata, which is arbitrary XML. | ||
// See metadata.go for more info. | ||
type InstanceMetadata struct { | ||
Raw []byte `xml:",innerxml" json:"-"` | ||
parsed map[string]interface{} | ||
} | ||
|
||
// AmazonMetadataType is information about AZ's, AMI's, and the AWS instance | ||
// AmazonMetadataType is information about AZ's, AMI's, and the AWS instance. | ||
// <xsd:complexType name="amazonMetdataType"> | ||
// from http://docs.amazonwebservices.com/AWSEC2/latest/DeveloperGuide/index.html?AESDG-chapter-instancedata.html | ||
type AmazonMetadataType struct { | ||
|
@@ -133,13 +134,20 @@ type AmazonMetadataType struct { | |
InstanceType string `xml:"instance-type" json:"instance-type"` | ||
} | ||
|
||
// DataCenterInfo is only really useful when running in AWS. | ||
// DataCenterInfo indicates which type of data center hosts this instance | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for all those comments! Great job |
||
// and conveys details about the instance's environment. | ||
type DataCenterInfo struct { | ||
Name string `xml:"name" json:"name"` | ||
Metadata AmazonMetadataType `xml:"metadata" json:"metadata"` | ||
// Name indicates which type of data center hosts this instance. | ||
Name string | ||
// Metadata provides details specific to an Amazon data center, | ||
// populated and honored when the Name field's value is "Amazon". | ||
Metadata AmazonMetadataType | ||
// AlternateMetadata provides details specific to a data center other than Amazon, | ||
// populated and honored when the Name field's value is not "Amazon". | ||
AlternateMetadata map[string]string | ||
} | ||
|
||
// LeaseInfo tells us about the renewal from Eureka, including how old it is | ||
// LeaseInfo tells us about the renewal from Eureka, including how old it is. | ||
type LeaseInfo struct { | ||
RenewalIntervalInSecs int32 `xml:"renewalIntervalInSecs" json:"renewalIntervalInSecs"` | ||
DurationInSecs int32 `xml:"durationInSecs" json:"durationInSecs"` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we call
Flush()
here?https://golang.org/pkg/encoding/xml/#Encoder.EncodeToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation mentions creating an
Encoder
:Here, I didn't create the
Encoder
; it's created implicitly over infargo.marshal
in file net.go. Note thatxml.Marshal
creates anEncoder
and callsEncode
on it, which in turn flushes when it's done writing. Hence I don't think that callingFlush
explicitly here is necessary.