diff --git a/pkg/cloudprovider/providers/azure/BUILD b/pkg/cloudprovider/providers/azure/BUILD index 75848013e5d..925dc350986 100644 --- a/pkg/cloudprovider/providers/azure/BUILD +++ b/pkg/cloudprovider/providers/azure/BUILD @@ -15,6 +15,7 @@ go_library( "azure_backoff.go", "azure_blob.go", "azure_file.go", + "azure_instance_metadata.go", "azure_instances.go", "azure_loadbalancer.go", "azure_routes.go", diff --git a/pkg/cloudprovider/providers/azure/azure.go b/pkg/cloudprovider/providers/azure/azure.go index daba02d13b9..1ec9db11b31 100644 --- a/pkg/cloudprovider/providers/azure/azure.go +++ b/pkg/cloudprovider/providers/azure/azure.go @@ -104,6 +104,9 @@ type Config struct { CloudProviderRateLimitQPS float32 `json:"cloudProviderRateLimitQPS" yaml:"cloudProviderRateLimitQPS"` // Rate limit Bucket Size CloudProviderRateLimitBucket int `json:"cloudProviderRateLimitBucket" yaml:"cloudProviderRateLimitBucket"` + + // Use instance metadata service where possible + UseInstanceMetadata bool `json:"useInstanceMetadata" yaml:"useInstanceMetadata"` } // Cloud holds the config and clients diff --git a/pkg/cloudprovider/providers/azure/azure_instance_metadata.go b/pkg/cloudprovider/providers/azure/azure_instance_metadata.go new file mode 100644 index 00000000000..7a418c46453 --- /dev/null +++ b/pkg/cloudprovider/providers/azure/azure_instance_metadata.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package azure + +import ( + "encoding/json" + "io/ioutil" + "net/http" +) + +// This is just for tests injection +var metadataURL = "http://169.254.169.254/metadata" + +// SetMetadataURLForTesting is used to modify the URL used for +// accessing the metadata server. Should only be used for testing! +func SetMetadataURLForTesting(url string) { + metadataURL = url +} + +// NetworkMetadata contains metadata about an instance's network +type NetworkMetadata struct { + Interface []NetworkInterface `json:"interface"` +} + +// NetworkInterface represents an instances network interface. +type NetworkInterface struct { + IPV4 NetworkData `json:"ipv4"` + IPV6 NetworkData `json:"ipv6"` + MAC string `json:"macAddress"` +} + +// NetworkData contains IP information for a network. +type NetworkData struct { + IPAddress []IPAddress `json:"ipAddress"` + Subnet []Subnet `json:"subnet"` +} + +// IPAddress represents IP address information. +type IPAddress struct { + PrivateIP string `json:"privateIPAddress"` + PublicIP string `json:"publicIPAddress"` +} + +// Subnet represents subnet information. +type Subnet struct { + Address string `json:"address"` + Prefix string `json:"prefix"` +} + +// QueryMetadataJSON queries the metadata server and populates the passed in object +func QueryMetadataJSON(path string, obj interface{}) error { + data, err := queryMetadataBytes(path, "json") + if err != nil { + return err + } + return json.Unmarshal(data, obj) +} + +// QueryMetadataText queries the metadata server and returns the corresponding text +func QueryMetadataText(path string) (string, error) { + data, err := queryMetadataBytes(path, "text") + if err != nil { + return "", err + } + return string(data), err +} + +func queryMetadataBytes(path, format string) ([]byte, error) { + client := &http.Client{} + + req, err := http.NewRequest("GET", metadataURL+path, nil) + if err != nil { + return nil, err + } + req.Header.Add("Metadata", "True") + + q := req.URL.Query() + q.Add("format", format) + q.Add("api-version", "2017-04-02") + req.URL.RawQuery = q.Encode() + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return ioutil.ReadAll(resp.Body) +} diff --git a/pkg/cloudprovider/providers/azure/azure_instances.go b/pkg/cloudprovider/providers/azure/azure_instances.go index e624a08d463..256428d8a39 100644 --- a/pkg/cloudprovider/providers/azure/azure_instances.go +++ b/pkg/cloudprovider/providers/azure/azure_instances.go @@ -29,6 +29,16 @@ import ( // NodeAddresses returns the addresses of the specified instance. func (az *Cloud) NodeAddresses(name types.NodeName) ([]v1.NodeAddress, error) { + if az.UseInstanceMetadata { + text, err := QueryMetadataText("instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress") + if err != nil { + return nil, err + } + return []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: text}, + {Type: v1.NodeHostName, Address: string(name)}, + }, nil + } ip, err := az.getIPForMachine(name) if err != nil { glog.Errorf("error: az.NodeAddresses, az.getIPForMachine(%s), err=%v", name, err) diff --git a/pkg/cloudprovider/providers/azure/azure_test.go b/pkg/cloudprovider/providers/azure/azure_test.go index 08d74b023d3..9b9050579eb 100644 --- a/pkg/cloudprovider/providers/azure/azure_test.go +++ b/pkg/cloudprovider/providers/azure/azure_test.go @@ -17,7 +17,11 @@ limitations under the License. package azure import ( + "encoding/json" "fmt" + "net/http" + "net/http/httptest" + "reflect" "strings" "testing" @@ -816,3 +820,60 @@ func TestSplitProviderID(t *testing.T) { } } + +func TestMetadataParsing(t *testing.T) { + data := ` +{ + "interface": [ + { + "ipv4": { + "ipAddress": [ + { + "privateIpAddress": "10.0.1.4", + "publicIpAddress": "X.X.X.X" + } + ], + "subnet": [ + { + "address": "10.0.1.0", + "prefix": "24" + } + ] + }, + "ipv6": { + "ipAddress": [ + + ] + }, + "macAddress": "002248020E1E" + } + ] +} +` + + network := NetworkMetadata{} + if err := json.Unmarshal([]byte(data), &network); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + ip := network.Interface[0].IPV4.IPAddress[0].PrivateIP + if ip != "10.0.1.4" { + t.Errorf("Unexpected value: %s, expected 10.0.1.4", ip) + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, data) + })) + defer server.Close() + + SetMetadataURLForTesting(server.URL) + + networkJSON := NetworkMetadata{} + if err := QueryMetadataJSON("/some/path", &networkJSON); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if !reflect.DeepEqual(network, networkJSON) { + t.Errorf("Unexpected inequality:\n%#v\nvs\n%#v", network, networkJSON) + } +}