使用 Python 编写 KVM 脚本,第 2 部分: 添加 GUI 来使用 libvirt 和 Python 管理 KVM

本文介绍了如何使用Python和wxPython库构建图形用户界面,以管理使用KVM的虚拟机。主要内容包括添加GUI以显示和操作运行状态、实现基于用户操作的控制流程、以及增强监视功能。

简介: 由两部分组成的此系列文章将探索如何使用 Python 创建脚本,以使用 KVM 来管理虚拟机。在本期中,您将学习如何添加一个 GUI 来扩展简单的状态和显示工具。

本系列的 使用 Python 为 KVM 编写脚本,第 1 部分:libvirt 介绍了使用 libvirt 和 Python 编写基于内核的虚拟机 (KVM) 脚本的基础知识。本期使用了上一期介绍的概念构建一些实用工具应用程序,添加一个图形用户界面 (GUI)。主要有两种既具有 Python 绑定又可跨平台的 GUI 工具包。第一个是 Qt,它现在归诺基亚所有;第二个是 wxPython。二者都具有大量支持者,许多开源项目都在使用它们。

出于个人偏好,我在本文重点介绍 wxPython。首先将简短介绍 wxPython 和正确设置的基本知识。再提供一些简短的示例程序,然后再与 libvirt 集成。此方法应该提供了足够的 wxPython 基础知识,供您构建简单的程序,以及扩展该程序来添加功能。希望您能掌握这些概念,并扩展它们以满足您的具体需要。

wxPython 基础知识

一个不错的起点是从一些基本的定义入手。wxPython 库实际上是基于 C++ 的 wxWidgets 上的一个包装器。在创建 GUI 的上下文中,一个部件 在本质上就是一个构建块。部件分层结构的最顶层包含 5 个独立的部件:

wx.Frame
wx.Dialog
wx.PopupWindow
wx.MDIParentFrame
wx.MDIChildFrame

这里的大部分示例都基于 wx.Frame,因为它在本质上实现了一个单一模态的窗口。

在 wxPython 中,您可以按原样实例化 Frame 类,或者继承它以添加或增强功能。一定要理解部件在一个框架中的显示方式,这样您在知道如何正确放置它们。布局通过绝对定位或使用调整器来确定。调整器 是一个方便的工具,在用户单击并拖动一边或一角来更改窗口大小时,它会调整部件大小。

wxPython 程序的最简单形式必须有一些代码行进行设置。一种典型的主要例程可能类似于 清单 1


清单 1. 设备 XML 定义

				
if __name__ == "__main__":
    app = wx.App(False)
	frame = MyFrame()
	frame.Show()
	app.MainLoop()

每个 wxPython 应用程序是 wx.App() 的一个实例,必须如清单 1 所示进行实例化。当将 False 传递到 wx.App 时,它表明 “不要将 stdout 和 stderr 重定向到一个窗口”。下一行通过实例化 MyFrame() 类来创建一个框架。接着显示框架并将控制权转交给 app.MainLoop()MyFrame() 类通常包含一个 __init__ 函数,以使用您选择的部件初始化框架。您还会在这里将任何部件事件连接到他们的正确处理函数。

现在有必要提一下 wxPython 随带的一个方便的调试工具。此工具称为部件检查工具(参见 图 1),仅需要两行代码即可使用。首先,您必须使用下述代码导入它:

import wx.lib.inspection

接着,要使用时,您只需调用 Show() 函数:

wx.lib.inspectin.InspectionTool().Show()

单击菜单工具栏上的 Events 图标会在激活事件时动态地显示事件。如果您不确定特定部件支持哪些事件,这是一种在事件发生时查看事件的真正快捷的方式。当应用程序正在运行时,它还会让您更好地了解幕后发生的情况。


图 1. wxPython 部件检查工具
该屏幕截图显示了 wxPython 部件检查工具,其中包含 PyCrust  输出中来自部件树框架和框架上并列显示的对象信息。


向命令行工具添加 GUI

