/* 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, ) } } } } }