//===----------------------------------------------------------------------===// // Copyright © 2026 Apple Inc. and the container project 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 // // https://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. //===----------------------------------------------------------------------===// import ContainerTestSupport import Foundation import Testing @Suite(.serialized) struct TestClusterNetworkingSerial { private static let testImage = WarmupImage.alpine320 private func dumpEnv(_ f: ContainerFixture, clusterName: String) { print("=== ENV DUMP [\(clusterName)] ===") if let result = try? f.run(["system", "status"]) { print("[system status]\n\(result.output)") } if let result = try? f.run(["list"]) { print("[container list]\n\(result.output)") } if let result = try? f.run(["inspect", clusterName]) { print("[inspect \(clusterName)] stdout: \(result.output) stderr: \(result.error)") } print("=== END ENV DUMP ===") } private func waitForPod(_ f: ContainerFixture, node: String, podName: String, timeoutSeconds: Int) throws { print("[cluster-net] waitForPod \(podName) on \(node) (timeout=\(timeoutSeconds)s)") let (_, status) = try f.kubectl( node: node, args: [ "wait", "--for=condition=Ready", "pod/\(podName)", "--timeout=\(timeoutSeconds)s", ]) guard status == 0 else { let (podStatus, _) = try f.kubectl(node: node, args: ["get", "pod", podName, "--no-headers"]) print("[cluster-net] pod \(podName) status: \(podStatus.trimmingCharacters(in: .whitespacesAndNewlines))") let (podDesc, _) = try f.kubectl(node: node, args: ["describe", "pod", podName]) print("[cluster-net] pod \(podName) describe:\n\(podDesc.prefix(1000))") throw CommandError.executionFailed("pod \(podName) did not become ready within \(timeoutSeconds)s") } print("[cluster-net] pod \(podName) is Ready") } // Verify that a pod schedules, reaches Running, and can be exec'd into. @Test func testPodsScheduleAndRun() async throws { try await ContainerFixture.with { f in let name = "cluster-\(f.testID)" f.addCleanup { _ = try? f.run(["cluster", "delete", "--name", name]) } try f.restoreWarmupImage(.kindestNodeV1_35_5) print("[cluster-net] cluster create --name \(name)") let result = try f.run(["cluster", "create", "--name", name]) print("[cluster-net] cluster create exit=\(result.status)") if result.status != 0 { print("[cluster-net] cluster create stderr: \(result.error)") dumpEnv(f, clusterName: name) f.dumpNodeDiagnostics(node: name) } try result.check() print("[cluster-net] pulling \(Self.testImage.rawValue)") try f.restoreWarmupImage(Self.testImage) print("[cluster-net] cluster load-image --name \(name) \(Self.testImage.rawValue)") let loadResult = try f.run(["cluster", "load-image", "--name", name, Self.testImage.rawValue]) print("[cluster-net] cluster load-image exit=\(loadResult.status)") if loadResult.status != 0 { print("[cluster-net] cluster load-image stderr: \(loadResult.error)") } #expect(loadResult.status == 0) let (_, createStatus) = try f.kubectl( node: name, args: [ "run", "test-pod", "--image=\(Self.testImage.rawValue)", "--image-pull-policy=Never", "--restart=Never", "--", "sleep", "300", ]) #expect(createStatus == 0) try waitForPod(f, node: name, podName: "test-pod", timeoutSeconds: 300) let (output, execStatus) = try f.kubectl( node: name, args: [ "exec", "test-pod", "--", "echo", "hello", ]) #expect(execStatus == 0) #expect(output.contains("hello")) } } // Verify pod-to-service communication and CoreDNS resolution on a single node. @Test func testPodToServiceCommunication() async throws { try await ContainerFixture.with { f in let name = "cluster-\(f.testID)" f.addCleanup { _ = try? f.run(["cluster", "delete", "--name", name]) } try f.restoreWarmupImage(.kindestNodeV1_35_5) print("[cluster-net] cluster create --name \(name)") let result = try f.run(["cluster", "create", "--name", name]) print("[cluster-net] cluster create exit=\(result.status)") if result.status != 0 { print("[cluster-net] cluster create stderr: \(result.error)") dumpEnv(f, clusterName: name) } try result.check() print("[cluster-net] pulling \(Self.testImage.rawValue)") try f.restoreWarmupImage(Self.testImage) print("[cluster-net] cluster load-image --name \(name) \(Self.testImage.rawValue)") let loadResult = try f.run(["cluster", "load-image", "--name", name, Self.testImage.rawValue]) print("[cluster-net] cluster load-image exit=\(loadResult.status)") if loadResult.status != 0 { print("[cluster-net] cluster load-image stderr: \(loadResult.error)") } #expect(loadResult.status == 0) // Server: alpine busybox httpd serving a static response. let (_, serverStatus) = try f.kubectl( node: name, args: [ "run", "server", "--image=\(Self.testImage.rawValue)", "--image-pull-policy=Never", "--restart=Never", "--port=8080", "--", "sh", "-c", "while true; do printf 'HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\nConnection: close\\r\\n\\r\\nok' | nc -l -p 8080; done", ]) #expect(serverStatus == 0) try waitForPod(f, node: name, podName: "server", timeoutSeconds: 300) // Expose server as a ClusterIP service. let (_, exposeStatus) = try f.kubectl( node: name, args: [ "expose", "pod", "server", "--port=8080", "--name=server-svc", ]) #expect(exposeStatus == 0) // Client pod that stays alive so we can exec into it. let (_, clientStatus) = try f.kubectl( node: name, args: [ "run", "client", "--image=\(Self.testImage.rawValue)", "--image-pull-policy=Never", "--restart=Never", "--", "sleep", "300", ]) #expect(clientStatus == 0) try waitForPod(f, node: name, podName: "client", timeoutSeconds: 300) // Reach server via the service DNS name — exercises CoreDNS + kube-proxy. print("[cluster-net] wget from client to server-svc:8080") let (response, wgetStatus) = try f.kubectl( node: name, args: [ "exec", "client", "--", "sh", "-c", "sleep 2 && wget -qO- http://server-svc:8080", ]) print("[cluster-net] wget exit=\(wgetStatus) response=\(response.trimmingCharacters(in: .whitespacesAndNewlines))") #expect(wgetStatus == 0) #expect(response.contains("ok")) } } }