mirror of https://github.com/k3s-io/k3s
603 lines
17 KiB
Go
603 lines
17 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 gce
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/oauth2/google"
|
|
|
|
cloudprovider "k8s.io/cloud-provider"
|
|
)
|
|
|
|
func TestReadConfigFile(t *testing.T) {
|
|
const s = `[Global]
|
|
token-url = my-token-url
|
|
token-body = my-token-body
|
|
project-id = my-project
|
|
network-project-id = my-network-project
|
|
network-name = my-network
|
|
subnetwork-name = my-subnetwork
|
|
secondary-range-name = my-secondary-range
|
|
node-tags = my-node-tag1
|
|
node-instance-prefix = my-prefix
|
|
multizone = true
|
|
regional = true
|
|
`
|
|
reader := strings.NewReader(s)
|
|
config, err := readConfig(reader)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected config parsing error %v", err)
|
|
}
|
|
|
|
expected := &ConfigFile{Global: ConfigGlobal{
|
|
TokenURL: "my-token-url",
|
|
TokenBody: "my-token-body",
|
|
ProjectID: "my-project",
|
|
NetworkProjectID: "my-network-project",
|
|
NetworkName: "my-network",
|
|
SubnetworkName: "my-subnetwork",
|
|
SecondaryRangeName: "my-secondary-range",
|
|
NodeTags: []string{"my-node-tag1"},
|
|
NodeInstancePrefix: "my-prefix",
|
|
Multizone: true,
|
|
Regional: true,
|
|
}}
|
|
|
|
if !reflect.DeepEqual(expected, config) {
|
|
t.Fatalf("Expected config file values to be read into ConfigFile struct. \nExpected:\n%+v\nActual:\n%+v", expected, config)
|
|
}
|
|
}
|
|
|
|
func TestExtraKeyInConfig(t *testing.T) {
|
|
const s = `[Global]
|
|
project-id = my-project
|
|
unknown-key = abc
|
|
network-name = my-network
|
|
`
|
|
reader := strings.NewReader(s)
|
|
config, err := readConfig(reader)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected config parsing error %v", err)
|
|
}
|
|
if config.Global.ProjectID != "my-project" || config.Global.NetworkName != "my-network" {
|
|
t.Fatalf("Expected config values to continue to be read despite extra key-value pair.")
|
|
}
|
|
}
|
|
|
|
func TestGetRegion(t *testing.T) {
|
|
zoneName := "us-central1-b"
|
|
regionName, err := GetGCERegion(zoneName)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error from GetGCERegion: %v", err)
|
|
}
|
|
if regionName != "us-central1" {
|
|
t.Errorf("Unexpected region from GetGCERegion: %s", regionName)
|
|
}
|
|
gce := &Cloud{
|
|
localZone: zoneName,
|
|
region: regionName,
|
|
}
|
|
zones, ok := gce.Zones()
|
|
if !ok {
|
|
t.Fatalf("Unexpected missing zones impl")
|
|
}
|
|
zone, err := zones.GetZone(context.TODO())
|
|
if err != nil {
|
|
t.Fatalf("unexpected error %v", err)
|
|
}
|
|
if zone.Region != "us-central1" {
|
|
t.Errorf("Unexpected region: %s", zone.Region)
|
|
}
|
|
}
|
|
|
|
func TestComparingHostURLs(t *testing.T) {
|
|
tests := []struct {
|
|
host1 string
|
|
zone string
|
|
name string
|
|
expectEqual bool
|
|
}{
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/cool-project/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v23/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v24/projects/1234567/regions/us-central1/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: true,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-c",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: false,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx1",
|
|
expectEqual: false,
|
|
},
|
|
{
|
|
host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1",
|
|
zone: "us-central1-f",
|
|
name: "kubernetes-node-fhx",
|
|
expectEqual: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
link1 := hostURLToComparablePath(test.host1)
|
|
testInstance := &gceInstance{
|
|
Name: canonicalizeInstanceName(test.name),
|
|
Zone: test.zone,
|
|
}
|
|
link2 := testInstance.makeComparableHostPath()
|
|
if test.expectEqual && link1 != link2 {
|
|
t.Errorf("expected link1 and link2 to be equal, got %s and %s", link1, link2)
|
|
} else if !test.expectEqual && link1 == link2 {
|
|
t.Errorf("expected link1 and link2 not to be equal, got %s and %s", link1, link2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSplitProviderID(t *testing.T) {
|
|
providers := []struct {
|
|
providerID string
|
|
|
|
project string
|
|
zone string
|
|
instance string
|
|
|
|
fail bool
|
|
}{
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "project-example-164317",
|
|
zone: "us-central1-f",
|
|
instance: "kubernetes-node-fhx1",
|
|
fail: false,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "project-example.164317",
|
|
zone: "us-central1-f",
|
|
instance: "kubernetes-node-fhx1",
|
|
fail: false,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-fkubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + ":/project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: "aws://project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1/",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317//kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317/kubernetes-node-fhx1",
|
|
project: "",
|
|
zone: "",
|
|
instance: "",
|
|
fail: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range providers {
|
|
project, zone, instance, err := splitProviderID(test.providerID)
|
|
if (err != nil) != test.fail {
|
|
t.Errorf("Expected to fail=%t, with pattern %v", test.fail, test)
|
|
}
|
|
|
|
if test.fail {
|
|
continue
|
|
}
|
|
|
|
if project != test.project {
|
|
t.Errorf("Expected %v, but got %v", test.project, project)
|
|
}
|
|
if zone != test.zone {
|
|
t.Errorf("Expected %v, but got %v", test.zone, zone)
|
|
}
|
|
if instance != test.instance {
|
|
t.Errorf("Expected %v, but got %v", test.instance, instance)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetZoneByProviderID(t *testing.T) {
|
|
tests := []struct {
|
|
providerID string
|
|
|
|
expectedZone cloudprovider.Zone
|
|
|
|
fail bool
|
|
description string
|
|
}{
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1",
|
|
expectedZone: cloudprovider.Zone{FailureDomain: "us-central1-f", Region: "us-central1"},
|
|
fail: false,
|
|
description: "standard gce providerID",
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/us-central1-f/kubernetes-node-fhx1/",
|
|
expectedZone: cloudprovider.Zone{},
|
|
fail: true,
|
|
description: "too many slashes('/') trailing",
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example.164317//kubernetes-node-fhx1",
|
|
expectedZone: cloudprovider.Zone{},
|
|
fail: true,
|
|
description: "too many slashes('/') embedded",
|
|
},
|
|
{
|
|
providerID: ProviderName + "://project-example-164317/uscentral1f/kubernetes-node-fhx1",
|
|
expectedZone: cloudprovider.Zone{},
|
|
fail: true,
|
|
description: "invalid name of the GCE zone",
|
|
},
|
|
}
|
|
|
|
gce := &Cloud{
|
|
localZone: "us-central1-f",
|
|
region: "us-central1",
|
|
}
|
|
for _, test := range tests {
|
|
zone, err := gce.GetZoneByProviderID(context.TODO(), test.providerID)
|
|
if (err != nil) != test.fail {
|
|
t.Errorf("Expected to fail=%t, provider ID %v, tests %s", test.fail, test, test.description)
|
|
}
|
|
|
|
if test.fail {
|
|
continue
|
|
}
|
|
|
|
if zone != test.expectedZone {
|
|
t.Errorf("Expected %v, but got %v", test.expectedZone, zone)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateCloudConfigs(t *testing.T) {
|
|
configBoilerplate := ConfigGlobal{
|
|
TokenURL: "",
|
|
TokenBody: "",
|
|
ProjectID: "project-id",
|
|
NetworkName: "network-name",
|
|
SubnetworkName: "",
|
|
SecondaryRangeName: "",
|
|
NodeTags: []string{"node-tag"},
|
|
NodeInstancePrefix: "node-prefix",
|
|
Multizone: false,
|
|
Regional: false,
|
|
APIEndpoint: "",
|
|
LocalZone: "us-central1-a",
|
|
AlphaFeatures: []string{},
|
|
}
|
|
|
|
cloudBoilerplate := CloudConfig{
|
|
APIEndpoint: "",
|
|
ProjectID: "project-id",
|
|
NetworkProjectID: "",
|
|
Region: "us-central1",
|
|
Zone: "us-central1-a",
|
|
ManagedZones: []string{"us-central1-a"},
|
|
NetworkName: "network-name",
|
|
SubnetworkName: "",
|
|
NetworkURL: "",
|
|
SubnetworkURL: "",
|
|
SecondaryRangeName: "",
|
|
NodeTags: []string{"node-tag"},
|
|
TokenSource: google.ComputeTokenSource(""),
|
|
NodeInstancePrefix: "node-prefix",
|
|
UseMetadataServer: true,
|
|
AlphaFeatureGate: &AlphaFeatureGate{map[string]bool{}},
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
config func() ConfigGlobal
|
|
cloud func() CloudConfig
|
|
}{
|
|
{
|
|
name: "Empty Config",
|
|
config: func() ConfigGlobal { return configBoilerplate },
|
|
cloud: func() CloudConfig { return cloudBoilerplate },
|
|
},
|
|
{
|
|
name: "Nil token URL",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.TokenURL = "nil"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.TokenSource = nil
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Network Project ID",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.NetworkProjectID = "my-awesome-project"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.NetworkProjectID = "my-awesome-project"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Specified API Endpint",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.APIEndpoint = "https://www.googleapis.com/compute/staging_v1/"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.APIEndpoint = "https://www.googleapis.com/compute/staging_v1/"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Network & Subnetwork names",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.NetworkName = "my-network"
|
|
v.SubnetworkName = "my-subnetwork"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.NetworkName = "my-network"
|
|
v.SubnetworkName = "my-subnetwork"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Network & Subnetwork URLs",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.NetworkName = "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/my-network"
|
|
v.SubnetworkName = "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/my-subnetwork"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.NetworkName = ""
|
|
v.SubnetworkName = ""
|
|
v.NetworkURL = "https://www.googleapis.com/compute/v1/projects/project-id/global/networks/my-network"
|
|
v.SubnetworkURL = "https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/my-subnetwork"
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Multizone",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.Multizone = true
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.ManagedZones = nil
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Regional",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.Regional = true
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.Regional = true
|
|
v.ManagedZones = nil
|
|
return v
|
|
},
|
|
},
|
|
{
|
|
name: "Secondary Range Name",
|
|
config: func() ConfigGlobal {
|
|
v := configBoilerplate
|
|
v.SecondaryRangeName = "my-secondary"
|
|
return v
|
|
},
|
|
cloud: func() CloudConfig {
|
|
v := cloudBoilerplate
|
|
v.SecondaryRangeName = "my-secondary"
|
|
return v
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
resultCloud, err := generateCloudConfig(&ConfigFile{Global: tc.config()})
|
|
if err != nil {
|
|
t.Fatalf("Unexpect error: %v", err)
|
|
}
|
|
|
|
v := tc.cloud()
|
|
if !reflect.DeepEqual(*resultCloud, v) {
|
|
t.Errorf("Got: \n%v\nWant\n%v\n", v, *resultCloud)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewAlphaFeatureGate(t *testing.T) {
|
|
testCases := []struct {
|
|
alphaFeatures []string
|
|
expectEnabled []string
|
|
expectDisabled []string
|
|
}{
|
|
// enable foo bar
|
|
{
|
|
alphaFeatures: []string{"foo", "bar"},
|
|
expectEnabled: []string{"foo", "bar"},
|
|
expectDisabled: []string{"aaa"},
|
|
},
|
|
// no alpha feature
|
|
{
|
|
alphaFeatures: []string{},
|
|
expectEnabled: []string{},
|
|
expectDisabled: []string{"foo", "bar"},
|
|
},
|
|
// unsupported alpha feature
|
|
{
|
|
alphaFeatures: []string{"aaa", "foo"},
|
|
expectEnabled: []string{"foo"},
|
|
expectDisabled: []string{},
|
|
},
|
|
// enable foo
|
|
{
|
|
alphaFeatures: []string{"foo"},
|
|
expectEnabled: []string{"foo"},
|
|
expectDisabled: []string{"bar"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
featureGate := NewAlphaFeatureGate(tc.alphaFeatures)
|
|
|
|
for _, key := range tc.expectEnabled {
|
|
if !featureGate.Enabled(key) {
|
|
t.Errorf("Expect %q to be enabled.", key)
|
|
}
|
|
}
|
|
for _, key := range tc.expectDisabled {
|
|
if featureGate.Enabled(key) {
|
|
t.Errorf("Expect %q to be disabled.", key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetRegionInURL(t *testing.T) {
|
|
cases := map[string]string{
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/a": "us-central1",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west2/subnetworks/b": "us-west2",
|
|
"projects/my-project/regions/asia-central1/subnetworks/c": "asia-central1",
|
|
"regions/europe-north2": "europe-north2",
|
|
"my-url": "",
|
|
"": "",
|
|
}
|
|
for input, output := range cases {
|
|
result := getRegionInURL(input)
|
|
if result != output {
|
|
t.Errorf("Actual result %q does not match expected result %q for input: %q", result, output, input)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindSubnetForRegion(t *testing.T) {
|
|
s := []string{
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/default-38b01f54907a15a7",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-east1/subnetworks/default-277eec3815f742b6",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-east4/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-northeast1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-east1/subnetworks/default-8e020b4b8b244809",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/australia-southeast1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/southamerica-east1/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west3/subnetworks/default",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-southeast1/subnetworks/default",
|
|
"",
|
|
}
|
|
actual := findSubnetForRegion(s, "asia-east1")
|
|
expectedResult := "https://www.googleapis.com/compute/v1/projects/my-project/regions/asia-east1/subnetworks/default-8e020b4b8b244809"
|
|
if actual != expectedResult {
|
|
t.Errorf("Actual result %q does not match expected result %q", actual, expectedResult)
|
|
}
|
|
|
|
var nilSlice []string
|
|
res := findSubnetForRegion(nilSlice, "us-central1")
|
|
if res != "" {
|
|
t.Errorf("expected an empty result, got %v", res)
|
|
}
|
|
}
|
|
|
|
func TestLastComponent(t *testing.T) {
|
|
cases := map[string]string{
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/a": "a",
|
|
"https://www.googleapis.com/compute/v1/projects/my-project/regions/us-central1/subnetworks/b": "b",
|
|
"projects/my-project/regions/us-central1/subnetworks/c": "c",
|
|
"d": "d",
|
|
"": "",
|
|
}
|
|
for input, output := range cases {
|
|
result := lastComponent(input)
|
|
if result != output {
|
|
t.Errorf("Actual result %q does not match expected result %q for input: %q", result, output, input)
|
|
}
|
|
}
|
|
}
|