Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/main/ssh/orcad-managed-tunnel-active.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/** The managed tunnels a client holds, and the one rule for whether one still belongs to its server. */
import { getManagedOrcadFenceEnvironmentId } from '../../shared/managed-orcad-ssh-owner'
import {
getRuntimeSshAccess,
type KnownRuntimeEnvironment,
type RuntimeSshTunnelLink
} from '../../shared/runtime-environments'
import type { SshTarget } from '../../shared/ssh-types'
import type { SshConnection } from './ssh-connection'
import type { PortForwardEntry, SshPortForwardManager } from './ssh-port-forward'

export type ActiveOrcadTunnel = {
connection: SshConnection
forwardId: string
localPort: number
/** The port orcad bound, which can differ from the persisted (preferred) one. */
remotePort: number
preferredPort: number
sshTargetGeneration: number
targetId: string
transportGeneration: number
}

export type ManagedTunnelExpectation = {
environmentId: string
sshTargetId: string
sshTargetGeneration: number
localPort: number
remotePort: number
}

/** The environment's SSH access while it and its target still name this tunnel; null otherwise. */
export function managedTunnelAccess(
environment: KnownRuntimeEnvironment | null | undefined,
target: SshTarget | null | undefined,
expected: ManagedTunnelExpectation
): RuntimeSshTunnelLink | null {
const access = environment ? getRuntimeSshAccess(environment) : undefined
return environment?.id === expected.environmentId &&
environment.connectionDependency === 'ssh-tunnel' &&
access?.sshTargetId === expected.sshTargetId &&
access.sshTargetGeneration === expected.sshTargetGeneration &&
access.localPort === expected.localPort &&
access.remotePort === expected.remotePort &&
target?.generation === expected.sshTargetGeneration &&
getManagedOrcadFenceEnvironmentId(target) === expected.environmentId
? access
: null
}

/** Removes `entry`'s forward, and the record only if nothing replaced it meanwhile. */
export async function dropActiveOrcadTunnel(
active: Map<string, ActiveOrcadTunnel>,
forwards: SshPortForwardManager,
environmentId: string,
entry: ActiveOrcadTunnel
): Promise<void> {
await forwards.removeForwardAndWait(entry.forwardId)
if (active.get(environmentId) === entry) {
active.delete(environmentId)
}
}

