Skip to content

Commit 62dda69

Browse files
authored
Merge pull request #2005 from Nordix/mquhuy/ensure-cleanup-e2e-tests
🌱 Ensure E2E cleanup
2 parents f4e5bdf + 48c952f commit 62dda69

File tree

3 files changed

+166
-1
lines changed

3 files changed

+166
-1
lines changed

test/e2e/shared/openstack.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
4040
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
4141
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
42+
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
4243
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
4344
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
4445
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
@@ -871,3 +872,28 @@ func GetFlavorFromName(e2eCtx *E2EContext, name string) (*flavors.Flavor, error)
871872

872873
return flavors.Get(computeClient, flavorID).Extract()
873874
}
875+
876+
func DumpOpenStackLoadBalancers(e2eCtx *E2EContext, filter loadbalancers.ListOpts) ([]loadbalancers.LoadBalancer, error) {
877+
providerClient, clientOpts, _, err := GetTenantProviderClient(e2eCtx)
878+
if err != nil {
879+
_, _ = fmt.Fprintf(GinkgoWriter, "error creating provider client: %s\n", err)
880+
return nil, err
881+
}
882+
883+
loadBalancerClient, err := openstack.NewLoadBalancerV2(providerClient, gophercloud.EndpointOpts{
884+
Region: clientOpts.RegionName,
885+
})
886+
if err != nil {
887+
return nil, fmt.Errorf("error creating network client: %s", err)
888+
}
889+
890+
allPages, err := loadbalancers.List(loadBalancerClient, filter).AllPages()
891+
if err != nil {
892+
return nil, fmt.Errorf("error getting load balancers: %s", err)
893+
}
894+
loadBalancersList, err := loadbalancers.ExtractLoadBalancers(allPages)
895+
if err != nil {
896+
return nil, fmt.Errorf("error getting load balancers: %s", err)
897+
}
898+
return loadBalancersList, nil
899+
}

test/e2e/suites/e2e/e2e_suite_test.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,29 @@ import (
2323
"os"
2424
"testing"
2525

26+
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
27+
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
28+
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
29+
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
30+
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
2631
. "github.com/onsi/ginkgo/v2"
2732
. "github.com/onsi/gomega"
2833
"k8s.io/klog/v2"
34+
"k8s.io/utils/ptr"
2935
ctrl "sigs.k8s.io/controller-runtime"
3036

3137
"sigs.k8s.io/cluster-api-provider-openstack/test/e2e/shared"
3238
)
3339

34-
var e2eCtx *shared.E2EContext
40+
var (
41+
e2eCtx *shared.E2EContext
42+
initialServers []servers.Server
43+
initialNetworks []networks.Network
44+
initialSecurityGroups []groups.SecGroup
45+
initialLoadBalancers []loadbalancers.LoadBalancer
46+
initialVolumes []volumes.Volume
47+
err error
48+
)
3549

