返回介绍

13.5 系统功能模块设计

发布于 2024-01-29 22:54:22 字数 12553 浏览 0 评论 0 收藏 0

13.5.1 前端数据加载模块

OMServer平台的Web前端采用prototype.js作为默认Ajax框架,通过get方式向定义好的Django视图发起请求,功能视图通过HttpResponse方法直接输出结果,前端会将输出的结果做页面渲染。图13-6为应用ID(app_categId)等于1的HttpResponse输出结果,前端会将这个结果串进行分割,然后填充页面元素,后端返回主机信息。

图13-6 后端返回主机信息

前端各区域对应的数据库表及视图方法见图13-7。

图13-7 前端各区域对应后台方法及数据库表

局部方法代码如下:

【/data/www/OMserverweb/autoadmin/views.py】

"""
=Return server IP list
=返回服务器列表方法
"""
def server_list(request):
  ip=""
  ip_hostname=""
  if not 'app_categId' in request.GET:
     app_categId=""
  else:
     app_categId=request.GET['app_categId']   #获取用户选择的应用分类ID
  #ServerList为server_list表模型对象,实现过滤获取的应用分类ID相匹配的主机列表
  ServerListObj = ServerList.objects.filter(server_app_id=app_categId)
  for e in ServerListObj:
     ip+=","+e.server_lip
     ip_hostname+=","+e.server_lip+"*"+e.server_name
  server_list_string=ip[1:]+"|"+ip_hostname[1:]
  # 输出格式:192.168.1.10,192.168.1.20|192.168.1.10*sn2012-07-010,\
  #192.168.1.20*sn2013-08-020,其中“|”分隔符前部分为IP地址,作为HTML <option>
   下拉框显示项,
  #分隔符后部分为<option>的value,以“*”号作为分隔符,目的是为后端提供主机名及IP两种
   目标地址支持
  return HttpResponse(server_list_string)
"""
=Return module list
=返回功能模块列表方法
"""
def module_list(request):
  module_id="-1"
  module_name=u"请选择功能模块..."
  # ModuleList为module_list表模型对象,实现读取所有模块列表,以模块id做排序
  ModuleObj = ModuleList.objects.order_by('id')
  for e in ModuleObj:
     module_id+=","+str(e.id)
     module_name+=","+e.module_name
  module_list_string=module_name+"|"+module_id
  #输出格式:“请选择功能模块...,查看系统日志,查看最新登录,查看系统版本|-1,1001,1002,1003”
  #其中“|”号分隔模块名称与模块ID,Web前端获取数据后通过JavaScript做拆分与组装
  return HttpResponse(module_list_string)

13.5.2 数据传输模块设计

传输模块采用rpyc分布式计算框架,利用分布式特点可以实现多台主控设备的支持,具备一定横向扩展及容灾能力。rpyc分为两种角色,一种为Server端,另一种为Client端,与传统的Socket工作方式一样,区别是rpyc实现了更高级的封装,支持同步与异步操作、回调和远程服务以及透明的对象代理,可以轻松在Server与Client之间传递Python的任意对象,在性能方面也非常高效。下面介绍的是Django的module_run视图方法,实现接收功能模块的提交参数、加密、发送、接收功能模块运行结果等,局部方法代码如下:

【/data/www/OMserverweb/autoadmin/views.py】

"""
= Run module
= 运行模块视图方法(向rpyc服务器端发起任何请求)
"""
def module_run(request):
  import rpyc
  put_string=""
  if not 'ModuleID' in request.GET:   #接收模块ID、操作主机、模块扩展参数等(更多源码已省略)
     Module_Id=""
  else:
     Module_Id=request.GET['ModuleID']
     put_string+=Module_Id+"@@"
     ……
  try:
     conn=rpyc.connect('192.168.1.20',11511)  #连接rpyc主控端主机,端口:11511
     #调用rpyc Server的login方法实现账号、密码校验,屏蔽恶意的连接
     conn.root.login('OMuser','KJS23o4ij09gHF734iuhsdfhkGYSihoiwhj38u4h')
  except Exception,e:
     logger.error('connect rpyc server error:'+str(e))
     return HttpResponse('connect rpyc server error:'+str(e))
  #对请求数据串使用tencode方法进行加密,密钥使用Django 中settings.SECRET_KEY的值
  put_string=tencode(put_string,settings.SECRET_KEY)
  #调用rpyc Server的Runcommands方法实现功能模块的任务下发,返回的结果使用tdecode进行解密
  OPresult=tdecode(conn.root.Runcommands(put_string),settings.SECRET_KEY)
  return HttpResponse(OPresult)   #输出结果供前端渲染

