Python 상태 판정
85분 안팎
학습 목표
표준 입력 JSON의 서비스·HTTP·블록·inode 상태를 판정하고 결정적인 JSON 결과를 출력합니다.
개념
관측과 판정을 분리하는 이유
어제는 서비스가 active였지만 HTTP 응답이 500이었습니다. 두 값을 같은 정상 표시로 합치면 어떤 계층에서 문제가 있었는지 알 수 없습니다. 이번 레슨은 서비스·HTTP·블록·inode 관측 JSON을 받아 하나의 결정적인 판정 JSON으로 바꾸는 순수 함수를 작성합니다. 함수는 명령이나 네트워크에 접근하지 않습니다. 같은 입력이면 같은 출력이 나오므로 VM을 고장 내지 않고도 여러 장애와 경계 입력을 검증할 수 있습니다.
입력 계약을 먼저 적습니다
최상위는 객체이고 service, http, blocks, inodes, errors 다섯 키만 가집니다. service는 active·inactive·failed·unknown 중 하나입니다. http는 0부터 599 사이 정수이며 0은 관측하지 못한 경우의 자리 값입니다. blocks와 inodes는 0부터 100 사이 정수 백분율입니다. errors는 허용된 관측 오류 코드의 배열입니다. 추가 키까지 거부하는 이유는 토큰이나 원문 응답이 조용히 판정 경로로 들어오는 것을 막기 위해서입니다.
JSON 문법과 의미 검사는 다릅니다
json.loads가 성공해도 값이 올바른 관측이라는 뜻은 아닙니다. 배열, 빈 객체, http가 문자열인 객체, 음수 사용률 모두 유효 JSON일 수 있지만 이번 계약에는 맞지 않습니다. 먼저 JSON을 읽고 이어 자료형과 범위를 검사합니다. 문법 실패는 JSONDecodeError 계열 ValueError로 처리하고 입력 원문은 출력하지 않습니다. 오류 메시지를 그대로 보여주는 대신 reasons에 INPUT을 넣어 담당자가 입력 생성기를 조사하게 합니다.
논리값을 숫자로 인정하지 않습니다
Python에서 bool은 int의 하위 형식이라 isinstance(True, int)는 참입니다. 사용률 true를 1로 받아들이면 타입이 틀린 입력이 정상으로 통과합니다. 여기서는 type(value) is int로 정수만 허용합니다. 소수 79.5도 받지 않으며 수집 단계에서 정수 백분율을 제공해야 합니다. 다른 환경에서 더 정밀한 비율이 필요하면 관측 단위와 비교 기준을 먼저 바꾸고 이 함수와 테스트를 함께 수정합니다.
세 가지 상태의 의미
OK는 모든 유효 관측이 기준을 만족했다는 뜻입니다. FAIL은 관측에 성공했지만 서비스나 HTTP 또는 사용률이 계약을 위반했다는 뜻입니다. UNKNOWN은 입력 계약 또는 관측이 불완전해 이 판정으로 정상 여부를 확정할 수 없다는 뜻입니다. UNKNOWN을 정상으로 처리하면 점검 자체가 고장 난 시기를 놓칩니다. 반대로 UNKNOWN을 서비스 중단으로 단정하면 관측 도구의 오류와 대상 장애를 구분할 수 없습니다.
판정 순서를 코드에 고정합니다
먼저 최상위와 필드를 검사해 잘못된 입력은 UNKNOWN과 INPUT으로 반환합니다. 다음으로 errors가 비어 있지 않거나 service가 unknown이면 UNKNOWN과 OBSERVATION으로 반환합니다. 마지막으로 완전한 입력에서 네 정상 기준을 비교합니다. 여러 문제가 있으면 모든 원인을 서비스·HTTP·블록·inode 순으로 담습니다. 첫 실패만 반환하지 않아 데이터 저장 부족과 HTTP 실패가 함께 나타난 상황을 한 번에 읽을 수 있습니다.
사용률 경계는 과제의 정책입니다
블록과 inode는 80% 이상이면 FAIL로 정했습니다. 따라서 79는 정상 범위이고 정확히 80은 경고이며 100도 경고입니다. 이 기준은 모든 서비스의 보편적 운영 기준이 아니라 작은 실습실의 비교 계약입니다. 실제 임계값은 증가 속도·남은 절대 공간·확장 소요와 함께 정해야 합니다. 이번 함수에서는 두 사용률의 단위를 섞거나 하나만 검사하지 않고 같은 명시 기준을 적용합니다.
서비스 상태와 HTTP는 독립 검사입니다
active라고 해도 앱이 준비되지 않았거나 요청 처리에 오류가 있을 수 있습니다. HTTP가 200이어도 systemd가 관리하는 서비스가 아닌 다른 프로세스가 응답했을 수 있습니다. 두 조건을 각각 검사하고 이 과제의 정상은 service=active와 http=200을 모두 만족해야 합니다. /health 200은 앞 모듈 /items 데이터 복원 검증을 대체하지 않습니다. 복구 상태는 미션에서 앞 모듈 기록과 별도 백업 검사를 연결합니다.
관측 실패에는 저장된 숫자를 믿지 않습니다
errors에 TIMEOUT이 있고 http=200이 남아 있어도 이 입력은 UNKNOWN입니다. 수집기가 일부 값을 채운 뒤 다른 명령에 실패했을 수 있기 때문입니다. 이 과제는 부분 관측의 원인을 INPUT 또는 OBSERVATION으로 요약하고 원시 JSON은 별도로 확인합니다. 실패 원인을 세밀하게 전달하려면 추가 계약이 필요하지만 오류가 있는 숫자를 정상 근거로 사용하지 않는 순서는 유지합니다.
결정적인 출력은 비교 비용을 줄입니다
결과 객체는 status와 reasons만 가집니다. json.dumps에 sort_keys=True와 간결한 separators를 주어 출력 키 순서와 공백 형식을 고정합니다. 이유 배열은 미리 정한 순서로 누적합니다. 같은 의미의 JSON이 실행마다 다르게 출력되면 사람이 변경 내용을 읽기 어렵습니다. 판정 출력에 현재 시각을 넣지 않고 기록 단계가 시각을 추가하도록 나누면 함수 테스트도 일정한 문자열로 비교할 수 있습니다.
예외 범위는 입력 읽기에 한정합니다
parse는 문자열을 JSON으로 읽어 evaluate에 전달하고 ValueError 또는 TypeError를 INPUT으로 변환합니다. 모든 Exception을 잡아 OK를 반환하는 처리는 하지 않습니다. 입력 문제를 예상 가능한 결과로 바꾸되 프로그래밍 결함을 조용히 감추지 않는 것이 목적입니다. 반환 JSON에 원문 문자열이나 예외 메시지가 포함되지 않으므로 손상 입력에 실습용 가짜 토큰이 들어 있어도 판정 로그에는 남지 않습니다.
브라우저 테스트는 OS 상태를 바꾸지 않습니다
실습은 표준 입력 전체를 읽어 결과 JSON 한 줄을 출력합니다. starter에는 입력 읽기와 고정 UNKNOWN 출력이 있고 evaluate의 계약을 직접 완성합니다. 정상 79, 정확한 80, inode만 80, 서비스와 HTTP 동시 실패, 오류 배열, 손상 JSON, 문자열 숫자, 논리값과 범위 이탈을 테스트합니다. 입력의 모든 키를 가정하고 바로 비교하기보다 자료형을 확인해야 손상 입력에서도 지정 결과를 출력할 수 있습니다.
실패한 채점을 디버깅합니다
정상 테스트만 맞고 80 테스트가 틀리면 비교 연산이 초과인지 이상인지 확인합니다. inode만 실패한 사례가 OK면 블록 검사만 구현했을 가능성이 있습니다. 손상 입력에서 traceback이 나오면 JSON 읽기 예외 경계를 확인합니다. 예상 reasons 순서가 다르면 set으로 원인을 모았는지 살펴봅니다. 출력 앞에 디버그 문장이 있으면 제거하고 stdout을 결과 JSON 전용으로 사용합니다.
함수가 끝난 지점에서 할 수 있는 설명
완료한 사람은 입력 손상과 실제 대상 실패를 구분하고 경계 80에서 어떤 원인이 생기는지 설명할 수 있어야 합니다. 표준 입력의 새 JSON에도 출력 계약을 지키며 서비스와 두 저장 지표를 빠짐없이 평가합니다. 파일 입출력의 with와 JSON 직렬화 일반 문법은 더 읽기를 활용합니다. 다음 레슨에서는 이 판정에 실패 전달 경로를 붙이되 관측·판정 함수에 네트워크 책임을 섞지 않습니다.
따라하기
정수와 논리값을 구분
자료형 검사가 왜 필요한지 직접 비교합니다.
print(isinstance(True, int))
print(type(True) is int)실행 결과
True False
정확한 경계 비교
실습 기준은 80 이상입니다. 수집 단위는 정수 백분율입니다.
for used in (79, 80, 100):
print(used, "FAIL" if used >= 80 else "OK")실행 결과
79 OK 80 FAIL 100 FAIL
JSON 손상을 결과로 변환
이 코드는 문법 실패만 다룹니다. 실습 함수에는 필드 의미 검사도 추가합니다.
import json
try:
json.loads("{broken")
except ValueError:
print(json.dumps({"status":"UNKNOWN","reasons":["INPUT"]},sort_keys=True,separators=(",",":")))실행 결과
{"reasons":["INPUT"],"status":"UNKNOWN"}
독립 실패 원인을 누적
한 입력에서 여러 원인을 순서대로 남기는 방법을 확인합니다.
reasons=[]
for failed, reason in [(True,"SERVICE"),(True,"HTTP"),(False,"BLOCKS"),(True,"INODES")]:
if failed:
reasons.append(reason)
print(reasons)실행 결과
['SERVICE', 'HTTP', 'INODES']
확인 문제
실습
표준 입력 JSON을 읽어 위 계약의 판정 JSON 한 줄을 출력합니다. 다섯 키·자료형·범위·오류 배열을 검증하고 80 이상 경계와 네 실패 원인 순서를 구현합니다. 잘못된 JSON은 UNKNOWN/INPUT, 관측 불명은 UNKNOWN/OBSERVATION입니다. 입력 원문은 출력하지 않습니다.
모범 답안
import json, sys
def evaluate(v):
required = {'service', 'http', 'blocks', 'inodes', 'errors'}
if not isinstance(v, dict) or set(v) != required:
return {'status':'UNKNOWN', 'reasons':['INPUT']}
if v['service'] not in ('active','inactive','failed','unknown') or not isinstance(v['errors'],list):
return {'status':'UNKNOWN', 'reasons':['INPUT']}
if any(type(v[k]) is not int or not 0 <= v[k] <= top for k,top in [('http',599),('blocks',100),('inodes',100)]):
return {'status':'UNKNOWN', 'reasons':['INPUT']}
if any(x not in ('SERVICE','HTTP','BLOCKS','INODES','TIMEOUT') for x in v['errors']):
return {'status':'UNKNOWN', 'reasons':['INPUT']}
if v['errors'] or v['service']=='unknown':
return {'status':'UNKNOWN', 'reasons':['OBSERVATION']}
reasons=[]
if v['service']!='active': reasons.append('SERVICE')
if v['http']!=200: reasons.append('HTTP')
if v['blocks']>=80: reasons.append('BLOCKS')
if v['inodes']>=80: reasons.append('INODES')
return {'status':'FAIL' if reasons else 'OK','reasons':reasons}
def parse(text):
try: return evaluate(json.loads(text))
except (ValueError, TypeError): return {'status':'UNKNOWN','reasons':['INPUT']}
print(json.dumps(parse(sys.stdin.read()),sort_keys=True,separators=(',',':')))
더 읽기
면접 질문
- 점검 스크립트가 실패를 알리는 방식을 설명합니다.