186 lines
7.2 KiB
Python
186 lines
7.2 KiB
Python
from flask import Blueprint, render_template, request, jsonify
|
|
from backend.models.user import db
|
|
from backend.models.idrac_server import IdracServer
|
|
from backend.models.server_config import ServerConfigSnapshot
|
|
from backend.services.idrac_redfish_client import DellRedfishClient
|
|
import concurrent.futures
|
|
import json
|
|
import hashlib
|
|
from datetime import datetime
|
|
|
|
server_config_bp = Blueprint('server_config', __name__, url_prefix='/server-config')
|
|
|
|
@server_config_bp.route('/')
|
|
def index():
|
|
"""서버 설정 분석 및 비교 페이지"""
|
|
return render_template('server_config.html')
|
|
|
|
def calculate_checksum(data):
|
|
"""JSON 데이터의 SHA256 체크섬 계산"""
|
|
json_str = json.dumps(data, sort_keys=True)
|
|
return hashlib.sha256(json_str.encode('utf-8')).hexdigest()
|
|
|
|
@server_config_bp.route('/api/sync', methods=['POST'])
|
|
def sync_configs():
|
|
"""
|
|
선택한 서버들의 설정을 iDRAC에서 가져와 DB에 저장 (동기화)
|
|
"""
|
|
try:
|
|
data = request.json
|
|
server_ids = data.get('server_ids', [])
|
|
categories = data.get('categories', ['bios', 'idrac', 'system'])
|
|
|
|
if not server_ids:
|
|
return jsonify({'success': False, 'message': '서버를 선택해주세요.'})
|
|
|
|
results = {'success': [], 'failed': []}
|
|
|
|
def process_server(server_id):
|
|
server = IdracServer.query.get(server_id)
|
|
if not server:
|
|
return {'id': server_id, 'status': 'failed', 'message': 'Server not found'}
|
|
|
|
client = DellRedfishClient(server.ip_address, server.username, server.password)
|
|
if not client.check_connection():
|
|
return {'id': server.id, 'name': server.name, 'status': 'failed', 'message': 'Connection failed'}
|
|
|
|
sync_results = []
|
|
|
|
# System Info
|
|
if 'system' in categories:
|
|
sys_data = client.get_system_info_detailed()
|
|
if sys_data:
|
|
save_snapshot(server.id, 'system', sys_data)
|
|
sync_results.append('system')
|
|
|
|
# BIOS Attributes
|
|
if 'bios' in categories:
|
|
bios_data = client.get_bios_attributes_all()
|
|
if bios_data:
|
|
save_snapshot(server.id, 'bios', bios_data)
|
|
sync_results.append('bios')
|
|
|
|
# iDRAC Attributes
|
|
if 'idrac' in categories:
|
|
idrac_data = client.get_idrac_attributes_all()
|
|
if idrac_data:
|
|
save_snapshot(server.id, 'idrac', idrac_data)
|
|
sync_results.append('idrac')
|
|
|
|
return {'id': server.id, 'name': server.name, 'status': 'success', 'synced': sync_results}
|
|
|
|
def save_snapshot(server_id, config_type, data):
|
|
# 기존 스냅샷 확인
|
|
snapshot = ServerConfigSnapshot.query.filter_by(
|
|
server_id=server_id,
|
|
config_type=config_type
|
|
).first()
|
|
|
|
new_hash = calculate_checksum(data)
|
|
|
|
if snapshot:
|
|
# 변경사항이 있는 경우에만 업데이트
|
|
if snapshot.hash_value != new_hash:
|
|
snapshot.data = data
|
|
snapshot.hash_value = new_hash
|
|
snapshot.updated_at = datetime.utcnow() # 명시적 업데이트 시간 갱신
|
|
else:
|
|
snapshot = ServerConfigSnapshot(
|
|
server_id=server_id,
|
|
config_type=config_type,
|
|
data=data,
|
|
hash_value=new_hash
|
|
)
|
|
db.session.add(snapshot)
|
|
|
|
# 트랜잭션은 개별 commit 혹은 묶어서 commit.
|
|
# 여기서는 스레드 안전성을 위해 함수 내에서는 session 조작만 하고
|
|
# 실제 commit은 메인 스레드나 별도 처리 필요하지만, Flask-SQLAlchemy는 scoped_session이므로
|
|
# 각 요청(스레드)마다 세션이 다를 수 있음.
|
|
# 하지만 ThreadPoolExecutor 사용 시 app context 문제가 발생할 수 있음.
|
|
# safe way: return data -> main thread saves. OR push app context.
|
|
|
|
# NOTE: For simplicity in this refactor with ThreadPoolExecutor,
|
|
# we will handle DB operations inside the thread but need app context.
|
|
# See below for implementation adjustment.
|
|
return True
|
|
|
|
# DB 저장을 위해 app context 필요
|
|
from flask import current_app
|
|
app = current_app._get_current_object()
|
|
|
|
def process_with_context(server_id):
|
|
with app.app_context():
|
|
try:
|
|
res = process_server(server_id)
|
|
db.session.commit() # Commit per server
|
|
return res
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
return {'id': server_id, 'status': 'failed', 'message': str(e)}
|
|
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
|
future_to_id = {executor.submit(process_with_context, sid): sid for sid in server_ids}
|
|
|
|
for future in concurrent.futures.as_completed(future_to_id):
|
|
try:
|
|
res = future.result()
|
|
if res['status'] == 'success':
|
|
results['success'].append(res)
|
|
else:
|
|
results['failed'].append(res)
|
|
except Exception as e:
|
|
results['failed'].append({'error': str(e)})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'results': results
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'message': f"Sync Error: {str(e)}"})
|
|
|
|
@server_config_bp.route('/api/fetch', methods=['POST'])
|
|
def fetch_configs():
|
|
"""
|
|
DB에 저장된 서버 설정 스냅샷 조회
|
|
"""
|
|
try:
|
|
data = request.json
|
|
server_ids = data.get('server_ids', [])
|
|
categories = data.get('categories', ['bios', 'idrac', 'system'])
|
|
|
|
if not server_ids:
|
|
return jsonify({'success': False, 'message': '서버를 선택해주세요.'})
|
|
|
|
results = {}
|
|
|
|
for server_id in server_ids:
|
|
server = IdracServer.query.get(server_id)
|
|
if not server:
|
|
continue
|
|
|
|
server_data = {'id': server.id, 'name': server.name, 'ip': server.ip_address}
|
|
|
|
for cat in categories:
|
|
snapshot = ServerConfigSnapshot.query.filter_by(
|
|
server_id=server_id,
|
|
config_type=cat
|
|
).first()
|
|
|
|
if snapshot:
|
|
server_data[cat] = snapshot.data
|
|
server_data[f'{cat}_updated'] = snapshot.updated_at.isoformat()
|
|
else:
|
|
server_data[cat] = None
|
|
|
|
results[server_id] = server_data
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'results': results
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'message': f"Fetch Error: {str(e)}"})
|