ES /docs

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/request500 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#

  1. 2026-07-11 01:16:47 KST — integration(7177) OPC access token 정상 발급 (직전 성공)
  2. 2026-07-11 01:34:27 KST — Step 1 OAuth2.0 authentication 시작 (IntegrationRepository#opc_access_token)
  3. 2026-07-11 01:34:29 KST — Step 1 OAuth2.0 token 획득 성공, Step 2 OPC API access token 요청 시작
  4. 2026-07-11 01:34:29 KST — Oracle primavera-us2.oraclecloud.com/primediscovery/apitoken/request500 Internal Server Error 반환 → 이 클러스터의 에러 로그 발생
  5. 2026-07-11 02:12:12 KST 이후 — 별개 OIC flow(tomtest-…)에서 ICS-11388 / HTTP 409 Conflict (Service Instance stopped) 관찰. 본 클러스터와는 다른 오류 시그니처

Error Log#

Datadog Logs

text
[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 호출.

app/repositories/integration_repository.rb:363-374ruby
# 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:170Cupix::HttpClient.get(opc_api_url, headers) 가 Oracle 로 요청을 보내고, RestClient::Exception (500) 가 발생. rescue 절이 응답 body(JSON) 를 파싱해 그대로 로그에 남긴다.

app/operations/opc_operation.rb:197-222ruby
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 쿼리 (재현용):

text
service:cupixworks-api "OpcOperation"

시간대(2026-07-10 15:30 ~ 17:30 UTC) 내 integration(7177) 이력 발췌:

text
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 만):
text
service:cupixworks-api status:error "[OpcOperation] Failed to get OPC API access token" "500 Internal Server Error"
  • OPC token 요청 대비 실패 비율(같은 기간):
text
service:cupixworks-api "[OpcOperation]"
  • 동일 integration 의 반복 실패 감지 (integration id 별):
text
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 규모)