Repository navigation
feat(connector): [LBT-147] VM 접속 대상 검증과 SSH 연결 관리 - #84
paekseobin-crypto wants to merge 17 commits into
Conversation
imcherry5778
left a comment
There was a problem hiding this comment.
Request Changes — LBT-147 (reviewed exact HEAD ed7843e25be9)
최신 branch diff, 관련 production caller/consumer, LBT-147 Acceptance, D-18 v5 / D-21 v10, 테스트 및 게시 Evidence를 다시 대조했습니다. Go/Web/Contracts CI는 현재 HEAD에서 모두 성공했고, 로컬 OpenStack 검증 로그도 남아 있습니다. 다만 아래 **3건(핵심 P1 1건, P2 2건)**은 아직 소스 경로에서 해소되지 않아 승인할 수 없습니다. 아래 내용은 코드 경로에 근거한 리뷰 finding이며, 제가 이번 리뷰에서 해당 실패를 직접 재현했다고 주장하지 않습니다.
1. [P1] Channel 단위 취소가 같은 VM의 정상 SSH 세션까지 종료
vmaccess.Manager.OpenSession의 channel-open context callback(164행), vmaccess.Session.Setup의 callback(280행)이 모두 retire(connection)을 호출합니다. retireLocked는 공유 ssh.Client를 닫으므로, 이미 정상 실행 중인 PTY A가 있어도 별도 PTY/SFTP B의 NewSession/RequestPty/RequestSubsystem 지연 및 timeout/cancel만으로 A까지 끊길 수 있습니다. 현재 정상 Close 독립성 테스트와 단독 timeout 테스트는 이 동시성 조합을 검증하지 않습니다. 이는 LBT-147 완료 조건(채널 독립성·실패/취소 정리), D-18의 독립 PTY/SFTP 경계에서 중요한 공백입니다.
요청: 동일 VM의 기존 PTY A가 실제 I/O 중인 상태에서 신규 채널 B의 open/setup timeout 또는 cancel을 강제하고 A의 I/O와 연결이 유지되는지 검증하는 회귀 테스트를 추가해 주세요. 필요하다면 channel-only cancellation/cleanup 경계를 보완해 주세요. SSH 라이브러리 제약 때문에 transport 전체 폐기가 불가피하다면, 단순 PR 제약 기재만으로 완료 처리하지 말고 D-18/AC 대비 예외를 decision owner가 명시적으로 수용하고 영향을 추적해야 합니다.
2. [P2] Provider 조회 장애가 영구적인 INVALID_SESSION으로 잘못 분류
openstack.Adapter.ResolveVMAccess(33–35행)는 resolveServerAddress의 모든 non-context 오류를 vmaccess.ErrInvalidTarget으로 교체합니다. 예컨대 Nova GET 503/Neutron List 503도 Manager.OpenSession을 거쳐 wss.handleTerminalOpen에서 INVALID_SESSION이 됩니다. 실제 대상/generation 불일치와 일시적인 Provider 장애를 구분해야 재시도·사용자 오류 처리도 올바르게 동작합니다.
요청: 소유권/대상 불일치만 ErrInvalidTarget에 매핑하고, 조회·인증·도달성·timeout 등 운영 장애는 안전한 retryable/unavailable 계열로 구분해 주세요(외부로 raw Provider/SSH error를 노출하지 않아야 합니다). Nova/Neutron 503 및 진짜 mismatch의 Control 결과 코드 회귀를 추가해 주세요.
3. [P2] Shutdown defer 역순으로 종료 사유가 SSH_DISCONNECTED/PTY_EXITED로 경합
runControl(250–252행)의 defer TerminalManager.CloseAll("SERVICE_RESTARTING") 다음에 defer VMAccess.Close()를 등록하여 실제 실행은 VMAccess 종료 → TerminalSession 종료가 됩니다. 첫 번째 호출이 SSH transport를 끊으면 Session.startPtyPump의 종료 경로가 먼저 SSH_DISCONNECTED 또는 PTY_EXITED로 Session을 닫을 수 있고, closeOnce로 인해 나중의 SERVICE_RESTARTING이 덮어쓰이지 않습니다. 이는 확정된 D-21의 종료 원인 구분과 어긋납니다.
요청: shutdown 시 TerminalSession을 SERVICE_RESTARTING으로 명시 종료한 뒤 SSH Manager를 정리하도록 순서를 보장하고, active PTY+Data WSS를 둔 종료 경쟁 테스트에서 reason 및 ENDED 1회 발행을 검증해 주세요.
검증 요청: 수정 후 새 exact HEAD로 해당 동시성·오류 분류·shutdown 회귀, go test -race 및 관련 CI 결과를 공유해 주세요. 이번 PR의 로컬 OpenStack Evidence와 미검증 KT Cloud/LBT-145 선행조건은 별개로 유지하며, 이번 리뷰가 KT Cloud Acceptance 충족을 의미하지는 않습니다. 수정 사항을 반영하면 새 HEAD 기준으로 재리뷰하겠습니다.
|
|
||
| // x/crypto/ssh channel-open has no context. A stalled setup retires the | ||
| // transport to release all pending reads rather than leaving a goroutine. | ||
| stop := context.AfterFunc(ctx, func() { m.retire(c) }) |
There was a problem hiding this comment.
[P1 · 동일 근본 원인(1/2)] NewSession()이 stall된 상태에서 이 요청의 ctx만 만료돼도 m.retire(c)가 공유 ssh.Client를 종료합니다. 이미 동작 중인 다른 PTY/SFTP Channel이 같이 끊기는지 검증되지 않았습니다. 같은 VM에서 A=활성 I/O, B=stalling NewSession + cancel 회귀를 추가하고, 가능하면 B 실패만 격리해 주세요. 아래 Session.Setup에도 동일 문제가 있습니다.
| func (s *Session) Setup(ctx context.Context, setup func(*ssh.Session) error) error { | ||
| ctx, cancel := context.WithTimeout(ctx, s.manager.config.SetupTimeout) | ||
| defer cancel() | ||
| stop := context.AfterFunc(ctx, func() { s.manager.retire(s.connection) }) |
There was a problem hiding this comment.
[P1 · 동일 근본 원인(2/2)] B의 RequestPty/RequestSubsystem setup timeout/cancel이 s.manager.retire(s.connection)을 호출하여 이미 실행 중인 A의 공유 transport까지 닫습니다. 현재 테스트는 정상 Channel.Close 독립성만 보여줍니다. A가 정상 echo 중, B Setup이 stall/cancel된 뒤 A가 계속 echo 가능한지 회귀와 channel 격리(또는 승인된 명시적 예외)를 요청합니다.
| } | ||
| address, err := a.resolveServerAddress(ctx, target.VMKey, target.ServerID, &target) | ||
| if err != nil { | ||
| return vmaccess.Endpoint{}, safeContextError(ctx, vmaccess.ErrInvalidTarget) |
There was a problem hiding this comment.
[P2] resolveServerAddress()의 Nova GET 실패/Neutron List 오류(예: HTTP 503)까지 여기서 모두 ErrInvalidTarget이 됩니다. 그 결과 OpenSession→handleTerminalOpen이 일시적인 Provider 장애를 INVALID_SESSION으로 회신합니다. 진짜 lab/gen/VM mismatch만 invalid로 유지하고 운영 장애는 안전한 unavailable/retryable 분류로 전파해 주세요. Nova/Neutron 503 vs target mismatch Control 코드 테스트를 부탁드립니다.
| } | ||
| defer connectorApp.TerminalManager.CloseAll("SERVICE_RESTARTING") | ||
| if connectorApp.VMAccess != nil { | ||
| defer connectorApp.VMAccess.Close() |
There was a problem hiding this comment.
[P2] defer는 역순이라 현재 VMAccess.Close()가 TerminalManager.CloseAll("SERVICE_RESTARTING")보다 먼저 실행됩니다. 이때 PTY pump가 SSH 종료를 먼저 관측하면 SSH_DISCONNECTED/PTY_EXITED로 closeOnce가 확정될 수 있습니다. Terminal을 SERVICE_RESTARTING으로 먼저 닫고 SSH를 정리하도록 보장하고, active PTY/Data WSS 상태의 종료 사유·중복 ENDED 회귀를 추가해 주세요.
KT D1 Management Tier 조회와 LBT-147 target affinity 검증을 함께 유지한다. Control 연결 소유권에 맞춘 종료 회귀와 backpressure 관측 안정화를 포함한다.
변경 요약
LBT-147의 VM Access 기반 전체를 구현합니다. 인증된 LabInstance/generation/VM key/Provider Server에 맞는 Management SSH를 연결하고, 같은 VM의 PTY/SFTP가 SSH transport를 공유하되 채널 수명은 독립적으로 관리합니다.
1midle timeout으로 transport를 정리합니다.maind78296f를 feature branch에 통합했습니다. main 자체를 push/merge하지 않았습니다.리뷰 및 추가 감사 수정
CODEOWNER review와 전체 pre-merge audit에서 확인한 문제를 모두 수정했습니다.
elapsed >= budget하한이 정상 deadline을 false fail했습니다. production runtime timeout은 변경하지 않고context.DeadlineExceeded, 기존 상한,export_timeout진단을 검증하도록 test assertion만 수정했습니다.internal/poll.(*FD).Writestack을 요구한 OS 의존 assertion, run38229011898은 server EOF fixture, run38229988462은 남은 EOF fixture를 검출했습니다. runtime stack 문자열 의존과 server의 인위적1KiB receive buffer를 제거하고, 실제 read pause·200ms pending input/resize·750ms shutdown·worker release·backlog drain/EOF·ErrClosed 계약은 유지했습니다._fixtures2개의gofmt누락을 검출해 format 단계에서 실패했습니다. 수정 전 파일과 run/job·해시를 보존하고 fixture를 gofmt만 적용했으며, 실행 source를 current로 과대표기하던 문구도 historical snapshot과 e838 검증으로 분리했습니다. 전체 Go 파일 출력0, overlay compile/cloud0, package21, checksum20, ZIP405 CRC/SHA를 재검증했습니다.최신 main 통합 및 현재 HEAD 재검증 (2026-10-13)
현재 HEAD는
cf19feee51f8cfeaf4557fa11063f0be1e41ddbd입니다.maind78296f의 LBT-158 KT D1 조회 코드와 LBT-147의 LabInstance/generation/VM/Server target affinity 검증을 함께 유지했습니다. 일반 OpenStack은 기존 Management port 경로, KT D1 설정은 명시적 non-shared ACTIVE Management Tier 경로를 사용합니다.TERMINAL_ENDED(SERVICE_RESTARTING)전송 전에 Control socket을 닫는 시도는 상위 회귀가 검출하여 제거했습니다.go vet ./...,go test ./...,go build ./..., 관련 6개 패키지 race 10회, WSS 종료 100회, 앱 종료 10회, SSH backpressure 50회 PASS. Web typecheck/lint/build 및 13파일 163테스트, OpenAPI·JSON Schema 8개·runtime·migration 7개 PASS.labbit-testacceptance PASS: gen1 Provision/SSH/PTTY/SFTP/shared transport/idle retirement, gen2 Reset/new target/stale gen1 거절, 추적 리소스 14개 cleanup, 사후 discoveryservers=0.관련 Jira
최종 검증
최신 Jira LBT-147와 D-18 v5, D-20 v7, D-21 v10, 테스트 전략 v3, MVP v19를 다시 확인했습니다. 전체 PR diff와 consumer, 실패·보안·수명주기, 이전 Evidence까지 독립 reviewer가 별도로 검토했습니다.
finalGoSource(ce02+working manager test)에서 fmt/mod download/vet/unit/build PASS.2885 test/subtest PASS,fail0,7SKIP(cloud opt-in6 + Windows POSIX0600 assertion1). 이후 e838 delta는 별도 전체 local Go·targeted/race·Linux CI로 검증npm ci, typecheck/lint/test/build PASS.13파일163tests. package-lock 변경 없음e8384cb: Linux Go run38231218638의 format/vet/unit/build/PostgreSQL16 integration 전부 PASS. Web38231218734, Contracts38231218756, backend image build38231218620 PASS15b2c30: Go run38233543891 format/vet/unit/build/PostgreSQL16, Web38233544015, Contracts38233543924, backend image build38233543929 전부 PASSda6b670: M2 Provision,M3 Lifecycle,Control WSS full lifecycle,Internet ON/OFF 전부 PASS. 실행별 exact-ID cleanup assertion + 사후 inventory matching0. 이후 Provider/cloud4 production logic은 불변이지만 최신 source 실행으로 바꿔 부르지 않습니다.bd8071d, race PASS214.69s. authenticated/pinned shared SSH, strict target, PTY IO/resize, PTY close 뒤 같은 shared SSH SFTP INIT/VERSION/REALPATH, idle 전후 SSH_CONNECTION identity 변화, gen2 Reset/new target/stale1 거절. tracked14 exact-ID absence + final matching0actual2VMSource의 a5b2d12+working production/test source에서12 parent/subtest PASS + child runControl healthy/unread3PASS. 실제 VM별30 open/exec/close, healthy A와 B exec cancel/deadline 각10, scoped Nova/Neutron503, affinity/404, hostile signer 거절/정상 pin 복구. tracked10 및 보조SG/keypair 정리 + final matching0. 이후 e838의50ms Data wait는 실제 cloud 미재실행이며 loopback/current Linux로 검증gofmt -l출력0, ZIP405 entries CRC/entry SHA, artifact checksums20, known local secret/key+PEM/Bearer scoped scan0, 재현 준비 source/fixture/output guard 및 opt-out overlay compile cloud0 PASS. 원격 format/stack/EOF/Data ENDED 실패와 수정 전 source를 모두 보존초기 실패도 원본에 보존했습니다. 실제 fixture 첫 실행의 child cwd/readiness budget 문제, 최신 main tracing false failure, CRLF gofmt 진단, 수정 전 Data/SSH red와 첫 candidate fixture 오류, 원격 run38224917705/38226445266/38229011898/38229988462를 최종 PASS로 덮어쓰지 않았습니다.
Evidence: 요약, 전체 audit·AC 추적, 전체 파일 검토, 독립 코드 검토, 독립 게시 검토, 실행 요약, 게시 검증.
남은 미검증 범위와 제약
확인
cf19fee원격 CI: Go·PostgreSQL16, Web, Contracts, Backend Image