关于rpyc服务器端的实现原理,首先接收rpyc客户端传递过来的信息,通过解密方法还原出模块ID、操作对象、模块扩展参数等信息,再通过exec方法导入相应的功能模块(要事先完成编写,否则会提示找不到指定功能模块),调用功能模块的相关方法,实现操作任务向业务集群服务器下发与执行,最后将任务执行结果串进行格式化、加密后返回给Web层。完整实现代码如下:

【/home/test/OMServer/OMservermain.py】

# -*- coding: utf-8 -*-
import time
import os,sys
import re
from cPickle import dumps
from rpyc import Service
from rpyc.utils.server import ThreadedServer
import logging
from libraries import *
from config import *
#定义服务器端模块存放路径
sysdir=os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.sep.join((sysdir,'modules/'+AUTO_PLATFORM)))
class ManagerService(Service):
  #定义login认证方法,对外开放调用的方法,rpyc要求加上“ exposed_”前缀,调用时使用
  # login即可
  def exposed_login(self,user,passwd):
     if user=="OMuser" and passwd=="KJS23o4ij09gHF734iuhsdfhkGYSihoiwhj38u4h":
        self.Checkout_pass=True   #认证结果标记变量,值为“True”则认证通过,反之
                          #认证失败
     else:
        self.Checkout_pass=False
  def exposed_Runcommands(self,get_string):
     logging.basicConfig(level=logging.DEBUG,   #启用系统日志记录
             format='%(asctime)s [%(levelname)s] %(message)s',
             filename=sys.path[0]+'/logs/omsys.log',
             filemode='a')
     #判断是否通过认证
     try:
        if self.Checkout_pass!=True:
          return tencode("User verify failed!",SECRET_KEY)
     except:
          return tencode("Invalid Login!",SECRET_KEY)
     #获取rpyc Client的请求串get_string,通过tdecode方法解密后再进行分隔,分隔符为“@@”
     self.get_string_array=tdecode(get_string,SECRET_KEY).split('@@')
     self.ModuleId=self.get_string_array[0]   #获取功能模块ID
     self.Hosts=self.get_string_array[1]   #获取操作目标主机
     sys_param_array=[]   #获取功能模块的扩展参数并追加到列表
     for i in range(2,len(self.get_string_array)-1):
        sys_param_array.append(self.get_string_array[i])
     #加载模块ID应对的模块名,格式为“Mid_”+模块ID,如“Mid_1001.py”
     mid="Mid_"+self.ModuleId
     importstring = "from "+mid+" import Modulehandle"
     try:
        exec importstring
     except:
        return tencode(u"module\""+mid+u"\"does not exist, Please add
it",SECRET_KEY)
     #调用模块相关方法,下发执行任务
     Runobj=Modulehandle(self.ModuleId,self.Hosts,sys_param_array)
     Runmessages=Runobj.run
     #根据不同主控端组件格式化输出,支持Func、Ansible、Saltstack
     if AUTO_PLATFORM=="func":
        if type(Runmessages) == dict:
          returnString = func_transform(Runmessages,self.Hosts)
        else:
          returnString = str(Runmessages).strip
     elif AUTO_PLATFORM=="ansible":
        if type(Runmessages) == dict:
          returnString = ansible_transform(Runmessages,self.Hosts)
        else:
          returnString = str(Runmessages).strip
     elif AUTO_PLATFORM=="saltstack":
        if type(Runmessages) == dict:
          returnString = saltstack_transform(Runmessages,self.Hosts)
        else:
          returnString = str(Runmessages).strip
     #对返回给rpyc Client的数据串进行加密
     return tencode(returnString,SECRET_KEY)
s=ThreadedServer(ManagerService,port=11511,auto_register=False)
s.start   #启动rpyc服务监听、接收、响应请求

数据传输的安全性关系到整个运营平台的生命线,因此严格做好入侵安全防范至关重要。OMServer平台采用base64.b64encode、base64.b64decode加上密钥混淆算法(RC4)实现数据的加密与解密。OMServer平台遵循一个原则,数据在传输之前调用tencode方法进行加密,在数据接收完毕后调用dencode方法进行解密。解密的密钥采用项目settings.py中的SECRET_KEY变量值。同时在rpyc服务器端添加login方法,实现逻辑层的安全防护。

【/home/test/OMServer/libraries.py】

# -*- coding: utf-8 -*-
#!/usr/bin/env python
import random, base64
from hashlib import sha1
#RC4加密算法
def crypt(data, key):
  x = 0
  box = range(256)
  for i in range(256):
     x = (x + box[i] + ord(key[i % len(key)])) % 256
     box[i], box[x] = box[x], box[i]
  x = y = 0
  out = []
  for char in data:
     x = (x + 1) % 256
     y = (y + box[x]) % 256
     box[x], box[y] = box[y], box[x]
     out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
  return ''.join(out)
#使用RC4算法加密编码后的数据,data为加密的数据,key为密钥
def tencode(data, key, encode=base64.b64encode, salt_length=16):
  """RC4 encryption with random salt and final encoding"""
  salt = ''
  for n in range(salt_length):
     salt += chr(random.randrange(256))
  data = salt + crypt(data, sha1(key + salt).digest)
  if encode:
     data = encode(data)
  return data
#使用RC4算法解密编码后的数据,data为加密的数据,key为密钥
def tdecode(data, key, decode=base64.b64decode, salt_length=16):
  if decode:
     data = decode(data)
  salt = data[:salt_length]
  return crypt(data[salt_length:], sha1(key + salt).digest)

13.5.3 平台功能模块扩展

OMServer平台模块的扩展需要完成两件事情,一是在前端添加模块基本信息,二是在服务器端编写对应的任务模块,下面对具体内容进行详细说明。

(1)添加前端模块

添加前端模块包括指定模块名称、功能说明、模块扩展(HTML表单作为模块参数)等,具体操作是点击首页的【添加模块】按钮,跳转到“添加模块”表单页面,其中最关键的是“模块扩展”输入框,支持所有HTML表单元素,后台通过name属性引用其值(value)。OMServer目前支持最多两个扩展参数,name属性要求使用“sys_param_1”、“sys_param_2”作为其定义值,当然,扩展更多参数的改造成本也非常低。在本示例中添加“重启进程服务”模块,具体操作如图13-8所示。

提交后将返回新增模块的ID,该模块ID同时会作为服务器端任务模块的后缀名,如图13-9所示,记下模块ID“1007”,前端模块添加完毕。

图13-8 添加前端模块

图13-9 提交前端模块添加

(2)添加服务器端任务模块

服务器端模块的作用是负责具体远程操作任务的功能封装,支持3种Python自动化操作组件,包括Saltstack、Ansible、Func。不同组件的API语法及返回数据结构都不一样,因此OMServer在设计时就将不同组件的模块进行隔离,具体模块目录结构如图13-10所示,在模块目录(modules)下组件名作为二级目录名,二级目录下为具体的任务模块,文件名称由“Mid_”+模块ID组成,与前端生成的模块ID进行关联。

图13-10 服务器端模块目录结构

关于任务模块的编写,不同组件的实现规范和方法都不一样,在编写任务模块之前需要更新配置文件config.py的两个选项,其中“AUTO_PLATFORM”为指定组件环境,可选项为“ansible”、“saltstack”、“func”,“SECRET_KEY”为指定加密、解密的密钥,与项目settings.py中的SECRET_KEY变量保持一致。另外modules/(ansible|saltstack|func)/Public_lib.py文件的作用是导入、定义各组件的API模块包及全局参数,同时也增加代码的复用性。

【/home/test/OMServer/config.py】

# -*- coding: utf-8 -*-
#!/usr/bin/env python
AUTO_PLATFORM = "saltstack"   #指定组件环境,支持Saltstack、Ansible、Func
#密钥,与项目中setting.py的SECRET_KEY变量保持一致
SECRET_KEY = "ctmj#&;8hrgow_^sj$ejt@9fzsmh_o)-=(byt5jmg=e3#foya6u"

服务器端任务模块由Modulehandle类及其两个方法组成,其中__init__方法作用为初始化模块基本信息,包括操作主机列表、模块ID、模块扩展参数等;run方法实现组件API的调用以及返回执行结果。针对“重启进程服务”这个任务模块,下面分别介绍3个组件的不同实现方法。

1)编写Ansible组件ID为“1007”模块。