本系列的 使用 Python 为 KVM 编写脚本,第 1 部分:libvirt 提供了一个简单工具来显示所有运行的虚拟机 (VM) 的状态。使用 wxPython,可以轻松地将该工具更改为 GUI 工具。wx.ListCtrl 部件提供了您以列表形式显示信息所需的功能。要使用 wx.ListCtrl 部件,您必须使用以下语法将它添加到您的框架中:

self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)

您可以从多种不同样式中选择,包括前面使用的 wx.LC_REPORTwx.SUNKEN_BORDER 选项。第一个选项将 wx.ListCtrl 设置为报告模式,这是四种可用模式之一。其他选项包括图标、小图标和列表。要添加 wx.SUNKEN_BORDER 这样的样式,您只需使用竖杠 (|)。一些样式是相互排斥的,比如不同的边框样式,所以如果您有任何疑虑,请查阅 wxPython wiki(参见 参考资料)。

实例化 wx.ListCtrl 部件之后,您就可以开始向它添加内容了,比如列标题。InsertColumn 方法有两个强制性参数和两个可选参数。第一个是列索引,它从 0 开始,接下来是一个设置标题的字符串。第三个用于格式化,应该类似于 LIST_FORMAT_CENTER_LEFT_RIGHT。最后,您可以传入一个整数来设置固定宽度,或者使用 wx.LIST_AUTOSIZE 自动调整列。

现在您已配置了 wx.ListCtrl 部件,您可以使用 InsertStringItem SetStringItem 方法向它填充数据了。wx.ListCtrl 部件中的每一个新行都必须使用 InsertStringItem 方法添加。两个强制性参数指定在何处执行插入,包含表示在列表顶部插入的值 0 和要插入在该位置的字符串。InsertStringItem 返回一个整数,表示插入字符串的行数。您可以为列表调用 GetItemCount(),使用返回值供索引附加到底部,如 清单 2 所示。


清单 2. 命令行工具的 GUI 版本

				
import wx
import libvirt

conn=libvirt.open("qemu:///system")

class MyApp(wx.App):
    def OnInit(self):
        frame = wx.Frame(None, -1, "KVM Info")
        id=wx.NewId()
        self.list=wx.ListCtrl(frame,id,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        self.list.Show(True)
        self.list.InsertColumn(0,"ID")
        self.list.InsertColumn(1,"Name")
        self.list.InsertColumn(2,"State")
        self.list.InsertColumn(3,"Max Mem")
        self.list.InsertColumn(4,"# of vCPUs")
        self.list.InsertColumn(5,"CPU Time (ns)")

        for i,id in enumerate(conn.listDomainsID()):
            dom = conn.lookupByID(id)
            infos = dom.info()
            pos = self.list.InsertStringItem(i,str(id)) 
            self.list.SetStringItem(pos,1,dom.name())
            self.list.SetStringItem(pos,2,str(infos[0]))
            self.list.SetStringItem(pos,3,str(infos[1]))
            self.list.SetStringItem(pos,4,str(infos[3]))
            self.list.SetStringItem(pos,5,str(infos[2]))
            
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

app = MyApp(0)
app.MainLoop()

图 2 显示了这些工作的结果。


图 2. GUI KVM 信息工具
该屏幕截图显示了 KVM 信息,包括 ID、Name、State、Max Mem、# of vCPUs 和 CPU Time 列

您可以改善这个表的外观。一种明显的改进可能是重新调整列。为此,可以将 width = 参数添加到 InsertColumn 调用中,或者使用一行代码,比如:

self.ListCtrl.SetColumnWidth(column,wx.LIST_AUTOSIZE)

您可以做的另一件事是添加一个调整器,以便控件能根据父窗口进行调整。为此,可以在几行代码中使用一个 wxBoxSizer。首先,创建调整器,然后向它添加您希望针对主窗口进行调整的部件。可能的代码如下所示:

self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.list, proportion=1,flag=wx.EXPAND | wx.ALL, border=5)
self.sizer.Add(self.button, flag=wx.EXPAND | wx.ALL, border=5)
self.panel.SetSizerAndFit(self.sizer)

