
Individual implementations are not yet being moved. Fixed all dependencies which call the interface. Fixed golint exceptions to reflect the move. Added project info as per @dims and https://github.com/kubernetes/kubernetes-template-project. Added dims to the security contacts. Fixed minor issues. Added missing template files. Copied ControllerClientBuilder interface to cp. This allows us to break the only dependency on K8s/K8s. Added TODO to ControllerClientBuilder. Fixed GoDeps. Factored in feedback from JustinSB.
890 lines
24 KiB
Go
890 lines
24 KiB
Go
/*
|
|
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 vsphere
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
lookup "github.com/vmware/govmomi/lookup/simulator"
|
|
"github.com/vmware/govmomi/property"
|
|
"github.com/vmware/govmomi/simulator"
|
|
"github.com/vmware/govmomi/simulator/vpx"
|
|
sts "github.com/vmware/govmomi/sts/simulator"
|
|
"github.com/vmware/govmomi/vapi/rest"
|
|
vapi "github.com/vmware/govmomi/vapi/simulator"
|
|
"github.com/vmware/govmomi/vapi/tags"
|
|
"github.com/vmware/govmomi/vim25/mo"
|
|
"k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/rand"
|
|
cloudprovider "k8s.io/cloud-provider"
|
|
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib"
|
|
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere/vclib/fixtures"
|
|
)
|
|
|
|
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
|
|
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
|
var localhostCert = `-----BEGIN CERTIFICATE-----
|
|
MIIBjzCCATmgAwIBAgIRAKpi2WmTcFrVjxrl5n5YDUEwDQYJKoZIhvcNAQELBQAw
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
|
|
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
|
|
QQC9fEbRszP3t14Gr4oahV7zFObBI4TfA5i7YnlMXeLinb7MnvT4bkfOJzE6zktn
|
|
59zP7UiHs3l4YOuqrjiwM413AgMBAAGjaDBmMA4GA1UdDwEB/wQEAwICpDATBgNV
|
|
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQnMCWCC2V4
|
|
YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUA
|
|
A0EAUsVE6KMnza/ZbodLlyeMzdo7EM/5nb5ywyOxgIOCf0OOLHsPS9ueGLQX9HEG
|
|
//yjTXuhNcUugExIjM/AIwAZPQ==
|
|
-----END CERTIFICATE-----`
|
|
|
|
// localhostKey is the private key for localhostCert.
|
|
var localhostKey = `-----BEGIN RSA PRIVATE KEY-----
|
|
MIIBOwIBAAJBAL18RtGzM/e3XgavihqFXvMU5sEjhN8DmLtieUxd4uKdvsye9Phu
|
|
R84nMTrOS2fn3M/tSIezeXhg66quOLAzjXcCAwEAAQJBAKcRxH9wuglYLBdI/0OT
|
|
BLzfWPZCEw1vZmMR2FF1Fm8nkNOVDPleeVGTWoOEcYYlQbpTmkGSxJ6ya+hqRi6x
|
|
goECIQDx3+X49fwpL6B5qpJIJMyZBSCuMhH4B7JevhGGFENi3wIhAMiNJN5Q3UkL
|
|
IuSvv03kaPR5XVQ99/UeEetUgGvBcABpAiBJSBzVITIVCGkGc7d+RCf49KTCIklv
|
|
bGWObufAR8Ni4QIgWpILjW8dkGg8GOUZ0zaNA6Nvt6TIv2UWGJ4v5PoV98kCIQDx
|
|
rIiZs5QbKdycsv9gQJzwQAogC8o04X3Zz3dsoX+h4A==
|
|
-----END RSA PRIVATE KEY-----`
|
|
|
|
func configFromEnv() (cfg VSphereConfig, ok bool) {
|
|
var InsecureFlag bool
|
|
var err error
|
|
cfg.Global.VCenterIP = os.Getenv("VSPHERE_VCENTER")
|
|
cfg.Global.VCenterPort = os.Getenv("VSPHERE_VCENTER_PORT")
|
|
cfg.Global.User = os.Getenv("VSPHERE_USER")
|
|
cfg.Global.Password = os.Getenv("VSPHERE_PASSWORD")
|
|
cfg.Global.Datacenter = os.Getenv("VSPHERE_DATACENTER")
|
|
cfg.Network.PublicNetwork = os.Getenv("VSPHERE_PUBLIC_NETWORK")
|
|
cfg.Global.DefaultDatastore = os.Getenv("VSPHERE_DATASTORE")
|
|
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 os.Getenv("VSPHERE_INSECURE") != "" {
|
|
InsecureFlag, err = strconv.ParseBool(os.Getenv("VSPHERE_INSECURE"))
|
|
} else {
|
|
InsecureFlag = false
|
|
}
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cfg.Global.InsecureFlag = InsecureFlag
|
|
|
|
ok = (cfg.Global.VCenterIP != "" &&
|
|
cfg.Global.User != "")
|
|
|
|
return
|
|
}
|
|
|
|
// configFromSim starts a vcsim instance and returns config for use against the vcsim instance.
|
|
// The vcsim instance is configured with an empty tls.Config.
|
|
func configFromSim() (VSphereConfig, func()) {
|
|
return configFromSimWithTLS(new(tls.Config), true)
|
|
}
|
|
|
|
// configFromSimWithTLS starts a vcsim instance and returns config for use against the vcsim instance.
|
|
// The vcsim instance is configured with a tls.Config. The returned client
|
|
// config can be configured to allow/decline insecure connections.
|
|
func configFromSimWithTLS(tlsConfig *tls.Config, insecureAllowed bool) (VSphereConfig, func()) {
|
|
var cfg VSphereConfig
|
|
model := simulator.VPX()
|
|
|
|
err := model.Create()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
model.Service.TLS = tlsConfig
|
|
s := model.Service.NewServer()
|
|
|
|
// STS simulator
|
|
path, handler := sts.New(s.URL, vpx.Setting)
|
|
model.Service.ServeMux.Handle(path, handler)
|
|
|
|
// vAPI simulator
|
|
path, handler = vapi.New(s.URL, vpx.Setting)
|
|
model.Service.ServeMux.Handle(path, handler)
|
|
|
|
// Lookup Service simulator
|
|
model.Service.RegisterSDK(lookup.New())
|
|
|
|
cfg.Global.InsecureFlag = insecureAllowed
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
// configFromEnvOrSim returns config from configFromEnv if set, otherwise returns configFromSim.
|
|
func configFromEnvOrSim() (VSphereConfig, func()) {
|
|
cfg, ok := configFromEnv()
|
|
if ok {
|
|
return cfg, func() {}
|
|
}
|
|
return configFromSim()
|
|
}
|
|
|
|
func TestReadConfig(t *testing.T) {
|
|
_, err := readConfig(nil)
|
|
if err == nil {
|
|
t.Errorf("Should fail when no config is provided: %s", err)
|
|
}
|
|
|
|
cfg, err := readConfig(strings.NewReader(`
|
|
[Global]
|
|
server = 0.0.0.0
|
|
port = 443
|
|
user = user
|
|
password = password
|
|
insecure-flag = true
|
|
datacenter = us-west
|
|
vm-uuid = 1234
|
|
vm-name = vmname
|
|
ca-file = /some/path/to/a/ca.pem
|
|
`))
|
|
if err != nil {
|
|
t.Fatalf("Should succeed when a valid config is provided: %s", err)
|
|
}
|
|
|
|
if cfg.Global.VCenterIP != "0.0.0.0" {
|
|
t.Errorf("incorrect vcenter ip: %s", cfg.Global.VCenterIP)
|
|
}
|
|
|
|
if cfg.Global.Datacenter != "us-west" {
|
|
t.Errorf("incorrect datacenter: %s", cfg.Global.Datacenter)
|
|
}
|
|
|
|
if cfg.Global.VMUUID != "1234" {
|
|
t.Errorf("incorrect vm-uuid: %s", cfg.Global.VMUUID)
|
|
}
|
|
|
|
if cfg.Global.VMName != "vmname" {
|
|
t.Errorf("incorrect vm-name: %s", cfg.Global.VMName)
|
|
}
|
|
|
|
if cfg.Global.CAFile != "/some/path/to/a/ca.pem" {
|
|
t.Errorf("incorrect ca-file: %s", cfg.Global.CAFile)
|
|
}
|
|
}
|
|
|
|
func TestNewVSphere(t *testing.T) {
|
|
cfg, ok := configFromEnv()
|
|
if !ok {
|
|
t.Skipf("No config found in environment")
|
|
}
|
|
|
|
_, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestVSphereLogin(t *testing.T) {
|
|
cfg, cleanup := configFromEnvOrSim()
|
|
defer cleanup()
|
|
|
|
// Create vSphere configuration object
|
|
vs, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
|
|
// Create context
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Create vSphere client
|
|
vcInstance, ok := vs.vsphereInstanceMap[cfg.Global.VCenterIP]
|
|
if !ok {
|
|
t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP)
|
|
}
|
|
|
|
err = vcInstance.conn.Connect(ctx)
|
|
if err != nil {
|
|
t.Errorf("Failed to connect to vSphere: %s", err)
|
|
}
|
|
vcInstance.conn.Logout(ctx)
|
|
}
|
|
|
|
func TestVSphereLoginByToken(t *testing.T) {
|
|
cfg, cleanup := configFromSim()
|
|
defer cleanup()
|
|
|
|
// Configure for SAML token auth
|
|
cfg.Global.User = localhostCert
|
|
cfg.Global.Password = localhostKey
|
|
|
|
// Create vSphere configuration object
|
|
vs, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create vSphere client
|
|
vcInstance, ok := vs.vsphereInstanceMap[cfg.Global.VCenterIP]
|
|
if !ok {
|
|
t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP)
|
|
}
|
|
|
|
err = vcInstance.conn.Connect(ctx)
|
|
if err != nil {
|
|
t.Errorf("Failed to connect to vSphere: %s", err)
|
|
}
|
|
vcInstance.conn.Logout(ctx)
|
|
}
|
|
|
|
func TestVSphereLoginWithCaCert(t *testing.T) {
|
|
caCertPEM, err := ioutil.ReadFile(fixtures.CaCertPath)
|
|
if err != nil {
|
|
t.Fatalf("Could not read ca cert from file")
|
|
}
|
|
|
|
serverCert, err := tls.LoadX509KeyPair(fixtures.ServerCertPath, fixtures.ServerKeyPath)
|
|
if err != nil {
|
|
t.Fatalf("Could not load server cert and server key from files: %#v", err)
|
|
}
|
|
|
|
certPool := x509.NewCertPool()
|
|
if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok {
|
|
t.Fatalf("Cannot add CA to CAPool")
|
|
}
|
|
|
|
tlsConfig := tls.Config{
|
|
Certificates: []tls.Certificate{serverCert},
|
|
RootCAs: certPool,
|
|
}
|
|
|
|
cfg, cleanup := configFromSimWithTLS(&tlsConfig, false)
|
|
defer cleanup()
|
|
|
|
cfg.Global.CAFile = fixtures.CaCertPath
|
|
|
|
// Create vSphere configuration object
|
|
vs, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create vSphere client
|
|
vcInstance, ok := vs.vsphereInstanceMap[cfg.Global.VCenterIP]
|
|
if !ok {
|
|
t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP)
|
|
}
|
|
|
|
err = vcInstance.conn.Connect(ctx)
|
|
if err != nil {
|
|
t.Errorf("Failed to connect to vSphere: %s", err)
|
|
}
|
|
vcInstance.conn.Logout(ctx)
|
|
}
|
|
|
|
func TestZonesNoConfig(t *testing.T) {
|
|
_, ok := new(VSphere).Zones()
|
|
if ok {
|
|
t.Fatalf("Zones() should return false without VCP configured")
|
|
}
|
|
}
|
|
|
|
func TestZones(t *testing.T) {
|
|
// Any context will do
|
|
ctx := context.Background()
|
|
|
|
// Create a vcsim instance
|
|
cfg, cleanup := configFromSim()
|
|
defer cleanup()
|
|
|
|
// Create vSphere configuration object
|
|
vs, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
|
|
// Configure region and zone categories
|
|
vs.cfg.Labels.Region = "k8s-region"
|
|
vs.cfg.Labels.Zone = "k8s-zone"
|
|
|
|
// Create vSphere client
|
|
vsi, ok := vs.vsphereInstanceMap[cfg.Global.VCenterIP]
|
|
if !ok {
|
|
t.Fatalf("Couldn't get vSphere instance: %s", cfg.Global.VCenterIP)
|
|
}
|
|
|
|
err = vsi.conn.Connect(ctx)
|
|
if err != nil {
|
|
t.Errorf("Failed to connect to vSphere: %s", err)
|
|
}
|
|
|
|
// Lookup Datacenter for this test's Workspace
|
|
dc, err := vclib.GetDatacenter(ctx, vsi.conn, vs.cfg.Workspace.Datacenter)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Lookup VM's host where we'll attach tags
|
|
host, err := dc.GetHostByVMUUID(ctx, vs.vmUUID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Property Collector instance
|
|
pc := property.DefaultCollector(vsi.conn.Client)
|
|
|
|
// Tag manager instance
|
|
m := tags.NewManager(rest.NewClient(vsi.conn.Client))
|
|
|
|
// Create a region category
|
|
regionID, err := m.CreateCategory(ctx, &tags.Category{Name: vs.cfg.Labels.Region})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a region tag
|
|
regionID, err = m.CreateTag(ctx, &tags.Tag{CategoryID: regionID, Name: "k8s-region-US"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a zone category
|
|
zoneID, err := m.CreateCategory(ctx, &tags.Category{Name: vs.cfg.Labels.Zone})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a zone tag
|
|
zoneID, err = m.CreateTag(ctx, &tags.Tag{CategoryID: zoneID, Name: "k8s-zone-US-CA1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a random category
|
|
randomID, err := m.CreateCategory(ctx, &tags.Category{Name: "random-cat"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a random tag
|
|
randomID, err = m.CreateTag(ctx, &tags.Tag{CategoryID: randomID, Name: "random-tag"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Attach a random tag to VM's host
|
|
if err = m.AttachTag(ctx, randomID, host); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Expecting Zones() to return true, indicating VCP supports the Zones interface
|
|
zones, ok := vs.Zones()
|
|
if !ok {
|
|
t.Fatalf("zones=%t", ok)
|
|
}
|
|
|
|
// GetZone() tests, covering error and success paths
|
|
tests := []struct {
|
|
name string // name of the test for logging
|
|
fail bool // expect GetZone() to return error if true
|
|
prep func() // prepare vCenter state for the test
|
|
}{
|
|
{"no tags", true, func() {
|
|
// no prep
|
|
}},
|
|
{"no zone tag", true, func() {
|
|
if err = m.AttachTag(ctx, regionID, host); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}},
|
|
{"host tags set", false, func() {
|
|
if err = m.AttachTag(ctx, zoneID, host); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}},
|
|
{"host tags removed", true, func() {
|
|
if err = m.DetachTag(ctx, zoneID, host); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = m.DetachTag(ctx, regionID, host); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}},
|
|
{"dc region, cluster zone", false, func() {
|
|
var h mo.HostSystem
|
|
if err = pc.RetrieveOne(ctx, host.Reference(), []string{"parent"}, &h); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Attach region tag to Datacenter
|
|
if err = m.AttachTag(ctx, regionID, dc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Attach zone tag to Cluster
|
|
if err = m.AttachTag(ctx, zoneID, h.Parent); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test.prep()
|
|
|
|
zone, err := zones.GetZone(ctx)
|
|
if test.fail {
|
|
if err == nil {
|
|
t.Errorf("%s: expected error", test.name)
|
|
} else {
|
|
t.Logf("%s: expected error=%s", test.name, err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("%s: %s", test.name, err)
|
|
}
|
|
t.Logf("zone=%#v", zone)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInstances(t *testing.T) {
|
|
cfg, ok := configFromEnv()
|
|
if !ok {
|
|
t.Skipf("No config found in environment")
|
|
}
|
|
|
|
vs, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
|
|
i, ok := vs.Instances()
|
|
if !ok {
|
|
t.Fatalf("Instances() returned false")
|
|
}
|
|
|
|
nodeName, err := vs.CurrentNodeName(context.TODO(), "")
|
|
if err != nil {
|
|
t.Fatalf("CurrentNodeName() failed: %s", err)
|
|
}
|
|
|
|
nonExistingVM := types.NodeName(rand.String(15))
|
|
instanceID, err := i.InstanceID(context.TODO(), nodeName)
|
|
if err != nil {
|
|
t.Fatalf("Instances.InstanceID(%s) failed: %s", nodeName, err)
|
|
}
|
|
t.Logf("Found InstanceID(%s) = %s\n", nodeName, instanceID)
|
|
|
|
_, err = i.InstanceID(context.TODO(), nonExistingVM)
|
|
if err == cloudprovider.InstanceNotFound {
|
|
t.Logf("VM %s was not found as expected\n", nonExistingVM)
|
|
} else if err == nil {
|
|
t.Fatalf("Instances.InstanceID did not fail as expected, VM %s was found", nonExistingVM)
|
|
} else {
|
|
t.Fatalf("Instances.InstanceID did not fail as expected, err: %v", err)
|
|
}
|
|
|
|
addrs, err := i.NodeAddresses(context.TODO(), nodeName)
|
|
if err != nil {
|
|
t.Fatalf("Instances.NodeAddresses(%s) failed: %s", nodeName, err)
|
|
}
|
|
found := false
|
|
for _, addr := range addrs {
|
|
if addr.Type == v1.NodeHostName {
|
|
found = true
|
|
}
|
|
}
|
|
if found == false {
|
|
t.Fatalf("NodeAddresses does not report hostname, %s %s", nodeName, addrs)
|
|
}
|
|
t.Logf("Found NodeAddresses(%s) = %s\n", nodeName, addrs)
|
|
}
|
|
|
|
func TestVolumes(t *testing.T) {
|
|
cfg, ok := configFromEnv()
|
|
if !ok {
|
|
t.Skipf("No config found in environment")
|
|
}
|
|
|
|
vs, err := newControllerNode(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
|
|
}
|
|
|
|
nodeName, err := vs.CurrentNodeName(context.TODO(), "")
|
|
if err != nil {
|
|
t.Fatalf("CurrentNodeName() failed: %s", err)
|
|
}
|
|
|
|
volumeOptions := &vclib.VolumeOptions{
|
|
CapacityKB: 1 * 1024 * 1024,
|
|
Tags: nil,
|
|
Name: "kubernetes-test-volume-" + rand.String(10),
|
|
DiskFormat: "thin"}
|
|
|
|
volPath, err := vs.CreateVolume(volumeOptions)
|
|
if err != nil {
|
|
t.Fatalf("Cannot create a new VMDK volume: %v", err)
|
|
}
|
|
|
|
_, err = vs.AttachDisk(volPath, "", "")
|
|
if err != nil {
|
|
t.Fatalf("Cannot attach volume(%s) to VM(%s): %v", volPath, nodeName, err)
|
|
}
|
|
|
|
err = vs.DetachDisk(volPath, "")
|
|
if err != nil {
|
|
t.Fatalf("Cannot detach disk(%s) from VM(%s): %v", volPath, nodeName, err)
|
|
}
|
|
|
|
// todo: Deleting a volume after detach currently not working through API or UI (vSphere)
|
|
// err = vs.DeleteVolume(volPath)
|
|
// if err != nil {
|
|
// t.Fatalf("Cannot delete VMDK volume %s: %v", volPath, err)
|
|
// }
|
|
}
|
|
|
|
func TestSecretVSphereConfig(t *testing.T) {
|
|
var vs *VSphere
|
|
var (
|
|
username = "user"
|
|
password = "password"
|
|
)
|
|
var testcases = []struct {
|
|
testName string
|
|
conf string
|
|
expectedIsSecretProvided bool
|
|
expectedUsername string
|
|
expectedPassword string
|
|
expectedError error
|
|
expectedThumbprints map[string]string
|
|
}{
|
|
{
|
|
testName: "Username and password with old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretName and SecretNamespace in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
secret-namespace = "kube-system"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedIsSecretProvided: true,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretName and SecretNamespace with Username and Password in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
secret-namespace = "kube-system"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedIsSecretProvided: true,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretName and SecretNamespace with Username missing in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
password = password
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
secret-namespace = "kube-system"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedIsSecretProvided: true,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretNamespace missing with Username and Password in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretNamespace and Username missing in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
password = password
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedError: ErrUsernameMissing,
|
|
},
|
|
{
|
|
testName: "SecretNamespace and Password missing in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
user = user
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedError: ErrPasswordMissing,
|
|
},
|
|
{
|
|
testName: "SecretNamespace, Username and Password missing in old configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedError: ErrUsernameMissing,
|
|
},
|
|
{
|
|
testName: "Username and password with new configuration but username and password in global section",
|
|
conf: `[Global]
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
[VirtualCenter "0.0.0.0"]
|
|
[Workspace]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
folder = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "Username and password with new configuration, username and password in virtualcenter section",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
port = 443
|
|
insecure-flag = true
|
|
datacenter = us-west
|
|
[VirtualCenter "0.0.0.0"]
|
|
user = user
|
|
password = password
|
|
[Workspace]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
folder = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretName and SecretNamespace with new configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
secret-name = "vccreds"
|
|
secret-namespace = "kube-system"
|
|
datacenter = us-west
|
|
[VirtualCenter "0.0.0.0"]
|
|
[Workspace]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
folder = kubernetes
|
|
`,
|
|
expectedIsSecretProvided: true,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "SecretName and SecretNamespace with Username missing in new configuration",
|
|
conf: `[Global]
|
|
server = 0.0.0.0
|
|
port = 443
|
|
insecure-flag = true
|
|
datacenter = us-west
|
|
secret-name = "vccreds"
|
|
secret-namespace = "kube-system"
|
|
[VirtualCenter "0.0.0.0"]
|
|
password = password
|
|
[Workspace]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
folder = kubernetes
|
|
`,
|
|
expectedIsSecretProvided: true,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
testName: "virtual centers with a thumbprint",
|
|
conf: `[Global]
|
|
server = global
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
thumbprint = "thumbprint:global"
|
|
working-dir = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
expectedThumbprints: map[string]string{
|
|
"global": "thumbprint:global",
|
|
},
|
|
},
|
|
{
|
|
testName: "Multiple virtual centers with different thumbprints",
|
|
conf: `[Global]
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
[VirtualCenter "0.0.0.0"]
|
|
thumbprint = thumbprint:0
|
|
[VirtualCenter "no_thumbprint"]
|
|
[VirtualCenter "1.1.1.1"]
|
|
thumbprint = thumbprint:1
|
|
[Workspace]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
folder = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
expectedThumbprints: map[string]string{
|
|
"0.0.0.0": "thumbprint:0",
|
|
"1.1.1.1": "thumbprint:1",
|
|
},
|
|
},
|
|
{
|
|
testName: "Multiple virtual centers use the global CA cert",
|
|
conf: `[Global]
|
|
user = user
|
|
password = password
|
|
datacenter = us-west
|
|
ca-file = /some/path/to/my/trusted/ca.pem
|
|
[VirtualCenter "0.0.0.0"]
|
|
user = user
|
|
password = password
|
|
[VirtualCenter "1.1.1.1"]
|
|
user = user
|
|
password = password
|
|
[Workspace]
|
|
server = 0.0.0.0
|
|
datacenter = us-west
|
|
folder = kubernetes
|
|
`,
|
|
expectedUsername: username,
|
|
expectedPassword: password,
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, testcase := range testcases {
|
|
t.Logf("Executing Testcase: %s", testcase.testName)
|
|
cfg, err := readConfig(strings.NewReader(testcase.conf))
|
|
if err != nil {
|
|
t.Fatalf("Should succeed when a valid config is provided: %s", err)
|
|
}
|
|
vs, err = buildVSphereFromConfig(cfg)
|
|
if err != testcase.expectedError {
|
|
t.Fatalf("Should succeed when a valid config is provided: %s", err)
|
|
}
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if vs.isSecretInfoProvided != testcase.expectedIsSecretProvided {
|
|
t.Fatalf("SecretName and SecretNamespace was expected in config %s. error: %s",
|
|
testcase.conf, err)
|
|
}
|
|
if !testcase.expectedIsSecretProvided {
|
|
for _, vsInstance := range vs.vsphereInstanceMap {
|
|
if vsInstance.conn.Username != testcase.expectedUsername {
|
|
t.Fatalf("Expected username %s doesn't match actual username %s in config %s. error: %s",
|
|
testcase.expectedUsername, vsInstance.conn.Username, testcase.conf, err)
|
|
}
|
|
if vsInstance.conn.Password != testcase.expectedPassword {
|
|
t.Fatalf("Expected password %s doesn't match actual password %s in config %s. error: %s",
|
|
testcase.expectedPassword, vsInstance.conn.Password, testcase.conf, err)
|
|
}
|
|
}
|
|
}
|
|
// Check, if all the expected thumbprints are configured
|
|
for instanceName, expectedThumbprint := range testcase.expectedThumbprints {
|
|
instanceConfig, ok := vs.vsphereInstanceMap[instanceName]
|
|
if !ok {
|
|
t.Fatalf("Could not find configuration for instance %s", instanceName)
|
|
}
|
|
if actualThumbprint := instanceConfig.conn.Thumbprint; actualThumbprint != expectedThumbprint {
|
|
t.Fatalf(
|
|
"Expected thumbprint for instance '%s' to be '%s', got '%s'",
|
|
instanceName, expectedThumbprint, actualThumbprint,
|
|
)
|
|
}
|
|
}
|
|
// Check, if all all connections are configured with the global CA certificate
|
|
if expectedCaPath := cfg.Global.CAFile; expectedCaPath != "" {
|
|
for name, instance := range vs.vsphereInstanceMap {
|
|
if actualCaPath := instance.conn.CACert; actualCaPath != expectedCaPath {
|
|
t.Fatalf(
|
|
"Expected CA certificate path for instance '%s' to be the globally configured one ('%s'), got '%s'",
|
|
name, expectedCaPath, actualCaPath,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|