
ãæ¬¡äžä»£é«éRDBå"Tsurugi"ãTsurugi UDTF ã§ Web ãã©ãŠã¶ããä»®æ³ç«¯æ«ãæäœãã
Tsurugi ã¯ããã©ã³ã¶ã¯ã·ã§ã³åŠçåãã«èšèšããããªãŒãã³ãœãŒã¹ã®ããŒã¿ããŒã¹ã§ããSQL å®è¡ã®æ çµã¿ã®ãªãã§ ãŠãŒã¶ãŒå®çŸ©è¡šå€é¢æ°ïŒUDTFïŒ ãå©çšãããšãã¯ãšãªã®äžéšãšããŠå€éšããã»ã¹ã gRPC ãµãŒãã¹ãžåŠçãå§è²ã§ããŸãã
ãã®æ§è³ªã䜿ããšãTsurugi ãåãªãèšé²å ã§ã¯ãªããå€éšãµãŒãã¹ãåŒã³åºãäžç¶ç¹ãšããŠæ±ããŸããããã§ã¯ãWeb ãã©ãŠã¶äžã®ã¿ãŒããã«ãã Tsurugi UDTF ãçµç±ããŠä»®æ³ç«¯æ«ïŒptyïŒãæäœããå ¥åºåã®èšŒè·¡ã Tsurugi ã«èç©ããæ§æã玹ä»ããŸãã
UDTF ã§äœãã§ããã
éåžžãWeb ã¿ãŒããã«ã¯ãã©ãŠã¶ããã¢ããªã±ãŒã·ã§ã³ãµãŒããžæ¥ç¶ãããããã SSH ã pty ãæäœããŸãããã®æ§æã§ã¯ãç«¯æ«æäœã®å®è¡çµè·¯ãšç£æ»ãã°ã®ä¿åçµè·¯ãåããããããªããŸãã
Tsurugi UDTF ãäžç¶ç¹ã«ãããšãtty äžç¶ãµãŒãã¹ã¯ SQL ãã `Relay` ãšãã衚å€é¢æ°ãåŒã³åºãã ãã§ãã¿ãŒããã«ãµãŒãã¹ã«åŠçãå±ããããŸããå ¥åã®éä¿¡ãåºåã®ååŸãããŒãããŒããšãã£ãç«¯æ«æäœã SQL ã®å®è¡æèã«èŒããããããããDB ãèµ·ç¹ã«å€éšããã»ã¹ãåãããæ§æãäœããŸãã
ãã®æ§æã§å®çŸã§ããããšã¯ãäž»ã«æ¬¡ã® 3 ç¹ã§ãã
Web ãã©ãŠã¶ããä»®æ³ç«¯æ«ãžæ¥ç¶ãããªã¢ãŒã端æ«ã®ããã«å¯Ÿè©±æäœã§ããã
端æ«å ¥åºåã Tsurugi çµç±ã§æµããæäœçµè·¯ãããŒã¿ããŒã¹äžå¿ã«æŽçã§ããã
å ¥åãšåºåã®ã€ãã³ãã Tsurugi ã«ä¿åããåŸããæ€çŽ¢ã»ç¢ºèªã»åçã§ããã
æ¥ç¶æ§æ
Web ãã©ãŠã¶ã¯ WSS ã§ tty äžç¶ãµãŒãã¹ã«æ¥ç¶ããŸããtty äžç¶ãµãŒãã¹ã¯ iceaxe çµç±ã§ Tsurugi ã« SQL ãçºè¡ããTsurugi ã® UDF å®è¡ç°å¢ã gRPC ã¯ã©ã€ã¢ã³ããšããŠã¿ãŒããã«ãµãŒãã¹ãåŒã³åºããŸããã¿ãŒããã«ãµãŒãã¹ã¯ä»®æ³ç«¯æ«ã管çããå ¥åã pty ã«æž¡ããŠåºåãåãåºããŸãã

ãã€ã³ãã¯ãtty äžç¶ãµãŒãã¹ãã¿ãŒããã«ãµãŒãã¹ãçŽæ¥åŒã³åºãã®ã§ã¯ãªããTsurugi UDTF ãçµç±ããŠç«¯æ«æäœãè¡ãããšã§ããããã«ãããç«¯æ«æäœã®å®è¡ãšç£æ»ããŒã¿ã®ä¿åãåãããŒã¿ããŒã¹äžå¿ã®æ§æã«å¯ããããŸãã
SQL ããç«¯æ«æäœãåŒã³åºã
tty äžç¶ãµãŒãã¹ã¯ã1 è¡ã®ãã©ã¡ãŒã¿è¡šã«å¯Ÿã㊠`CROSS APPLY` ã§ UDTF ãåŒã³åºããŸãã`op` ã«ã¯æäœçš®å¥ãæž¡ãã`session_id` ã§ç«¯æ«ã»ãã·ã§ã³ãèå¥ãã`payload` ã«å ¥åããŒã¿ãå ¥ããŸãã
SELECT u.output_data
FROM (VALUES (
CAST(:op AS VARCHAR(32)),
CAST(:session_id AS VARCHAR(500)),
CAST(:payload AS VARBINARY(262144)),
CAST(:max_bytes AS INT)))
AS p(op, session_id, payload, max_bytes)
CROSS APPLY Relay(p.op, p.session_id, p.payload, p.max_bytes) AS u(output_data)ãã® `Relay` ã¯ãgRPC ã® Server Streaming RPC ãšããŠå®çŸ©ããŸããTsurugi ããèŠããš UDTF ã¯è¡šãè¿ã颿°ãªã®ã§ããªã¯ãšã¹ã 1 ä»¶ã«å¯Ÿã㊠0 è¡ä»¥äžã® `RelayRow` ãè¿ãã圢ã«ããŸãã
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.nautilus_technologies.tsurugi_tty.udtf";
option java_outer_classname = "TerminalRelayUdtfProto";
service TerminalRelayUdtf {
rpc Relay(RelayRequest) returns (stream RelayRow);
}
message RelayRequest {
string op = 1;
string session_id = 2;
bytes payload = 3;
uint32 max_bytes = 4;
}
message RelayRow {
bytes output_data = 1;
}`Relay` ã¯ã次ã®ãããªæäœãã¿ãŒããã«ãµãŒãã¹ãžå±ããŸãã

