现在只能实现半自动,需要手动编辑更新说明,脚本用python实现,用到的主要模块 ConfigParser,ssh,multiprocessing


#!/usr/bin/env python

# -*- coding=utf-8 -*-

import sys,os,ssh,ConfigParser,time

from multiprocessing import Process,Value


class game_update(object):

   def __init__(self):

       if len(sys.argv) != 3 or sys.argv[1] == '-h' or sys.argv[1] == '--help':

           print 'Useage:\n' + '\t' + sys.argv[0] + ' Object ' + 'update_log'

           sys.exit('Example:\n' + '\t' + sys.argv[0] + ' ds-rtm ' + '2013-05-23-07_RTM.txt')

       self.script_dir = os.getcwd() # 脚本所在目录

       self.cur_date = time.strftime('%Y-%m-%d') # 系统当前日期

       self.cur_hour = time.strftime('%H') # 系统当前小时

       # 服务器IP信息列表文件所在位置

       self.iplist_ini = self.script_dir+'/conf/iplist.ini'

       # 上传patchinfo文件的主机信息

       self.patchinfo_ini = self.script_dir+'/conf/patchinfo.ini'

       # 上传服务器更新包,自动更新包,手动更新包的主机信息

       self.account_ini = self.script_dir+'/conf/account.ini'

       # 更新日志所在目录

       self.update_file = self.script_dir+'/update_log/'+sys.argv[1]+os.sep+sys.argv[2]

       #  命令执行结果日志文件

       self.logfile = self.script_dir+'/result_log/'+sys.argv[1]+os.sep+self.cur_date+os.sep+sys.argv[2]

       # 停服命令

       self.stop_command = 'cd /data/'  + sys.argv[1].split('-')[0] + 'GameServer/Server && ./stop.sh'

       # 开服命令

       self.start_command = 'cd /data/' + sys.argv[1].split('-')[0] + 'GameServer/Server && ./run.sh'

       # 执行更新命令

       self.update_command = '/bin/sh ' + sys.argv[2]

   def init_dir(self):

       '''

       初始化脚本使用的目录,目录结构如下:

       ./conf         # 配置文件目录

       ./result_log   # 记录更新日志产生的的日志

       ./update_log   # 存放patchinfo文件和更新日志

       '''

       if not os.path.isdir(self.script_dir + '/result_log/' + sys.argv[1] + '/' + self.cur_date):

           os.makedirs(self.script_dir + '/result_log/' + sys.argv[1] + '/' + self.cur_date)

       if not os.path.isdir(self.script_dir + '/conf'):

           os.makedirs(self.script_dir + '/conf')

       if not os.path.isdir(self.script_dir + '/update_log/' + sys.argv[1]):

           os.makedirs(self.script_dir + '/update_log/' + sys.argv[1])

   def get_ipaddr(self):

       '''

       获取ip列表,用户名,密码

       '''

       ip_par = ConfigParser.ConfigParser()

       ip_par.readfp(open(self.iplist_ini))

       self.user_ip=ip_par.get(sys.argv[1],'username')

       self.pass_ip=ip_par.get(sys.argv[1],'password')

       self.port_ip=int(ip_par.get(sys.argv[1],'port'))

       self.all_addr = []

       self.all_option = ip_par.options(sys.argv[1])

       self.all_option.remove('username')

       self.all_option.remove('password')

       self.all_option.remove('port')

       for option in self.all_option:

           self.all_addr.append(ip_par.get(sys.argv[1],option)+':'+option)

   def get_patchinfo(self):

       '''

       获取上传patchinfo文件的主机信息,用户名密码等信息

       '''

       cfp_patch = ConfigParser.ConfigParser()

       cfp_patch.readfp(open(self.patchinfo_ini))

       self.addr_patch = cfp_patch.get(sys.argv[1],'address')

       self.port_patch = cfp_patch.get(sys.argv[1],'port')

       self.user_patch = cfp_patch.get(sys.argv[1],'username')

       self.pass_patch= cfp_patch.get(sys.argv[1],'password')

       self.local_file = cfp_patch.get(sys.argv[1],'local_file')

       self.remote_path = cfp_patch.get(sys.argv[1],'remote_path')

       self.use_ssl = cfp_patch.get(sys.argv[1],'use_ssl')

   def upload_patchinfo(self):

       '''

       上传patchinfo文件

       '''

       import ftplib

       if self.use_ssl == 'no':

           ftp = ftplib.FTP()

       else:

           ftp = ftplib.FTP_TLS()

       ftp.connect(host=self.addr_patch,port=self.port_patch)

       ftp.login(user=self.user_patch,passwd=self.pass_patch)

       remote_file = os.path.basename(self.local_file)

       ftp.cwd(self.remote_path)

       patchinfo_file = open(self.local_file)

       ftp.storbinary('STOR'+remote_file,patchinfo_file)

       ftp.quit

   def write_log(self,command_output='',hostname='',hostid='',comm_name=''):

       '''

       将服务器返回的输出,写入到文件中

       '''

       self.init_dir()

       log_file = open(self.logfile,'ab')

       log_file.write("%s [%s:%s:%s] %s %s %s" %('-'*20,hostid,hostname,sys.argv[1],'Start','-'*20,'\n'))

       log_file.write(comm_name+'\n')

       log_file.write('\t' + command_output)

       log_file.write("%s [%s:%s:%s] %s %s %s" %('-'*20,hostid,hostname,sys.argv[1],'End','-'*20,'\n'))

       log_file.close()

   def stop_game(self):

       ''' 执行停服命令 '''

       self.luncher(command=self.stop_command,file='')

   def start_game(self):

       ''' 执行开服命令 '''

       self.luncher(command=self.start_command,file='')

   def exec_update(self):

       ''' 执行更新 '''

       self.luncher(command=self.update_command,file='')

   def generate_execfile(self):

       ''' 把需要执行的命令,存放在服务器上,通过 /bin/sh sya.argv[2] 执行更新 '''

       try:

           a = open(self.update_file)

       except:

           raise IOError('Can\'t open ' + self.update_file)

       file_handler = a.readlines()

       self.luncher(command='',file=file_handler)

   def luncher(self,command='',file=''):

       ''' 根据主机列表'all_addr'的长度产生相应数量的进程,并调用connector方法执行ssh连接 '''

       self.zero_ = Value('i',0)

       luncher_box = []

       for s in xrange(len(self.all_addr)):

           hostip = self.all_addr[s].split(':')[0]

           hostid = self.all_addr[s].split(':')[1]

           arguments = (hostip,hostid,command,file,self.zero_)

           luncher_box.append(Process(target=self.connector,args=(arguments)))

       for p in xrange(len(luncher_box)):

           luncher_box[p].start()

       for j in xrange(len(luncher_box)):

           luncher_box[j].join()

   def connector(self,args1,args2,args3,args4,args5):

       ''' 执行ssh连接; 根据参数不同,决定是执行命令或者是上传需要执行的更新文件 '''

       hostip = args1

       hostid = args2

       command = args3

       exec_file = args4

       sclient = ssh.SSHClient()

       sclient.set_missing_host_key_policy(ssh.AutoAddPolicy())

       sclient.connect(hostname=hostip,port=self.port_ip,username=self.user_ip,password=self.pass_ip,timeout=10)

       if exec_file == '':

           chan = sclient.get_transport().open_session()

           chan.get_pty()

           out = chan.makefile()

           chan.exec_command(command)

           command_status  = int(chan.recv_exit_status())

           self.write_log(command_output=out.read(),hostname=hostip,hostid=hostid,comm_name=command)

           args5.value += command_status

       else:

           sftp = sclient.open_sftp()

           sftp_file = sftp.file(sys.argv[2],mode='w')

           f_line = len(exec_file)

           for n in xrange(len(exec_file)):

               if n == f_line-1:

                   sftp_file.write(exec_file[n][0:-1])

               else:

                   sftp_file.write(exec_file[n][0:-1]+'&&')

           sftp.close()

       sclient.close()


if __name__ == '__main__':

   u = game_update() # 绑定game_update到实例u上

   u.init_dir() # 执行game_update的init_dir方法,初始化目录

   u.get_ipaddr() # 读取指定服的IP列表及用户信息

   u.get_patchinfo() # 读取上传patchinfo文件的主机ip等信息

   u.generate_execfile() # 生成执行更新的文件,并通过sftp上传到指定服务器上

   u.stop_game() # 执行停服操作

   if u.zero_.value == 0: # 如果停服成功,并执行更新,如果不成功退出脚本

       u.exec_update()

   else:

       sys.exit('stop game fail')

   if u.zero_.value == 0: # 如果更新没有问题,执行开服

       u.start_game()

   else:

       sys.exit('update game fail')