diff --git a/pkg/cloudprovider/providers/gce/gce_instances.go b/pkg/cloudprovider/providers/gce/gce_instances.go index e6616fed49d..6446f26ff18 100644 --- a/pkg/cloudprovider/providers/gce/gce_instances.go +++ b/pkg/cloudprovider/providers/gce/gce_instances.go @@ -17,7 +17,6 @@ limitations under the License. package gce import ( - "errors" "fmt" "net/http" "strconv" @@ -62,7 +61,27 @@ func (gce *GCECloud) NodeAddresses(_ types.NodeName) ([]v1.NodeAddress, error) { // This method will not be called from the node that is requesting this ID. // i.e. metadata service and other local methods cannot be used here func (gce *GCECloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddress, error) { - return []v1.NodeAddress{}, errors.New("unimplemented") + project, zone, name, err := splitProviderID(providerID) + if err != nil { + return []v1.NodeAddress{}, err + } + + instance, err := gce.service.Instances.Get(project, zone, canonicalizeInstanceName(name)).Do() + if err != nil { + return []v1.NodeAddress{}, fmt.Errorf("error while querying for providerID %q: %v", providerID, err) + } + + if len(instance.NetworkInterfaces) < 1 { + return []v1.NodeAddress{}, fmt.Errorf("could not find network interfaces for providerID %q", providerID) + } + networkInterface := instance.NetworkInterfaces[0] + + nodeAddresses := []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: networkInterface.NetworkIP}} + for _, config := range networkInterface.AccessConfigs { + nodeAddresses = append(nodeAddresses, v1.NodeAddress{Type: v1.NodeExternalIP, Address: config.NatIP}) + } + + return nodeAddresses, nil } // InstanceTypeByProviderID returns the cloudprovider instance type of the node @@ -70,7 +89,15 @@ func (gce *GCECloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddr // node that is requesting this ID. i.e. metadata service and other local // methods cannot be used here func (gce *GCECloud) InstanceTypeByProviderID(providerID string) (string, error) { - return "", errors.New("unimplemented") + project, zone, name, err := splitProviderID(providerID) + if err != nil { + return "", err + } + instance, err := gce.getInstanceFromProjectInZoneByName(project, zone, name) + if err != nil { + return "", err + } + return instance.Type, nil } // ExternalID returns the cloud provider ID of the node with the specified NodeName (deprecated). @@ -339,30 +366,38 @@ func (gce *GCECloud) getInstancesByNames(names []string) ([]*gceInstance, error) func (gce *GCECloud) getInstanceByName(name string) (*gceInstance, error) { // Avoid changing behaviour when not managing multiple zones for _, zone := range gce.managedZones { - name = canonicalizeInstanceName(name) - mc := newInstancesMetricContext("get", zone) - res, err := gce.service.Instances.Get(gce.projectID, zone, name).Do() - mc.Observe(err) + instance, err := gce.getInstanceFromProjectInZoneByName(gce.projectID, zone, name) if err != nil { - glog.Errorf("getInstanceByName: failed to get instance %s; err: %v", name, err) - if isHTTPErrorCode(err, http.StatusNotFound) { continue } return nil, err } - return &gceInstance{ - Zone: lastComponent(res.Zone), - Name: res.Name, - ID: res.Id, - Disks: res.Disks, - Type: lastComponent(res.MachineType), - }, nil + return instance, nil } return nil, cloudprovider.InstanceNotFound } +func (gce *GCECloud) getInstanceFromProjectInZoneByName(project, zone, name string) (*gceInstance, error) { + name = canonicalizeInstanceName(name) + mc := newInstancesMetricContext("get", zone) + res, err := gce.service.Instances.Get(project, zone, name).Do() + mc.Observe(err) + if err != nil { + glog.Errorf("getInstanceFromProjectInZoneByName: failed to get instance %s; err: %v", name, err) + return nil, err + } + + return &gceInstance{ + Zone: lastComponent(res.Zone), + Name: res.Name, + ID: res.Id, + Disks: res.Disks, + Type: lastComponent(res.MachineType), + }, nil +} + func getInstanceIDViaMetadata() (string, error) { result, err := metadata.Get("instance/hostname") if err != nil { diff --git a/pkg/cloudprovider/providers/gce/gce_test.go b/pkg/cloudprovider/providers/gce/gce_test.go index 5bba03bf97c..c05f0bdccbe 100644 --- a/pkg/cloudprovider/providers/gce/gce_test.go +++ b/pkg/cloudprovider/providers/gce/gce_test.go @@ -158,3 +158,93 @@ func TestCreateFirewallFails(t *testing.T) { t.Errorf("error expected when creating firewall without any tags found") } } + +func TestSplitProviderID(t *testing.T) { + providers := []struct { + providerID string + + project string + zone string + instance string + + fail bool + }{ + { + providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1", + project: "project-example-164317", + zone: "us-central1-f", + instance: "kubernetes-node-fhx1", + fail: false, + }, + { + providerID: ProviderName + "://project-example.164317/us-central1-f/kubernetes-node-fhx1", + project: "project-example.164317", + zone: "us-central1-f", + instance: "kubernetes-node-fhx1", + fail: false, + }, + { + providerID: ProviderName + "://project-example-164317/us-central1-fkubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + ":/project-example-164317/us-central1-f/kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: "aws://project-example-164317/us-central1-f/kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1/", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + "://project-example.164317//kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + { + providerID: ProviderName + "://project-example.164317/kubernetes-node-fhx1", + project: "", + zone: "", + instance: "", + fail: true, + }, + } + + for _, test := range providers { + project, zone, instance, err := splitProviderID(test.providerID) + if (err != nil) != test.fail { + t.Errorf("Expected to failt=%t, with pattern %v", test.fail, test) + } + + if test.fail { + continue + } + + if project != test.project { + t.Errorf("Expected %v, but got %v", test.project, project) + } + if zone != test.zone { + t.Errorf("Expected %v, but got %v", test.zone, zone) + } + if instance != test.instance { + t.Errorf("Expected %v, but got %v", test.instance, instance) + } + } +} diff --git a/pkg/cloudprovider/providers/gce/gce_util.go b/pkg/cloudprovider/providers/gce/gce_util.go index 2748d7c0dd5..8f59cd5c495 100644 --- a/pkg/cloudprovider/providers/gce/gce_util.go +++ b/pkg/cloudprovider/providers/gce/gce_util.go @@ -17,7 +17,9 @@ limitations under the License. package gce import ( + "errors" "fmt" + "regexp" "strings" "k8s.io/apimachinery/pkg/types" @@ -35,6 +37,8 @@ type gceInstance struct { Type string } +var providerIdRE = regexp.MustCompile(`^` + ProviderName + `://([^/]+)/([^/]+)/([^/]+)$`) + func getProjectAndZone() (string, string, error) { result, err := metadata.Get("instance/zone") if err != nil { @@ -100,3 +104,14 @@ func isHTTPErrorCode(err error, code int) bool { apiErr, ok := err.(*googleapi.Error) return ok && apiErr.Code == code } + +// splitProviderID splits a provider's id into core components. +// A providerID is build out of '${ProviderName}://${project-id}/${zone}/${instance-name}' +// See cloudprovider.GetInstanceProviderID. +func splitProviderID(providerID string) (project, zone, instance string, err error) { + matches := providerIdRE.FindStringSubmatch(providerID) + if len(matches) != 4 { + return "", "", "", errors.New("error splitting providerID") + } + return matches[1], matches[2], matches[3], nil +}