mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			715 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			715 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2014 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 openstack
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"regexp"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/gophercloud/gophercloud"
 | |
| 	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
 | |
| 	"k8s.io/api/core/v1"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/api/resource"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/util/rand"
 | |
| 	"k8s.io/apimachinery/pkg/util/wait"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	testClusterName = "testCluster"
 | |
| 
 | |
| 	volumeStatusTimeoutSeconds = 30
 | |
| 	// volumeStatus* is configuration of exponential backoff for
 | |
| 	// waiting for specified volume status. Starting with 1
 | |
| 	// seconds, multiplying by 1.2 with each step and taking 13 steps at maximum
 | |
| 	// it will time out after 32s, which roughly corresponds to 30s
 | |
| 	volumeStatusInitDelay = 1 * time.Second
 | |
| 	volumeStatusFactor    = 1.2
 | |
| 	volumeStatusSteps     = 13
 | |
| )
 | |
| 
 | |
| func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string) {
 | |
| 	backoff := wait.Backoff{
 | |
| 		Duration: volumeStatusInitDelay,
 | |
| 		Factor:   volumeStatusFactor,
 | |
| 		Steps:    volumeStatusSteps,
 | |
| 	}
 | |
| 	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
 | |
| 		getVol, err := os.getVolume(volumeName)
 | |
| 		if err != nil {
 | |
| 			return false, err
 | |
| 		}
 | |
| 		if getVol.Status == status {
 | |
| 			t.Logf("Volume (%s) status changed to %s after %v seconds\n",
 | |
| 				volumeName,
 | |
| 				status,
 | |
| 				volumeStatusTimeoutSeconds)
 | |
| 			return true, nil
 | |
| 		}
 | |
| 		return false, nil
 | |
| 	})
 | |
