Files
iDRAC_Info/backend/routes/idrac_routes_base.py
2025-10-21 20:29:39 +09:00

670 lines
22 KiB
Python

"""
Dell iDRAC 멀티 서버 펌웨어 관리 라우트
backend/routes/idrac_routes.py
- CSRF 보호 제외 추가
"""
from flask import Blueprint, render_template, request, jsonify, current_app
from werkzeug.utils import secure_filename
import os
from datetime import datetime
from backend.services.idrac_redfish_client import DellRedfishClient
from backend.models.idrac_server import IdracServer, db
from flask_socketio import emit
import threading
# Blueprint 생성
idrac_bp = Blueprint('idrac', __name__, url_prefix='/idrac')
# 설정
UPLOAD_FOLDER = 'uploads/firmware'
ALLOWED_EXTENSIONS = {'exe', 'bin'}
MAX_FILE_SIZE = 500 * 1024 * 1024 # 500MB
def allowed_file(filename):
"""허용된 파일 형식 확인"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# ========================================
# 메인 페이지
# ========================================
@idrac_bp.route('/')
def index():
"""iDRAC 멀티 서버 관리 메인 페이지"""
return render_template('idrac_firmware.html')
# ========================================
# 서버 관리 API
# ========================================
@idrac_bp.route('/api/servers', methods=['GET'])
def get_servers():
"""등록된 서버 목록 조회"""
try:
group = request.args.get('group') # 그룹 필터
query = IdracServer.query.filter_by(is_active=True)
if group and group != 'all':
query = query.filter_by(group_name=group)
servers = query.order_by(IdracServer.name).all()
return jsonify({
'success': True,
'servers': [s.to_dict() for s in servers]
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/servers', methods=['POST'])
def add_server():
"""서버 추가"""
try:
data = request.json
# 필수 필드 확인
if not all([data.get('name'), data.get('ip_address'), data.get('password')]):
return jsonify({
'success': False,
'message': '필수 필드를 모두 입력하세요 (서버명, IP, 비밀번호)'
})
# 중복 IP 확인
existing = IdracServer.query.filter_by(ip_address=data['ip_address']).first()
if existing:
return jsonify({
'success': False,
'message': f'이미 등록된 IP입니다: {data["ip_address"]}'
})
# 서버 생성
server = IdracServer(
name=data['name'],
ip_address=data['ip_address'],
username=data.get('username', 'root'),
password=data['password'],
group_name=data.get('group_name'),
location=data.get('location'),
model=data.get('model'),
notes=data.get('notes')
)
db.session.add(server)
db.session.commit()
return jsonify({
'success': True,
'message': f'서버 {server.name} 추가 완료',
'server': server.to_dict()
})
except Exception as e:
db.session.rollback()
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/servers/<int:server_id>', methods=['PUT'])
def update_server(server_id):
"""서버 정보 수정"""
try:
server = IdracServer.query.get(server_id)
if not server:
return jsonify({
'success': False,
'message': '서버를 찾을 수 없습니다'
})
data = request.json
# 업데이트
if 'name' in data:
server.name = data['name']
if 'ip_address' in data:
server.ip_address = data['ip_address']
if 'username' in data:
server.username = data['username']
if 'password' in data:
server.password = data['password']
if 'group_name' in data:
server.group_name = data['group_name']
if 'location' in data:
server.location = data['location']
if 'model' in data:
server.model = data['model']
if 'notes' in data:
server.notes = data['notes']
db.session.commit()
return jsonify({
'success': True,
'message': '서버 정보 수정 완료',
'server': server.to_dict()
})
except Exception as e:
db.session.rollback()
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/servers/<int:server_id>', methods=['DELETE'])
def delete_server(server_id):
"""서버 삭제 (소프트 삭제)"""
try:
server = IdracServer.query.get(server_id)
if not server:
return jsonify({
'success': False,
'message': '서버를 찾을 수 없습니다'
})
server.is_active = False
db.session.commit()
return jsonify({
'success': True,
'message': f'서버 {server.name} 삭제 완료'
})
except Exception as e:
db.session.rollback()
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/groups', methods=['GET'])
def get_groups():
"""등록된 그룹 목록"""
try:
groups = db.session.query(IdracServer.group_name)\
.filter(IdracServer.is_active == True)\
.filter(IdracServer.group_name.isnot(None))\
.distinct()\
.all()
group_list = [g[0] for g in groups if g[0]]
return jsonify({
'success': True,
'groups': group_list
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
# ========================================
# 연결 및 상태 확인 API
# ========================================
@idrac_bp.route('/api/servers/<int:server_id>/test', methods=['POST'])
def test_connection(server_id):
"""단일 서버 연결 테스트"""
try:
server = IdracServer.query.get(server_id)
if not server:
return jsonify({
'success': False,
'message': '서버를 찾을 수 없습니다'
})
client = DellRedfishClient(server.ip_address, server.username, server.password)
if client.check_connection():
server.status = 'online'
server.last_connected = datetime.utcnow()
db.session.commit()
return jsonify({
'success': True,
'message': f'{server.name} 연결 성공'
})
else:
server.status = 'offline'
db.session.commit()
return jsonify({
'success': False,
'message': f'{server.name} 연결 실패'
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/servers/test-multi', methods=['POST'])
def test_connections_multi():
"""다중 서버 일괄 연결 테스트"""
try:
data = request.json
server_ids = data.get('server_ids', [])
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
try:
client = DellRedfishClient(server.ip_address, server.username, server.password)
if client.check_connection():
server.status = 'online'
server.last_connected = datetime.utcnow()
results.append({
'server_id': server.id,
'server_name': server.name,
'success': True,
'message': '연결 성공'
})
else:
server.status = 'offline'
results.append({
'server_id': server.id,
'server_name': server.name,
'success': False,
'message': '연결 실패'
})
except Exception as e:
server.status = 'offline'
results.append({
'server_id': server.id,
'server_name': server.name,
'success': False,
'message': str(e)
})
db.session.commit()
success_count = sum(1 for r in results if r['success'])
return jsonify({
'success': True,
'results': results,
'summary': {
'total': len(results),
'success': success_count,
'failed': len(results) - success_count
}
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/servers/<int:server_id>/firmware', methods=['GET'])
def get_server_firmware(server_id):
"""단일 서버 펌웨어 버전 조회"""
try:
server = IdracServer.query.get(server_id)
if not server:
return jsonify({
'success': False,
'message': '서버를 찾을 수 없습니다'
})
client = DellRedfishClient(server.ip_address, server.username, server.password)
inventory = client.get_firmware_inventory()
# BIOS 버전 업데이트
for item in inventory:
if 'BIOS' in item.get('Name', ''):
server.current_bios = item.get('Version')
break
db.session.commit()
return jsonify({
'success': True,
'data': inventory
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
# ========================================
# 멀티 서버 펌웨어 업로드 API
# ========================================
@idrac_bp.route('/api/upload-multi', methods=['POST'])
def upload_multi():
"""다중 서버에 펌웨어 일괄 업로드"""
try:
if 'file' not in request.files:
return jsonify({
'success': False,
'message': '파일이 없습니다'
})
file = request.files['file']
server_ids_str = request.form.get('server_ids')
if not server_ids_str:
return jsonify({
'success': False,
'message': '서버를 선택하세요'
})
server_ids = [int(x) for x in server_ids_str.split(',')]
if file.filename == '':
return jsonify({
'success': False,
'message': '파일이 선택되지 않았습니다'
})
if not allowed_file(file.filename):
return jsonify({
'success': False,
'message': '허용되지 않은 파일 형식입니다 (.exe, .bin만 가능)'
})
# 로컬에 임시 저장
filename = secure_filename(file.filename)
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
local_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(local_path)
# 백그라운드 스레드로 업로드 시작
from backend.services import watchdog_handler
socketio = watchdog_handler.socketio
def upload_to_servers():
results = []
for idx, server_id in enumerate(server_ids):
server = IdracServer.query.get(server_id)
if not server:
continue
try:
# 진행 상황 전송
if socketio:
socketio.emit('upload_progress', {
'server_id': server.id,
'server_name': server.name,
'status': 'uploading',
'progress': 0,
'message': '업로드 시작...'
})
server.status = 'updating'
db.session.commit()
client = DellRedfishClient(server.ip_address, server.username, server.password)
result = client.upload_firmware_staged(local_path)
if result['success']:
server.status = 'online'
server.last_updated = datetime.utcnow()
results.append({
'server_id': server.id,
'server_name': server.name,
'success': True,
'job_id': result['job_id'],
'message': '업로드 완료'
})
if socketio:
socketio.emit('upload_progress', {
'server_id': server.id,
'server_name': server.name,
'status': 'completed',
'progress': 100,
'message': '업로드 완료',
'job_id': result['job_id']
})
else:
server.status = 'online'
results.append({
'server_id': server.id,
'server_name': server.name,
'success': False,
'message': result.get('message', '업로드 실패')
})
if socketio:
socketio.emit('upload_progress', {
'server_id': server.id,
'server_name': server.name,
'status': 'failed',
'progress': 0,
'message': result.get('message', '업로드 실패')
})
db.session.commit()
except Exception as e:
server.status = 'online'
db.session.commit()
results.append({
'server_id': server.id,
'server_name': server.name,
'success': False,
'message': str(e)
})
if socketio:
socketio.emit('upload_progress', {
'server_id': server.id,
'server_name': server.name,
'status': 'failed',
'progress': 0,
'message': str(e)
})
# 최종 결과 전송
if socketio:
success_count = sum(1 for r in results if r['success'])
socketio.emit('upload_complete', {
'results': results,
'summary': {
'total': len(results),
'success': success_count,
'failed': len(results) - success_count
}
})
# 스레드 시작
thread = threading.Thread(target=upload_to_servers)
thread.daemon = True
thread.start()
return jsonify({
'success': True,
'message': f'{len(server_ids)}대 서버에 업로드 시작',
'filename': filename
})
except Exception as e:
return jsonify({
'success': False,
'message': f'업로드 오류: {str(e)}'
})
# ========================================
# 기존 단일 서버 API (호환성 유지)
# ========================================
@idrac_bp.route('/api/files/local', methods=['GET'])
def list_local_files():
"""로컬에 저장된 DUP 파일 목록"""
try:
files = []
if os.path.exists(UPLOAD_FOLDER):
for filename in os.listdir(UPLOAD_FOLDER):
filepath = os.path.join(UPLOAD_FOLDER, filename)
if os.path.isfile(filepath):
file_size = os.path.getsize(filepath)
files.append({
'name': filename,
'size': file_size,
'size_mb': round(file_size / (1024 * 1024), 2),
'uploaded_at': datetime.fromtimestamp(
os.path.getmtime(filepath)
).strftime('%Y-%m-%d %H:%M:%S')
})
return jsonify({
'success': True,
'files': files
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
@idrac_bp.route('/api/files/local/<filename>', methods=['DELETE'])
def delete_local_file(filename):
"""로컬 DUP 파일 삭제"""
try:
filepath = os.path.join(UPLOAD_FOLDER, secure_filename(filename))
if os.path.exists(filepath):
os.remove(filepath)
return jsonify({
'success': True,
'message': f'{filename} 삭제 완료'
})
else:
return jsonify({
'success': False,
'message': '파일을 찾을 수 없습니다'
})
except Exception as e:
return jsonify({
'success': False,
'message': f'삭제 오류: {str(e)}'
})
# ========================================
# 서버 재부팅 API (멀티)
# ========================================
@idrac_bp.route('/api/servers/reboot-multi', methods=['POST'])
def reboot_servers_multi():
"""선택한 서버들 일괄 재부팅"""
try:
data = request.json
server_ids = data.get('server_ids', [])
reboot_type = data.get('type', 'GracefulRestart')
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
try:
client = DellRedfishClient(server.ip_address, server.username, server.password)
result = client.reboot_server(reboot_type)
if result:
results.append({
'server_id': server.id,
'server_name': server.name,
'success': True,
'message': '재부팅 시작'
})
else:
results.append({
'server_id': server.id,
'server_name': server.name,
'success': False,
'message': '재부팅 실패'
})
except Exception as e:
results.append({
'server_id': server.id,
'server_name': server.name,
'success': False,
'message': str(e)
})
success_count = sum(1 for r in results if r['success'])
return jsonify({
'success': True,
'results': results,
'summary': {
'total': len(results),
'success': success_count,
'failed': len(results) - success_count
}
})
except Exception as e:
return jsonify({
'success': False,
'message': f'오류: {str(e)}'
})
# Blueprint 등록 함수
def register_idrac_routes(app):
"""
iDRAC 멀티 서버 관리 Blueprint 등록
Args:
app: Flask 애플리케이션 인스턴스
"""
# uploads 디렉토리 생성
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# Blueprint 등록
app.register_blueprint(idrac_bp)
# CSRF 보호 제외 - iDRAC API 엔드포인트
try:
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect()
# API 엔드포인트는 CSRF 검증 제외
csrf.exempt(idrac_bp)
except:
pass # CSRF 설정 실패해도 계속 진행
# DB 테이블 생성
with app.app_context():
db.create_all()