OPC apitoken endpoint returns HTTP 500 — no retry logic
RCA: [OpcOperation] Failed to get OPC API access token — Oracle Primavera 500
Overview#
What Happened#
2026-07-11 01:34:29 KST 에 cupixworks-api 에서 Oracle Primavera Cloud (OPC) integration(id=7177) 의 access token 발급이 실패했다. OpcOperation.get_opc_api_access_token 이 Oracle Integration Cloud (OIC) 엔드포인트를 호출했고, OIC 내부에서 다시 호출한 https://primavera-us2.oraclecloud.com/primediscovery/apitoken/request 가 500 Internal Server Error 를 반환했다. 발생 횟수는 1회이며, 동일 integration 이 사고 17분 전(01:16:47 KST)과 이후에는 정상 응답을 받고 있어 외부(Oracle) 측 순간적 장애로 판단된다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::Parameter (rescue path in OpcOperation.get_opc_api_access_token) |
| exception.message | 500 Internal Server Error |
| top_frame | app/operations/opc_operation.rb:212-216 |
| upstream | https://primavera-us2.oraclecloud.com/primediscovery/apitoken/request (Oracle Primavera Cloud) |
| env | production, us-west-2 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api / Oracle Primavera Integration | 2 (this cluster + sibling fb376a8b-…) |
integration(7177) 의 단일 token 요청 1건 실패. 이후 요청은 정상 |
Timeline#
- 2026-07-11 01:16:47 KST — integration(7177) OPC access token 정상 발급 (직전 성공)
- 2026-07-11 01:34:27 KST — Step 1 OAuth2.0 authentication 시작 (
IntegrationRepository#opc_access_token) - 2026-07-11 01:34:29 KST — Step 1 OAuth2.0 token 획득 성공, Step 2 OPC API access token 요청 시작
- 2026-07-11 01:34:29 KST — Oracle
primavera-us2.oraclecloud.com/primediscovery/apitoken/request가500 Internal Server Error반환 → 이 클러스터의 에러 로그 발생 - 2026-07-11 02:12:12 KST 이후 — 별개 OIC flow(
tomtest-…)에서ICS-11388 / HTTP 409 Conflict(Service Instance stopped) 관찰. 본 클러스터와는 다른 오류 시그니처
Error Log#
[OpcOperation] Failed to get OPC API access token: {"type"=>"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1", "title"=>"Internal Server Error", "detail"=>"", "o:errorCode"=>"", "o:errorDetails"=>[{"type"=>"http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1", "title"=>"Internal Server Error", "o:errorCode"=>"500", "o:errorPath"=>"<![CDATA[InboundJaxrsResponse{context=ClientResponse{method=POST, uri=https://primavera-us2.oraclecloud.com/primediscovery/apitoken/request?scope=http%3A%2F%2Fprimavera-us2.oraclecloud.com%2Fapi, status=500, reason=Server Error}}]]>", "instance"=>"<![CDATA[.The 500 Internal Server Error is a very general HTTP status code that means something has gone wrong on the server side, but the target service could not be more specific on what the exact problem is. Try invoking the target service using cURL. If the problem persists, contact the target service admin.]]>"}]} - error: 500 Internal Server Error
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-07-11 01:34:29 KST
- 최근 발생: 2026-07-11 01:34:29 KST
- 사용자 영향: integration(7177) 의 단일 OPC access token 요청이 실패. 호출 컨텍스트는 사용자-트리거 요청으로 보이며 (17분 전에도 동일 integration 이 정상 응답), 재시도 시 즉시 복구되었을 가능성이 높음. 시스템적 장애는 아님.
Root Cause Summary#
Oracle Primavera Cloud 의 upstream 서비스인 primavera-us2.oraclecloud.com/primediscovery/apitoken/request 가 순간적으로 500 Internal Server Error 를 반환했다. OpcOperation.get_opc_api_access_token 은 이 응답을 정상적으로 rescue 하여 Cupix::Errors::Parameter (code OPB824) 로 변환했지만, 그 전에 Cupix::Logger.error(...) 로 원본 응답 body 를 로깅했기 때문에 error-sweeper 가 클러스터로 감지했다. 코드 자체의 결함이 아니라 외부 dependency 의 transient 실패이며, 같은 integration 이 사고 전후로 정상 동작하고 있어 재발성 이슈가 아니다.
Technical Analysis#
Code Path#
Entry point: app/repositories/integration_repository.rb:365 — Step 2 에서 OpcOperation.get_opc_api_access_token 호출.
# Step 2: Use OAuth token to get OPC API access token
begin
opc_token_response = OpcOperation.get_opc_api_access_token(
opc_api_url: @model.opc['opc_access_token_url'],
oauth_access_token: oauth_access_token
)
Cupix::Logger.info(
"[Integration] Successfully obtained OPC API access token for integration(#{@model.id})",
class: self.class.name,
function: __method__
)
Failure point: app/operations/opc_operation.rb:170 — Cupix::HttpClient.get(opc_api_url, headers) 가 Oracle 로 요청을 보내고, RestClient::Exception (500) 가 발생. rescue 절이 응답 body(JSON) 를 파싱해 그대로 로그에 남긴다.
rescue RestClient::Exception => e
error_body = e.response&.body.to_s
error_response = if html_response?(error_body)
{ 'error' => 'service_error', 'error_description' => 'Service returned an error page' }
else
begin
JSON.parse(error_body)
rescue JSON::ParserError
{ 'error' => error_body.presence || 'unknown_error' }
end
end
error_message = extract_error_message(error_response)
Cupix::Logger.error(
"[OpcOperation] Failed to get OPC API access token: #{error_response} - error: #{e.message}",
class: self.name,
function: __method__
)
raise Cupix::Errors::Parameter.new(
code: ErrorCodes::INVALID_OPC_ACCESS_TOKEN_URL,
reason: "OPC API access token request failed (Step 2): #{error_message}",
message: e.message
)
기대 동작: Oracle 응답이 성공(200)이면 result['accessToken'] 을 반환. 실제 동작: Oracle 이 500 Internal Server Error 를 반환, RestClient 가 예외로 승격, rescue 에서 error 레벨로 로깅. 로직 상 처리는 정확하지만, 외부의 transient 500 이 항상 status:error 로 저장되어 error-tracking 노이즈가 된다.
Log Evidence#
Datadog 쿼리 (재현용):
service:cupixworks-api "OpcOperation"
시간대(2026-07-10 15:30 ~ 17:30 UTC) 내 integration(7177) 이력 발췌:
2026-07-11 01:16:47 KST info [Integration] Successfully obtained OPC API access token for integration(7177)
2026-07-11 01:34:27 KST info [Integration] Getting OPC access token for integration(7177) - Step 1: OAuth2.0 authentication
2026-07-11 01:34:29 KST info [Integration] Successfully obtained OAuth2.0 token for integration(7177) - Step 2: Requesting OPC API access token
2026-07-11 01:34:29 KST error [OpcOperation] Failed to get OPC API access token: {..."o:errorPath"=>".../primediscovery/apitoken/request..., status=500"...} - error: 500 Internal Server Error
2026-07-11 01:34:29 KST error [Integration] Failed to get OPC API access token for integration(7177): 500 Internal Server Error
핵심 관찰:
- Step 1 (OIC OAuth2.0) 은 성공했고 Step 2 (OPC access token) 에서만 실패.
- 실패 응답의
o:errorPath가 Oracle 내부 downstream (primavera-us2.oraclecloud.com/primediscovery/apitoken/request) 의 500 을 나타냄. - 동일 integration(7177) 이 사고 17분 전과 이후 계속 정상. 즉 자격 증명/URL/scope 등 구성 이슈가 아니다.
- 전체 14일 retention 내에서
"500 Internal Server Error"+"primediscovery"조합은 이 1건뿐이다 (service:cupixworks-api "500 Internal Server Error" "primediscovery"쿼리 결과 1건).
동일 시간대의 별개 flow(tomtest-…)에서 관찰된 ICS-11388 / 409 Conflict "Service Instance has been stopped" 는 tenant 가 다른 OIC 인스턴스의 별도 이슈이며, 본 클러스터의 upstream (scp-dpr-construction-1-...) 과는 다른 endpoint 이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Oracle Primavera (primavera-us2.oraclecloud.com/primediscovery/apitoken/request) 의 순간적 upstream 장애 |
응답 body 의 o:errorPath 가 Oracle 내부 downstream 500 을 명시. 동일 integration(7177) 이 사고 17분 전(01:16:47 KST)과 그 이전에 반복 성공. 14일 전체에서 1회 발생. |
— | Confirmed |
| H2 | Cupix 측 잘못된 자격 증명/URL/scope 로 401 또는 400 을 500 으로 오해 | — | 응답에 error/error_description 필드 없이 title=Internal Server Error, o:errorCode="500" 표시. OIC 자체는 Step 1 에서 정상 OAuth token 발급 성공. 같은 자격 증명으로 사고 전/후 성공. |
Rejected |
| H3 | OIC Service Instance stop (ICS-11388) 이 원인 | 같은 시간대에 다른 flow 에서 ICS-11388 (409) 관찰 | 본 클러스터의 URL(scp-dpr-construction-1-...) 은 ICS-11388 을 낸 tomtest-... 인스턴스와 다르며, HTTP 상태도 409 가 아니라 500 이다. 시그니처 불일치 |
Rejected |
| H4 | Cupix 측 network/timeout 이슈 | — | RestClient::Exception 이 아닌 Errno::*/Timeout 이면 line 128 의 StandardError 분기(다른 로그 포맷)로 갔을 것. 실제로는 body 를 가진 500 응답이 도착. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 없음. 단일 발생, 외부 dependency 의 transient 500 이며 코드 결함이 아니다. 사용자 재시도로 이미 복구된 것으로 보인다.
단기 개선 (1주 이내)#
app/operations/opc_operation.rb:212-216의 로그 레벨을 조건부로 완화 검토. Oracle upstream 5xx 는 재시도 가능한 transient 실패로 알려져 있으므로warn레벨이 적절하며,error는 4xx (자격 증명/scope 오류) 및 반복 실패 케이스로 한정. 그렇게 하면 error-tracking 노이즈가 줄고, 진짜 configuration 문제만 알람으로 남는다. (참고: memory 의 "Assess error severity" 패턴)IntegrationRepository#opc_access_token(Step 2) 에 짧은 retry 정책 추가 검토. 예: HTTP 5xx 응답 시 exponential backoff 로 1~2회 재시도. 단일 사용자 액션이 Oracle 의 순간적 500 으로 실패하지 않도록 한다. 정확한 retry 조건과 최대 대기 시간은 팀 정책에 맞춰 결정.
장기 개선 (재발 방지)#
- Oracle Primavera 통합 endpoint 에 대한 별도 SLO/dashboard 를 구축하여, upstream 5xx 율을 추적. 임계치 초과 시에만 알람. 개별 500 로그를 error-tracking 으로 잡는 대신 aggregated 지표로 감시.
- OPB 에러 코드 체계에
UPSTREAM_5XX계열을 추가하여, 파라미터 오류(OPB821-OPB824)와 external 장애를 구분. 현재는 500 이더라도INVALID_OPC_ACCESS_TOKEN_URL(OPB824) 로 뭉뚱그려지고 있어, 오퍼레이터가 원인을 오해할 수 있다.
Monitoring#
- OPC token 실패율 (upstream 5xx 만):
service:cupixworks-api status:error "[OpcOperation] Failed to get OPC API access token" "500 Internal Server Error"
- OPC token 요청 대비 실패 비율(같은 기간):
service:cupixworks-api "[OpcOperation]"
- 동일 integration 의 반복 실패 감지 (integration id 별):
service:cupixworks-api "[Integration] Failed to get OPC API access token"
임계치 제안: 500 Internal Server Error 로그가 5분 내 3건 이상 발생 시 알람 (transient 실패는 무시, 지속적 upstream 장애만 감지).
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (즉시 코드 변경 없음. 단기 개선은 log level 조정 + 선택적 retry — standard 규모)