gRPC ã§åŒã³åºãããåŽã§ã¯ã`op` ã«å¿ã㊠pty ã»ãã·ã§ã³ãæäœããŸãã`SEND_INPUT` ã¯å ¥åãæžã蟌ã¿ã`POLL_OUTPUT` ã¯åºåããããšãã ã `RelayRow` ãšããŠè¿ããŸãã
@Override
public void relay(RelayRequest request, StreamObserver<RelayRow> responseObserver) {
String op = request.getOp().toUpperCase(Locale.ROOT);
String sid = request.getSessionId();
try {
switch (op) {
case "HEARTBEAT" -> {
PtySession s = sessions.getOrCreate(sid);
s.touch();
}
case "SEND_INPUT" -> {
PtySession s = sessions.getOrCreate(sid);
s.writeInput(request.getPayload().toByteArray());
}
case "POLL_OUTPUT" -> {
byte[] out = sessions.pollOutput(sid, request.getMaxBytes());
if (out.length > 0) {
responseObserver.onNext(
RelayRow.newBuilder().setOutputData(ByteString.copyFrom(out)).build());
}
}
default -> {
responseObserver.onError(
Status.INVALID_ARGUMENT
.withDescription("unknown op: " + request.getOp())
.asRuntimeException());
return;
}
}
responseObserver.onCompleted();
} catch (IOException e) {
responseObserver.onError(
Status.INTERNAL
.withDescription("relay io error: " + e.getMessage())
.withCause(e)
.asRuntimeException());
}
}ãã® SQL ã®çµæãšããŠè¿ã£ãŠãã `output_data` ã tty äžç¶ãµãŒãã¹ã WSS ã§ãã©ãŠã¶ãžæ»ãããšã§ãå©çšè ã«ã¯éåžžã®ã¿ãŒããã«æäœãšåãããã«èŠããŸãã
ç«¯æ«æäœãèšé²ãã
ç«¯æ«æäœã§ã¯ãäœãå ¥åããã©ã®ãããªåºåãè¿ã£ãããåŸãã確èªã§ããããšãéèŠã§ãããã®æ§æã§ã¯ããã©ãŠã¶ããåãåã£ãå ¥åãšãã¿ãŒããã«ãµãŒãã¹ããæ»ã£ãŠããåºåã Tsurugi ã«èšé²ããŸãã
èšé²ãããã€ãã³ãã¯ãã»ãã·ã§ã³åäœã§äžèЧåã§ããŸããæ¬ææ€çŽ¢ã«ãã£ãŠç¹å®ã®ã³ãã³ããåºåãæ¢ããããä¿åãããåºåãæå»æ å ±ã«æ²¿ã£ãŠåçãããã§ããŸããããã«ãåã€ãã³ãã«é£éããã·ã¥ãä»äžããããšã§ããã°ã®æ¬ èœãäžæŽåãæ€åºã§ããããã«ããŠããŸãã
ã€ãŸã Tsurugi ã¯ãç«¯æ«æäœã®åŒã³åºãå£ã§ãããšåæã«ãæäœèšŒè·¡ãèç©ããŠåç §ããå Žæã«ããªããŸãã
ãã®æ§æã®é¢çœã
Tsurugi UDTF ã䜿ããšãããŒã¿ããŒã¹ã®å€åŽã«ããåŠçã SQL ã®åœ¢ã§åŒã³åºããŸããä»åã®ããã«ç«¯æ«ãµãŒãã¹ãæ¥ç¶ãããšããã©ãŠã¶ããã®ãªã¢ã«ã¿ã€ã ãªç«¯æ«æäœã§ãã£ãŠããããŒã¿ããŒã¹ãäžå¿ã«çµè·¯ãèšèšã§ããŸãã
ãã®èãæ¹ã¯ãç«¯æ«æäœã ãã«éããŸãããSQL ããå€éšãµãŒãã¹ãåŒã³åºãããã®çµæã衚ãšããŠåãåããå¿ èŠãªèšŒè·¡ãåãããŒã¿ããŒã¹ã«æ®ãããšãã圢ã«ã§ãããããç£æ»æ§ãæ±ããããå€éšåŠçãšã®é£æºã«å¿çšã§ããŸãã
Tsurugi UDTF ã¯ãããŒã¿ããŒã¹ã®äžã ãã§å®çµããªãåŠçããããŒã¿ããŒã¹ã®å®è¡æèã«èªç¶ã«åã蟌ãããã®ä»çµã¿ãšããŠäœ¿ããŸããWeb ãã©ãŠã¶ãtty äžç¶ãµãŒãã¹ãã¿ãŒããã«ãµãŒãã¹ãä»®æ³ç«¯æ«ãã€ãªãä»åã®æ§æã¯ããã®äžäŸã§ãã
ãã®ä»ã®å"Tsurugi"ã䜿ãéã«åœ¹ç«ã€Tipséã¯ã³
次äžä»£é«éRDBå"Tsurugi"ã¯ããªãŒãã³ãœãŒã¹ã§å
¬éäžã§ãã
ãã²ããŠã³ããŒãããŠã¿ãŠãè§Šã£ãŠã¿ãŠãã ããã