From 0212454c39a78598b88c7e756c005631c941e31a Mon Sep 17 00:00:00 2001 From: Michael Wagner Date: Sun, 15 Dec 2019 19:08:52 +0100 Subject: [PATCH 1/3] tests(openstack): cover GetApiIngressStatus --- upup/pkg/fi/cloudup/openstack/BUILD.bazel | 18 +- upup/pkg/fi/cloudup/openstack/cloud_test.go | 439 ++++++++++++++++++ .../gophercloud/testhelper/BUILD.bazel | 13 + .../gophercloud/testhelper/client/BUILD.bazel | 13 + .../gophercloud/testhelper/client/fake.go | 17 + .../gophercloud/testhelper/convenience.go | 348 ++++++++++++++ .../gophercloud/gophercloud/testhelper/doc.go | 4 + .../testhelper/fixture/BUILD.bazel | 13 + .../gophercloud/testhelper/fixture/helper.go | 31 ++ .../gophercloud/testhelper/http_responses.go | 91 ++++ vendor/modules.txt | 3 + 11 files changed, 989 insertions(+), 1 deletion(-) create mode 100644 upup/pkg/fi/cloudup/openstack/cloud_test.go create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/doc.go create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go create mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go diff --git a/upup/pkg/fi/cloudup/openstack/BUILD.bazel b/upup/pkg/fi/cloudup/openstack/BUILD.bazel index a3333408c05d8..94e417f0cf055 100644 --- a/upup/pkg/fi/cloudup/openstack/BUILD.bazel +++ b/upup/pkg/fi/cloudup/openstack/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -66,3 +66,19 @@ go_library( "//vendor/k8s.io/klog:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["cloud_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/apis/kops:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/fixture:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) diff --git a/upup/pkg/fi/cloudup/openstack/cloud_test.go b/upup/pkg/fi/cloudup/openstack/cloud_test.go new file mode 100644 index 0000000000000..5d39f54887d4f --- /dev/null +++ b/upup/pkg/fi/cloudup/openstack/cloud_test.go @@ -0,0 +1,439 @@ +/* +Copyright 2019 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 ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "sort" + "testing" + + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" + "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" + "github.com/gophercloud/gophercloud/testhelper/fixture" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kops/pkg/apis/kops" +) + +func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { + tests := []struct { + desc string + cluster *kops.Cluster + loadbalancers []loadbalancers.LoadBalancer + floatingIPs []floatingips.FloatingIP + instances serverList + cloudFloatingEnabled bool + expectedAPIIngress []kops.ApiIngressStatus + expectedError error + }{ + { + desc: "Loadbalancer configured master public name set", + cluster: &kops.Cluster{ + Spec: kops.ClusterSpec{ + MasterPublicName: "master.k8s.local", + CloudConfig: &kops.CloudConfiguration{ + Openstack: &kops.OpenstackConfiguration{ + Loadbalancer: &kops.OpenstackLoadbalancerConfig{}, + }, + }, + }, + }, + loadbalancers: []loadbalancers.LoadBalancer{ + { + ID: "lb_id", + Name: "name", + VipAddress: "10.1.2.3", + VipPortID: "vip_port_id", + VipSubnetID: "vip_subnet_id", + VipNetworkID: "vip_network_id", + }, + }, + floatingIPs: []floatingips.FloatingIP{ + { + ID: "id", + FixedIP: "10.1.2.3", + InstanceID: "lb_id", + IP: "8.8.8.8", + }, + }, + expectedAPIIngress: []kops.ApiIngressStatus{ + { + IP: "8.8.8.8", + }, + }, + }, + { + desc: "Loadbalancer configured master public name set multiple IPs match", + cluster: &kops.Cluster{ + Spec: kops.ClusterSpec{ + MasterPublicName: "master.k8s.local", + CloudConfig: &kops.CloudConfiguration{ + Openstack: &kops.OpenstackConfiguration{ + Loadbalancer: &kops.OpenstackLoadbalancerConfig{}, + }, + }, + }, + }, + loadbalancers: []loadbalancers.LoadBalancer{ + { + ID: "lb_id", + Name: "name", + VipAddress: "10.1.2.3", + VipPortID: "vip_port_id", + VipSubnetID: "vip_subnet_id", + VipNetworkID: "vip_network_id", + }, + }, + floatingIPs: []floatingips.FloatingIP{ + { + ID: "cluster", + FixedIP: "10.1.2.3", + InstanceID: "lb_id", + IP: "8.8.8.8", + }, + { + ID: "something_else", + FixedIP: "192.168.2.3", + InstanceID: "xx_id", + IP: "2.2.2.2", + }, + { + ID: "yet_another", + FixedIP: "10.1.2.3", + InstanceID: "yy_id", + IP: "9.9.9.9", + }, + }, + expectedAPIIngress: []kops.ApiIngressStatus{ + {IP: "8.8.8.8"}, + {IP: "9.9.9.9"}, + }, + }, + { + desc: "Loadbalancer configured master public name not set", + cluster: &kops.Cluster{ + Spec: kops.ClusterSpec{ + CloudConfig: &kops.CloudConfiguration{ + Openstack: &kops.OpenstackConfiguration{ + Loadbalancer: &kops.OpenstackLoadbalancerConfig{}, + }, + }, + }, + }, + expectedAPIIngress: nil, + }, + { + desc: "No Loadbalancer configured no floating enabled", + cluster: &kops.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster.k8s.local", + }, + Spec: kops.ClusterSpec{ + CloudConfig: &kops.CloudConfiguration{ + Openstack: &kops.OpenstackConfiguration{}, + }, + }, + }, + instances: []servers.Server{ + { + ID: "master1_no_lb_no_floating", + Metadata: map[string]string{ + "k8s": "cluster.k8s.local", + "KopsRole": "Master", + }, + Addresses: map[string]interface{}{ + "1": []Address{ + {Addr: "1.2.3.4"}, + {Addr: "2.3.4.5"}, + }, + "2": []Address{ + {Addr: "3.4.5.6"}, + {Addr: "4.5.6.7"}, + }, + }, + }, + { + ID: "master2_no_lb_no_floating", + Metadata: map[string]string{ + "k8s": "cluster.k8s.local", + "KopsRole": "Master", + }, + Addresses: map[string]interface{}{ + "1": []Address{ + {Addr: "10.20.30.40"}, + {Addr: "20.30.40.50"}, + }, + "2": []Address{ + {Addr: "30.40.50.60"}, + {Addr: "40.50.60.70"}, + }, + }, + }, + { + ID: "node_no_lb_no_floating", + Metadata: map[string]string{ + "k8s": "cluster.k8s.local", + "KopsRole": "Node", + }, + Addresses: map[string]interface{}{ + "1": []Address{ + {Addr: "110.120.130.140", IPType: "floating"}, + {Addr: "120.130.140.150", IPType: "private"}, + }, + }, + }, + }, + expectedAPIIngress: []kops.ApiIngressStatus{ + {IP: "1.2.3.4"}, + {IP: "2.3.4.5"}, + {IP: "3.4.5.6"}, + {IP: "4.5.6.7"}, + {IP: "10.20.30.40"}, + {IP: "20.30.40.50"}, + {IP: "30.40.50.60"}, + {IP: "40.50.60.70"}, + }, + }, + { + desc: "No Loadbalancer configured with floating enabled", + cluster: &kops.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster.k8s.local", + }, + Spec: kops.ClusterSpec{ + CloudConfig: &kops.CloudConfiguration{ + Openstack: &kops.OpenstackConfiguration{}, + }, + }, + }, + instances: []servers.Server{ + { + ID: "master1_no_lb_floating", + Metadata: map[string]string{ + "k8s": "cluster.k8s.local", + "KopsRole": "Master", + }, + Addresses: map[string]interface{}{ + "1": []map[string]interface{}{ + {"Addr": "1.2.3.4", "OS-EXT-IPS:type": "floating"}, + {"Addr": "2.3.4.5", "OS-EXT-IPS:type": "private"}, + }, + "2": []map[string]string{ + {"Addr": "3.4.5.6", "OS-EXT-IPS:type": "private"}, + {"Addr": "4.5.6.7", "OS-EXT-IPS:type": "floating"}, + }, + }, + }, + { + ID: "master2_no_lb_floating", + Metadata: map[string]string{ + "k8s": "cluster.k8s.local", + "KopsRole": "Master", + }, + Addresses: map[string]interface{}{ + "1": []map[string]string{ + {"Addr": "10.20.30.40", "OS-EXT-IPS:type": "private"}, + {"Addr": "20.30.40.50", "OS-EXT-IPS:type": "private"}, + }, + "2": []map[string]string{ + {"Addr": "30.40.50.60", "OS-EXT-IPS:type": "private"}, + {"Addr": "40.50.60.70", "OS-EXT-IPS:type": "floating"}, + }, + }, + }, + { + ID: "node_no_lb_floating", + Metadata: map[string]string{ + "k8s": "cluster.k8s.local", + "KopsRole": "Node", + }, + Addresses: map[string]interface{}{ + "1": []map[string]string{ + {"Addr": "110.120.130.140", "OS-EXT-IPS:type": "floating"}, + {"Addr": "120.130.140.150", "OS-EXT-IPS:type": "private"}, + }, + }, + }, + }, + cloudFloatingEnabled: true, + expectedAPIIngress: []kops.ApiIngressStatus{ + {IP: "1.2.3.4"}, + {IP: "4.5.6.7"}, + {IP: "40.50.60.70"}, + }, + }, + } + + for _, testCase := range tests { + t.Run(testCase.desc, func(t *testing.T) { + testhelper.SetupHTTP() + defer testhelper.TeardownHTTP() + fixture.SetupHandler( + t, + "/servers/detail", + http.MethodGet, + "", + string(mustJSONMarshal(json.Marshal( + struct { + Servers []servers.Server `json:"servers"` + }{ + Servers: testCase.instances, + }, + ))), + http.StatusOK, + ) + for _, server := range testCase.instances { + fixture.SetupHandler( + t, + fmt.Sprintf("/servers/%s", server.ID), + http.MethodGet, + "", + string(mustJSONMarshal(json.Marshal( + struct { + Server servers.Server `json:"server"` + }{ + Server: server, + }, + ))), + http.StatusOK, + ) + } + fixture.SetupHandler( + t, + "/lbaas/loadbalancers", + http.MethodGet, + "", + string(mustJSONMarshal(json.Marshal( + struct{ LoadBalancers []loadbalancers.LoadBalancer }{ + LoadBalancers: testCase.loadbalancers, + }, + ))), + http.StatusOK, + ) + fixture.SetupHandler( + t, + "/os-floating-ips", + http.MethodGet, + "", + string(mustJSONMarshal(json.Marshal( + struct { + FloatingIPs []floatingips.FloatingIP `json:"floating_ips"` + }{ + FloatingIPs: testCase.floatingIPs, + }, + ))), + http.StatusOK, + ) + testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + t.Errorf("Unexpected request for `%v`", r.URL) + }) + + cloud := &openstackCloud{ + floatingEnabled: testCase.cloudFloatingEnabled, + lbClient: client.ServiceClient(), + novaClient: client.ServiceClient(), + } + + ingress, err := cloud.GetApiIngressStatus(testCase.cluster) + + compareErrors(t, testCase.expectedError, err) + + sortedExpected := sortByIP(testCase.expectedAPIIngress) + sortedActual := sortByIP(ingress) + + sort.Sort(sortedExpected) + sort.Sort(sortedActual) + + if !reflect.DeepEqual(sortedActual, sortedExpected) { + t.Errorf("Ingress status differ: expected\n%+#v\n\tgot:\n%+#v\n", testCase.expectedAPIIngress, ingress) + } + }) + } +} + +type sortByIP []kops.ApiIngressStatus + +// Len is the number of elements in the collection. +func (s sortByIP) Len() int { + return len(s) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (s sortByIP) Less(i int, j int) bool { + return s[i].IP < s[j].IP +} + +// Swap swaps the elements with indexes i and j. +func (s sortByIP) Swap(i int, j int) { + s[i], s[j] = s[j], s[i] +} + +type serverList []servers.Server + +func (s serverList) Get(id string) *servers.Server { + for _, server := range s { + if server.ID == id { + return &server + } + } + return nil +} + +func mustJSONMarshal(data []byte, err error) []byte { + if err != nil { + panic(err) + } + return data +} + +func compareErrors(t *testing.T, actual, expected error) { + t.Helper() + if pointersAreBothNil(t, "errors", actual, expected) { + return + } + a := fmt.Sprintf("%v", actual) + e := fmt.Sprintf("%v", expected) + if a != e { + t.Errorf("error differs: %+v instead of %+v", actual, expected) + } +} + +func pointersAreBothNil(t *testing.T, name string, actual, expected interface{}) bool { + t.Helper() + if actual == nil && expected == nil { + return true + } + if !reflect.ValueOf(actual).IsValid() { + return false + } + if reflect.ValueOf(actual).IsNil() && reflect.ValueOf(expected).IsNil() { + return true + } + if actual == nil && expected != nil { + t.Fatalf("%s differ: actual is nil, expected is not", name) + } + if actual != nil && expected == nil { + t.Fatalf("%s differ: expected is nil, actual is not", name) + } + return false +} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel new file mode 100644 index 0000000000000..1c54a4116951f --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "convenience.go", + "doc.go", + "http_responses.go", + ], + importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/testhelper", + importpath = "github.com/gophercloud/gophercloud/testhelper", + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel new file mode 100644 index 0000000000000..9997b45473ad6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fake.go"], + importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/testhelper/client", + importpath = "github.com/gophercloud/gophercloud/testhelper/client", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go b/vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go new file mode 100644 index 0000000000000..3d81cc97b9cc6 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go @@ -0,0 +1,17 @@ +package client + +import ( + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/testhelper" +) + +// Fake token to use. +const TokenID = "cbc36478b0bd8e67e89469c7749d4127" + +// ServiceClient returns a generic service client for use in tests. +func ServiceClient() *gophercloud.ServiceClient { + return &gophercloud.ServiceClient{ + ProviderClient: &gophercloud.ProviderClient{TokenID: TokenID}, + Endpoint: testhelper.Endpoint(), + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go b/vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go new file mode 100644 index 0000000000000..25f6720e82cf0 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go @@ -0,0 +1,348 @@ +package testhelper + +import ( + "bytes" + "encoding/json" + "fmt" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" +) + +const ( + logBodyFmt = "\033[1;31m%s %s\033[0m" + greenCode = "\033[0m\033[1;32m" + yellowCode = "\033[0m\033[1;33m" + resetCode = "\033[0m\033[1;31m" +) + +func prefix(depth int) string { + _, file, line, _ := runtime.Caller(depth) + return fmt.Sprintf("Failure in %s, line %d:", filepath.Base(file), line) +} + +func green(str interface{}) string { + return fmt.Sprintf("%s%#v%s", greenCode, str, resetCode) +} + +func yellow(str interface{}) string { + return fmt.Sprintf("%s%#v%s", yellowCode, str, resetCode) +} + +func logFatal(t *testing.T, str string) { + t.Fatalf(logBodyFmt, prefix(3), str) +} + +func logError(t *testing.T, str string) { + t.Errorf(logBodyFmt, prefix(3), str) +} + +type diffLogger func([]string, interface{}, interface{}) + +type visit struct { + a1 uintptr + a2 uintptr + typ reflect.Type +} + +// Recursively visits the structures of "expected" and "actual". The diffLogger function will be +// invoked with each different value encountered, including the reference path that was followed +// to get there. +func deepDiffEqual(expected, actual reflect.Value, visited map[visit]bool, path []string, logDifference diffLogger) { + defer func() { + // Fall back to the regular reflect.DeepEquals function. + if r := recover(); r != nil { + var e, a interface{} + if expected.IsValid() { + e = expected.Interface() + } + if actual.IsValid() { + a = actual.Interface() + } + + if !reflect.DeepEqual(e, a) { + logDifference(path, e, a) + } + } + }() + + if !expected.IsValid() && actual.IsValid() { + logDifference(path, nil, actual.Interface()) + return + } + if expected.IsValid() && !actual.IsValid() { + logDifference(path, expected.Interface(), nil) + return + } + if !expected.IsValid() && !actual.IsValid() { + return + } + + hard := func(k reflect.Kind) bool { + switch k { + case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: + return true + } + return false + } + + if expected.CanAddr() && actual.CanAddr() && hard(expected.Kind()) { + addr1 := expected.UnsafeAddr() + addr2 := actual.UnsafeAddr() + + if addr1 > addr2 { + addr1, addr2 = addr2, addr1 + } + + if addr1 == addr2 { + // References are identical. We can short-circuit + return + } + + typ := expected.Type() + v := visit{addr1, addr2, typ} + if visited[v] { + // Already visited. + return + } + + // Remember this visit for later. + visited[v] = true + } + + switch expected.Kind() { + case reflect.Array: + for i := 0; i < expected.Len(); i++ { + hop := append(path, fmt.Sprintf("[%d]", i)) + deepDiffEqual(expected.Index(i), actual.Index(i), visited, hop, logDifference) + } + return + case reflect.Slice: + if expected.IsNil() != actual.IsNil() { + logDifference(path, expected.Interface(), actual.Interface()) + return + } + if expected.Len() == actual.Len() && expected.Pointer() == actual.Pointer() { + return + } + for i := 0; i < expected.Len(); i++ { + hop := append(path, fmt.Sprintf("[%d]", i)) + deepDiffEqual(expected.Index(i), actual.Index(i), visited, hop, logDifference) + } + return + case reflect.Interface: + if expected.IsNil() != actual.IsNil() { + logDifference(path, expected.Interface(), actual.Interface()) + return + } + deepDiffEqual(expected.Elem(), actual.Elem(), visited, path, logDifference) + return + case reflect.Ptr: + deepDiffEqual(expected.Elem(), actual.Elem(), visited, path, logDifference) + return + case reflect.Struct: + for i, n := 0, expected.NumField(); i < n; i++ { + field := expected.Type().Field(i) + hop := append(path, "."+field.Name) + deepDiffEqual(expected.Field(i), actual.Field(i), visited, hop, logDifference) + } + return + case reflect.Map: + if expected.IsNil() != actual.IsNil() { + logDifference(path, expected.Interface(), actual.Interface()) + return + } + if expected.Len() == actual.Len() && expected.Pointer() == actual.Pointer() { + return + } + + var keys []reflect.Value + if expected.Len() >= actual.Len() { + keys = expected.MapKeys() + } else { + keys = actual.MapKeys() + } + + for _, k := range keys { + expectedValue := expected.MapIndex(k) + actualValue := actual.MapIndex(k) + + if !expectedValue.IsValid() { + logDifference(path, nil, actual.Interface()) + return + } + if !actualValue.IsValid() { + logDifference(path, expected.Interface(), nil) + return + } + + hop := append(path, fmt.Sprintf("[%v]", k)) + deepDiffEqual(expectedValue, actualValue, visited, hop, logDifference) + } + return + case reflect.Func: + if expected.IsNil() != actual.IsNil() { + logDifference(path, expected.Interface(), actual.Interface()) + } + return + default: + if expected.Interface() != actual.Interface() { + logDifference(path, expected.Interface(), actual.Interface()) + } + } +} + +func deepDiff(expected, actual interface{}, logDifference diffLogger) { + if expected == nil || actual == nil { + logDifference([]string{}, expected, actual) + return + } + + expectedValue := reflect.ValueOf(expected) + actualValue := reflect.ValueOf(actual) + + if expectedValue.Type() != actualValue.Type() { + logDifference([]string{}, expected, actual) + return + } + deepDiffEqual(expectedValue, actualValue, map[visit]bool{}, []string{}, logDifference) +} + +// AssertEquals compares two arbitrary values and performs a comparison. If the +// comparison fails, a fatal error is raised that will fail the test +func AssertEquals(t *testing.T, expected, actual interface{}) { + if expected != actual { + logFatal(t, fmt.Sprintf("expected %s but got %s", green(expected), yellow(actual))) + } +} + +// CheckEquals is similar to AssertEquals, except with a non-fatal error +func CheckEquals(t *testing.T, expected, actual interface{}) { + if expected != actual { + logError(t, fmt.Sprintf("expected %s but got %s", green(expected), yellow(actual))) + } +} + +// AssertDeepEquals - like Equals - performs a comparison - but on more complex +// structures that requires deeper inspection +func AssertDeepEquals(t *testing.T, expected, actual interface{}) { + pre := prefix(2) + + differed := false + deepDiff(expected, actual, func(path []string, expected, actual interface{}) { + differed = true + t.Errorf("\033[1;31m%sat %s expected %s, but got %s\033[0m", + pre, + strings.Join(path, ""), + green(expected), + yellow(actual)) + }) + if differed { + logFatal(t, "The structures were different.") + } +} + +// CheckDeepEquals is similar to AssertDeepEquals, except with a non-fatal error +func CheckDeepEquals(t *testing.T, expected, actual interface{}) { + pre := prefix(2) + + deepDiff(expected, actual, func(path []string, expected, actual interface{}) { + t.Errorf("\033[1;31m%s at %s expected %s, but got %s\033[0m", + pre, + strings.Join(path, ""), + green(expected), + yellow(actual)) + }) +} + +func isByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) bool { + return bytes.Equal(expectedBytes, actualBytes) +} + +// AssertByteArrayEquals a convenience function for checking whether two byte arrays are equal +func AssertByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) { + if !isByteArrayEquals(t, expectedBytes, actualBytes) { + logFatal(t, "The bytes differed.") + } +} + +// CheckByteArrayEquals a convenience function for silent checking whether two byte arrays are equal +func CheckByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) { + if !isByteArrayEquals(t, expectedBytes, actualBytes) { + logError(t, "The bytes differed.") + } +} + +// isJSONEquals is a utility function that implements JSON comparison for AssertJSONEquals and +// CheckJSONEquals. +func isJSONEquals(t *testing.T, expectedJSON string, actual interface{}) bool { + var parsedExpected, parsedActual interface{} + err := json.Unmarshal([]byte(expectedJSON), &parsedExpected) + if err != nil { + t.Errorf("Unable to parse expected value as JSON: %v", err) + return false + } + + jsonActual, err := json.Marshal(actual) + AssertNoErr(t, err) + err = json.Unmarshal(jsonActual, &parsedActual) + AssertNoErr(t, err) + + if !reflect.DeepEqual(parsedExpected, parsedActual) { + prettyExpected, err := json.MarshalIndent(parsedExpected, "", " ") + if err != nil { + t.Logf("Unable to pretty-print expected JSON: %v\n%s", err, expectedJSON) + } else { + // We can't use green() here because %#v prints prettyExpected as a byte array literal, which + // is... unhelpful. Converting it to a string first leaves "\n" uninterpreted for some reason. + t.Logf("Expected JSON:\n%s%s%s", greenCode, prettyExpected, resetCode) + } + + prettyActual, err := json.MarshalIndent(actual, "", " ") + if err != nil { + t.Logf("Unable to pretty-print actual JSON: %v\n%#v", err, actual) + } else { + // We can't use yellow() for the same reason. + t.Logf("Actual JSON:\n%s%s%s", yellowCode, prettyActual, resetCode) + } + + return false + } + return true +} + +// AssertJSONEquals serializes a value as JSON, parses an expected string as JSON, and ensures that +// both are consistent. If they aren't, the expected and actual structures are pretty-printed and +// shown for comparison. +// +// This is useful for comparing structures that are built as nested map[string]interface{} values, +// which are a pain to construct as literals. +func AssertJSONEquals(t *testing.T, expectedJSON string, actual interface{}) { + if !isJSONEquals(t, expectedJSON, actual) { + logFatal(t, "The generated JSON structure differed.") + } +} + +// CheckJSONEquals is similar to AssertJSONEquals, but nonfatal. +func CheckJSONEquals(t *testing.T, expectedJSON string, actual interface{}) { + if !isJSONEquals(t, expectedJSON, actual) { + logError(t, "The generated JSON structure differed.") + } +} + +// AssertNoErr is a convenience function for checking whether an error value is +// an actual error +func AssertNoErr(t *testing.T, e error) { + if e != nil { + logFatal(t, fmt.Sprintf("unexpected error %s", yellow(e.Error()))) + } +} + +// CheckNoErr is similar to AssertNoErr, except with a non-fatal error +func CheckNoErr(t *testing.T, e error) { + if e != nil { + logError(t, fmt.Sprintf("unexpected error %s", yellow(e.Error()))) + } +} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/doc.go b/vendor/github.com/gophercloud/gophercloud/testhelper/doc.go new file mode 100644 index 0000000000000..25b4dfebbbe3d --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/doc.go @@ -0,0 +1,4 @@ +/* +Package testhelper container methods that are useful for writing unit tests. +*/ +package testhelper diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel new file mode 100644 index 0000000000000..253debf7a2c17 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["helper.go"], + importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/testhelper/fixture", + importpath = "github.com/gophercloud/gophercloud/testhelper/fixture", + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", + ], +) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go b/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go new file mode 100644 index 0000000000000..fe98c86f99e0b --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go @@ -0,0 +1,31 @@ +package fixture + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + "github.com/gophercloud/gophercloud/testhelper/client" +) + +func SetupHandler(t *testing.T, url, method, requestBody, responseBody string, status int) { + th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, method) + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + + if requestBody != "" { + th.TestJSONRequest(t, r, requestBody) + } + + if responseBody != "" { + w.Header().Add("Content-Type", "application/json") + } + + w.WriteHeader(status) + + if responseBody != "" { + fmt.Fprintf(w, responseBody) + } + }) +} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go b/vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go new file mode 100644 index 0000000000000..e1f1f9ac0e897 --- /dev/null +++ b/vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go @@ -0,0 +1,91 @@ +package testhelper + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" +) + +var ( + // Mux is a multiplexer that can be used to register handlers. + Mux *http.ServeMux + + // Server is an in-memory HTTP server for testing. + Server *httptest.Server +) + +// SetupHTTP prepares the Mux and Server. +func SetupHTTP() { + Mux = http.NewServeMux() + Server = httptest.NewServer(Mux) +} + +// TeardownHTTP releases HTTP-related resources. +func TeardownHTTP() { + Server.Close() +} + +// Endpoint returns a fake endpoint that will actually target the Mux. +func Endpoint() string { + return Server.URL + "/" +} + +// TestFormValues ensures that all the URL parameters given to the http.Request are the same as values. +func TestFormValues(t *testing.T, r *http.Request, values map[string]string) { + want := url.Values{} + for k, v := range values { + want.Add(k, v) + } + + r.ParseForm() + if !reflect.DeepEqual(want, r.Form) { + t.Errorf("Request parameters = %v, want %v", r.Form, want) + } +} + +// TestMethod checks that the Request has the expected method (e.g. GET, POST). +func TestMethod(t *testing.T, r *http.Request, expected string) { + if expected != r.Method { + t.Errorf("Request method = %v, expected %v", r.Method, expected) + } +} + +// TestHeader checks that the header on the http.Request matches the expected value. +func TestHeader(t *testing.T, r *http.Request, header string, expected string) { + if actual := r.Header.Get(header); expected != actual { + t.Errorf("Header %s = %s, expected %s", header, actual, expected) + } +} + +// TestBody verifies that the request body matches an expected body. +func TestBody(t *testing.T, r *http.Request, expected string) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Unable to read body: %v", err) + } + str := string(b) + if expected != str { + t.Errorf("Body = %s, expected %s", str, expected) + } +} + +// TestJSONRequest verifies that the JSON payload of a request matches an expected structure, without asserting things about +// whitespace or ordering. +func TestJSONRequest(t *testing.T, r *http.Request, expected string) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Unable to read request body: %v", err) + } + + var actualJSON interface{} + err = json.Unmarshal(b, &actualJSON) + if err != nil { + t.Errorf("Unable to parse request body as JSON: %v", err) + } + + CheckJSONEquals(t, expected, actualJSON) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ef3b45e5ae53a..722a64a5e86cd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -275,6 +275,9 @@ github.com/gophercloud/gophercloud/internal github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts github.com/gophercloud/gophercloud/openstack/identity/v2/tenants +github.com/gophercloud/gophercloud/testhelper +github.com/gophercloud/gophercloud/testhelper/client +github.com/gophercloud/gophercloud/testhelper/fixture # github.com/gorilla/mux v1.7.0 github.com/gorilla/mux # github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 From 8393e35cfc36ce14681de94e5984c3b36753c462 Mon Sep 17 00:00:00 2001 From: Michael Wagner Date: Sun, 15 Dec 2019 19:16:29 +0100 Subject: [PATCH 2/3] fix(openstack): identify cluster ips correctly --- upup/pkg/fi/cloudup/openstack/cloud.go | 2 +- upup/pkg/fi/cloudup/openstack/cloud_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/upup/pkg/fi/cloudup/openstack/cloud.go b/upup/pkg/fi/cloudup/openstack/cloud.go index f360d1ca2e0dd..c26451549199c 100644 --- a/upup/pkg/fi/cloudup/openstack/cloud.go +++ b/upup/pkg/fi/cloudup/openstack/cloud.go @@ -644,7 +644,7 @@ func (c *openstackCloud) GetApiIngressStatus(cluster *kops.Cluster) ([]kops.ApiI for _, lb := range lbList { for _, fip := range fips { - if fip.FixedIP == lb.VipAddress { + if fip.InstanceID == lb.ID { ingresses = append(ingresses, kops.ApiIngressStatus{ IP: fip.IP, }) diff --git a/upup/pkg/fi/cloudup/openstack/cloud_test.go b/upup/pkg/fi/cloudup/openstack/cloud_test.go index 5d39f54887d4f..506599893e146 100644 --- a/upup/pkg/fi/cloudup/openstack/cloud_test.go +++ b/upup/pkg/fi/cloudup/openstack/cloud_test.go @@ -125,7 +125,6 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { }, expectedAPIIngress: []kops.ApiIngressStatus{ {IP: "8.8.8.8"}, - {IP: "9.9.9.9"}, }, }, { From db59fdfb072f53bca254febe6f268639a04d748a Mon Sep 17 00:00:00 2001 From: Michael Wagner Date: Wed, 18 Dec 2019 10:27:23 +0100 Subject: [PATCH 3/3] chore(openstack): use different mechanism to get fip association --- upup/pkg/fi/cloudup/openstack/BUILD.bazel | 5 +- upup/pkg/fi/cloudup/openstack/cloud.go | 17 +- upup/pkg/fi/cloudup/openstack/cloud_test.go | 117 ++++-- .../gophercloud/testhelper/BUILD.bazel | 13 - .../gophercloud/testhelper/client/BUILD.bazel | 13 - .../gophercloud/testhelper/client/fake.go | 17 - .../gophercloud/testhelper/convenience.go | 348 ------------------ .../gophercloud/gophercloud/testhelper/doc.go | 4 - .../testhelper/fixture/BUILD.bazel | 13 - .../gophercloud/testhelper/fixture/helper.go | 31 -- .../gophercloud/testhelper/http_responses.go | 91 ----- vendor/modules.txt | 3 - 12 files changed, 97 insertions(+), 575 deletions(-) delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/doc.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go delete mode 100644 vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go diff --git a/upup/pkg/fi/cloudup/openstack/BUILD.bazel b/upup/pkg/fi/cloudup/openstack/BUILD.bazel index 94e417f0cf055..1976f93b9a765 100644 --- a/upup/pkg/fi/cloudup/openstack/BUILD.bazel +++ b/upup/pkg/fi/cloudup/openstack/BUILD.bazel @@ -73,12 +73,11 @@ go_test( embed = [":go_default_library"], deps = [ "//pkg/apis/kops:go_default_library", + "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/testhelper/fixture:go_default_library", + "//vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/upup/pkg/fi/cloudup/openstack/cloud.go b/upup/pkg/fi/cloudup/openstack/cloud.go index c26451549199c..748465fa6b53c 100644 --- a/upup/pkg/fi/cloudup/openstack/cloud.go +++ b/upup/pkg/fi/cloudup/openstack/cloud.go @@ -636,17 +636,18 @@ func (c *openstackCloud) GetApiIngressStatus(cluster *kops.Cluster) ([]kops.ApiI if err != nil { return ingresses, fmt.Errorf("GetApiIngressStatus: Failed to list openstack loadbalancers: %v", err) } - // Must Find Floating IP related to this lb - fips, err := c.ListFloatingIPs() - if err != nil { - return ingresses, fmt.Errorf("GetApiIngressStatus: Failed to list floating IP's: %v", err) - } - for _, lb := range lbList { + // Must Find Floating IP related to this lb + fips, err := c.ListL3FloatingIPs(l3floatingip.ListOpts{ + PortID: lb.VipPortID, + }) + if err != nil { + return ingresses, fmt.Errorf("GetApiIngressStatus: Failed to list floating IP's: %v", err) + } for _, fip := range fips { - if fip.InstanceID == lb.ID { + if fip.PortID == lb.VipPortID { ingresses = append(ingresses, kops.ApiIngressStatus{ - IP: fip.IP, + IP: fip.FloatingIP, }) } } diff --git a/upup/pkg/fi/cloudup/openstack/cloud_test.go b/upup/pkg/fi/cloudup/openstack/cloud_test.go index 506599893e146..7bcc1e0b50077 100644 --- a/upup/pkg/fi/cloudup/openstack/cloud_test.go +++ b/upup/pkg/fi/cloudup/openstack/cloud_test.go @@ -20,16 +20,16 @@ import ( "encoding/json" "fmt" "net/http" + "net/http/httptest" "reflect" "sort" "testing" + "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" - "github.com/gophercloud/gophercloud/testhelper" - "github.com/gophercloud/gophercloud/testhelper/client" - "github.com/gophercloud/gophercloud/testhelper/fixture" + l3floatingips "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kops/pkg/apis/kops" ) @@ -40,6 +40,7 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { cluster *kops.Cluster loadbalancers []loadbalancers.LoadBalancer floatingIPs []floatingips.FloatingIP + l3FloatingIPs []l3floatingips.FloatingIP instances serverList cloudFloatingEnabled bool expectedAPIIngress []kops.ApiIngressStatus @@ -67,12 +68,12 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { VipNetworkID: "vip_network_id", }, }, - floatingIPs: []floatingips.FloatingIP{ + l3FloatingIPs: []l3floatingips.FloatingIP{ { ID: "id", FixedIP: "10.1.2.3", - InstanceID: "lb_id", - IP: "8.8.8.8", + PortID: "vip_port_id", + FloatingIP: "8.8.8.8", }, }, expectedAPIIngress: []kops.ApiIngressStatus{ @@ -96,31 +97,31 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { loadbalancers: []loadbalancers.LoadBalancer{ { ID: "lb_id", - Name: "name", + Name: "master.k8s.local", VipAddress: "10.1.2.3", VipPortID: "vip_port_id", VipSubnetID: "vip_subnet_id", VipNetworkID: "vip_network_id", }, }, - floatingIPs: []floatingips.FloatingIP{ + l3FloatingIPs: []l3floatingips.FloatingIP{ { ID: "cluster", FixedIP: "10.1.2.3", - InstanceID: "lb_id", - IP: "8.8.8.8", + PortID: "vip_port_id", + FloatingIP: "8.8.8.8", }, { ID: "something_else", FixedIP: "192.168.2.3", - InstanceID: "xx_id", - IP: "2.2.2.2", + PortID: "xx_id", + FloatingIP: "2.2.2.2", }, { ID: "yet_another", FixedIP: "10.1.2.3", - InstanceID: "yy_id", - IP: "9.9.9.9", + PortID: "yy_id", + FloatingIP: "9.9.9.9", }, }, expectedAPIIngress: []kops.ApiIngressStatus{ @@ -284,13 +285,11 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { for _, testCase := range tests { t.Run(testCase.desc, func(t *testing.T) { - testhelper.SetupHTTP() - defer testhelper.TeardownHTTP() - fixture.SetupHandler( - t, + mux := http.NewServeMux() + fixture( + mux, "/servers/detail", http.MethodGet, - "", string(mustJSONMarshal(json.Marshal( struct { Servers []servers.Server `json:"servers"` @@ -301,11 +300,10 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { http.StatusOK, ) for _, server := range testCase.instances { - fixture.SetupHandler( - t, + fixture( + mux, fmt.Sprintf("/servers/%s", server.ID), http.MethodGet, - "", string(mustJSONMarshal(json.Marshal( struct { Server servers.Server `json:"server"` @@ -316,11 +314,10 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { http.StatusOK, ) } - fixture.SetupHandler( - t, + fixture( + mux, "/lbaas/loadbalancers", http.MethodGet, - "", string(mustJSONMarshal(json.Marshal( struct{ LoadBalancers []loadbalancers.LoadBalancer }{ LoadBalancers: testCase.loadbalancers, @@ -328,11 +325,10 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { ))), http.StatusOK, ) - fixture.SetupHandler( - t, + fixture( + mux, "/os-floating-ips", http.MethodGet, - "", string(mustJSONMarshal(json.Marshal( struct { FloatingIPs []floatingips.FloatingIP `json:"floating_ips"` @@ -342,14 +338,54 @@ func Test_OpenstackCloud_GetApiIngressStatus(t *testing.T) { ))), http.StatusOK, ) - testhelper.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/floatingips", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + params := r.URL.Query() + portID := params.Get("port_id") + if portID == "" { + fmt.Fprint(w, string(mustJSONMarshal(json.Marshal( + struct { + FloatingIPs []l3floatingips.FloatingIP `json:"floatingips"` + }{ + FloatingIPs: testCase.l3FloatingIPs, + }, + )))) + return + } + for _, fip := range testCase.l3FloatingIPs { + if fip.PortID == portID { + json := string(mustJSONMarshal(json.Marshal( + struct { + FloatingIPs []l3floatingips.FloatingIP `json:"floatingips"` + }{ + FloatingIPs: []l3floatingips.FloatingIP{fip}, + }, + ))) + fmt.Fprint(w, json) + return + } + } + fmt.Fprint(w, string(mustJSONMarshal(json.Marshal( + struct { + FloatingIPs []l3floatingips.FloatingIP `json:"floatingips"` + }{ + FloatingIPs: []l3floatingips.FloatingIP{}, + }, + )))) + }) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { t.Errorf("Unexpected request for `%v`", r.URL) + http.Error(w, "Unexpected request", http.StatusInternalServerError) }) + testServer := httptest.NewServer(mux) + defer testServer.Close() cloud := &openstackCloud{ floatingEnabled: testCase.cloudFloatingEnabled, - lbClient: client.ServiceClient(), - novaClient: client.ServiceClient(), + lbClient: serviceClient(testServer.URL), + novaClient: serviceClient(testServer.URL), + neutronClient: serviceClient(testServer.URL), } ingress, err := cloud.GetApiIngressStatus(testCase.cluster) @@ -398,6 +434,25 @@ func (s serverList) Get(id string) *servers.Server { return nil } +func serviceClient(url string) *gophercloud.ServiceClient { + return &gophercloud.ServiceClient{ + ProviderClient: &gophercloud.ProviderClient{}, + Endpoint: url + "/", + } +} + +func fixture(mux *http.ServeMux, url string, method string, responseBody string, status int) { + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + if r.Method != method { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(status) + fmt.Fprint(w, responseBody) + }) +} + func mustJSONMarshal(data []byte, err error) []byte { if err != nil { panic(err) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel deleted file mode 100644 index 1c54a4116951f..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "convenience.go", - "doc.go", - "http_responses.go", - ], - importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/testhelper", - importpath = "github.com/gophercloud/gophercloud/testhelper", - visibility = ["//visibility:public"], -) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel deleted file mode 100644 index 9997b45473ad6..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/client/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["fake.go"], - importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/testhelper/client", - importpath = "github.com/gophercloud/gophercloud/testhelper/client", - visibility = ["//visibility:public"], - deps = [ - "//vendor/github.com/gophercloud/gophercloud:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", - ], -) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go b/vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go deleted file mode 100644 index 3d81cc97b9cc6..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/client/fake.go +++ /dev/null @@ -1,17 +0,0 @@ -package client - -import ( - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/testhelper" -) - -// Fake token to use. -const TokenID = "cbc36478b0bd8e67e89469c7749d4127" - -// ServiceClient returns a generic service client for use in tests. -func ServiceClient() *gophercloud.ServiceClient { - return &gophercloud.ServiceClient{ - ProviderClient: &gophercloud.ProviderClient{TokenID: TokenID}, - Endpoint: testhelper.Endpoint(), - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go b/vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go deleted file mode 100644 index 25f6720e82cf0..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/convenience.go +++ /dev/null @@ -1,348 +0,0 @@ -package testhelper - -import ( - "bytes" - "encoding/json" - "fmt" - "path/filepath" - "reflect" - "runtime" - "strings" - "testing" -) - -const ( - logBodyFmt = "\033[1;31m%s %s\033[0m" - greenCode = "\033[0m\033[1;32m" - yellowCode = "\033[0m\033[1;33m" - resetCode = "\033[0m\033[1;31m" -) - -func prefix(depth int) string { - _, file, line, _ := runtime.Caller(depth) - return fmt.Sprintf("Failure in %s, line %d:", filepath.Base(file), line) -} - -func green(str interface{}) string { - return fmt.Sprintf("%s%#v%s", greenCode, str, resetCode) -} - -func yellow(str interface{}) string { - return fmt.Sprintf("%s%#v%s", yellowCode, str, resetCode) -} - -func logFatal(t *testing.T, str string) { - t.Fatalf(logBodyFmt, prefix(3), str) -} - -func logError(t *testing.T, str string) { - t.Errorf(logBodyFmt, prefix(3), str) -} - -type diffLogger func([]string, interface{}, interface{}) - -type visit struct { - a1 uintptr - a2 uintptr - typ reflect.Type -} - -// Recursively visits the structures of "expected" and "actual". The diffLogger function will be -// invoked with each different value encountered, including the reference path that was followed -// to get there. -func deepDiffEqual(expected, actual reflect.Value, visited map[visit]bool, path []string, logDifference diffLogger) { - defer func() { - // Fall back to the regular reflect.DeepEquals function. - if r := recover(); r != nil { - var e, a interface{} - if expected.IsValid() { - e = expected.Interface() - } - if actual.IsValid() { - a = actual.Interface() - } - - if !reflect.DeepEqual(e, a) { - logDifference(path, e, a) - } - } - }() - - if !expected.IsValid() && actual.IsValid() { - logDifference(path, nil, actual.Interface()) - return - } - if expected.IsValid() && !actual.IsValid() { - logDifference(path, expected.Interface(), nil) - return - } - if !expected.IsValid() && !actual.IsValid() { - return - } - - hard := func(k reflect.Kind) bool { - switch k { - case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: - return true - } - return false - } - - if expected.CanAddr() && actual.CanAddr() && hard(expected.Kind()) { - addr1 := expected.UnsafeAddr() - addr2 := actual.UnsafeAddr() - - if addr1 > addr2 { - addr1, addr2 = addr2, addr1 - } - - if addr1 == addr2 { - // References are identical. We can short-circuit - return - } - - typ := expected.Type() - v := visit{addr1, addr2, typ} - if visited[v] { - // Already visited. - return - } - - // Remember this visit for later. - visited[v] = true - } - - switch expected.Kind() { - case reflect.Array: - for i := 0; i < expected.Len(); i++ { - hop := append(path, fmt.Sprintf("[%d]", i)) - deepDiffEqual(expected.Index(i), actual.Index(i), visited, hop, logDifference) - } - return - case reflect.Slice: - if expected.IsNil() != actual.IsNil() { - logDifference(path, expected.Interface(), actual.Interface()) - return - } - if expected.Len() == actual.Len() && expected.Pointer() == actual.Pointer() { - return - } - for i := 0; i < expected.Len(); i++ { - hop := append(path, fmt.Sprintf("[%d]", i)) - deepDiffEqual(expected.Index(i), actual.Index(i), visited, hop, logDifference) - } - return - case reflect.Interface: - if expected.IsNil() != actual.IsNil() { - logDifference(path, expected.Interface(), actual.Interface()) - return - } - deepDiffEqual(expected.Elem(), actual.Elem(), visited, path, logDifference) - return - case reflect.Ptr: - deepDiffEqual(expected.Elem(), actual.Elem(), visited, path, logDifference) - return - case reflect.Struct: - for i, n := 0, expected.NumField(); i < n; i++ { - field := expected.Type().Field(i) - hop := append(path, "."+field.Name) - deepDiffEqual(expected.Field(i), actual.Field(i), visited, hop, logDifference) - } - return - case reflect.Map: - if expected.IsNil() != actual.IsNil() { - logDifference(path, expected.Interface(), actual.Interface()) - return - } - if expected.Len() == actual.Len() && expected.Pointer() == actual.Pointer() { - return - } - - var keys []reflect.Value - if expected.Len() >= actual.Len() { - keys = expected.MapKeys() - } else { - keys = actual.MapKeys() - } - - for _, k := range keys { - expectedValue := expected.MapIndex(k) - actualValue := actual.MapIndex(k) - - if !expectedValue.IsValid() { - logDifference(path, nil, actual.Interface()) - return - } - if !actualValue.IsValid() { - logDifference(path, expected.Interface(), nil) - return - } - - hop := append(path, fmt.Sprintf("[%v]", k)) - deepDiffEqual(expectedValue, actualValue, visited, hop, logDifference) - } - return - case reflect.Func: - if expected.IsNil() != actual.IsNil() { - logDifference(path, expected.Interface(), actual.Interface()) - } - return - default: - if expected.Interface() != actual.Interface() { - logDifference(path, expected.Interface(), actual.Interface()) - } - } -} - -func deepDiff(expected, actual interface{}, logDifference diffLogger) { - if expected == nil || actual == nil { - logDifference([]string{}, expected, actual) - return - } - - expectedValue := reflect.ValueOf(expected) - actualValue := reflect.ValueOf(actual) - - if expectedValue.Type() != actualValue.Type() { - logDifference([]string{}, expected, actual) - return - } - deepDiffEqual(expectedValue, actualValue, map[visit]bool{}, []string{}, logDifference) -} - -// AssertEquals compares two arbitrary values and performs a comparison. If the -// comparison fails, a fatal error is raised that will fail the test -func AssertEquals(t *testing.T, expected, actual interface{}) { - if expected != actual { - logFatal(t, fmt.Sprintf("expected %s but got %s", green(expected), yellow(actual))) - } -} - -// CheckEquals is similar to AssertEquals, except with a non-fatal error -func CheckEquals(t *testing.T, expected, actual interface{}) { - if expected != actual { - logError(t, fmt.Sprintf("expected %s but got %s", green(expected), yellow(actual))) - } -} - -// AssertDeepEquals - like Equals - performs a comparison - but on more complex -// structures that requires deeper inspection -func AssertDeepEquals(t *testing.T, expected, actual interface{}) { - pre := prefix(2) - - differed := false - deepDiff(expected, actual, func(path []string, expected, actual interface{}) { - differed = true - t.Errorf("\033[1;31m%sat %s expected %s, but got %s\033[0m", - pre, - strings.Join(path, ""), - green(expected), - yellow(actual)) - }) - if differed { - logFatal(t, "The structures were different.") - } -} - -// CheckDeepEquals is similar to AssertDeepEquals, except with a non-fatal error -func CheckDeepEquals(t *testing.T, expected, actual interface{}) { - pre := prefix(2) - - deepDiff(expected, actual, func(path []string, expected, actual interface{}) { - t.Errorf("\033[1;31m%s at %s expected %s, but got %s\033[0m", - pre, - strings.Join(path, ""), - green(expected), - yellow(actual)) - }) -} - -func isByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) bool { - return bytes.Equal(expectedBytes, actualBytes) -} - -// AssertByteArrayEquals a convenience function for checking whether two byte arrays are equal -func AssertByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) { - if !isByteArrayEquals(t, expectedBytes, actualBytes) { - logFatal(t, "The bytes differed.") - } -} - -// CheckByteArrayEquals a convenience function for silent checking whether two byte arrays are equal -func CheckByteArrayEquals(t *testing.T, expectedBytes []byte, actualBytes []byte) { - if !isByteArrayEquals(t, expectedBytes, actualBytes) { - logError(t, "The bytes differed.") - } -} - -// isJSONEquals is a utility function that implements JSON comparison for AssertJSONEquals and -// CheckJSONEquals. -func isJSONEquals(t *testing.T, expectedJSON string, actual interface{}) bool { - var parsedExpected, parsedActual interface{} - err := json.Unmarshal([]byte(expectedJSON), &parsedExpected) - if err != nil { - t.Errorf("Unable to parse expected value as JSON: %v", err) - return false - } - - jsonActual, err := json.Marshal(actual) - AssertNoErr(t, err) - err = json.Unmarshal(jsonActual, &parsedActual) - AssertNoErr(t, err) - - if !reflect.DeepEqual(parsedExpected, parsedActual) { - prettyExpected, err := json.MarshalIndent(parsedExpected, "", " ") - if err != nil { - t.Logf("Unable to pretty-print expected JSON: %v\n%s", err, expectedJSON) - } else { - // We can't use green() here because %#v prints prettyExpected as a byte array literal, which - // is... unhelpful. Converting it to a string first leaves "\n" uninterpreted for some reason. - t.Logf("Expected JSON:\n%s%s%s", greenCode, prettyExpected, resetCode) - } - - prettyActual, err := json.MarshalIndent(actual, "", " ") - if err != nil { - t.Logf("Unable to pretty-print actual JSON: %v\n%#v", err, actual) - } else { - // We can't use yellow() for the same reason. - t.Logf("Actual JSON:\n%s%s%s", yellowCode, prettyActual, resetCode) - } - - return false - } - return true -} - -// AssertJSONEquals serializes a value as JSON, parses an expected string as JSON, and ensures that -// both are consistent. If they aren't, the expected and actual structures are pretty-printed and -// shown for comparison. -// -// This is useful for comparing structures that are built as nested map[string]interface{} values, -// which are a pain to construct as literals. -func AssertJSONEquals(t *testing.T, expectedJSON string, actual interface{}) { - if !isJSONEquals(t, expectedJSON, actual) { - logFatal(t, "The generated JSON structure differed.") - } -} - -// CheckJSONEquals is similar to AssertJSONEquals, but nonfatal. -func CheckJSONEquals(t *testing.T, expectedJSON string, actual interface{}) { - if !isJSONEquals(t, expectedJSON, actual) { - logError(t, "The generated JSON structure differed.") - } -} - -// AssertNoErr is a convenience function for checking whether an error value is -// an actual error -func AssertNoErr(t *testing.T, e error) { - if e != nil { - logFatal(t, fmt.Sprintf("unexpected error %s", yellow(e.Error()))) - } -} - -// CheckNoErr is similar to AssertNoErr, except with a non-fatal error -func CheckNoErr(t *testing.T, e error) { - if e != nil { - logError(t, fmt.Sprintf("unexpected error %s", yellow(e.Error()))) - } -} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/doc.go b/vendor/github.com/gophercloud/gophercloud/testhelper/doc.go deleted file mode 100644 index 25b4dfebbbe3d..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -/* -Package testhelper container methods that are useful for writing unit tests. -*/ -package testhelper diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel b/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel deleted file mode 100644 index 253debf7a2c17..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["helper.go"], - importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/testhelper/fixture", - importpath = "github.com/gophercloud/gophercloud/testhelper/fixture", - visibility = ["//visibility:public"], - deps = [ - "//vendor/github.com/gophercloud/gophercloud/testhelper:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/testhelper/client:go_default_library", - ], -) diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go b/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go deleted file mode 100644 index fe98c86f99e0b..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/fixture/helper.go +++ /dev/null @@ -1,31 +0,0 @@ -package fixture - -import ( - "fmt" - "net/http" - "testing" - - th "github.com/gophercloud/gophercloud/testhelper" - "github.com/gophercloud/gophercloud/testhelper/client" -) - -func SetupHandler(t *testing.T, url, method, requestBody, responseBody string, status int) { - th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, method) - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - - if requestBody != "" { - th.TestJSONRequest(t, r, requestBody) - } - - if responseBody != "" { - w.Header().Add("Content-Type", "application/json") - } - - w.WriteHeader(status) - - if responseBody != "" { - fmt.Fprintf(w, responseBody) - } - }) -} diff --git a/vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go b/vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go deleted file mode 100644 index e1f1f9ac0e897..0000000000000 --- a/vendor/github.com/gophercloud/gophercloud/testhelper/http_responses.go +++ /dev/null @@ -1,91 +0,0 @@ -package testhelper - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "reflect" - "testing" -) - -var ( - // Mux is a multiplexer that can be used to register handlers. - Mux *http.ServeMux - - // Server is an in-memory HTTP server for testing. - Server *httptest.Server -) - -// SetupHTTP prepares the Mux and Server. -func SetupHTTP() { - Mux = http.NewServeMux() - Server = httptest.NewServer(Mux) -} - -// TeardownHTTP releases HTTP-related resources. -func TeardownHTTP() { - Server.Close() -} - -// Endpoint returns a fake endpoint that will actually target the Mux. -func Endpoint() string { - return Server.URL + "/" -} - -// TestFormValues ensures that all the URL parameters given to the http.Request are the same as values. -func TestFormValues(t *testing.T, r *http.Request, values map[string]string) { - want := url.Values{} - for k, v := range values { - want.Add(k, v) - } - - r.ParseForm() - if !reflect.DeepEqual(want, r.Form) { - t.Errorf("Request parameters = %v, want %v", r.Form, want) - } -} - -// TestMethod checks that the Request has the expected method (e.g. GET, POST). -func TestMethod(t *testing.T, r *http.Request, expected string) { - if expected != r.Method { - t.Errorf("Request method = %v, expected %v", r.Method, expected) - } -} - -// TestHeader checks that the header on the http.Request matches the expected value. -func TestHeader(t *testing.T, r *http.Request, header string, expected string) { - if actual := r.Header.Get(header); expected != actual { - t.Errorf("Header %s = %s, expected %s", header, actual, expected) - } -} - -// TestBody verifies that the request body matches an expected body. -func TestBody(t *testing.T, r *http.Request, expected string) { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Errorf("Unable to read body: %v", err) - } - str := string(b) - if expected != str { - t.Errorf("Body = %s, expected %s", str, expected) - } -} - -// TestJSONRequest verifies that the JSON payload of a request matches an expected structure, without asserting things about -// whitespace or ordering. -func TestJSONRequest(t *testing.T, r *http.Request, expected string) { - b, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Errorf("Unable to read request body: %v", err) - } - - var actualJSON interface{} - err = json.Unmarshal(b, &actualJSON) - if err != nil { - t.Errorf("Unable to parse request body as JSON: %v", err) - } - - CheckJSONEquals(t, expected, actualJSON) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 722a64a5e86cd..ef3b45e5ae53a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -275,9 +275,6 @@ github.com/gophercloud/gophercloud/internal github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts github.com/gophercloud/gophercloud/openstack/identity/v2/tenants -github.com/gophercloud/gophercloud/testhelper -github.com/gophercloud/gophercloud/testhelper/client -github.com/gophercloud/gophercloud/testhelper/fixture # github.com/gorilla/mux v1.7.0 github.com/gorilla/mux # github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7