根据Ansible组件模块的开发原理,通过调用command模块实现远程命令执行,使用copy模块实现文件远程同步,详细源码如下:

【/home/test/OMServer/modules/ansible/Mid_1007.py】

# -*- coding: utf-8 -*-
from Public_lib import *
#重启应用模块进程服务#
class Modulehandle:
  def __init__(self,moduleid,hosts,sys_param_row):   #初始化方法无须改动
     self.hosts = ""
     self.Runresult = ""
     self.moduleid = moduleid   #模块ID
     self.sys_param_array= sys_param_row   #模块扩展参数列表
     self.hosts=target_host(hosts,"IP") #格式化主机信息,参数“IP”为IP地址,“SN”为主机名
  #任务下发、执行方法
  def run(self):
     try:    #根据模块扩展参数定义执行的不同命令集
        commonname=str(self.sys_param_array[0])
        if commonname=="resin":
          self.command="/etc/init.d/resin restart"
        elif commonname=="nginx":
          self.command="/etc/init.d/nginx restart"
        elif commonname=="haproxy":
          self.command="/etc/init.d/haproxy restart"
        elif commonname=="apache":
          self.command="/etc/init.d/httpd restart"
        elif commonname=="mysql":
          self.command="/etc/init.d/mysql restart"
        elif commonname=="lighttpd":
          self.command="/etc/init.d/lighttpd restart"
        #调用Ansible提供的API(command模块),执行远程命令
        self.Runresult = ansible.runner.Runner(
        pattern=self.hosts, forks=forks,
        module_name="command", module_args=self.command,).run
        if len(self.Runresult['dark']) == 0 and len(self.Runresult['contacted']) == 0:
          return "No hosts found,请确认主机已经添加ansible环境!"
     except Exception,e:
        return str(e)
     return self.Runresult   #返回执行结果