最后对 self.panel.SetSizerAndFit() 的调用要求 wxPython 基于嵌入部件调整器的最小尺寸,设置窗格的初始大小。这有助于基于屏幕内容为您提供大小合理的初始屏幕。


基于用户操作的控制流

关于 wx.ListCtrl 部件的一个好处是,您可以检测用户何时单击了部件的具体部分,并基于该信息执行某种操作。此功能允许您基于用户对列标题的单击,按字母顺序对列进行正向或反向排列。完成此任务的技术使用了一种回调机制。您必须提供一个函数,通过将部件与处理方法绑定在一起来处理您希望处理的每个操作。为此,使用 Bind 方法。

每个部件有一定数量关联事件。还有与鼠标等实体关联的事件。鼠标事件具有 EVT_LEFT_DOWNEVT_LEFT_UPEVT_LEFT_DCLICK 这样的名称,以及与其他按钮相同的命名约定。您可以通过附加到 EVT_MOUSE_EVENTS 类型来处理所有鼠标事件。难点在于在您感兴趣的应用程序或窗口上下文中捕获事件。

当控件传递到事件处理函数时,它必须执行必要的步骤来处理该操作,然后将控件返回之前所在的地方。这是一种事件驱动的编程模型,每个 GUI 都必须实现它来及时地处理用户操作。许多现代 GUI 应用程序实现了多线程来避免让用户感觉程序没有响应。本文后面将简短介绍这一主题。

计时器代表着程序可能必须处理的另一种事件类型。例如,您可能希望以用户定义的间隔执行定期监视功能。您将需要提供一个屏幕,用户可在该屏幕上指定间隔,接着启动一个计时器以在它到期时触发一个事件。计时器到期会触发一个事件,您可以使用该事件激活一段代码。再次依据用户偏好,您可能需要设置或重新开始计时。可以轻松地使用此技术开发 VM 监视工具。

清单 3 提供了一个简单的演示应用程序,它包含一个按钮和静态文本行。使用 wx.StaticText 是一种将字符串输出到窗口的轻松方式。它的理念是单击该按钮一次会启动一个计时器并记录开始时间,同时将标签更改为 Stop。再次单击该按钮会填入结束时间文本框,将按钮更改回 Start


清单 3. 包含一个按钮和静态文本的简单应用程序

				
import wx
from time import gmtime, strftime

class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Buttons")
        self.panel = wx.Panel(self, wx.ID_ANY)
 
        self.button = wx.Button(self.panel, id=wx.ID_ANY, label="Start")
        self.button.Bind(wx.EVT_BUTTON, self.onButton)

    def onButton(self, event):
        if self.button.GetLabel() == "Start":
            self.button.SetLabel("Stop")
            strtime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            wx.StaticText(self, -1, 'Start Time = ' + strtime, (25, 75))
        else:
            self.button.SetLabel("Start")
            stptime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            wx.StaticText(self, -1, 'Stop Time = ' + stptime, (25, 100))
 
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()


增强的监视 GUI

现在,您可以添加前面介绍的简单监视 GUI 了。在拥有创建应用程序所需的一切内容之前,还需要理解 wxPython 的另一个方面。向 wx.ListCtrl 部件的第一行添加一个复选框,可实现基于复选框的状态在多行上执行操作。为此,您可以使用 wxPython 所称的 mixin。在本质上,mixin 是一个帮助器类,它向父部件添加某种类型的功能。要添加复选框 mixin,只需使用以下代码来实例化它:

listmix.CheckListCtrlMixin.__init__(self)		

也可以利用事件来添加单击列标题来选择或清除所有复选框的功能。这样,只需几次单击即可执行启动或停止所有 VM 等操作。您需要编写一些事件处理函数,以与之前更改按钮标签相同的方式来响应合适的事件。以下是设置列单击事件处理函数所需的代码行:

self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)

