- 本书赞誉
- 前言
- 第一部分 基础篇
- 第1章 系统基础信息模块详解
- 第2章 业务服务监控详解
- 第3章 定制业务质量报表详解
- 第4章 Python 与系统安全
- 第二部分 高级篇
- 第5章 系统批量运维管理器 pexpect 详解
- 第6章 系统批量运维管理器 paramiko 详解
- 第7章 系统批量运维管理器Fabric详解
- 第8章 从零开发一个轻量级 WebServer
- 第9章 集中化管理平台 Ansible 详解
- 第10章 集中化管理平台 Saltstack 详解
- 第11章 统一网络控制器 Func 详解
- 第12章 Python 大数据应用详解
- 第三部分 案例篇
- 第13章 从零开始打造 B/S 自动化运维平台
- 第14章 打造 Linux 系统安全审计功能
- 第15章 构建分布式质量监控平台
- 第16章 构建桌面版 C/S 自动化运维平台
16.5 系统功能模块设计
16.5.1 用户登录模块
OManager平台的登录采用了双重安全校验机制:一种为传统的用户名与密码匹配,另一种为密钥文件校验方式,实现的原理是在密钥文件中输入任意随机字符串,通过平台自带的md5sum.exe工具计算出该文件的md5,将生成的md5字符串更新到users(用户表)管理员账号对应的Privatekey字段,以root用户的密钥numbers/root.pem为例,使用方法见图16-6和图16-7。
图16-6 查看密钥文件md5
图16-7 数据库存储的密钥文件md5数据
管理员登录时首先获得选择密钥文件的md5,再与数据库中的Privatekey字段进行匹配,建议由超级管理员提前开设好所有用户的账号信息,包括用户名、密码及密钥。再统一将密钥文件以人为单位进行发放。验证的实现方法源码如下:
def Check(self,name, password,Privatekey): import md5 m = md5.new(password) #使用md5模块计算密码的md5串 md5pass=m.hexdigest myrow=DBclass #创建数据库连接对象(自定义类) sql = "select admin,privileges from users where admin='%s' and passwd='%s' \ and Privatekey='%s'"% (name, md5pass,Privatekey) #参照MySQL中的用户名、密码、密钥进行校验 result = myrow.fetchallq(sql) return result #返回结果集
下面是计算密钥文件md5的实现方法,主要用到了hashlib模块:
#计算文件md5值,参数fileName为实体文件路径;参数excludeLine为排除的文本行; #参数includeLine为额外包含的行 def md5(fileName, excludeLine="", includeLine=""): m = hashlib.md5 #使用hashlib模块生成一个md5 hash对象 try: fd = open(fileName,"rb") #打开密钥文件 except IOError: print "Unable to open the file in readmode:", filename return eachLine = fd.readline while eachLine: #遍历密钥文件 if excludeLine and eachLine.startswith(excludeLine): #排除指定的行 continue m.update(eachLine) #用update方法对行字符串进行md5加密且不断做更新处理 eachLine = fd.readline m.update(includeLine) #对额外包含的行做更新和加密处理 fd.close return m.hexdigest #返回十六进制结果
调用计算文件密钥md5方法:
md5(self.Privatekey.GetValue) #self.Privatekey.GetValue为用户选择的密钥文件路径
16.5.2 系统配置功能
OManager平台将常用的参数配置化,包括连接数据库、主控端、传输密钥等信息,当外部环境发生变化时无须做代码变更,简单更新配置即可,提高了平台的易用性,降低使用门槛,具体是通过ConfigParser模块操作ini文件实现,效果见图16-8。
图16-8 系统配置功能
手工修改ini文件与界面操作达到的效果是一样的,平台ini文件格式如下:
【data/config.ini】
[system] height = 765 width = 1024 version = v2014 upversion = 10026 ip = 192.168.1.20 port = 11511 timeout = 10 max_servers = 10 secret_key = ctmj#&;8hrgow_^sj$ejt@9fzsmh_o)-=(byt5jmg=e3#foya6u upgrade_url = http://update.domain.com/upgrade [db] db_ip = 192.168.1.10 db_user = servmanageruser db_pass = 123456#abc db_name = OManager
使用ConfigParser模块操作ini配置文件非常方便,通过get、set方法来读取与更新配置文件。读配置源码如下:
self.cf = ConfigParser.ConfigParser #创建ConfigParser对象 self.cf.read(sys.path[0]+'/data/config.ini',encoding='utf8') #读取配置文件 #读取“system”节中所有键值到指定的变量 self.cf = ConfigParser.ConfigParser self.cf.read(sys.path[0]+'/data/config.ini') self._syswidth= self.cf.get("system","Width") self._sysheight= self.cf.get("system","Height") self._timeout=self.cf.get("system","Timeout") self._ip=self.cf.get("system","IP") self._port=self.cf.get("system","Port") self._max_servers=self.cf.get("system","max_servers") self._secret_key=self.cf.get("system","secret_key") self._sysversion= self.cf.get("system","Version") self._sysUpversion= self.cf.get("system","Upversion") self._upgrade_url= self.cf.get("system","upgrade_url") #读取“db”节中所有键值到指定的变量 self._db_ip= self.cf.get("db","db_ip") self._db_user= self.cf.get("db","db_user") self._db_pass= self.cf.get("db","db_pass") self._db_name= self.cf.get("db","db_name")
更新配置也非常简单,将get方法更换成set,再指定ini的节、键、值三个元素即可。下面是更新数据库IP参数的代码,其中self.DB_ip.GetValue为输入框的内容。
self.cf.set("db", "db_ip", self.DB_ip.GetValue)
16.5.3 服务器分类模块
为了让OManager更具通用性,平台的服务器信息依赖企业现有资产库数据,通过平台规范好的格式生成XML文件,结合Tree与ListBox控件实现功能分类与服务器联动,效果见图16-9。
图16-9 服务器分类选择
服务器分类的组织形式与OMserver保持一致,即功能分类→业务分类→服务器。图16-10为服务器类别的XML数据文件,用来描述服务器类别信息。
【data/ServerOptioninfo.xml】
图16-10 服务器分类的XML文件
其中,“<AppClass id="1">”标签id属性值为功能分类ID号,“<appname>应用服务器</appname>”使用<appname>子元素描述功能分类名称。服务器信息的XML数据文件用来描述服务器的详细属性,详细内容见图16-11,属性与子元素说明见表16-2。
【data/Serverinfo.xml】(部分内容)
图16-11 服务器信息的XML文件
表16-2 属性与子元素说明
每个管理员所负责的服务器资源通常都不一样,一般以服务器功能分类的维度划分。OManager可为这种权限要求提供支持,实现的思路是在users表的privileges(权限角色)字段定义服务器分类ID,其中“root”为特殊权限,代表超级管理员,所有服务器资源都可见。账号“demo”的权限配置,以及在平台中展示的效果见图16-12。
图16-12 用户权限配置及展示效果
当窗体(wx.Frame)初始化时,“服务器类别”控件会自动加载数据,实现的方法是通过遍历以上提到的两个XML数据,将当前账号的权限ID列表与服务器类别ID进行关联,获取所具备的权限,即拥有的服务器类别ID。“应用名称”则通过服务器信息的“<option>”元素与服务器类别ID进行匹配,实现源码如下:
import xml.etree.ElementTree as ET import os import sys root_tree = ET.parse(sys.path[0]+'/data/ServerOptioninfo.xml') #打开服务器类别XML文档 class_tree = ET.parse(sys.path[0]+'/data/Serverinfo.xml') #打开服务器信息XML文档 root_doc = root_tree.getroot #获得服务器类别XML文档root节点 class_doc = class_tree.getroot #获得服务器信息XML文档root节点 class ServerClassList: def Resurn_list(self,UserPrivileges): #返回服务器类别、应用方法 ServerList_KEY=[] #定义返回的服务器类别、应用信息列表对象 serverclass=[] #定义服务器类别列表对象 serverapp=[] #定义业务应用列表对象 for root_child in root_doc: #遍历服务器类别节点 if not root_child.get('id') in UserPrivileges and not UserPrivileges[0]=="root": continue #没有权限的服务器类别将被忽略 serverclass.append(root_child[0].text.encode('gbk')) # 追加服务器类别名称 <appname> serverapp=[] for class_child in class_doc: #遍历服务器信息节点 #如与功能分类ID相匹配,则追加 <app> 到 serverapp #通过index方法产生的异常判断当前 <app> 是否已经存在于 serverapp 中 if class_child[6].text==root_child.get('id'): try: serverapp.index(class_child[4].text.encode('gbk')) except: serverapp.append(class_child[4].text.encode('gbk')) serverclass.append(serverapp) ServerList_KEY.append(serverclass) serverclass=[] #返回结果串格式:[['应用服务器',['www.a.com','www.b.com']],['数据库服务器',['www.c.com']]...] return ServerList_KEY
16.5.4 系统升级功能
相比B/S结构程序,C/S结构的另一缺点是不方便升级,部分软件甚至要求重新安装、重启计算机等操作。为了解决此问题,OManager在系统升级方面结合了B/S的模式,将升级包放在远端,由管理员触发升级操作,同时不影响当前的其他操作,重启OManager程序后即可完成升级。OManager系统升级流程图见图16-13。
图16-13 系统升级流程图
OManager系统升级的原理:首先将升级描述文件(updateMS.xml)、升级包上传至版本服务器,由管理员触发升级操作,再通过urllib模块实现HTTP方式下载updateMS.xml文件并进行分析,获取所有需要升级的程序包,包括远程URL及下载本地存储地址,最后遍历下载所有升级包到指定的位置,完成整个升级过程。下面是升级描述文件updateMS.xml的示例:
【tmp/updateMS.xml】
<?xml version="1.0" encoding="UTF-8"?> <wml> <AppClass id="1"> <localsrc>data/Serverinfo.xml</localsrc> <remotesrc>/data/Serverinfo.xml</remotesrc> </AppClass> <AppClass id="2"> <localsrc>data/ServerOptioninfo.xml</localsrc> <remotesrc>/data/ServerOptioninfo.xml</remotesrc> </AppClass> <AppClass id="3"> <localsrc>OManager.10026.exe</localsrc> <remotesrc>/OManager.exe</remotesrc> </AppClass> </wml>
在此XML文件中,localsrc与remotesrc分别表示本地存储地址及远程URL路径,远程URL文件与本地路径建议保持一致,可以提高系统的可维护性,如本地的“data/Serverinfo.xml”与远程的“/data/Serverinfo.xml”,远程升级包目录结构见图16-14。
图16-14 远程升级包存储路径
在此配置中,需要升级的程序包为OManager.exe、Serverinfo.xml、ServerOptioninfo.xml三个,变更项包括了添加主机信息、主程序优化等,下面介绍升级步骤。
1)上传升级相关文件到版本服务器指定位置,具体见图16-14。
2)更新数据库中平台最新版本号,即更新upgrade表的version字段,如更新版本号为“10026”;用户会根据data/config.ini中的upversion键值与最新版本号进行匹配,当小于最新版本号时将触发升级。
3)点击“系统升级”工具栏图标进行升级,升级成功后如图16-15所示。
升级结束后,平台根目录下多了一个“OManager.10026.exe”最新版本的程序包,见图16-16。
运行“OManager.10026.exe”,在主程序左侧的服务器列表框中多了一台“218.31.20.11”主机,见图16-17。再查看data/config.ini中的upversion键值,已经改为“10026”,说明系统已成功升级。
图16-15 系统升级成功
图16-16 升级后的文件列表
图16-17 更新后的主机列表
介绍完系统升级的操作过程,下面介绍OManager实现升级功能的源码分析,使用模块urllib.urlopen(url).read方法实现HTTP协议文件下载,使用xml.etree.ElementTree模块实现XML文件的分析。
def load_data(self,event): # 系统升级方法 try: if self.button.GetLabel==u"关闭": self.Destroy url=self.updateURL+"/updateMS.xml" # 指定升级描述文件远程及本地路径 localfile=sys.path[0]+'/tmp/updateMS.xml' if not self.download(url,localfile): #下载升级描述文件 return except Exception,e: wx.MessageBox(u"更新描述文件下载失败"+str(e),u"OManager:",style=wx.OK|wx.ICON_ERROR) self.Destroy return try: #打开升级描述文件,为下面的分析做好准备 import xml.etree.ElementTree as ET update_tree = ET.parse(sys.path[0]+'/tmp/updateMS.xml') up_doc = update_tree.getroot except Exception,e: wx.MessageBox(u"导入更新包出错",u"OManager:",style=wx.OK|wx.ICON_ERROR) self.Destroy return try: #遍历描述文件,获取升级描述文件中所有程序包的远程及本地路径,调用 #download方法实现下载 upgrade_count=0 for cur_child in up_doc: upgrade_count+=1 url=self.updateURL+cur_child[1].text localfile=sys.path[0]+'/'+cur_child[0].text if self.download(url,localfile)==False: break self.cf.set("system", "Upversion", self.lastversion) # 更新config.ini #最新版本号 self.cf.write(open(sys.path[0]+'/data/config.ini', "w")) self.ConnStaticText.SetLabel(u"成功更新"+str(upgrade_count)+"个数据包...") self.button.SetLabel(u"关闭") except Exception,e: wx.MessageBox(u"系统文件下载失败","OManager",style=wx.OK|wx.ICON_ERROR) self.Destroy return finally: pass event.Skip
16.5.5 客户端模块编写
OManager提供客户端模块开发支持,与OMserver的实现思想一样,区别是OMserver基于HTML表单来定义,而OManager基于XRC。XRC(XML Resource)的设计来源于wxWidgets,原理是将界面设计的工作从程序中独立出来,类似于Django开发框架中模板系统的角色,目的是将业务逻辑与界面进行分离,好处是代码的结构会更加清晰,可读性也会大大提高。具体做法是通过XML格式定义系统界面,当程序运行时再载入。XRC的使用手册见http://wiki.wxwidgets.org/Using_XML_Resources_with_XRC。OManager平台将功能模块采用XRC设计,在主程序中按功能分类导入,效果见图16-18和图16-19。
图16-18 功能模块菜单
OManager平台提供了最多2个控件参数的定义,控件类别支持wxSpinCtrl(微调控制器)、wxListBox(列表控件)、wxTextCtrl(文本输入控件)等,当然,扩展更多的控件类型也非常简单,前提是需要了解各控件的属性及方法,其中控件值会被当成模块参数通过rpyc传输到服务器端。下面为“bas_1001_系统日志.xrc”功能模块的设计,包括一个容量控件wxPanel对象,wxPanel对象包含了两个对象,一个文字标签控件wxStaticText对象,通过<label>元素定义该模块的功能文字说明;另一个对象为微调控制器wxSpinCtrl,通过<value>元素定义默认值,<min>与<max>定义控件的最小值及最大值。更多的控件介绍请参考:http://wiki.wxwidgets.org/Using_XML_Resources_with_XRC。该功能模块的XRC定义内容如下:
图16-19 功能模块窗口
【Module/bas_1001_系统日志.xrc】
<?xml version="1.0" encoding="utf-8"?> <resource> <object class="wxPanel" name="panel"> <size>200,100</size> <object class="wxStaticText" name="label1"> <label>功能描述:显示服务器Message最新选择条数的记录。</label> <pos>30,20</pos> </object> <object class="wxSpinCtrl" name="Parameter1_object_id"> <style>wxSP_ARROW_KEYS</style> <value>30</value> <min>1</min> <max>100</max> <pos>30,50</pos> </object> </object> </resource>
在主程序中,通过xrc.XmlResource方法加载XRC模块文件,使用xrc.XRCCTRL方法获取控件对象,使用对象的GetValue或GetStringSelection方法得到控件输入值,其中wxSpinCtrl、wxTextCtrl控件使用GetValue方法,wxListBox控件使用GetStringSelection方法。在主程序中调用XRC的方法,源码(部分)如下:
from wx import xrc self.res = xrc.XmlResource(sys.path[0]+'/Module/bas_1001_系统日志.xrc') #加载模块资源文件 panel = self.res.LoadPanel(self, "panel") #加载panel面板控件 try: self.Parameter1 = xrc.XRCCTRL(panel, 'Parameter1_object_id') #加载控件1对象名 except Exception,e: pass #获取不同控件的返回值,GetClassName方法返回控件类别名,用于定位不同控件获取value的方法 try: if self.Parameter1.GetClassName=="wxSpinCtrl": self.Parameter1_value=self.Parameter1.GetValue elif self.Parameter1.GetClassName=="wxListBox": self.Parameter1_value=self.Parameter1.GetStringSelection except Exception,e: pass …
平台功能模块XRC文件命名遵循一定的标准规范,即“模块功能类别_模块ID_功能中文名称.xrc”,文件名将会以“_”作为分隔符,拆分的数据将应用到系统功能中,比如文件名前缀“模块功能类别”会根据不同类别代号加载到不同功能菜单,实现源码(部分)如下:
bashmenu = wx.Menu #定义"基本功能"二级菜单 appmenu = wx.Menu #定义"应用功能"二级菜单 dbmenu = wx.Menu #定义"数据库功能"二级菜单 servicemenu = wx.Menu #定义"后台服务功能"二级菜单 middlemenu = wx.Menu #定义"中间件功能"二级菜单 #根据不同XRC文件前缀,将三级菜单追加到对应的二级菜单中 for file_info in self.Moduledetail: file_array=string.split(file_info,'_') if file_info[0:3]=="bas": bashmenu.Append(int(file_array[1]),file_array[2],file_array[2]) elif file_info[0:3]=="app": appmenu.Append(int(file_array[1]),file_array[2],file_array[2]) elif file_info[0:3]=="dba": dbmenu.Append(int(file_array[1]),file_array[2],file_array[2]) elif file_info[0:3]=="ser": servicemenu.Append(int(file_array[1]),file_array[2],file_array[2]) elif file_info[0:3]=="mid": middlemenu.Append(int(file_array[1]),file_array[2],file_array[2])
文件“模块ID”段将作为该模块的唯一标识,与服务器端模块进行匹配。另外,要求模块XRC文件必须存放于平台Module目录。以下为客户端的所有模块清单,其中ID为“100*”的模块,服务器端已完成对接,其他部分读者可以根据自身的需求自行开发或扩展,平台功能模块XRC文件列表见图16-20。
图16-20 功能模块XRC文件列表
16.5.6 执行功能模块
由于OManager只有两层结构,与服务器端的通信就是一个交互过程,由客户端发起任务请求,服务器执行任务并返回操作结果,操作步骤见图16-21。
图16-21 功能模块执行步骤
为提高平台的通用性及兼容度,OManager的数据封装、传输、加密方式及服务器端与OMserver一致,即传输采用了rpyc框架、RC4加密算法、服务器端同一监听服务。服务器端的实现本节不再做介绍,具体可参考13.5.3节。下面介绍基于wxPython实现的客户端提交任务的几个方法。
try: conn=rpyc.connect(self._ip,int(self._port)) #连接rpyc服务器 #调用login方法实现通信账号、密码校验 conn.root.login('OMuser','KJS23o4ij09gHF734iuhsdfhkGYSihoiwhj38u4h') except Exception,e: message=u"系统提示:连接远程服务器超时。"+str(e) wx.MessageBox(message,u"OManager服务器管理平台:",style=wx.OK|wx.ICON_ERROR) return #调用OnGetSelectServerinfo方法获取计算机名、字符串、服务器数量 _server_list=self.OnGetSelectServerinfo('serverserial_ip',1,int(self._max_servers)) #判断用户是否选择了至少一台服务器,不选择则直接返回 if not _server_list: return #操作记录调用了Addsyslogs方法写入user_logs表,用于操作记录追溯 Intologs.Addsyslogs(self.CurrentAdmin,u"操作对象:"+\ self.OnGetSelectServerinfo('lip',1,20)+u"-操作MID:"+GetModelestrrow[0]) #合并提交串,格式:“模块ID@@主机IP*主机名,N@@参数1@@参数2@@” 例如:“1001@@192.168.1.21*SN2013-08-021@@30@@” put_string+=str(GetModelestrrow[0])+"@@"+_server_list+"@@"+Parameter_string #调用tencode方法对提交串进行加密 put_string=FunApp.tencode(put_string,self._secret_key) # #调用rpyc的Runcommands方法执行任务,返回的结果通过tdecode方法解密OPresult= FunApp.tdecode(conn.root.Runcommands(put_string),self._secret_key).decode('utf8') #在“输出消息”框输出返回结果 self.OnWriteMessageBox(FunApp.format_str(OPresult)) conn.close
下面为“输出消息”框输出消息方法,使用SetInsertionPoint(0)获取消息插入点,通过WriteText方法写入消息,代码如下:
def OnWriteMessageBox(self,message): t = time.localtime(time.time) st = time.strftime("%Y-%m-%d %H:%M:%S", t) #获取当前系统时间 self.SysMessaegText.SetInsertionPoint(0) #设置消息框插入点,参数0为开始位置 #将方法参数message(消息内容)写入消息框 self.SysMessaegText.WriteText("++++++++++++"+str(st)+"++++++++++++++++\n"+message+"\n") self.SysMessaegText.SetInsertionPoint(0)
执行任务返回的结果见图16-22。另外OManager的窗体元素支持任意角度的组合、分离、拖动等,管理员可以根据不同喜好进行调整。
16.5.7 平台程序发布
为了让平台在没有Python以及第三方模块包的环境中正常运行,对源程序进行打包发布是项目最后一个环节,对此pyinstaller(http://www.pyinstaller.org)提供了很好的解决方案,其支持Linux与Windows平台可执行程序的制作,简单易用。Pyinstaller 2.0无须安装,解压即可使用,下面为平台打包的bat批处理脚本。
图16-22 功能模块执行结果
【install.bat】
cd D:\python\OManager\OManager d: rd /S /Q dist rd /S /Q build del logdict2.7.3.final.0-1.log python d:/soft/pyinstaller-2.0/pyinstaller.py --onedir -w --icon=img/imac.ico OManager.py copy MD5sum.exe dist\OManager xcopy /s data dist\OManager\data\ xcopy /s img dist\OManager\img\ xcopy /s Module dist\OManager\Module\ xcopy /s numbers dist\OManager\numbers\ xcopy /s tmp dist\OManager\tmp\ rd /S /Q build rd /S /Q build del logdict2.7.3.final.0-1.log
假设项目目录为“D:\python\OManager\OManager”,参数“--onedir”为创建的一个目录,包含exe文件以及相关依赖类包;“-w”表示制作视窗界面,无控制台(命令行);“--icon”指定执行程序图标;“OManager.py”为平台入口源程序。通过xcopy复制平台相关目录到打包路径(如dist\OManager)。打包后的目录结构见图16-23。
图16-23 打包后生成的文件列表
最后一步就是制作安装包,我们可以简单对目录制作压缩包发布,也可以使用更加专业的安装包制作工具,如Advanced Installer、Inno Setup、Smart Install Maker等,最终将生成一个安装包文件“Setup.exe”,单击安装后的效果见图16-24。
图16-24 系统安装界面
参考提示 RC4加密算法参考文章 http://www.snip2code.com/Snippet/27937/Blockout-encryption-decryption-methods-p 。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论