99 lines
4.0 KiB
TypeScript
99 lines
4.0 KiB
TypeScript
/**
|
||||
|
|
* 可达性(Reachability)—— **"怎么到这台 worker"的可序列化描述**(覆盖网络 S0)。
|
|||
|
|
*
|
|||
|
|
* ## 为什么需要它
|
|||
|
|
* 现状里"怎么到一台 worker"被写死成两件事:`dsh_hosts.endpoint` 存一个 URL,
|
|||
|
|
* `clusterInstanceHost` 存一个**全局**主机名。两者都**表达不出"经谁中转"**,而现网
|
|||
|
|
* 恰好有两类语义完全不同的 host,字符串却同形:
|
|||
|
|
*
|
|||
|
|
* | hostId | endpoint | 真实语义 |
|
|||
|
|
* |---|---|---|
|
|||
|
|
* | `w-47` | `http://127.0.0.1:19100` | **直连本机**(Manager 与 worker 同机,node 直接监听) |
|
|||
|
|
* | `w-106` | `http://127.0.0.1:19000` | **经 47 上 sshd 的反向隧道落点**(隧道的副作用) |
|
|||
|
|
*
|
|||
|
|
* ⇒ 换会合 / 中继组件时,表里**无法表达**「via(经哪个中继)+ 真实可达地址」,
|
|||
|
|
* 只能改表;越晚改代价越大(会合中继拆分方案 §2 C3)。
|
|||
|
|
*
|
|||
|
|
* `Reachability` 把这件事显式化:`via` 指向一个 `Rendezvous` 实现,`address` 是真实地址。
|
|||
|
|
*
|
|||
|
|
* ## 边界(勿破)
|
|||
|
|
* 本模块**只做地址的表征与解析**,不承载任何权威状态 —— 归属 / 租约 / 骨干资格
|
|||
|
|
* 一律仍只由控制面写(与 `集群化改造方案 §1.3` 数据分层一致)。
|
|||
|
|
*
|
|||
|
|
* @module dshs/net/reachability
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/** 同机直连:Manager 与 worker 在同一台机器上,不经任何中转。 */
|
|||
|
|
export const VIA_LOCAL = 'local'
|
|||
|
|
|
|||
|
|
/** 今天唯一在跑的中转方式 = **Manager 主机上的 sshd 反向隧道**(S4 之后应被 relay 取代)。 */
|
|||
|
|
export const VIA_MANAGER_SSH = 'manager-ssh'
|
|||
|
|
|
|||
|
|
export interface Reachability {
|
|||
|
|
/** 哪台 worker。 */
|
|||
|
|
hostId: string
|
|||
|
|
/** **经谁可达** —— 一个 `Rendezvous` 实现的 id。 */
|
|||
|
|
via: string
|
|||
|
|
/** agent 的真实地址 `host:port`(**不含 scheme**)。 */
|
|||
|
|
address: string
|
|||
|
|
/** 传输层。 */
|
|||
|
|
scheme: 'http' | 'https'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 只取址所需的最小形状 —— 避免本模块反向依赖 `supervisor`。 */
|
|||
|
|
export interface HostAddressable {
|
|||
|
|
hostId?: string
|
|||
|
|
agentUrl?: string
|
|||
|
|
reachability?: Reachability
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 把可达性拼成 agent 基址 —— **全仓唯一的拼接点**,别在别处再拼 `scheme://address`。
|
|||
|
|
*/
|
|||
|
|
export function agentBaseUrl(reach: Reachability): string {
|
|||
|
|
return `${reach.scheme}://${reach.address}`.replace(/\/+$/, '')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 取一台 host 的 agent 基址:**可达性优先,回退旧 `agentUrl`**。
|
|||
|
|
*
|
|||
|
|
* S0 阶段的等价性:现网每个 host 都只有 `agentUrl`(`reachability` 全为 `undefined`)
|
|||
|
|
* ⇒ 本函数返回的就是原先直接用的那个字符串,**行为零变化**。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 两者皆缺时**抛错**,不返回空串 —— "静默打到空地址"是跨机下最难查的失败。
|
|||
|
|
*/
|
|||
|
|
export function agentBaseUrlOf(host: HostAddressable): string {
|
|||
|
|
if (host.reachability !== undefined) return agentBaseUrl(host.reachability)
|
|||
|
|
if (host.agentUrl !== undefined && host.agentUrl !== '') return host.agentUrl.replace(/\/+$/, '')
|
|||
|
|
throw new Error(`host "${host.hostId ?? '?'}" 既无 reachability 也无 agentUrl:拒绝静默降级`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从旧的 `endpoint` 字符串解析出 `Reachability`(S2 迁移回填用)。
|
|||
|
|
*
|
|||
|
|
* 兼容面:`endpoint` 历史上是完整 URL(`http://127.0.0.1:19000`),也容忍裸
|
|||
|
|
* `host:port` —— 没写 scheme 时按 `http` 处理,与 `RemoteSpawner` 原先"直接把它当
|
|||
|
|
* fetch 基址"的行为一致(fetch 会补 `http://`)。
|
|||
|
|
*/
|
|||
|
|
export function parseReachability(
|
|||
|
|
hostId: string,
|
|||
|
|
endpoint: string,
|
|||
|
|
via: string = VIA_MANAGER_SSH,
|
|||
|
|
): Reachability {
|
|||
|
|
const trimmed = endpoint.trim()
|
|||
|
|
const matched = /^(https?):\/\/(.*)$/i.exec(trimmed)
|
|||
|
|
if (matched !== null) {
|
|||
|
|
return {
|
|||
|
|
hostId,
|
|||
|
|
via,
|
|||
|
|
address: matched[2].replace(/\/+$/, ''),
|
|||
|
|
scheme: matched[1].toLowerCase() === 'https' ? 'https' : 'http',
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return { hostId, via, address: trimmed.replace(/\/+$/, ''), scheme: 'http' }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** `Reachability` → 旧 `endpoint` 字符串(与 `parseReachability` 互逆,回填/回滚用)。 */
|
|||
|
|
export function toEndpoint(reach: Reachability): string {
|
|||
|
|
return agentBaseUrl(reach)
|
|||
|
|
}
|