change tk
This commit is contained in:
291
lvm_operations_tkinter.py
Normal file
291
lvm_operations_tkinter.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# lvm_operations_tkinter.py
|
||||
import logging
|
||||
import subprocess
|
||||
from tkinter import messagebox
|
||||
from system_info import SystemInfoManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LvmOperations:
|
||||
"""LVM 操作类 - Tkinter 版本"""
|
||||
|
||||
def __init__(self, system_manager: SystemInfoManager = None):
|
||||
self.system_manager = system_manager
|
||||
|
||||
def _execute_shell_command(self, command_list, error_message, root_privilege=True,
|
||||
suppress_critical_dialog_on_stderr_match=None, input_data=None,
|
||||
show_dialog=True):
|
||||
"""执行 shell 命令"""
|
||||
full_cmd_str = ' '.join(command_list)
|
||||
logger.info(f"执行命令: {full_cmd_str}")
|
||||
|
||||
try:
|
||||
if self.system_manager:
|
||||
stdout, stderr = self.system_manager._run_command(
|
||||
command_list,
|
||||
root_privilege=root_privilege,
|
||||
check_output=True,
|
||||
input_data=input_data
|
||||
)
|
||||
else:
|
||||
result = subprocess.run(
|
||||
["sudo"] + command_list if root_privilege else command_list,
|
||||
capture_output=True, text=True, check=True,
|
||||
input=input_data
|
||||
)
|
||||
stdout = result.stdout
|
||||
stderr = result.stderr
|
||||
|
||||
if stdout:
|
||||
logger.debug(f"命令 '{full_cmd_str}' 标准输出:\n{stdout.strip()}")
|
||||
if stderr:
|
||||
logger.debug(f"命令 '{full_cmd_str}' 标准错误:\n{stderr.strip()}")
|
||||
|
||||
return True, stdout, stderr
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
stderr_output = e.stderr.strip() if e.stderr else ""
|
||||
logger.error(f"{error_message} 命令: {full_cmd_str}")
|
||||
logger.error(f"退出码: {e.returncode}")
|
||||
logger.error(f"标准输出: {e.stdout.strip() if e.stdout else ''}")
|
||||
logger.error(f"标准错误: {stderr_output}")
|
||||
|
||||
should_suppress_dialog = False
|
||||
if suppress_critical_dialog_on_stderr_match:
|
||||
if isinstance(suppress_critical_dialog_on_stderr_match, str):
|
||||
if suppress_critical_dialog_on_stderr_match in stderr_output:
|
||||
should_suppress_dialog = True
|
||||
elif isinstance(suppress_critical_dialog_on_stderr_match, (list, tuple)):
|
||||
for pattern in suppress_critical_dialog_on_stderr_match:
|
||||
if pattern in stderr_output:
|
||||
should_suppress_dialog = True
|
||||
break
|
||||
|
||||
if should_suppress_dialog:
|
||||
logger.info(f"特定错误 '{stderr_output}' 匹配抑制条件,已抑制错误对话框。")
|
||||
elif show_dialog:
|
||||
messagebox.showerror("错误", f"{error_message}\n错误详情: {stderr_output}")
|
||||
|
||||
return False, e.stdout if e.stdout else "", stderr_output
|
||||
|
||||
except FileNotFoundError:
|
||||
logger.error(f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
||||
if show_dialog:
|
||||
messagebox.showerror("错误", f"命令 '{command_list[0]}' 未找到。请确保已安装相关工具。")
|
||||
return False, "", "Command not found."
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"执行命令时发生未知错误: {e}")
|
||||
if show_dialog:
|
||||
messagebox.showerror("错误", f"执行命令时发生未知错误: {e}")
|
||||
return False, "", str(e)
|
||||
|
||||
def create_pv(self, device_path):
|
||||
"""创建物理卷"""
|
||||
if not device_path or not device_path.startswith('/dev/'):
|
||||
messagebox.showerror("错误", "无效的设备路径。")
|
||||
return False
|
||||
|
||||
if not messagebox.askyesno("确认创建物理卷",
|
||||
f"您确定要在 {device_path} 上创建物理卷吗?\n"
|
||||
"此操作将擦除设备上的数据!",
|
||||
default=messagebox.NO):
|
||||
logger.info(f"用户取消了在 {device_path} 上创建物理卷的操作。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试在 {device_path} 上创建物理卷。")
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
["pvcreate", "-y", device_path],
|
||||
f"在 {device_path} 上创建物理卷失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"物理卷已在 {device_path} 上成功创建。")
|
||||
return True
|
||||
else:
|
||||
if "partition" in stderr.lower() or "分区" in stderr:
|
||||
if messagebox.askyesno("警告",
|
||||
f"设备 {device_path} 似乎已有分区。\n"
|
||||
"是否继续擦除并创建物理卷?",
|
||||
default=messagebox.NO):
|
||||
success_wipe, _, _ = self._execute_shell_command(
|
||||
["wipefs", "-a", device_path],
|
||||
f"擦除设备 {device_path} 失败"
|
||||
)
|
||||
if success_wipe:
|
||||
success_pv, _, stderr_pv = self._execute_shell_command(
|
||||
["pvcreate", "-y", device_path],
|
||||
f"在 {device_path} 上创建物理卷失败"
|
||||
)
|
||||
if success_pv:
|
||||
messagebox.showinfo("成功", f"物理卷已在 {device_path} 上成功创建。")
|
||||
return True
|
||||
else:
|
||||
messagebox.showerror("错误", f"在 {device_path} 上创建物理卷失败: {stderr_pv}")
|
||||
else:
|
||||
messagebox.showinfo("信息", f"未在已分区设备 {device_path} 上创建物理卷。")
|
||||
return False
|
||||
|
||||
def delete_pv(self, device_path):
|
||||
"""删除物理卷"""
|
||||
if not device_path or not device_path.startswith('/dev/'):
|
||||
messagebox.showerror("错误", "无效的设备路径。")
|
||||
return False
|
||||
|
||||
if not messagebox.askyesno("确认删除物理卷",
|
||||
f"您确定要删除物理卷 {device_path} 吗?",
|
||||
default=messagebox.NO):
|
||||
logger.info(f"用户取消了删除物理卷 {device_path} 的操作。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试删除物理卷 {device_path}。")
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
["pvremove", "-y", device_path],
|
||||
f"删除物理卷 {device_path} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"物理卷 {device_path} 已成功删除。")
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_vg(self, vg_name, pvs):
|
||||
"""创建卷组"""
|
||||
if not vg_name or not pvs:
|
||||
messagebox.showerror("错误", "无效的卷组名称或物理卷路径。")
|
||||
return False
|
||||
|
||||
if not messagebox.askyesno("确认创建卷组",
|
||||
f"您确定要创建卷组 {vg_name} 吗?\n"
|
||||
f"使用物理卷: {', '.join(pvs)}",
|
||||
default=messagebox.NO):
|
||||
logger.info(f"用户取消了创建卷组 {vg_name} 的操作。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试创建卷组 {vg_name},使用物理卷: {pvs}")
|
||||
cmd = ["vgcreate", vg_name] + pvs
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
cmd,
|
||||
f"创建卷组 {vg_name} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"卷组 {vg_name} 已成功创建。")
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_vg(self, vg_name):
|
||||
"""删除卷组"""
|
||||
if not vg_name:
|
||||
messagebox.showerror("错误", "无效的卷组名称。")
|
||||
return False
|
||||
|
||||
if not messagebox.askyesno("确认删除卷组",
|
||||
f"您确定要删除卷组 {vg_name} 吗?\n"
|
||||
"此操作将删除该卷组及其所有逻辑卷!",
|
||||
default=messagebox.NO):
|
||||
logger.info(f"用户取消了删除卷组 {vg_name} 的操作。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试删除卷组 {vg_name}。")
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
["vgremove", "-y", vg_name],
|
||||
f"删除卷组 {vg_name} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"卷组 {vg_name} 已成功删除。")
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_lv(self, lv_name, vg_name, size_gb, use_max_space=False):
|
||||
"""创建逻辑卷"""
|
||||
if not lv_name or not vg_name:
|
||||
messagebox.showerror("错误", "无效的逻辑卷名称或卷组名称。")
|
||||
return False
|
||||
|
||||
if not use_max_space and size_gb <= 0:
|
||||
messagebox.showerror("错误", "逻辑卷大小必须大于0。")
|
||||
return False
|
||||
|
||||
size_str = "100%FREE" if use_max_space else f"{size_gb}G"
|
||||
|
||||
if not messagebox.askyesno("确认创建逻辑卷",
|
||||
f"您确定要在卷组 {vg_name} 中创建逻辑卷 {lv_name} 吗?\n"
|
||||
f"大小: {size_str}",
|
||||
default=messagebox.NO):
|
||||
logger.info(f"用户取消了创建逻辑卷 {lv_name} 的操作。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试在卷组 {vg_name} 中创建逻辑卷 {lv_name},大小: {size_str}")
|
||||
cmd = ["lvcreate", "-y", "-n", lv_name, "-L", size_str, vg_name] if not use_max_space else \
|
||||
["lvcreate", "-y", "-n", lv_name, "-l", "100%FREE", vg_name]
|
||||
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
cmd,
|
||||
f"创建逻辑卷 {lv_name} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"逻辑卷 {lv_name} 已在卷组 {vg_name} 中成功创建。")
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_lv(self, lv_name, vg_name):
|
||||
"""删除逻辑卷"""
|
||||
if not lv_name or not vg_name:
|
||||
messagebox.showerror("错误", "无效的逻辑卷名称或卷组名称。")
|
||||
return False
|
||||
|
||||
if not messagebox.askyesno("确认删除逻辑卷",
|
||||
f"您确定要删除逻辑卷 {vg_name}/{lv_name} 吗?\n"
|
||||
"此操作将删除该逻辑卷及其所有数据!",
|
||||
default=messagebox.NO):
|
||||
logger.info(f"用户取消了删除逻辑卷 {lv_name} 的操作。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试删除逻辑卷 {vg_name}/{lv_name}。")
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
["lvremove", "-y", f"{vg_name}/{lv_name}"],
|
||||
f"删除逻辑卷 {vg_name}/{lv_name} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"逻辑卷 {vg_name}/{lv_name} 已成功删除。")
|
||||
return True
|
||||
return False
|
||||
|
||||
def activate_lv(self, lv_name, vg_name):
|
||||
"""激活逻辑卷"""
|
||||
if not lv_name or not vg_name:
|
||||
messagebox.showerror("错误", "无效的逻辑卷名称或卷组名称。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试激活逻辑卷 {vg_name}/{lv_name}。")
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
["lvchange", "-ay", f"{vg_name}/{lv_name}"],
|
||||
f"激活逻辑卷 {vg_name}/{lv_name} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"逻辑卷 {vg_name}/{lv_name} 已成功激活。")
|
||||
return True
|
||||
return False
|
||||
|
||||
def deactivate_lv(self, lv_name, vg_name):
|
||||
"""停用逻辑卷"""
|
||||
if not lv_name or not vg_name:
|
||||
messagebox.showerror("错误", "无效的逻辑卷名称或卷组名称。")
|
||||
return False
|
||||
|
||||
logger.info(f"尝试停用逻辑卷 {vg_name}/{lv_name}。")
|
||||
success, stdout, stderr = self._execute_shell_command(
|
||||
["lvchange", "-an", f"{vg_name}/{lv_name}"],
|
||||
f"停用逻辑卷 {vg_name}/{lv_name} 失败"
|
||||
)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"逻辑卷 {vg_name}/{lv_name} 已成功停用。")
|
||||
return True
|
||||
return False
|
||||
Reference in New Issue
Block a user