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)}"})