mirror of
https://github.com/apple/container.git
synced 2026-07-14 21:47:01 +00:00
76f387e3be
- Part of #1833. - CLI progress and registry test migrations were inadventently reverted by #1857. - Migrate TestCLINoParallelCases to TestCLIImagePruneSerial and TestCLINetworkPruneSerial. - Clean up test selection patterns in Makefile. - Remove all legacy CLITests files. - Use swift-testing `withKnownIssue` to run but ignore failures on flaky `testCreateNameLongestValid` and `testIsolatedNetwork`. - Extracts a fixture helper for tests requiring a retry loop.
216 lines
8.8 KiB
Swift
216 lines
8.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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 AsyncHTTPClient
|
|
import ContainerizationExtras
|
|
import Foundation
|
|
import Testing
|
|
|
|
@Suite
|
|
struct TestCLINetwork {
|
|
|
|
// MARK: - Tests
|
|
|
|
@available(macOS 26, *)
|
|
@Test func testNetworkCreateAndUse() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let net = "\(f.testID)-net"
|
|
let c = "\(f.testID)-c"
|
|
f.addCleanup { f.doNetworkDeleteIfExists(net) }
|
|
|
|
try f.doNetworkCreate(net)
|
|
|
|
let listResult = try f.run(["network", "ls", "--quiet"]).check()
|
|
let networkIds = listResult.output
|
|
.components(separatedBy: .newlines)
|
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
|
.filter { !$0.isEmpty }
|
|
#expect(networkIds == networkIds.sorted(), "network IDs should be sorted")
|
|
|
|
let port = UInt16.random(in: 50000..<60000)
|
|
try f.doLongRun(
|
|
name: c, image: "docker.io/library/python:alpine",
|
|
args: ["--network", net],
|
|
containerArgs: ["python3", "-m", "http.server", "--bind", "0.0.0.0", "\(port)"],
|
|
autoRemove: false)
|
|
f.addCleanup {
|
|
try? f.doStop(c)
|
|
try? f.doRemove(c)
|
|
}
|
|
try await f.waitForContainerRunning(c)
|
|
|
|
let container = try f.inspectContainer(c)
|
|
#expect(container.networks.count > 0)
|
|
let ip = container.networks[0].ipv4Address.address
|
|
let url = "http://\(ip):\(port)"
|
|
|
|
// waitForContainerRunning only tells us init is running; the python http
|
|
// server inside is still starting, so retry until it accepts connections.
|
|
let client = f.makeHTTPClient()
|
|
defer { _ = client.shutdown() }
|
|
try await f.retry(attempts: 10) {
|
|
do {
|
|
var req = HTTPClientRequest(url: url)
|
|
req.method = .GET
|
|
let resp = try await client.execute(req, timeout: .seconds(3))
|
|
return resp.status.code >= 200 && resp.status.code < 300
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(macOS 26, *)
|
|
@Test func testNetworkDeleteWithContainer() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let net = "\(f.testID)-net"
|
|
let c = "\(f.testID)-c"
|
|
f.addCleanup { f.doNetworkDeleteIfExists(net) }
|
|
f.addCleanup { try? f.doRemove(c, force: true) }
|
|
|
|
try f.doNetworkCreate(net)
|
|
try f.doCreate(name: c, networks: [net])
|
|
|
|
let deleteResult = try f.run(["network", "delete", net])
|
|
try #require(deleteResult.status != 0, "network delete should fail while container references it")
|
|
#expect(deleteResult.error.contains("delete failed"))
|
|
#expect(deleteResult.error.contains("[\"\(net)\"]"))
|
|
|
|
try f.doRemove(c, force: true)
|
|
try f.doNetworkDelete(net)
|
|
}
|
|
}
|
|
|
|
@available(macOS 26, *)
|
|
@Test func testNetworkLabels() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let net = "\(f.testID)-net"
|
|
f.addCleanup { f.doNetworkDeleteIfExists(net) }
|
|
|
|
try f.doNetworkCreate(net, args: ["--label", "foo=bar", "--label", "baz=qux"])
|
|
|
|
let network = try f.inspectNetwork(net)
|
|
let expectedLabels = ["foo": "bar", "baz": "qux"]
|
|
#expect(expectedLabels == network.configuration.labels.dictionary)
|
|
}
|
|
}
|
|
|
|
@Test func testNetworkMTU() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
|
|
let c = "\(f.testID)-c"
|
|
try f.doLongRun(name: c, image: image, args: ["--network", "default,mtu=1500"], autoRemove: false)
|
|
f.addCleanup {
|
|
try? f.doStop(c)
|
|
try? f.doRemove(c)
|
|
}
|
|
try await f.waitForContainerRunning(c)
|
|
let output = try f.doExec(c, cmd: ["ip", "link", "show", "eth0"])
|
|
#expect(output.contains("mtu 1500"), "expected mtu 1500 in ip link output: \(output)")
|
|
}
|
|
}
|
|
|
|
@available(macOS 26, *)
|
|
@Test func testIsolatedNetwork() async {
|
|
await withKnownIssue("curl error 7 despite retries", isIntermittent: true) {
|
|
try await ContainerFixture.with { f in
|
|
let net = "\(f.testID)-net"
|
|
let server = "\(f.testID)-server"
|
|
let pythonImage = "docker.io/library/python:alpine"
|
|
let curlImage = "docker.io/curlimages/curl:8.6.0"
|
|
|
|
f.addCleanup { f.doNetworkDeleteIfExists(net) }
|
|
f.addCleanup {
|
|
try? f.doStop(server)
|
|
try? f.doRemove(server)
|
|
}
|
|
|
|
try f.doNetworkCreate(net, args: ["--internal"])
|
|
|
|
let port = UInt16.random(in: 50000..<60000)
|
|
try f.doLongRun(
|
|
name: server, image: pythonImage,
|
|
args: ["--network", net],
|
|
containerArgs: ["python3", "-m", "http.server", "--bind", "0.0.0.0", "\(port)"],
|
|
autoRemove: false)
|
|
try await f.waitForContainerRunning(server)
|
|
|
|
let container = try f.inspectContainer(server)
|
|
#expect(container.networks.count > 0)
|
|
let ip = container.networks[0].ipv4Address.address
|
|
let serverURL = "http://\(ip):\(port)"
|
|
|
|
// Internal connection should succeed. `waitForContainerRunning` only
|
|
// proves the container's init is up; the python http.server inside
|
|
// may still be starting, so retry until it accepts connections.
|
|
try await f.retry(attempts: 10) {
|
|
let result = try f.run([
|
|
"run", "--rm", "--network", net, curlImage,
|
|
"curl", "--connect-timeout", "3", serverURL,
|
|
])
|
|
return result.status == 0
|
|
}
|
|
|
|
// External connection should be blocked — the isolated network has no gateway.
|
|
let externalResult = try f.run([
|
|
"run", "--rm", "--network", net, curlImage,
|
|
"curl", "--connect-timeout", "5", "http://google.com",
|
|
])
|
|
let hostOnlyBlockedCodes: Set<Int32> = [6, 7, 28]
|
|
#expect(
|
|
hostOnlyBlockedCodes.contains(externalResult.status),
|
|
"external connection from isolated network should be blocked, got exit \(externalResult.status)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test func testNetworkListTableFormat() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let net = "\(f.testID)-net"
|
|
f.addCleanup { f.doNetworkDeleteIfExists(net) }
|
|
try f.doNetworkCreate(net)
|
|
|
|
let result = try f.run(["network", "list"]).check()
|
|
#expect(["NETWORK", "SUBNET"].allSatisfy { result.output.contains($0) })
|
|
#expect(result.output.contains(net))
|
|
}
|
|
}
|
|
|
|
@Test func testNetworkListJSONFormat() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let net = "\(f.testID)-net"
|
|
f.addCleanup { f.doNetworkDeleteIfExists(net) }
|
|
try f.doNetworkCreate(net)
|
|
|
|
let result = try f.run(["network", "list", "--format", "json"]).check()
|
|
guard let json = try JSONSerialization.jsonObject(with: result.outputData) as? [[String: Any]] else {
|
|
Issue.record("JSON output should be an array of objects")
|
|
return
|
|
}
|
|
#expect(json.contains { ($0["id"] as? String) == net })
|
|
}
|
|
}
|
|
|
|
@Test func testInspectMissingNetworkFails() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let result = try f.run(["network", "inspect", "definitely-missing-\(f.testID)"])
|
|
#expect(result.status != 0)
|
|
#expect(result.error.contains("network not found"))
|
|
}
|
|
}
|
|
}
|