wx.EVT_LIST_COL_CLICK 在单击任何列标题时触发。要确定单击了哪一列,可以使用 event.GetColumn() 方法。以下是 OnColClick 事件的一个简单的处理函数:

def OnColClick(self, event):
    print "column clicked %d\n" % event.GetColumn()
	event.Skip()

如果需要将事件传播给其他处理函数,event.Skip() 调用非常重要。尽管在此实例中可能不需要它,但当多个处理函数需要处理相同事件时,不使用它可能会出现问题。wxPython wiki 站点上对事件传播进行了很好的讨论,这比我在这里的介绍详细得多。

最后,向两个按钮处理函数添加代码来启动或停止所有选择的 VM。只需几行代码,即可迭代 wx.ListCtrl 中的各行并获取 VM ID,如 清单 4 所示。


清单 4. 启动和停止选择的 VM

				
#!/usr/bin/env python

import wx
import wx.lib.mixins.listctrl as listmix
import libvirt

conn=libvirt.open("qemu:///system")

class CheckListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin, 
                                 listmix.ListCtrlAutoWidthMixin):
    def __init__(self, *args, **kwargs):
        wx.ListCtrl.__init__(self, *args, **kwargs)
        listmix.CheckListCtrlMixin.__init__(self)
        listmix.ListCtrlAutoWidthMixin.__init__(self)
        self.setResizeColumn(2)

class MainWindow(wx.Frame):

    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.panel = wx.Panel(self)
        self.list = CheckListCtrl(self.panel, style=wx.LC_REPORT)
        self.list.InsertColumn(0, "Check", width = 175)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)

        self.list.InsertColumn(1,"Max Mem", width = 100)
        self.list.InsertColumn(2,"# of vCPUs", width = 100)

        for i,id in enumerate(conn.listDefinedDomains()):
            dom = conn.lookupByName(id)
            infos = dom.info()
            pos = self.list.InsertStringItem(1,dom.name()) 
            self.list.SetStringItem(pos,1,str(infos[1]))
            self.list.SetStringItem(pos,2,str(infos[3]))

        self.StrButton = wx.Button(self.panel, label="Start")
        self.Bind(wx.EVT_BUTTON, self.onStrButton, self.StrButton)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.list, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
        self.sizer.Add(self.StrButton, flag=wx.EXPAND | wx.ALL, border=5)
        self.panel.SetSizerAndFit(self.sizer)
        self.Show()

    def onStrButton(self, event):
        if self.StrButton.GetLabel() == "Start":
	    num = self.list.GetItemCount()
            for i in range(num):
                if self.list.IsChecked(i):
                    dom = conn.lookupByName(self.list.GetItem(i, 0).Text)
                    dom.create()
                    print "%d started" % dom.ID()
 
    def OnColClick(self, event):
         item = self.list.GetColumn(0)
         if item is not None:
             if item.GetText() == "Check":
                 item.SetText("Uncheck")
                 self.list.SetColumn(0, item)
                 num = self.list.GetItemCount()
                 for i in range(num):
                     self.list.CheckItem(i,True)
             else:
                 item.SetText("Check")
                 self.list.SetColumn(0, item)
                 num = self.list.GetItemCount()
                 for i in range(num):
                     self.list.CheckItem(i,False)

         event.Skip()
 
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()


在 KVM 中的 VM 状态方面,这里有两个亮点需要指出:当使用 libvirt 中的 listDomainsID() 方法时,正在运行的 VM 会显示出来。要查看没有运行的机器,必须使用 listDefinedDomains()。必须保持这两部分独立,才能知道可以启动哪些 VM 和可以停止哪些 VM。


结束语

本文主要介绍了使用 wxPython 构建 GUI 包装器所需的步骤,该包装器使用 libvirt 来管理 KVM。wxPython 库功能丰富,提供了许多部件来支持构建具有专业外观的基于 GUI 的应用程序。本文仅介绍其中的一小部分功能,希望您能够进一步探索。务必查阅更多 参考资料,以有助于应用程序正常运行。


参考资料

学习

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值