export function recordActiveOrcadTunnel(
active: Map<string, ActiveOrcadTunnel>,
environmentId: string,
forward: PortForwardEntry,
tunnel: Omit<ActiveOrcadTunnel, 'forwardId' | 'localPort' | 'remotePort'>
): void {
active.set(environmentId, {
...tunnel,
forwardId: forward.id,
localPort: forward.localPort,
remotePort: forward.remotePort
})
}
36 changes: 12 additions & 24 deletions src/main/ssh/orcad-managed-tunnel-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ import { SshPortForwardManager } from './ssh-port-forward'
import { OrcadManagedTunnelTransportProvider } from './orcad-managed-tunnel-transport'
import {
OrcadManagedTunnelResumeRecovery,
type ActiveOrcadTunnel,
type OrcadManagedServingCheck,
type OrcadManagedTunnelProbe,
type OrcadManagedTunnelResumeOptions
} from './orcad-managed-tunnel-resume'
import type { OrcadManagedServing } from './orcad-managed-serving'
import {
dropActiveOrcadTunnel,
managedTunnelAccess,
recordActiveOrcadTunnel,
type ActiveOrcadTunnel
} from './orcad-managed-tunnel-active'
import { checkManagedTunnelServing, type OrcadTunnelServing } from './orcad-managed-tunnel-serving'
import type { getSshTargetRegistryStore } from './ssh-target-registry'
import {
Expand Down Expand Up @@ -132,11 +137,8 @@ export class OrcadManagedTunnelManager {
if (!forward) {
throw new Error('Orca SSH tunnel setup was superseded.')
}
this.active.set(environmentId, {
recordActiveOrcadTunnel(this.active, environmentId, forward, {
connection,
forwardId: forward.id,
localPort: forward.localPort,
remotePort: forward.remotePort,
preferredPort: checks.preferredPort ?? remotePort,
sshTargetGeneration: target.generation,
targetId: target.id,
Expand Down Expand Up @@ -220,24 +222,16 @@ export class OrcadManagedTunnelManager {

const managerGeneration = this.managerGeneration
const ownershipGeneration = this.ownershipGenerations.get(environment.id) ?? 0
const expected = { environmentId: environment.id, ...deployment }
const stillOwned = (): boolean => {
const currentTarget = targetStore.getTarget(target.id)
const currentEnvironment = resolveCurrent()
const currentAccess = currentEnvironment ? getRuntimeSshAccess(currentEnvironment) : undefined
return (
this.managerGeneration === managerGeneration &&
(this.ownershipGenerations.get(environment.id) ?? 0) === ownershipGeneration &&
currentTarget?.generation === target.generation &&
getManagedOrcadFenceEnvironmentId(currentTarget) === environment.id &&
currentEnvironment?.id === environment.id &&
currentEnvironment.runtimeId === environment.runtimeId &&
currentEnvironment?.runtimeId === environment.runtimeId &&
(currentEnvironment.pairingRevision ?? currentEnvironment.createdAt) ===
(environment.pairingRevision ?? environment.createdAt) &&
currentEnvironment.connectionDependency === 'ssh-tunnel' &&
currentAccess?.sshTargetId === deployment.sshTargetId &&
currentAccess.sshTargetGeneration === deployment.sshTargetGeneration &&
currentAccess.localPort === deployment.localPort &&
currentAccess.remotePort === deployment.remotePort
managedTunnelAccess(currentEnvironment, targetStore.getTarget(target.id), expected) !== null
)
}
const connection = await connectionManager.connect(target)
Expand All @@ -262,10 +256,7 @@ export class OrcadManagedTunnelManager {
connection
})
if (active && stillOwned()) {
await this.forwards.removeForwardAndWait(active.forwardId)
if (this.active.get(environment.id) === active) {
this.active.delete(environment.id)
}
await dropActiveOrcadTunnel(this.active, this.forwards, environment.id, active)
}
if (!stillOwned()) {
return
Expand All @@ -283,11 +274,8 @@ export class OrcadManagedTunnelManager {
if (!forward) {
return
}
this.active.set(environment.id, {
recordActiveOrcadTunnel(this.active, environment.id, forward, {
connection,
forwardId: forward.id,
localPort: forward.localPort,
remotePort: forward.remotePort,
preferredPort: deployment.remotePort,
sshTargetGeneration: target.generation,
targetId: target.id,
Expand Down
79 changes: 32 additions & 47 deletions src/main/ssh/orcad-managed-tunnel-resume.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { sendRemoteRuntimeRequest } from '../../shared/remote-runtime-client'
import { getManagedOrcadFenceEnvironmentId } from '../../shared/managed-orcad-ssh-owner'
import {
getPreferredPairingOffer,
getRuntimeSshAccess,
type KnownRuntimeEnvironment,
type RuntimeSshTunnelLink
} from '../../shared/runtime-environments'
Expand All @@ -12,24 +10,18 @@ import type { OrcadManagedServing } from './orcad-managed-serving'
import type { SshConnectionManager } from './ssh-connection-manager'
import type { SshPortForwardManager } from './ssh-port-forward'
import type { getSshTargetRegistryStore } from './ssh-target-registry'
import {
dropActiveOrcadTunnel,
managedTunnelAccess,
recordActiveOrcadTunnel,
type ActiveOrcadTunnel
} from './orcad-managed-tunnel-active'
import {
environmentForwardChecks,
forwardToVerifiedOrcad,
type OrcadManagedTunnelTargeting
} from './orcad-managed-tunnel-target'

export type ActiveOrcadTunnel = {
connection: SshConnection
forwardId: string
localPort: number
/** The port orcad bound, which can differ from the persisted (preferred) one. */
remotePort: number
preferredPort: number
sshTargetGeneration: number
targetId: string
transportGeneration: number
}

export type OrcadManagedTunnelProbe = (
environment: KnownRuntimeEnvironment,
timeoutMs: number
Expand Down Expand Up @@ -120,7 +112,7 @@ export class OrcadManagedTunnelResumeRecovery {
if (await this.probeTunnel(resolved.environment, options.timeoutMs)) {
return
}
if (!this.stillOwned(environmentId, active, ownershipGeneration, managerGeneration, true)) {
if (!this.stillOwned(environmentId, active, ownershipGeneration, managerGeneration)) {
return
}
}
Expand All @@ -129,7 +121,7 @@ export class OrcadManagedTunnelResumeRecovery {
if (pending) {
await pending.catch(() => undefined)
}
if (!this.stillOwned(environmentId, active, ownershipGeneration, managerGeneration, true)) {
if (!this.stillOwned(environmentId, active, ownershipGeneration, managerGeneration)) {
return
}
const resolved = this.resolveEnvironment(environmentId, active, options)
Expand Down Expand Up @@ -167,7 +159,7 @@ export class OrcadManagedTunnelResumeRecovery {
): Promise<void> {
await connectionManager.reconnect(active.targetId)
if (
!this.stillOwned(environment.id, active, ownershipGeneration, managerGeneration, true) ||
!this.stillOwned(environment.id, active, ownershipGeneration, managerGeneration) ||
connectionManager !== this.dependencies.getConnectionManager() ||
connectionManager.getConnection(active.targetId) !== active.connection ||
connectionManager.getState(active.targetId)?.status !== 'connected' ||
Expand All @@ -184,12 +176,14 @@ export class OrcadManagedTunnelResumeRecovery {
return
}
if (currentActive === active) {
await this.dependencies.forwards.removeForwardAndWait(active.forwardId)
if (this.dependencies.active.get(environment.id) === active) {
this.dependencies.active.delete(environment.id)
}
await dropActiveOrcadTunnel(
this.dependencies.active,
this.dependencies.forwards,
environment.id,
active
)
}
if (!this.stillOwned(environment.id, active, ownershipGeneration, managerGeneration, true)) {
if (!this.stillOwned(environment.id, active, ownershipGeneration, managerGeneration)) {
return
}

Expand Down Expand Up @@ -217,17 +211,14 @@ export class OrcadManagedTunnelResumeRecovery {
...checks,
stillCurrent: () =>
active.connection.getTransportGeneration() === transportGeneration &&
this.stillOwned(environment.id, active, ownershipGeneration, managerGeneration, true) &&
this.stillOwned(environment.id, active, ownershipGeneration, managerGeneration) &&
this.resolveEnvironment(environment.id, active, options) !== null
})
if (!forward) {
return
}
this.dependencies.active.set(environment.id, {
recordActiveOrcadTunnel(this.dependencies.active, environment.id, forward, {
connection: active.connection,
forwardId: forward.id,
localPort: forward.localPort,
remotePort: forward.remotePort,
preferredPort: deployment.remotePort,
sshTargetGeneration: active.sshTargetGeneration,
targetId: active.targetId,
Expand All @@ -243,37 +234,31 @@ export class OrcadManagedTunnelResumeRecovery {
options: OrcadManagedTunnelResumeOptions
): ResolvedManagedTunnelEnvironment | null {
const environment = options.resolveEnvironment(environmentId)
const deployment = environment ? getRuntimeSshAccess(environment) : undefined
const target = this.dependencies.getTargetStore()?.getTarget(active.targetId)
if (
!environment ||
environment.connectionDependency !== 'ssh-tunnel' ||
!deployment ||
deployment.sshTargetId !== active.targetId ||
deployment.sshTargetGeneration !== active.sshTargetGeneration ||
deployment.localPort !== active.localPort ||
deployment.remotePort !== active.preferredPort ||
!target ||
target.generation !== active.sshTargetGeneration ||
getManagedOrcadFenceEnvironmentId(target) !== environmentId
) {
return null
}
return { deployment, environment }
const deployment = managedTunnelAccess(
environment,
this.dependencies.getTargetStore()?.getTarget(active.targetId),
{
environmentId,
sshTargetId: active.targetId,
sshTargetGeneration: active.sshTargetGeneration,
localPort: active.localPort,
remotePort: active.preferredPort
}
)
return environment && deployment ? { deployment, environment } : null
}

private stillOwned(
environmentId: string,
active: ActiveOrcadTunnel,
ownershipGeneration: number,
managerGeneration: number,
allowMissingActive = false
managerGeneration: number
): boolean {
const current = this.dependencies.active.get(environmentId)
return (
this.dependencies.getManagerGeneration() === managerGeneration &&
(this.dependencies.ownershipGenerations.get(environmentId) ?? 0) === ownershipGeneration &&
(current === active || (allowMissingActive && current === undefined))
(current === active || current === undefined)
)
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/ssh/orcad-managed-tunnel-serving.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/
import type { KnownRuntimeEnvironment } from '../../shared/runtime-environments'
import type { OrcadManagedServing } from './orcad-managed-serving'
import type { ActiveOrcadTunnel, OrcadManagedServingCheck } from './orcad-managed-tunnel-resume'
import type { OrcadManagedServingCheck } from './orcad-managed-tunnel-resume'
import type { ActiveOrcadTunnel } from './orcad-managed-tunnel-active'
import type { SshPortForwardManager } from './ssh-port-forward'
import type { SshTarget } from '../../shared/ssh-types'

Expand Down
Loading