3650
func init() {
3751
e2eCtx = shared.NewE2EContext()
@@ -55,10 +69,60 @@ var _ = SynchronizedBeforeSuite(func() []byte {
5569
return data
5670
}, func(data []byte) {
5771
shared.AllNodesBeforeSuite(e2eCtx, data)
72+
initialServers, err = shared.DumpOpenStackServers(e2eCtx, servers.ListOpts{})
73+
Expect(err).NotTo(HaveOccurred())
74+
initialNetworks, err = shared.DumpOpenStackNetworks(e2eCtx, networks.ListOpts{})
75+
Expect(err).NotTo(HaveOccurred())
76+
initialSecurityGroups, err = shared.DumpOpenStackSecurityGroups(e2eCtx, groups.ListOpts{})
77+
Expect(err).NotTo(HaveOccurred())
78+
initialLoadBalancers, err = shared.DumpOpenStackLoadBalancers(e2eCtx, loadbalancers.ListOpts{})
79+
Expect(err).NotTo(HaveOccurred())
80+
initialVolumes, err = shared.DumpOpenStackVolumes(e2eCtx, volumes.ListOpts{})
81+
Expect(err).NotTo(HaveOccurred())
5882
})
5983

84+
// CheckResourceCleanup checks if all resources created during the test are cleaned up by comparing the resources
85+
// before and after the test.
86+
// The function f is used to list the resources of type T, whose list opts is of type L.
87+
// The list of resources is then compared to the initialResources, using the ConsistOfIDs custom matcher.
88+
func CheckResourceCleanup[T any, L any](f func(*shared.E2EContext, L) ([]T, error), l L, initialResources []T) *string {
89+
endResources, err := f(e2eCtx, l)
90+
if err != nil {
91+
return ptr.To(err.Error())
92+
}
93+
94+
matcher := ConsistOfIDs(initialResources)
95+
success, err := matcher.Match(endResources)
96+
if err != nil {
97+
return ptr.To(err.Error())
98+
}
99+
if !success {
100+
return ptr.To(matcher.FailureMessage(endResources))
101+
}
102+
103+
return nil
104+
}
105+
60106
var _ = SynchronizedAfterSuite(func() {
61107
shared.AllNodesAfterSuite(e2eCtx)
62108
}, func() {
109+
failed := false
110+
for _, error := range []*string{
111+
CheckResourceCleanup(shared.DumpOpenStackServers, servers.ListOpts{}, initialServers),
112+
CheckResourceCleanup(shared.DumpOpenStackNetworks, networks.ListOpts{}, initialNetworks),
113+
CheckResourceCleanup(shared.DumpOpenStackSecurityGroups, groups.ListOpts{}, initialSecurityGroups),
114+
CheckResourceCleanup(shared.DumpOpenStackLoadBalancers, loadbalancers.ListOpts{}, initialLoadBalancers),
115+
CheckResourceCleanup(shared.DumpOpenStackVolumes, volumes.ListOpts{}, initialVolumes),
116+
} {
117+
if error != nil {
118+
GinkgoWriter.Println(*error)
119+
failed = true
120+
}
121+
}
122+
63123
shared.Node1AfterSuite(e2eCtx)
124+
125+
if failed {
126+
Fail("Not all resources were cleaned up")
127+
}
64128
})

test/e2e/suites/e2e/idmatcher_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"fmt"
21+
"reflect"
22+
23+
"github.com/onsi/gomega"
24+
"github.com/onsi/gomega/format"
25+
"github.com/onsi/gomega/gstruct"
26+
"github.com/onsi/gomega/types"
27+
)
28+
29+
type idMatcher struct {
30+
expected interface{}
31+
}
32+
33+
var (
34+
_ types.GomegaMatcher = &idMatcher{}
35+
_ format.GomegaStringer = &idMatcher{}
36+
)
37+
38+
func (m *idMatcher) Match(actual interface{}) (bool, error) {
39+
v := reflect.ValueOf(m.expected)
40+
id := v.FieldByName("ID").String()
41+
42+
matcher := &gstruct.FieldsMatcher{
43+
Fields: gstruct.Fields{
44+
"ID": gomega.Equal(id),
45+
},
46+
IgnoreExtras: true,
47+
}
48+
49+
return matcher.Match(actual)
50+
}
51+
52+
func (m *idMatcher) FailureMessage(actual interface{}) string {
53+
return fmt.Sprintf("Expected:\n%s\nto have the same ID as:\n%s", format.Object(actual, 1), format.Object(m.expected, 1))
54+
}
55+
56+
func (m *idMatcher) NegatedFailureMessage(actual interface{}) string {
57+
return fmt.Sprintf("Expected:\n%s\nnot to have the same ID as:\n%s", format.Object(actual, 1), format.Object(m.expected, 1))
58+
}
59+
60+
func (m *idMatcher) GomegaString() string {
61+
return fmt.Sprintf("ID match for:\n%s", format.Object(m.expected, 1))
62+
}
63+
64+
func IDOf(expected interface{}) types.GomegaMatcher {
65+
return &idMatcher{expected: expected}
66+
}
67+
68+
func ConsistOfIDs[T any](expected []T) types.GomegaMatcher {
69+
matchers := make([]types.GomegaMatcher, len(expected))
70+
for i := range expected {
71+
matchers[i] = IDOf(expected[i])
72+
}
73+
74+
return gomega.ConsistOf(matchers)
75+
}

0 commit comments

Comments
 (0)