| 	if err == wait.ErrWaitTimeout {
 | |
| 		t.Logf("Volume (%s) status did not change to %s after %v seconds\n",
 | |
| 			volumeName,
 | |
| 			status,
 | |
| 			volumeStatusTimeoutSeconds)
 | |
| 		return
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Cannot get existing Cinder volume (%s): %v", volumeName, err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestReadConfig(t *testing.T) {
 | |
| 	_, err := readConfig(nil)
 | |
| 	if err == nil {
 | |
| 		t.Errorf("Should fail when no config is provided: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	// Since we are setting env vars, we need to reset old
 | |
| 	// values for other tests to succeed.
 | |
| 	env := clearEnviron(t)
 | |
| 	defer resetEnviron(t, env)
 | |
| 
 | |
| 	os.Setenv("OS_PASSWORD", "mypass")
 | |
| 	defer os.Unsetenv("OS_PASSWORD")
 | |
| 
 | |
| 	os.Setenv("OS_TENANT_NAME", "admin")
 | |
| 	defer os.Unsetenv("OS_TENANT_NAME")
 | |
| 
 | |
| 	cfg, err := readConfig(strings.NewReader(`
 | |
|  [Global]
 | |
|  auth-url = http://auth.url
 | |
|  user-id = user
 | |
|  tenant-name = demo
 | |
|  region = RegionOne
 | |
|  [LoadBalancer]
 | |
|  create-monitor = yes
 | |
|  monitor-delay = 1m
 | |
|  monitor-timeout = 30s
 | |
|  monitor-max-retries = 3
 | |
|  [BlockStorage]
 | |
|  bs-version = auto
 | |
|  trust-device-path = yes
 | |
|  ignore-volume-az = yes
 | |
|  [Metadata]
 | |
|  search-order = configDrive, metadataService
 | |
|  `))
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Should succeed when a valid config is provided: %s", err)
 | |
| 	}
 | |
| 	if cfg.Global.AuthURL != "http://auth.url" {
 | |
| 		t.Errorf("incorrect authurl: %s", cfg.Global.AuthURL)
 | |
| 	}
 | |
| 
 | |
| 	if cfg.Global.UserID != "user" {
 | |
| 		t.Errorf("incorrect userid: %s", cfg.Global.UserID)
 | |
| 	}
 | |
| 
 | |
| 	if cfg.Global.Password != "mypass" {
 | |
| 		t.Errorf("incorrect password: %s", cfg.Global.Password)
 | |
| 	}
 | |
| 
 | |
| 	// config file wins over environment variable
 | |
| 	if cfg.Global.TenantName != "demo" {
 | |
| 		t.Errorf("incorrect tenant name: %s", cfg.Global.TenantName)
 | |
| 	}
 | |
| 
 | |
| 	if cfg.Global.Region != "RegionOne" {
 | |
| 		t.Errorf("incorrect region: %s", cfg.Global.Region)
 | |
| 	}
 | |
| 
 | |
| 	if !cfg.LoadBalancer.CreateMonitor {
 | |
| 		t.Errorf("incorrect lb.createmonitor: %t", cfg.LoadBalancer.CreateMonitor)
 | |
| 	}
 | |
| 	if cfg.LoadBalancer.MonitorDelay.Duration != 1*time.Minute {
 | |
| 		t.Errorf("incorrect lb.monitordelay: %s", cfg.LoadBalancer.MonitorDelay)
 | |
| 	}
 | |
| 	if cfg.LoadBalancer.MonitorTimeout.Duration != 30*time.Second {
 | |
| 		t.Errorf("incorrect lb.monitortimeout: %s", cfg.LoadBalancer.MonitorTimeout)
 | |
| 	}
 | |
| 	if cfg.LoadBalancer.MonitorMaxRetries != 3 {
 | |
| 		t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries)
 | |
| 	}
 | |
| 	if cfg.BlockStorage.TrustDevicePath != true {
 | |
| 		t.Errorf("incorrect bs.trustdevicepath: %v", cfg.BlockStorage.TrustDevicePath)
 | |
| 	}
 | |
| 	if cfg.BlockStorage.BSVersion != "auto" {
 | |
| 		t.Errorf("incorrect bs.bs-version: %v", cfg.BlockStorage.BSVersion)
 | |
| 	}
 | |
| 	if cfg.BlockStorage.IgnoreVolumeAZ != true {
 | |
| 		t.Errorf("incorrect bs.IgnoreVolumeAZ: %v", cfg.BlockStorage.IgnoreVolumeAZ)
 | |
| 	}
 | |
| 	if cfg.Metadata.SearchOrder != "configDrive, metadataService" {
 | |
| 		t.Errorf("incorrect md.search-order: %v", cfg.Metadata.SearchOrder)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestToAuthOptions(t *testing.T) {
 | |
| 	cfg := Config{}
 | |
| 	cfg.Global.Username = "user"
 | |
| 	cfg.Global.Password = "pass"
 | |
| 	cfg.Global.DomainID = "2a73b8f597c04551a0fdc8e95544be8a"
 | |
| 	cfg.Global.DomainName = "local"
 | |
| 	cfg.Global.AuthURL = "http://auth.url"
 | |
| 	cfg.Global.UserID = "user"
 | |
| 
 | |
| 	ao := cfg.toAuthOptions()
 | |
| 
 | |
| 	if !ao.AllowReauth {
 | |
| 		t.Errorf("Will need to be able to reauthenticate")
 | |
| 	}
 | |
| 	if ao.Username != cfg.Global.Username {
 | |
| 		t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username)
 | |
| 	}
 | |
| 	if ao.Password != cfg.Global.Password {
 | |
| 		t.Errorf("Password %s != %s", ao.Password, cfg.Global.Password)
 | |
| 	}
 | |
| 	if ao.DomainID != cfg.Global.DomainID {
 | |
| 		t.Errorf("DomainID %s != %s", ao.DomainID, cfg.Global.DomainID)
 | |
| 	}
 | |
| 	if ao.IdentityEndpoint != cfg.Global.AuthURL {
 | |
| 		t.Errorf("IdentityEndpoint %s != %s", ao.IdentityEndpoint, cfg.Global.AuthURL)
 | |
| 	}
 | |
| 	if ao.UserID != cfg.Global.UserID {
 | |
| 		t.Errorf("UserID %s != %s", ao.UserID, cfg.Global.UserID)
 | |
| 	}
 | |
| 	if ao.DomainName != cfg.Global.DomainName {
 | |
| 		t.Errorf("DomainName %s != %s", ao.DomainName, cfg.Global.DomainName)
 | |
| 	}
 | |
| 	if ao.TenantID != cfg.Global.TenantID {
 | |
| 		t.Errorf("TenantID %s != %s", ao.TenantID, cfg.Global.TenantID)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCheckOpenStackOpts(t *testing.T) {
 | |
| 	delay := MyDuration{60 * time.Second}
 | |
| 	timeout := MyDuration{30 * time.Second}
 | |
| 	tests := []struct {
 | |
| 		name          string
 | |
| 		openstackOpts *OpenStack
 | |
| 		expectedError error
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "test1",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				lbOpts: LoadBalancerOpts{
 | |
| 					LBVersion:            "v2",
 | |
| 					SubnetID:             "6261548e-ffde-4bc7-bd22-59c83578c5ef",
 | |
| 					FloatingNetworkID:    "38b8b5f9-64dc-4424-bf86-679595714786",
 | |
| 					LBMethod:             "ROUND_ROBIN",
 | |
| 					LBProvider:           "haproxy",
 | |
| 					CreateMonitor:        true,
 | |
| 					MonitorDelay:         delay,
 | |
| 					MonitorTimeout:       timeout,
 | |
| 					MonitorMaxRetries:    uint(3),
 | |
| 					ManageSecurityGroups: true,
 | |
| 				},
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: configDriveID,
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test2",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				lbOpts: LoadBalancerOpts{
 | |
| 					LBVersion:            "v2",
 | |
| 					FloatingNetworkID:    "38b8b5f9-64dc-4424-bf86-679595714786",
 | |
| 					LBMethod:             "ROUND_ROBIN",
 | |
| 					CreateMonitor:        true,
 | |
| 					MonitorDelay:         delay,
 | |
| 					MonitorTimeout:       timeout,
 | |
| 					MonitorMaxRetries:    uint(3),
 | |
| 					ManageSecurityGroups: true,
 | |
| 				},
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: configDriveID,
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test3",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				lbOpts: LoadBalancerOpts{
 | |
| 					LBVersion:            "v2",
 | |
| 					SubnetID:             "6261548e-ffde-4bc7-bd22-59c83578c5ef",
 | |
| 					FloatingNetworkID:    "38b8b5f9-64dc-4424-bf86-679595714786",
 | |
| 					LBMethod:             "ROUND_ROBIN",
 | |
| 					CreateMonitor:        true,
 | |
| 					MonitorTimeout:       timeout,
 | |
| 					MonitorMaxRetries:    uint(3),
 | |
| 					ManageSecurityGroups: true,
 | |
| 				},
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: configDriveID,
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: fmt.Errorf("monitor-delay not set in cloud provider config"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test4",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: "",
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot be empty"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test5",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: "value1,value2,value3",
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: fmt.Errorf("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test6",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: "value1",
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+
 | |
| 				"Supported elements include %q and %q", "value1", configDriveID, metadataID),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test7",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				lbOpts: LoadBalancerOpts{
 | |
| 					LBVersion:            "v2",
 | |
| 					SubnetID:             "6261548e-ffde-4bc7-bd22-59c83578c5ef",
 | |
| 					FloatingNetworkID:    "38b8b5f9-64dc-4424-bf86-679595714786",
 | |
| 					LBMethod:             "ROUND_ROBIN",
 | |
| 					CreateMonitor:        true,
 | |
| 					MonitorDelay:         delay,
 | |
| 					MonitorTimeout:       timeout,
 | |
| 					ManageSecurityGroups: true,
 | |
| 				},
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: configDriveID,
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: fmt.Errorf("monitor-max-retries not set in cloud provider config"),
 | |
| 		},
 | |
| 		{
 | |
| 			name: "test8",
 | |
| 			openstackOpts: &OpenStack{
 | |
| 				provider: nil,
 | |
| 				lbOpts: LoadBalancerOpts{
 | |
| 					LBVersion:            "v2",
 | |
| 					SubnetID:             "6261548e-ffde-4bc7-bd22-59c83578c5ef",
 | |
| 					FloatingNetworkID:    "38b8b5f9-64dc-4424-bf86-679595714786",
 | |
| 					LBMethod:             "ROUND_ROBIN",
 | |
| 					CreateMonitor:        true,
 | |
| 					MonitorDelay:         delay,
 | |
| 					MonitorMaxRetries:    uint(3),
 | |
| 					ManageSecurityGroups: true,
 | |
| 				},
 | |
| 				metadataOpts: MetadataOpts{
 | |
| 					SearchOrder: configDriveID,
 | |
| 				},
 | |
| 			},
 | |
| 			expectedError: fmt.Errorf("monitor-timeout not set in cloud provider config"),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, testcase := range tests {
 | |
| 		err := checkOpenStackOpts(testcase.openstackOpts)
 | |
| 
 | |
| 		if err == nil && testcase.expectedError == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		if (err != nil && testcase.expectedError == nil) || (err == nil && testcase.expectedError != nil) || err.Error() != testcase.expectedError.Error() {
 | |
| 			t.Errorf("%s failed: expected err=%q, got %q",
 | |
| 				testcase.name, testcase.expectedError, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCaller(t *testing.T) {
 | |
| 	called := false
 | |
| 	myFunc := func() { called = true }
 | |
| 
 | |
| 	c := newCaller()
 | |
| 	c.call(myFunc)
 | |
| 
 | |
| 	if !called {
 | |
| 		t.Errorf("caller failed to call function in default case")
 | |
| 	}
 | |
| 
 | |
| 	c.disarm()
 | |
| 	called = false
 | |
| 	c.call(myFunc)
 | |
| 
 | |
| 	if called {
 | |
| 		t.Error("caller still called function when disarmed")
 | |
| 	}
 | |
| 
 | |
| 	// Confirm the "usual" deferred caller pattern works as expected
 | |
| 
 | |
| 	called = false
 | |
| 	successCase := func() {
 | |
| 		c := newCaller()
 | |
| 		defer c.call(func() { called = true })
 | |
| 		c.disarm()
 | |
| 	}
 | |
| 	if successCase(); called {
 | |
| 		t.Error("Deferred success case still invoked unwind")
 | |
| 	}
 | |
| 
 | |
| 	called = false
 | |
| 	failureCase := func() {
 | |
| 		c := newCaller()
 | |
| 		defer c.call(func() { called = true })
 | |
| 	}
 | |
| 	if failureCase(); !called {
 | |
| 		t.Error("Deferred failure case failed to invoke unwind")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // An arbitrary sort.Interface, just for easier comparison
 | |
| type AddressSlice []v1.NodeAddress
 | |
| 
 | |
| func (a AddressSlice) Len() int           { return len(a) }
 | |
| func (a AddressSlice) Less(i, j int) bool { return a[i].Address < a[j].Address }
 | |
| func (a AddressSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | |
| 
 | |
| func TestNodeAddresses(t *testing.T) {
 | |
| 	srv := servers.Server{
 | |
| 		Status:     "ACTIVE",
 | |
| 		HostID:     "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
 | |
| 		AccessIPv4: "50.56.176.99",
 | |
| 		AccessIPv6: "2001:4800:790e:510:be76:4eff:fe04:82a8",
 | |
| 		Addresses: map[string]interface{}{
 | |
| 			"private": []interface{}{
 | |
| 				map[string]interface{}{
 | |
| 					"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b",
 | |
| 					"version":                 float64(4),
 | |
| 					"addr":                    "10.0.0.32",
 | |
| 					"OS-EXT-IPS:type":         "fixed",
 | |
| 				},
 | |
| 				map[string]interface{}{
 | |
| 					"version":         float64(4),
 | |
| 					"addr":            "50.56.176.36",
 | |
| 					"OS-EXT-IPS:type": "floating",
 | |
| 				},
 | |
| 				map[string]interface{}{
 | |
| 					"version": float64(4),
 | |
| 					"addr":    "10.0.0.31",
 | |
| 					// No OS-EXT-IPS:type
 | |
| 				},
 | |
| 			},
 | |
| 			"public": []interface{}{
 | |
| 				map[string]interface{}{
 | |
| 					"version": float64(4),
 | |
| 					"addr":    "50.56.176.35",
 | |
| 				},
 | |
| 				map[string]interface{}{
 | |
| 					"version": float64(6),
 | |
| 					"addr":    "2001:4800:780e:510:be76:4eff:fe04:84a8",
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	addrs, err := nodeAddresses(&srv)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("nodeAddresses returned error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	sort.Sort(AddressSlice(addrs))
 | |
| 	t.Logf("addresses is %v", addrs)
 | |
| 
 | |
| 	want := []v1.NodeAddress{
 | |
| 		{Type: v1.NodeInternalIP, Address: "10.0.0.31"},
 | |
| 		{Type: v1.NodeInternalIP, Address: "10.0.0.32"},
 | |
| 		{Type: v1.NodeExternalIP, Address: "2001:4800:780e:510:be76:4eff:fe04:84a8"},
 | |
| 		{Type: v1.NodeExternalIP, Address: "2001:4800:790e:510:be76:4eff:fe04:82a8"},
 | |
| 		{Type: v1.NodeExternalIP, Address: "50.56.176.35"},
 | |
| 		{Type: v1.NodeExternalIP, Address: "50.56.176.36"},
 | |
| 		{Type: v1.NodeExternalIP, Address: "50.56.176.99"},
 | |
| 	}
 | |
| 
 | |
| 	if !reflect.DeepEqual(want, addrs) {
 | |
| 		t.Errorf("nodeAddresses returned incorrect value %v", addrs)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestNewOpenStack(t *testing.T) {
 | |
| 	cfg, ok := configFromEnv()
 | |
| 	if !ok {
 | |
| 		t.Skip("No config found in environment")
 | |
| 	}
 | |
| 
 | |
| 	_, err := newOpenStack(cfg)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to construct/authenticate OpenStack: %s", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestLoadBalancer(t *testing.T) {
 | |
| 	cfg, ok := configFromEnv()
 | |
| 	if !ok {
 | |
| 		t.Skip("No config found in environment")
 | |
| 	}
 | |
| 
 | |
| 	versions := []string{"v2", ""}
 | |
| 
 | |
| 	for _, v := range versions {
 | |
| 		t.Logf("Trying LBVersion = '%s'\n", v)
 | |
| 		cfg.LoadBalancer.LBVersion = v
 | |
| 
 | |
| 		os, err := newOpenStack(cfg)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Failed to construct/authenticate OpenStack: %s", err)
 | |
| 		}
 | |
| 
 | |
| 		lb, ok := os.LoadBalancer()
 | |
| 		if !ok {
 | |
| 			t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?")
 | |
| 		}
 | |
| 
 | |
| 		_, exists, err := lb.GetLoadBalancer(context.TODO(), testClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "noexist"}})
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
 | |
| 		}
 | |
| 		if exists {
 | |
| 			t.Fatalf("GetLoadBalancer(\"noexist\") returned exists")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestZones(t *testing.T) {
 | |
| 	SetMetadataFixture(&FakeMetadata)
 | |
| 	defer ClearMetadata()
 | |
| 
 | |
| 	os := OpenStack{
 | |
| 		provider: &gophercloud.ProviderClient{
 | |
| 			IdentityBase: "http://auth.url/",
 | |
| 		},
 | |
| 		region: "myRegion",
 | |
| 	}
 | |
| 
 | |
| 	z, ok := os.Zones()
 | |
| 	if !ok {
 | |
| 		t.Fatalf("Zones() returned false")
 | |
| 	}
 | |
| 
 | |
| 	zone, err := z.GetZone(context.TODO())
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("GetZone() returned error: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	if zone.Region != "myRegion" {
 | |
| 		t.Fatalf("GetZone() returned wrong region (%s)", zone.Region)
 | |
| 	}
 | |
| 
 | |
| 	if zone.FailureDomain != "nova" {
 | |
| 		t.Fatalf("GetZone() returned wrong failure domain (%s)", zone.FailureDomain)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var diskPathRegexp = regexp.MustCompile("/dev/disk/(?:by-id|by-path)/")
 | |
| 
 | |
| func TestVolumes(t *testing.T) {
 | |
| 	cfg, ok := configFromEnv()
 | |
| 	if !ok {
 | |
| 		t.Skip("No config found in environment")
 | |
| 	}
 | |
| 
 | |
| 	os, err := newOpenStack(cfg)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed to construct/authenticate OpenStack: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	tags := map[string]string{
 | |
| 		"test": "value",
 | |
| 	}
 | |
| 	vol, _, _, _, err := os.CreateVolume("kubernetes-test-volume-"+rand.String(10), 1, "", "", &tags)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Cannot create a new Cinder volume: %v", err)
 | |
| 	}
 | |
| 	t.Logf("Volume (%s) created\n", vol)
 | |
| 
 | |
| 	WaitForVolumeStatus(t, os, vol, volumeAvailableStatus)
 | |
| 
 | |
| 	id, err := os.InstanceID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Cannot find instance id: %v - perhaps you are running this test outside a VM launched by OpenStack", err)
 | |
| 	} else {
 | |
| 		diskID, err := os.AttachDisk(id, vol)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Cannot AttachDisk Cinder volume %s: %v", vol, err)
 | |
| 		}
 | |
| 		t.Logf("Volume (%s) attached, disk ID: %s\n", vol, diskID)
 | |
| 
 | |
| 		WaitForVolumeStatus(t, os, vol, volumeInUseStatus)
 | |
| 
 | |
| 		devicePath := os.GetDevicePath(diskID)
 | |
| 		if diskPathRegexp.FindString(devicePath) == "" {
 | |
| 			t.Fatalf("GetDevicePath returned and unexpected path for Cinder volume %s, returned %s", vol, devicePath)
 | |
| 		}
 | |
| 		t.Logf("Volume (%s) found at path: %s\n", vol, devicePath)
 | |
| 
 | |
| 		err = os.DetachDisk(id, vol)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("Cannot DetachDisk Cinder volume %s: %v", vol, err)
 | |
| 		}
 | |
| 		t.Logf("Volume (%s) detached\n", vol)
 | |
| 
 | |
| 		WaitForVolumeStatus(t, os, vol, volumeAvailableStatus)
 | |
| 	}
 | |
| 
 | |
| 	expectedVolSize := resource.MustParse("2Gi")
 | |
| 	newVolSize, err := os.ExpandVolume(vol, resource.MustParse("1Gi"), expectedVolSize)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Cannot expand a Cinder volume: %v", err)
 | |
| 	}
 | |
| 	if newVolSize != expectedVolSize {
 | |
| 		t.Logf("Expected: %v but got: %v ", expectedVolSize, newVolSize)
 | |
| 	}
 | |
| 	t.Logf("Volume expanded to (%v) \n", newVolSize)
 | |
| 
 | |
| 	WaitForVolumeStatus(t, os, vol, volumeAvailableStatus)
 | |
| 
 | |
| 	err = os.DeleteVolume(vol)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Cannot delete Cinder volume %s: %v", vol, err)
 | |
| 	}
 | |
| 	t.Logf("Volume (%s) deleted\n", vol)
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestInstanceIDFromProviderID(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		providerID string
 | |
| 		instanceID string
 | |
| 		fail       bool
 | |
| 	}{
 | |
| 		{
 | |
| 			providerID: ProviderName + "://" + "/" + "7b9cf879-7146-417c-abfd-cb4272f0c935",
 | |
| 			instanceID: "7b9cf879-7146-417c-abfd-cb4272f0c935",
 | |
| 			fail:       false,
 | |
| 		},
 | |
| 		{
 | |
| 			providerID: "openstack://7b9cf879-7146-417c-abfd-cb4272f0c935",
 | |
| 			instanceID: "",
 | |
| 			fail:       true,
 | |
| 		},
 | |
| 		{
 | |
| 			providerID: "7b9cf879-7146-417c-abfd-cb4272f0c935",
 | |
| 			instanceID: "",
 | |
| 			fail:       true,
 | |
| 		},
 | |
| 		{
 | |
| 			providerID: "other-provider:///7b9cf879-7146-417c-abfd-cb4272f0c935",
 | |
| 			instanceID: "",
 | |
| 			fail:       true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, test := range testCases {
 | |
| 		instanceID, err := instanceIDFromProviderID(test.providerID)
 | |
| 		if (err != nil) != test.fail {
 | |
| 			t.Errorf("%s yielded `err != nil` as %t. expected %t", test.providerID, (err != nil), test.fail)
 | |
| 		}
 | |
| 
 | |
| 		if test.fail {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if instanceID != test.instanceID {
 | |
| 			t.Errorf("%s yielded %s. expected %s", test.providerID, instanceID, test.instanceID)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestToAuth3Options(t *testing.T) {
 | |
| 	cfg := Config{}
 | |
| 	cfg.Global.Username = "user"
 | |
| 	cfg.Global.Password = "pass"
 | |
| 	cfg.Global.DomainID = "2a73b8f597c04551a0fdc8e95544be8a"
 | |
| 	cfg.Global.DomainName = "local"
 | |
| 	cfg.Global.AuthURL = "http://auth.url"
 | |
| 	cfg.Global.UserID = "user"
 | |
| 
 | |
| 	ao := cfg.toAuth3Options()
 | |
| 
 | |
| 	if !ao.AllowReauth {
 | |
| 		t.Errorf("Will need to be able to reauthenticate")
 | |
| 	}
 | |
| 	if ao.Username != cfg.Global.Username {
 | |
| 		t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username)
 | |
| 	}
 | |
| 	if ao.Password != cfg.Global.Password {
 | |
| 		t.Errorf("Password %s != %s", ao.Password, cfg.Global.Password)
 | |
| 	}
 | |
| 	if ao.DomainID != cfg.Global.DomainID {
 | |
| 		t.Errorf("DomainID %s != %s", ao.DomainID, cfg.Global.DomainID)
 | |
| 	}
 | |
| 	if ao.IdentityEndpoint != cfg.Global.AuthURL {
 | |
| 		t.Errorf("IdentityEndpoint %s != %s", ao.IdentityEndpoint, cfg.Global.AuthURL)
 | |
| 	}
 | |
| 	if ao.UserID != cfg.Global.UserID {
 | |
| 		t.Errorf("UserID %s != %s", ao.UserID, cfg.Global.UserID)
 | |
| 	}
 | |
| 	if ao.DomainName != cfg.Global.DomainName {
 | |
| 		t.Errorf("DomainName %s != %s", ao.DomainName, cfg.Global.DomainName)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func clearEnviron(t *testing.T) []string {
 | |
| 	env := os.Environ()
 | |
| 	for _, pair := range env {
 | |
| 		if strings.HasPrefix(pair, "OS_") {
 | |
| 			i := strings.Index(pair, "=") + 1
 | |
| 			os.Unsetenv(pair[:i-1])
 | |
| 		}
 | |
| 	}
 | |
| 	return env
 | |
| }
 | |
| func resetEnviron(t *testing.T, items []string) {
 | |
| 	for _, pair := range items {
 | |
| 		if strings.HasPrefix(pair, "OS_") {
 | |
| 			i := strings.Index(pair, "=") + 1
 | |
| 			if err := os.Setenv(pair[:i-1], pair[i:]); err != nil {
 | |
| 				t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i-1], pair[i:], err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |