ES /docs

[Integration] Failed to get OPC API access token for integration(6652): 409 Conflict

RCA: [Integration] Failed to get OPC API access token for integration(6652): 409 Conflict

Error Log#

Datadog Logs

text
[Integration] Failed to get OPC API access token for integration(6652): 409 Conflict

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 5
  • 최초 발생: 2026-04-06T09:05:15.430Z
  • 최근 발생: 2026-04-06T09:12:36.400Z

Root Cause Summary#

OPC integration(6652)에 대한 API access token 획득 과정에서, Step 1(OAuth2.0 client credentials 인증)은 정상적으로 성공했으나, Step 2(OPC API access token 요청)에서 외부 OPC 서비스가 409 Conflict를 반환했습니다. OPC 서비스의 응답 본문에 포함된 에러 코드 ICS-11388과 메시지 "Service Instance has been stopped and is unavailable at this time"에 따르면, 해당 OPC Service Instance가 중지(stopped) 상태이며 현재 사용 불가능합니다. 이는 Cupix 코드의 버그가 아니라, 외부 OPC 서비스 인스턴스의 상태 문제(고객 또는 OPC 관리자에 의한 인스턴스 중지)에 기인합니다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/api/v1/integrations_controller.rb:35access_token 액션
  • 클라이언트가 POST /api/v1/facilities/y8iq59/integrations/opc/access_token을 호출합니다.
ruby
# app/controllers/api/v1/integrations_controller.rb:35-38
def access_token
  token = repository_instance.access_token
  render_json 200, token
end
  • IntegrationRepository#access_token에서 provider가 opc이므로 opc_access_token 호출 (app/repositories/integration_repository.rb:80-81):
ruby
# app/repositories/integration_repository.rb:76-82
def access_token
  check_failed_state('Failed to get access token')
  # OPC uses 2-step token retrieval process
  if @model.provider == 'opc'
    return opc_access_token
  end
  • Step 1: OpcOperation.get_access_token으로 OAuth2.0 token을 획득합니다 (app/repositories/integration_repository.rb:340-346). 이 단계는 성공합니다.
ruby
# app/repositories/integration_repository.rb:339-346
oauth_token_response = OpcOperation.get_access_token(
  access_token_url: @model.opc['oic_oauth_token_url'],
  client_id: @model.opc['client_id'],
  client_secret: @model.opc['client_secret'],
  scope: @model.opc['scope']
)
oauth_access_token = oauth_token_response['access_token']
  • Step 2 (Failure point): OpcOperation.get_opc_api_access_token에서 OPC API에 Bearer token으로 GET 요청을 보냅니다 (app/operations/opc_operation.rb:157-170):
ruby
# app/operations/opc_operation.rb:157-170
def self.get_opc_api_access_token(opc_api_url:, oauth_access_token:)
  headers = {
    authorization: "Bearer #{oauth_access_token}",
    accept: :json
  }
  # ...
  response = RestClient.get(opc_api_url, headers)
  • OPC 서비스가 409 Conflict로 응답하면, RestClient::Exception이 발생하고 에러 응답이 파싱됩니다 (app/operations/opc_operation.rb:197-222):
ruby
# app/operations/opc_operation.rb:197-222
rescue RestClient::Exception => e
  error_body = e.response&.body.to_s
  error_response = if html_response?(error_body)
                     # ...
                   else
                     begin
                       JSON.parse(error_body)
                     rescue JSON::ParserError
                       { 'error' => error_body.presence || 'unknown_error' }
                     end
                   end
  error_message = extract_error_message(error_response)
  # ...
  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
  )
  • 이 예외는 IntegrationRepository#opc_access_token의 rescue 블록에서 재발생되어 (app/repositories/integration_repository.rb:388-395) 컨트롤러로 전달되며, 최종적으로 HTTP 400으로 응답합니다.

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-api "Failed to get OPC API access token"
Time range: 2026-04-06T08:00:00Z to 2026-04-06T10:00:00Z
text
service:cupixworks-api "integration(6652)"
Time range: 2026-04-06T08:00:00Z to 2026-04-06T10:00:00Z
text
service:cupixworks-api "409 Conflict"
Time range: 2026-04-06T08:00:00Z to 2026-04-06T10:00:00Z

타임라인 (5회 반복 패턴 — 대표적으로 18:12:36 KST 시점):

  1. Step 1 시작 — OAuth2.0 인증 요청:
json
{
  "timestamp": "2026-04-06 18:12:36 KST",
  "status": "info",
  "message": "[Integration] Getting OPC access token for integration(6652) - Step 1: OAuth2.0 authentication",
  "class": "IntegrationRepository",
  "function": "opc_access_token"
}
  1. Step 1 성공 — Step 2 진행:
json
{
  "timestamp": "2026-04-06 18:12:36 KST",
  "status": "info",
  "message": "[Integration] Successfully obtained OAuth2.0 token for integration(6652) - Step 2: Requesting OPC API access token",
  "class": "IntegrationRepository",
  "function": "opc_access_token"
}
  1. Step 2 실패 — OPC API가 409 반환 (OpcOperation 레벨):
json
{
  "timestamp": "2026-04-06 18:12:36 KST",
  "status": "error",
  "message": "[OpcOperation] Failed to get OPC API access token: {\"errorCode\"=>\"ICS-11388\", \"status\"=>\"HTTP 409 Conflict\", \"title\"=>\"Service Instance has been stopped and is unavailable at this time. Please contact your Service Instance administrator for additional information.\", \"type\"=>\"https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10\"} - error: 409 Conflict",
  "class": "OpcOperation",
  "function": "get_opc_api_access_token"
}
  1. Step 2 실패 — IntegrationRepository 레벨 재로깅:
json
{
  "timestamp": "2026-04-06 18:12:36 KST",
  "status": "error",
  "message": "[Integration] Failed to get OPC API access token for integration(6652): 409 Conflict",
  "class": "IntegrationRepository",
  "function": "opc_access_token"
}
  1. 컨트롤러 응답 — HTTP 400 반환:
json
{
  "timestamp": "2026-04-06 18:12:36 KST",
  "status": "info",
  "message": "[400] POST /api/v1/facilities/y8iq59/integrations/opc/access_token (Api::V1::IntegrationsController#access_token)",
  "error": {
    "reason": "OPC API access token request failed (Step 2): Unknown error",
    "code": "OPB824",
    "message": "409 Conflict",
    "class": "Cupix::Errors::Parameter"
  }
}

전체 에러 발생 시간대 (5회):

  • 18:05:15 KST (09:05:15 UTC)
  • 18:09:42 KST (09:09:42 UTC)
  • 18:10:19 KST (09:10:19 UTC)
  • 18:11:26 KST (09:11:26 UTC)
  • 18:12:36 KST (09:12:36 UTC)

약 7분 동안 5회 반복 발생했으며, 모두 동일한 integration(6652)과 facility(y8iq59)에 대한 요청입니다. 사용자가 반복적으로 OPC 연동 기능을 시도한 것으로 보입니다.

Fix Recommendation#

즉시 조치 (Critical)#

이 에러는 외부 OPC Service Instance 중지 상태에 의한 것이므로, Cupix 코드 수정이 필요하지 않습니다. 고객에게 OPC Service Instance 관리자에게 연락하여 인스턴스를 재시작하도록 안내해야 합니다.

단기 개선 (1주 이내)#

  • 에러 메시지 개선 (app/operations/opc_operation.rb:210): extract_error_message가 OPC 응답의 title 필드를 추출하지 못하고 "Unknown error"로 표시됩니다. 컨트롤러 응답에서 reason: "OPC API access token request failed (Step 2): Unknown error"로 나타나는데, 실제 OPC 응답에는 title 필드에 의미 있는 메시지가 있습니다. extract_error_message 메서드(app/operations/opc_operation.rb:29-33)에 title 필드를 fallback 체인에 추가하면 사용자에게 더 명확한 에러 메시지를 제공할 수 있습니다.

  • 409 상태 코드 전용 처리: OPC의 ICS-11388 에러 코드(Service Instance stopped)를 감지하여, OPB824 (Invalid OPC Access Token URL) 대신 더 적절한 에러 코드와 메시지(예: "OPC Service Instance is stopped")를 반환하는 것이 사용자 경험에 도움이 됩니다.

장기 개선 (재발 방지)#

  • Integration 상태 자동 감지: OPC Service Instance가 중지 상태인 경우 integration의 state를 자동으로 업데이트하여, 반복적인 실패 요청을 방지하는 메커니즘을 고려할 수 있습니다.
  • 사용자 알림: OPC 연동 상태가 비정상일 때 프론트엔드에서 사용자에게 명확한 안내 메시지를 표시하여 반복 시도를 줄일 수 있습니다.

Monitoring#

  • OPC 409 에러 빈도를 모니터링하는 Datadog 쿼리:
text
service:cupixworks-api status:error "Failed to get OPC API access token" "409 Conflict"
  • Integration별 에러 빈도 추적:
text
service:cupixworks-api status:error @class:IntegrationRepository @function:opc_access_token

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial

이 에러는 외부 OPC 서비스의 인스턴스 상태 문제로 인한 것이며, Cupix 시스템의 데이터 손상이나 서비스 장애를 유발하지 않습니다. 영향 범위는 해당 integration(6652)을 사용하는 단일 facility(y8iq59)에 한정됩니다. 에러 메시지 개선은 사용자 경험 향상 차원의 선택적 개선 사항입니다.