From 99abd4bc7946d63763e7ede0e31e5f6000365916 Mon Sep 17 00:00:00 2001 From: jiatongw Date: Fri, 3 Aug 2018 13:24:42 -0700 Subject: [PATCH] Add zones support for vSphere cloud provider (in-tree) --- .../providers/vsphere/vclib/datacenter.go | 15 +++ .../providers/vsphere/vsphere.go | 111 +++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go b/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go index 96107a4a3d7..7d7f0207a65 100644 --- a/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go +++ b/pkg/cloudprovider/providers/vsphere/vclib/datacenter.go @@ -82,6 +82,21 @@ func (dc *Datacenter) GetVMByUUID(ctx context.Context, vmUUID string) (*VirtualM return &virtualMachine, nil } +// GetHostByVMUUID gets the host object from the given vmUUID +func (dc *Datacenter) GetHostByVMUUID(ctx context.Context, vmUUID string) (*types.ManagedObjectReference, error) { + virtualMachine, err := dc.GetVMByUUID(ctx, vmUUID) + var vmMo mo.VirtualMachine + pc := property.DefaultCollector(virtualMachine.Client()) + err = pc.RetrieveOne(ctx, virtualMachine.Reference(), []string{"summary.runtime.host"}, &vmMo) + if err != nil { + glog.Errorf("Failed to retrive VM runtime host, err: %v", err) + return nil, err + } + host := vmMo.Summary.Runtime.Host + glog.Infof("%s host is %s", virtualMachine.Reference(), vmMo.Summary.Runtime.Host) + return host, nil +} + // GetVMByPath gets the VM object from the given vmPath // vmPath should be the full path to VM and not just the name func (dc *Datacenter) GetVMByPath(ctx context.Context, vmPath string) (*VirtualMachine, error) { diff --git a/pkg/cloudprovider/providers/vsphere/vsphere.go b/pkg/cloudprovider/providers/vsphere/vsphere.go index 0ede2bd5215..6222cb0559f 100644 --- a/pkg/cloudprovider/providers/vsphere/vsphere.go +++ b/pkg/cloudprovider/providers/vsphere/vsphere.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "net" + "net/url" "os" "path" "path/filepath" @@ -33,6 +34,7 @@ import ( "gopkg.in/gcfg.v1" "github.com/golang/glog" + "github.com/vmware/govmomi/vapi/tags" "k8s.io/api/core/v1" k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/informers" @@ -177,6 +179,12 @@ type VSphereConfig struct { DefaultDatastore string `gcfg:"default-datastore"` ResourcePoolPath string `gcfg:"resourcepool-path"` } + + // Tag categories and tags which correspond to "built-in node labels: zones and region" + Labels struct { + Zone string `gcfg:"zone"` + Region string `gcfg:"region"` + } } type Volumes interface { @@ -808,8 +816,11 @@ func (vs *VSphere) LoadBalancer() (cloudprovider.LoadBalancer, bool) { // Zones returns an implementation of Zones for vSphere. func (vs *VSphere) Zones() (cloudprovider.Zones, bool) { - glog.V(1).Info("The vSphere cloud provider does not support zones") - return nil, false + if vs.cfg == nil { + glog.V(1).Info("The vSphere cloud provider does not support zones") + return nil, false + } + return vs, true } // Routes returns a false since the interface is not supported for vSphere. @@ -1306,3 +1317,99 @@ func (vs *VSphere) NodeManager() (nodeManager *NodeManager) { } return vs.nodeManager } + +func withTagsClient(ctx context.Context, connection *vclib.VSphereConnection, f func(c *tags.RestClient) error) error { + vsURL := connection.Client.URL() + vsURL.User = url.UserPassword(connection.Username, connection.Password) + c := tags.NewClient(vsURL, connection.Insecure, "") + if err := c.Login(ctx); err != nil { + return err + } + defer c.Logout(ctx) + return f(c) +} + +// GetZone implements Zones.GetZone +func (vs *VSphere) GetZone(ctx context.Context) (cloudprovider.Zone, error) { + nodeName, err := vs.CurrentNodeName(ctx, vs.hostName) + if err != nil { + glog.Errorf("Cannot get node name.") + return cloudprovider.Zone{}, err + } + zone := cloudprovider.Zone{} + vsi, err := vs.getVSphereInstanceForServer(vs.cfg.Workspace.VCenterIP, ctx) + if err != nil { + glog.Errorf("Cannot connent to vsphere. Get zone for node %s error", nodeName) + return cloudprovider.Zone{}, err + } + dc, err := vclib.GetDatacenter(ctx, vsi.conn, vs.cfg.Workspace.Datacenter) + if err != nil { + glog.Errorf("Cannot connent to datacenter. Get zone for node %s error", nodeName) + return cloudprovider.Zone{}, err + } + vmHost, err := dc.GetHostByVMUUID(ctx, vs.vmUUID) + if err != nil { + glog.Errorf("Cannot find VM runtime host. Get zone for node %s error", nodeName) + return cloudprovider.Zone{}, err + } + client := vsi.conn + err = withTagsClient(ctx, client, func(client *tags.RestClient) error { + tags, err := client.ListAttachedTags(ctx, vmHost) + if err != nil { + glog.Errorf("Cannot list attached tags. Get zone for node %s error", nodeName) + return err + } + for _, value := range tags { + tag, err := client.GetTag(ctx, value) + if err != nil { + glog.Errorf("Get tag %s error", value) + return err + } + category, err := client.GetCategory(ctx, tag.CategoryID) + if err != nil { + glog.Errorf("Get category %s error", value) + return err + } + switch { + + case category.Name == vs.cfg.Labels.Zone: + zone.FailureDomain = tag.Name + + case category.Name == vs.cfg.Labels.Region: + zone.Region = tag.Name + + default: + zone.FailureDomain = "" + zone.Region = "" + } + } + switch { + case zone.Region == "": + if vs.cfg.Labels.Zone != "" { + return fmt.Errorf("The zone in vSphere configuration file not match for node %s ", nodeName) + } + glog.Infof("No zones support for node %s error", nodeName) + return nil + case zone.FailureDomain == "": + if vs.cfg.Labels.Region != "" { + return fmt.Errorf("The zone in vSphere configuration file not match for node %s ", nodeName) + } + glog.Infof("No zones support for node %s error", nodeName) + return nil + } + return nil + }) + if err != nil { + glog.Errorf("Get zone for node %s error", nodeName) + return cloudprovider.Zone{}, err + } + return zone, nil +} + +func (vs *VSphere) GetZoneByNodeName(ctx context.Context, nodeName k8stypes.NodeName) (cloudprovider.Zone, error) { + return cloudprovider.Zone{}, cloudprovider.NotImplemented +} + +func (vs *VSphere) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) { + return cloudprovider.Zone{}, cloudprovider.NotImplemented +}