Merge pull request #63464 from dougm/vsphere-auth

Automatic merge from submit-queue (batch tested with PRs 63364, 63464). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

vsphere: use vim25.Client directly to support token authentication

**What this PR does / why we need it**:

This refactor is in support of SAML token authentication: #63209
Avoid use of govmomi.Client as it only supports username+password authentication via SessionManager.Login().
Using vim25.Client directly will allow VCP to add other authentication methods,
such as SessionManager.LoginByToken().

**Release note**:

```release-note
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2018-05-08 18:36:14 -07:00 committed by GitHub
commit a46ced041d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 114 additions and 49 deletions

View File

@ -39,6 +39,7 @@ go_test(
deps = [ deps = [
"//pkg/cloudprovider:go_default_library", "//pkg/cloudprovider:go_default_library",
"//pkg/cloudprovider/providers/vsphere/vclib:go_default_library", "//pkg/cloudprovider/providers/vsphere/vclib:go_default_library",
"//vendor/github.com/vmware/govmomi/simulator:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
], ],

View File

@ -360,7 +360,7 @@ func (nm *NodeManager) renewNodeInfo(nodeInfo *NodeInfo, reconnect bool) (*NodeI
return nil, err return nil, err
} }
} }
vm := nodeInfo.vm.RenewVM(vsphereInstance.conn.GoVmomiClient) vm := nodeInfo.vm.RenewVM(vsphereInstance.conn.Client)
return &NodeInfo{vm: &vm, dataCenter: vm.Datacenter, vcServer: nodeInfo.vcServer}, nil return &NodeInfo{vm: &vm, dataCenter: vm.Datacenter, vcServer: nodeInfo.vcServer}, nil
} }

View File

@ -26,7 +26,6 @@ go_library(
deps = [ deps = [
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/github.com/vmware/govmomi:go_default_library",
"//vendor/github.com/vmware/govmomi/find:go_default_library", "//vendor/github.com/vmware/govmomi/find:go_default_library",
"//vendor/github.com/vmware/govmomi/object:go_default_library", "//vendor/github.com/vmware/govmomi/object:go_default_library",
"//vendor/github.com/vmware/govmomi/pbm:go_default_library", "//vendor/github.com/vmware/govmomi/pbm:go_default_library",

View File

@ -18,19 +18,19 @@ package vclib
import ( import (
"context" "context"
"fmt" "net"
neturl "net/url" neturl "net/url"
"sync" "sync"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/session" "github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/soap"
) )
// VSphereConnection contains information for connecting to vCenter // VSphereConnection contains information for connecting to vCenter
type VSphereConnection struct { type VSphereConnection struct {
GoVmomiClient *govmomi.Client Client *vim25.Client
Username string Username string
Password string Password string
Hostname string Hostname string
@ -43,23 +43,23 @@ var (
clientLock sync.Mutex clientLock sync.Mutex
) )
// Connect makes connection to vCenter and sets VSphereConnection.GoVmomiClient. // Connect makes connection to vCenter and sets VSphereConnection.Client.
// If connection.GoVmomiClient is already set, it obtains the existing user session. // If connection.Client is already set, it obtains the existing user session.
// if user session is not valid, connection.GoVmomiClient will be set to the new client. // if user session is not valid, connection.Client will be set to the new client.
func (connection *VSphereConnection) Connect(ctx context.Context) error { func (connection *VSphereConnection) Connect(ctx context.Context) error {
var err error var err error
clientLock.Lock() clientLock.Lock()
defer clientLock.Unlock() defer clientLock.Unlock()
if connection.GoVmomiClient == nil { if connection.Client == nil {
connection.GoVmomiClient, err = connection.NewClient(ctx) connection.Client, err = connection.NewClient(ctx)
if err != nil { if err != nil {
glog.Errorf("Failed to create govmomi client. err: %+v", err) glog.Errorf("Failed to create govmomi client. err: %+v", err)
return err return err
} }
return nil return nil
} }
m := session.NewManager(connection.GoVmomiClient.Client) m := session.NewManager(connection.Client)
userSession, err := m.UserSession(ctx) userSession, err := m.UserSession(ctx)
if err != nil { if err != nil {
glog.Errorf("Error while obtaining user session. err: %+v", err) glog.Errorf("Error while obtaining user session. err: %+v", err)
@ -69,8 +69,8 @@ func (connection *VSphereConnection) Connect(ctx context.Context) error {
return nil return nil
} }
glog.Warningf("Creating new client session since the existing session is not valid or not authenticated") glog.Warningf("Creating new client session since the existing session is not valid or not authenticated")
connection.GoVmomiClient.Logout(ctx)
connection.GoVmomiClient, err = connection.NewClient(ctx) connection.Client, err = connection.NewClient(ctx)
if err != nil { if err != nil {
glog.Errorf("Failed to create govmomi client. err: %+v", err) glog.Errorf("Failed to create govmomi client. err: %+v", err)
return err return err
@ -78,19 +78,36 @@ func (connection *VSphereConnection) Connect(ctx context.Context) error {
return nil return nil
} }
// Logout calls SessionManager.Logout for the given connection.
func (connection *VSphereConnection) Logout(ctx context.Context) {
m := session.NewManager(connection.Client)
if err := m.Logout(ctx); err != nil {
glog.Errorf("Logout failed: %s", err)
}
}
// NewClient creates a new govmomi client for the VSphereConnection obj // NewClient creates a new govmomi client for the VSphereConnection obj
func (connection *VSphereConnection) NewClient(ctx context.Context) (*govmomi.Client, error) { func (connection *VSphereConnection) NewClient(ctx context.Context) (*vim25.Client, error) {
url, err := neturl.Parse(fmt.Sprintf("https://%s:%s/sdk", connection.Hostname, connection.Port)) url, err := soap.ParseURL(net.JoinHostPort(connection.Hostname, connection.Port))
if err != nil { if err != nil {
glog.Errorf("Failed to parse URL: %s. err: %+v", url, err) glog.Errorf("Failed to parse URL: %s. err: %+v", url, err)
return nil, err return nil, err
} }
url.User = neturl.UserPassword(connection.Username, connection.Password)
client, err := govmomi.NewClient(ctx, url, connection.Insecure) sc := soap.NewClient(url, connection.Insecure)
client, err := vim25.NewClient(ctx, sc)
if err != nil { if err != nil {
glog.Errorf("Failed to create new client. err: %+v", err) glog.Errorf("Failed to create new client. err: %+v", err)
return nil, err return nil, err
} }
m := session.NewManager(client)
err = m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password))
if err != nil {
return nil, err
}
if connection.RoundTripperCount == 0 { if connection.RoundTripperCount == 0 {
connection.RoundTripperCount = RoundTripperDefaultCount connection.RoundTripperCount = RoundTripperDefaultCount
} }

View File

@ -53,7 +53,8 @@ const (
// Test Constants // Test Constants
const ( const (
testDefaultDatacenter = "DC0" TestDefaultDatacenter = "DC0"
testDefaultDatastore = "LocalDS_0" TestDefaultDatastore = "LocalDS_0"
TestDefaultNetwork = "VM Network"
testNameNotFound = "enoent" testNameNotFound = "enoent"
) )

View File

@ -39,7 +39,7 @@ type Datacenter struct {
// GetDatacenter returns the DataCenter Object for the given datacenterPath // GetDatacenter returns the DataCenter Object for the given datacenterPath
// If datacenter is located in a folder, include full path to datacenter else just provide the datacenter name // If datacenter is located in a folder, include full path to datacenter else just provide the datacenter name
func GetDatacenter(ctx context.Context, connection *VSphereConnection, datacenterPath string) (*Datacenter, error) { func GetDatacenter(ctx context.Context, connection *VSphereConnection, datacenterPath string) (*Datacenter, error) {
finder := find.NewFinder(connection.GoVmomiClient.Client, false) finder := find.NewFinder(connection.Client, false)
datacenter, err := finder.Datacenter(ctx, datacenterPath) datacenter, err := finder.Datacenter(ctx, datacenterPath)
if err != nil { if err != nil {
glog.Errorf("Failed to find the datacenter: %s. err: %+v", datacenterPath, err) glog.Errorf("Failed to find the datacenter: %s. err: %+v", datacenterPath, err)
@ -52,7 +52,7 @@ func GetDatacenter(ctx context.Context, connection *VSphereConnection, datacente
// GetAllDatacenter returns all the DataCenter Objects // GetAllDatacenter returns all the DataCenter Objects
func GetAllDatacenter(ctx context.Context, connection *VSphereConnection) ([]*Datacenter, error) { func GetAllDatacenter(ctx context.Context, connection *VSphereConnection) ([]*Datacenter, error) {
var dc []*Datacenter var dc []*Datacenter
finder := find.NewFinder(connection.GoVmomiClient.Client, false) finder := find.NewFinder(connection.Client, false)
datacenters, err := finder.DatacenterList(ctx, "*") datacenters, err := finder.DatacenterList(ctx, "*")
if err != nil { if err != nil {
glog.Errorf("Failed to find the datacenter. err: %+v", err) glog.Errorf("Failed to find the datacenter. err: %+v", err)

View File

@ -47,14 +47,14 @@ func TestDatacenter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
vc := &VSphereConnection{GoVmomiClient: c} vc := &VSphereConnection{Client: c.Client}
_, err = GetDatacenter(ctx, vc, testNameNotFound) _, err = GetDatacenter(ctx, vc, testNameNotFound)
if err == nil { if err == nil {
t.Error("expected error") t.Error("expected error")
} }
dc, err := GetDatacenter(ctx, vc, testDefaultDatacenter) dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -74,7 +74,7 @@ func TestDatacenter(t *testing.T) {
t.Error("expected error") t.Error("expected error")
} }
vm, err := dc.GetVMByPath(ctx, testDefaultDatacenter+"/vm/"+avm.Name) vm, err := dc.GetVMByPath(ctx, TestDefaultDatacenter+"/vm/"+avm.Name)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -103,7 +103,7 @@ func TestDatacenter(t *testing.T) {
t.Error("expected error") t.Error("expected error")
} }
ds, err := dc.GetDatastoreByName(ctx, testDefaultDatastore) ds, err := dc.GetDatastoreByName(ctx, TestDefaultDatastore)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -113,7 +113,7 @@ func TestDatacenter(t *testing.T) {
t.Error("expected error") t.Error("expected error")
} }
_, err = dc.GetFolderByPath(ctx, testDefaultDatacenter+"/vm") _, err = dc.GetFolderByPath(ctx, TestDefaultDatacenter+"/vm")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -45,9 +45,9 @@ func TestDatastore(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
vc := &VSphereConnection{GoVmomiClient: c} vc := &VSphereConnection{Client: c.Client}
dc, err := GetDatacenter(ctx, vc, testDefaultDatacenter) dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -47,9 +47,9 @@ func TestFolder(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
vc := &VSphereConnection{GoVmomiClient: c} vc := &VSphereConnection{Client: c.Client}
dc, err := GetDatacenter(ctx, vc, testDefaultDatacenter) dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -46,9 +46,9 @@ func TestUtils(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
vc := &VSphereConnection{GoVmomiClient: c} vc := &VSphereConnection{Client: c.Client}
dc, err := GetDatacenter(ctx, vc, testDefaultDatacenter) dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -23,9 +23,9 @@ import (
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property" "github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
) )
@ -403,8 +403,8 @@ func (vm *VirtualMachine) deleteController(ctx context.Context, controllerDevice
} }
// RenewVM renews this virtual machine with new client connection. // RenewVM renews this virtual machine with new client connection.
func (vm *VirtualMachine) RenewVM(client *govmomi.Client) VirtualMachine { func (vm *VirtualMachine) RenewVM(client *vim25.Client) VirtualMachine {
dc := Datacenter{Datacenter: object.NewDatacenter(client.Client, vm.Datacenter.Reference())} dc := Datacenter{Datacenter: object.NewDatacenter(client, vm.Datacenter.Reference())}
newVM := object.NewVirtualMachine(client.Client, vm.VirtualMachine.Reference()) newVM := object.NewVirtualMachine(client, vm.VirtualMachine.Reference())
return VirtualMachine{VirtualMachine: newVM, Datacenter: &dc} return VirtualMachine{VirtualMachine: newVM, Datacenter: &dc}
} }

View File

@ -43,9 +43,9 @@ func TestVirtualMachine(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
vc := &VSphereConnection{GoVmomiClient: c} vc := &VSphereConnection{Client: c.Client}
dc, err := GetDatacenter(ctx, vc, testDefaultDatacenter) dc, err := GetDatacenter(ctx, vc, TestDefaultDatacenter)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -360,7 +360,10 @@ func populateVsphereInstanceMap(cfg *VSphereConfig) (map[string]*VSphereInstance
return vsphereInstanceMap, nil return vsphereInstanceMap, nil
} }
// Creates new Contreoller node interface and returns // getVMUUID allows tests to override GetVMUUID
var getVMUUID = GetVMUUID
// Creates new Controller node interface and returns
func newControllerNode(cfg VSphereConfig) (*VSphere, error) { func newControllerNode(cfg VSphereConfig) (*VSphere, error) {
var err error var err error
@ -399,7 +402,7 @@ func newControllerNode(cfg VSphereConfig) (*VSphere, error) {
glog.Errorf("Failed to get hostname. err: %+v", err) glog.Errorf("Failed to get hostname. err: %+v", err)
return nil, err return nil, err
} }
vs.vmUUID, err = GetVMUUID() vs.vmUUID, err = getVMUUID()
if err != nil { if err != nil {
glog.Errorf("Failed to get uuid. err: %+v", err) glog.Errorf("Failed to get uuid. err: %+v", err)
return nil, err return nil, err
@ -410,8 +413,8 @@ func newControllerNode(cfg VSphereConfig) (*VSphere, error) {
func logout(vs *VSphere) { func logout(vs *VSphere) {
for _, vsphereIns := range vs.vsphereInstanceMap { for _, vsphereIns := range vs.vsphereInstanceMap {
if vsphereIns.conn.GoVmomiClient != nil { if vsphereIns.conn.Client != nil {
vsphereIns.conn.GoVmomiClient.Logout(context.TODO()) vsphereIns.conn.Logout(context.TODO())
} }
} }

View File

@ -18,12 +18,14 @@ package vsphere
import ( import (
"context" "context"
"crypto/tls"
"log" "log"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"github.com/vmware/govmomi/simulator"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
"k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider"
@ -59,6 +61,50 @@ func configFromEnv() (cfg VSphereConfig, ok bool) {
return return
} }
// configFromEnvOrSim returns config from configFromEnv if set,
// otherwise starts a vcsim instance and returns config for use against the vcsim instance.
func configFromEnvOrSim() (VSphereConfig, func()) {
cfg, ok := configFromEnv()
if ok {
return cfg, func() {}
}
model := simulator.VPX()
err := model.Create()
if err != nil {
log.Fatal(err)
}
model.Service.TLS = new(tls.Config)
s := model.Service.NewServer()
cfg.Global.InsecureFlag = true
cfg.Global.VCenterIP = s.URL.Hostname()
cfg.Global.VCenterPort = s.URL.Port()
cfg.Global.User = s.URL.User.Username()
cfg.Global.Password, _ = s.URL.User.Password()
cfg.Global.Datacenter = vclib.TestDefaultDatacenter
cfg.Network.PublicNetwork = vclib.TestDefaultNetwork
cfg.Global.DefaultDatastore = vclib.TestDefaultDatastore
cfg.Disk.SCSIControllerType = os.Getenv("VSPHERE_SCSICONTROLLER_TYPE")
cfg.Global.WorkingDir = os.Getenv("VSPHERE_WORKING_DIR")
cfg.Global.VMName = os.Getenv("VSPHERE_VM_NAME")
if cfg.Global.WorkingDir == "" {
cfg.Global.WorkingDir = "vm" // top-level Datacenter.VmFolder
}
uuid := simulator.Map.Any("VirtualMachine").(*simulator.VirtualMachine).Config.Uuid
getVMUUID = func() (string, error) { return uuid, nil }
return cfg, func() {
getVMUUID = GetVMUUID
s.Close()
model.Remove()
}
}
func TestReadConfig(t *testing.T) { func TestReadConfig(t *testing.T) {
_, err := readConfig(nil) _, err := readConfig(nil)
if err == nil { if err == nil {
@ -110,10 +156,8 @@ func TestNewVSphere(t *testing.T) {
} }
func TestVSphereLogin(t *testing.T) { func TestVSphereLogin(t *testing.T) {
cfg, ok := configFromEnv() cfg, cleanup := configFromEnvOrSim()
if !ok { defer cleanup()
t.Skipf("No config found in environment")
}
// Create vSphere configuration object // Create vSphere configuration object
vs, err := newControllerNode(cfg) vs, err := newControllerNode(cfg)
@ -126,8 +170,8 @@ func TestVSphereLogin(t *testing.T) {
defer cancel() defer cancel()
// Create vSphere client // Create vSphere client
var vcInstance *VSphereInstance vcInstance, ok := vs.vsphereInstanceMap[cfg.Global.VCenterIP]
if vcInstance, ok = vs.vsphereInstanceMap[cfg.Global.VCenterIP]; !ok { if !ok {
t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP) t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP)
} }
@ -135,7 +179,7 @@ func TestVSphereLogin(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Failed to connect to vSphere: %s", err) t.Errorf("Failed to connect to vSphere: %s", err)
} }
defer vcInstance.conn.GoVmomiClient.Logout(ctx) defer vcInstance.conn.Logout(ctx)
} }
func TestZones(t *testing.T) { func TestZones(t *testing.T) {