@@ -21,6 +21,7 @@ import (
2121 "bytes"
2222 "encoding/json"
2323 "fmt"
24+ "net"
2425 "os"
2526 "time"
2627
@@ -83,6 +84,74 @@ func (f *Framework) AfterSuite() {
8384 f .shutdownDashboardTunnel ()
8485}
8586
87+ // DeployAPI7EE deploys the API7EE control plane once (runs on ginkgo node 1 only).
88+ // It returns a ready signal consumed by InitNodeConnections on all nodes.
89+ func (f * Framework ) DeployAPI7EE () []byte {
90+ API7EELicense = os .Getenv ("API7_EE_LICENSE" )
91+ if API7EELicense == "" {
92+ panic ("env {API7_EE_LICENSE} is required" )
93+ }
94+
95+ dashboardVersion = os .Getenv ("DASHBOARD_VERSION" )
96+ if dashboardVersion == "" {
97+ dashboardVersion = "dev"
98+ }
99+
100+ _ = k8s .DeleteNamespaceE (GinkgoT (), f .kubectlOpts , _namespace )
101+
102+ Eventually (func () error {
103+ _ , err := k8s .GetNamespaceE (GinkgoT (), f .kubectlOpts , _namespace )
104+ if k8serrors .IsNotFound (err ) {
105+ return nil
106+ }
107+ return fmt .Errorf ("namespace %s still exists" , _namespace )
108+ }, "1m" , "2s" ).Should (Succeed ())
109+
110+ k8s .CreateNamespace (GinkgoT (), f .kubectlOpts , _namespace )
111+
112+ f .DeployComponents ()
113+
114+ time .Sleep (1 * time .Minute )
115+
116+ // Create a temporary tunnel for one-time setup operations.
117+ // Each node will create its own persistent tunnel in InitNodeConnections.
118+ err := f .newDashboardTunnel ()
119+ Expect (err ).ShouldNot (HaveOccurred (), "creating temporary dashboard tunnel" )
120+ f .Logf ("Temporary dashboard tunnel: %s" , _dashboardHTTPTunnel .Endpoint ())
121+
122+ f .UploadLicense ()
123+ f .setDpManagerEndpoints ()
124+
125+ // Close the temporary tunnel; each node creates its own in InitNodeConnections.
126+ f .shutdownDashboardTunnel ()
127+
128+ return []byte ("ready" )
129+ }
130+
131+ // InitNodeConnections initializes per-node connections to the shared API7EE control plane.
132+ // It runs on every ginkgo parallel node after DeployAPI7EE completes.
133+ func (f * Framework ) InitNodeConnections (_ []byte ) {
134+ API7EELicense = os .Getenv ("API7_EE_LICENSE" )
135+
136+ dashboardVersion = os .Getenv ("DASHBOARD_VERSION" )
137+ if dashboardVersion == "" {
138+ dashboardVersion = "dev"
139+ }
140+
141+ err := f .newDashboardTunnel ()
142+ Expect (err ).ShouldNot (HaveOccurred (), "creating dashboard tunnel for node" )
143+ f .Logf ("Dashboard HTTP Tunnel: %s" , _dashboardHTTPTunnel .Endpoint ())
144+ }
145+
146+ // CloseNodeConnections closes per-node connections. Runs on every ginkgo parallel node.
147+ func (f * Framework ) CloseNodeConnections () {
148+ f .shutdownDashboardTunnel ()
149+ }
150+
151+ // TeardownInfrastructure cleans up suite-level resources. Runs on ginkgo node 1 only.
152+ // The Kind cluster is deleted by CI after the job, so this is a no-op.
153+ func (f * Framework ) TeardownInfrastructure () {}
154+
86155// DeployComponents deploy necessary components
87156func (f * Framework ) DeployComponents () {
88157 f .deploy ()
@@ -167,31 +236,40 @@ var (
167236 _dashboardHTTPSTunnel * k8s.Tunnel
168237)
169238
239+ // findFreePort returns an available TCP port on the local machine.
240+ // Using port 0 with net.Listen lets the OS pick a free port.
241+ func findFreePort () int {
242+ ln , err := net .Listen ("tcp" , ":0" )
243+ if err != nil {
244+ panic (fmt .Sprintf ("find free port: %v" , err ))
245+ }
246+ port := ln .Addr ().(* net.TCPAddr ).Port
247+ _ = ln .Close ()
248+ return port
249+ }
250+
170251func (f * Framework ) newDashboardTunnel () error {
171252 var (
172- httpNodePort int
173- httpsNodePort int
174- httpPort int
175- httpsPort int
253+ httpPort int
254+ httpsPort int
176255 )
177256
178257 service := k8s .GetService (f .GinkgoT , f .kubectlOpts , "api7ee3-dashboard" )
179258
180259 for _ , port := range service .Spec .Ports {
181260 switch port .Name {
182261 case "http" :
183- httpNodePort = int (port .NodePort )
184262 httpPort = int (port .Port )
185263 case "https" :
186- httpsNodePort = int (port .NodePort )
187264 httpsPort = int (port .Port )
188265 }
189266 }
190267
268+ // Use auto-assigned local ports so parallel ginkgo nodes don't conflict.
191269 _dashboardHTTPTunnel = k8s .NewTunnel (f .kubectlOpts , k8s .ResourceTypeService , "api7ee3-dashboard" ,
192- httpNodePort , httpPort )
270+ findFreePort () , httpPort )
193271 _dashboardHTTPSTunnel = k8s .NewTunnel (f .kubectlOpts , k8s .ResourceTypeService , "api7ee3-dashboard" ,
194- httpsNodePort , httpsPort )
272+ findFreePort () , httpsPort )
195273
196274 if err := _dashboardHTTPTunnel .ForwardPortE (f .GinkgoT ); err != nil {
197275 return err
0 commit comments