2)编写Saltstack组件ID为“1007”模块。

根据Saltstack组件模块的开发原理,通过调用cmd方法配置“cmd.run”与“cp.get_file”参数实现远程命令执行及文件远程同步,详细源码(部分)如下:

【/home/test/OMServer/modules/saltstack/Mid_1007.py】

def run(self):
     try:
        client = salt.client.LocalClient
        ……
        #调用Saltstack提供的API(cmd.run模块),执行远程命令
        self.Runresult  = client.cmd(self.hosts,'cmd.run',[self.command],\
expr_form='list')
        if len(self.Runresult) == 0:
          return "No hosts found,请确认主机已经添加saltstack环境!"
     except Exception,e:
        return str(e)
     return self.Runresult   #返回执行结果

3)编写Func组件ID为“1007”模块。

根据Func组件模块的开发原理,通过调用client.command.run方法实现远程命令执行,使用client.copyfile.copyfile方法实现文件远程同步,详细源码(部分)如下:

【/home/test/OMServer/modules/func/Mid_1007.py】

 def run(self):
     try:
        client = fc.Overlord(self.hosts)
……
#调用Func提供的API(command.run模块),执行远程命令
        commonname=str(self.sys_param_array[0])
        self.Runresult=client.command.run(self.command)
     except Exception,e:
        return str(e)
     return self.Runresult   #返回执行结果

任务模块编写完成后,启动服务端服务,运行以下命令:

# cd /home/test/OMServer
# python OMservermain.py &

最后,打开浏览器访问http://omserver.domain.com,效果见图13-11。

图13-11 远程操作功能截图

参考提示  RC4加密算法参考文章http://www.snip2code.com/Snippet/27937/Blockout-encryption-decryption-methods-p。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文