@@ -20,6 +20,10 @@ export ADB_PASSWORD="" # 비워두면
|
|||||||
# 편의: sqlplus 한 줄 connect 문자열 (자동 합성됨; 직접 안 건드려도 됨)
|
# 편의: sqlplus 한 줄 connect 문자열 (자동 합성됨; 직접 안 건드려도 됨)
|
||||||
# export ADB_CONN="${ADB_USER}/${ADB_PASSWORD}@${ADB_TNS}"
|
# export ADB_CONN="${ADB_USER}/${ADB_PASSWORD}@${ADB_TNS}"
|
||||||
|
|
||||||
|
# --- (2b) Spring Boot 백오피스 ---
|
||||||
|
export BACKOFFICE_ORDS_BASE_URL="" # 예: https://<actual-ords-domain>/ords
|
||||||
|
export BACKOFFICE_ORDS_TIMEOUT_SECONDS="10"
|
||||||
|
|
||||||
# --- (3) 데모용 ADB 엔드유저 비밀번호 (sql/adb/07_end_users.sql 에서 사용) ---
|
# --- (3) 데모용 ADB 엔드유저 비밀번호 (sql/adb/07_end_users.sql 에서 사용) ---
|
||||||
# ADB 비번 정책: 12자 이상, 대/소/숫자/특수 조합.
|
# ADB 비번 정책: 12자 이상, 대/소/숫자/특수 조합.
|
||||||
# 4명의 데모 유저:
|
# 4명의 데모 유저:
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public enum ProbeStatus {
|
|||||||
OBJECT_DISABLED,
|
OBJECT_DISABLED,
|
||||||
INVALID_TOKEN,
|
INVALID_TOKEN,
|
||||||
OBJECT_NOT_ACCESSIBLE,
|
OBJECT_NOT_ACCESSIBLE,
|
||||||
|
ORDS_NOT_CONFIGURED,
|
||||||
|
ORDS_UNAVAILABLE,
|
||||||
ORDS_TIMEOUT,
|
ORDS_TIMEOUT,
|
||||||
INVALID_ORDS_RESPONSE,
|
INVALID_ORDS_RESPONSE,
|
||||||
UNKNOWN_ERROR
|
UNKNOWN_ERROR
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ public class OrdsProbeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ProbeResult runProbe(ProbeCommand command) {
|
public ProbeResult runProbe(ProbeCommand command) {
|
||||||
|
if (properties.ords().baseUrl() == null || properties.ords().baseUrl().isBlank()) {
|
||||||
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
|
ProbeStatus.ORDS_NOT_CONFIGURED,
|
||||||
|
ProbeStatus.ORDS_NOT_CONFIGURED.name(),
|
||||||
|
"ORDS base URL이 설정되지 않았습니다. 실제 ORDS 도메인을 BACKOFFICE_ORDS_BASE_URL에 설정한 뒤 백오피스를 재시작하세요."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
BearerTokenRecord token = tokenService.findById(command.keyId());
|
BearerTokenRecord token = tokenService.findById(command.keyId());
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return auditAndReturn(command, ProbeResult.blocked(
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
@@ -102,8 +110,8 @@ public class OrdsProbeService {
|
|||||||
return auditAndReturn(command, ProbeResult.blocked(
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
status, status.name(), trimMessage(e.getResponseBodyAsString())));
|
status, status.name(), trimMessage(e.getResponseBodyAsString())));
|
||||||
} catch (ResourceAccessException e) {
|
} catch (ResourceAccessException e) {
|
||||||
ProbeStatus status = errorClassifier.isTimeout(e) ? ProbeStatus.ORDS_TIMEOUT : ProbeStatus.UNKNOWN_ERROR;
|
ProbeStatus status = classifyResourceAccess(e);
|
||||||
return auditAndReturn(command, ProbeResult.blocked(status, status.name(), e.getMessage()));
|
return auditAndReturn(command, ProbeResult.blocked(status, status.name(), resourceAccessMessage(status, e)));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return auditAndReturn(command, ProbeResult.blocked(
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
ProbeStatus.INVALID_ORDS_RESPONSE, "INVALID_ORDS_RESPONSE", e.getMessage()));
|
ProbeStatus.INVALID_ORDS_RESPONSE, "INVALID_ORDS_RESPONSE", e.getMessage()));
|
||||||
@@ -193,6 +201,29 @@ public class OrdsProbeService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ProbeStatus classifyResourceAccess(ResourceAccessException exception) {
|
||||||
|
if (errorClassifier.isTimeout(exception)) {
|
||||||
|
return ProbeStatus.ORDS_TIMEOUT;
|
||||||
|
}
|
||||||
|
if (errorClassifier.isUnavailable(exception)) {
|
||||||
|
return ProbeStatus.ORDS_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
return ProbeStatus.UNKNOWN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resourceAccessMessage(ProbeStatus status, ResourceAccessException exception) {
|
||||||
|
String detail = trimMessage(exception.getMessage());
|
||||||
|
if (status == ProbeStatus.ORDS_UNAVAILABLE) {
|
||||||
|
return "ORDS 서버에 연결할 수 없습니다. BACKOFFICE_ORDS_BASE_URL의 실제 ORDS 도메인, ORDS 실행 상태, 네트워크 접근을 확인하세요. 상세: "
|
||||||
|
+ detail;
|
||||||
|
}
|
||||||
|
if (status == ProbeStatus.ORDS_TIMEOUT) {
|
||||||
|
return "ORDS 응답 시간이 초과되었습니다. ORDS 상태와 BACKOFFICE_ORDS_TIMEOUT_SECONDS 설정을 확인하세요. 상세: "
|
||||||
|
+ detail;
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
private String trimMessage(String body) {
|
private String trimMessage(String body) {
|
||||||
if (body == null) {
|
if (body == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.cloudhandson.vpdbackoffice.service;
|
package com.cloudhandson.vpdbackoffice.service;
|
||||||
|
|
||||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
|
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import org.springframework.http.HttpStatusCode;
|
import org.springframework.http.HttpStatusCode;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.client.ResourceAccessException;
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
@@ -24,9 +27,19 @@ public class ProbeErrorClassifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTimeout(ResourceAccessException exception) {
|
public boolean isTimeout(ResourceAccessException exception) {
|
||||||
Throwable cause = exception;
|
return hasCause(exception, SocketTimeoutException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUnavailable(ResourceAccessException exception) {
|
||||||
|
return hasCause(exception, ConnectException.class)
|
||||||
|
|| hasCause(exception, NoRouteToHostException.class)
|
||||||
|
|| hasCause(exception, UnknownHostException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCause(Throwable throwable, Class<? extends Throwable> type) {
|
||||||
|
Throwable cause = throwable;
|
||||||
while (cause != null) {
|
while (cause != null) {
|
||||||
if (cause instanceof SocketTimeoutException) {
|
if (type.isInstance(cause)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
cause = cause.getCause();
|
cause = cause.getCause();
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ backoffice:
|
|||||||
token:
|
token:
|
||||||
max-days: ${BACKOFFICE_TOKEN_MAX_DAYS:365}
|
max-days: ${BACKOFFICE_TOKEN_MAX_DAYS:365}
|
||||||
ords:
|
ords:
|
||||||
base-url: ${BACKOFFICE_ORDS_BASE_URL:http://localhost:8081/ords}
|
base-url: ${BACKOFFICE_ORDS_BASE_URL:}
|
||||||
timeout-seconds: ${BACKOFFICE_ORDS_TIMEOUT_SECONDS:10}
|
timeout-seconds: ${BACKOFFICE_ORDS_TIMEOUT_SECONDS:10}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package com.cloudhandson.vpdbackoffice.service;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
|
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
|
|
||||||
class ProbeErrorClassifierTest {
|
class ProbeErrorClassifierTest {
|
||||||
|
|
||||||
@@ -27,4 +30,20 @@ class ProbeErrorClassifierTest {
|
|||||||
assertThat(classifier.classify(HttpStatus.UNAUTHORIZED, "unauthorized"))
|
assertThat(classifier.classify(HttpStatus.UNAUTHORIZED, "unauthorized"))
|
||||||
.isEqualTo(ProbeStatus.INVALID_TOKEN);
|
.isEqualTo(ProbeStatus.INVALID_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsOrdsConnectionRefused() {
|
||||||
|
assertThat(classifier.isUnavailable(new ResourceAccessException(
|
||||||
|
"I/O error",
|
||||||
|
new ConnectException("Connection refused")
|
||||||
|
))).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsOrdsTimeout() {
|
||||||
|
assertThat(classifier.isTimeout(new ResourceAccessException(
|
||||||
|
"I/O error",
|
||||||
|
new SocketTimeoutException("Read timed out")
|
||||||
|
))).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user