|
17 | 17 | import ArgumentParser |
18 | 18 | import Containerization |
19 | 19 | import ContainerizationError |
| 20 | +import ContainerizationExtras |
20 | 21 | import ContainerizationOCI |
21 | 22 | import ContainerizationOS |
22 | 23 | import Foundation |
@@ -2032,4 +2033,84 @@ extension IntegrationSuite { |
2032 | 2033 | throw error |
2033 | 2034 | } |
2034 | 2035 | } |
| 2036 | + |
| 2037 | + @available(macOS 26.0, *) |
| 2038 | + func testPodRDNSSUpdatesResolvConf() async throws { |
| 2039 | + let id = "test-pod-rdnss-updates-resolv-conf" |
| 2040 | + let bs = try await bootstrap(id) |
| 2041 | + |
| 2042 | + var network = try ContainerManager.VmnetNetwork() |
| 2043 | + |
| 2044 | + guard let interface = try network.create("\(id)-c1"), |
| 2045 | + let gateway = interface.ipv4Gateway |
| 2046 | + else { |
| 2047 | + throw IntegrationError.assert(msg: "failed to get vmnet interface or gateway") |
| 2048 | + } |
| 2049 | + |
| 2050 | + let pod = try LinuxPod(id, vmm: bs.vmm) { config in |
| 2051 | + config.cpus = 4 |
| 2052 | + config.memoryInBytes = 1024.mib() |
| 2053 | + config.bootLog = bs.bootLog |
| 2054 | + config.interfaces = [interface] |
| 2055 | + config.dns = DNS(nameservers: [gateway.description], enableRDNSSMonitor: true) |
| 2056 | + } |
| 2057 | + |
| 2058 | + try await pod.addContainer("container1", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container1")) { config in |
| 2059 | + config.process.arguments = ["sleep", "30"] |
| 2060 | + } |
| 2061 | + |
| 2062 | + try await pod.addContainer("container2", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container2")) { config in |
| 2063 | + config.process.arguments = ["sleep", "30"] |
| 2064 | + } |
| 2065 | + |
| 2066 | + do { |
| 2067 | + try await pod.create() |
| 2068 | + try await pod.startContainer("container1") |
| 2069 | + try await pod.startContainer("container2") |
| 2070 | + |
| 2071 | + // Both containers share the pod's DNS config with enableRDNSSMonitor = true, |
| 2072 | + // so vminitd starts the RDNSS monitor automatically. Poll each container's |
| 2073 | + // resolv.conf until an IPv6 nameserver appears. |
| 2074 | + for containerID in ["container1", "container2"] { |
| 2075 | + var found = false |
| 2076 | + let deadline = Date.now.addingTimeInterval(15) |
| 2077 | + while Date.now < deadline { |
| 2078 | + try await Task.sleep(for: .seconds(1)) |
| 2079 | + |
| 2080 | + let buffer = BufferWriter() |
| 2081 | + let exec = try await pod.execInContainer(containerID, processID: "check-resolv-\(containerID)") { config in |
| 2082 | + config.arguments = ["cat", "/etc/resolv.conf"] |
| 2083 | + config.stdout = buffer |
| 2084 | + } |
| 2085 | + try await exec.start() |
| 2086 | + let status = try await exec.wait() |
| 2087 | + try await exec.delete() |
| 2088 | + |
| 2089 | + if status.exitCode == 0, |
| 2090 | + let output = String(data: buffer.data, encoding: .utf8), |
| 2091 | + output.split(separator: "\n") |
| 2092 | + .contains(where: { |
| 2093 | + guard $0.hasPrefix("nameserver ") else { return false } |
| 2094 | + let addr = $0.dropFirst("nameserver ".count).trimmingCharacters(in: .whitespaces) |
| 2095 | + return (try? IPv6Address(addr)) != nil |
| 2096 | + }) |
| 2097 | + { |
| 2098 | + found = true |
| 2099 | + break |
| 2100 | + } |
| 2101 | + } |
| 2102 | + |
| 2103 | + guard found else { |
| 2104 | + throw IntegrationError.assert( |
| 2105 | + msg: "\(containerID): resolv.conf was not updated with an IPv6 nameserver from RDNSS within timeout" |
| 2106 | + ) |
| 2107 | + } |
| 2108 | + } |
| 2109 | + |
| 2110 | + try await pod.stop() |
| 2111 | + } catch { |
| 2112 | + try? await pod.stop() |
| 2113 | + throw error |
| 2114 | + } |
| 2115 | + } |
2035 | 2116 | } |
0 commit comments