现在只能实现半自动,需要手动编辑更新说明,脚本用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')