def check_test_task(package): """ :param package: 包内需包含task_name测试任务名称/v_user虚拟用户数/start_type启动类型 stop_type停止类型/if_error出错后续/start_time开始时间/end_time结束时间 :return: True, 解包后的数据/False, None """ # 接收的struct数据中,s需要解码,i不需要解码 try: tid, u, up, st1, st2, ie, exct, fs, st, et = struct.unpack( dataFormat['newTestTask'], package) st = st.decode().strip('\x00') et = et.decode().strip('\x00') except Exception as e: app_logger.error('解包异常:' + repr(e)) return False, None else: return True, { 'task_id': tid, 'v_user': u, 'ramp_up': up, 'start_type': st1, 'stop_type': st2, 'if_error': ie, 'exc_times': exct, 'file_size': fs, 'start_time': None if st == '' or st == 'None' else st, 'end_time': None if et == '' or et == 'None' else et }
def set(self, server_ip, startup_date, startup_flag, error_info=None): """ 记录启动日志 :param server_ip: 注册服务器的ip地址 :param startup_date: 启动日期 :param startup_flag: 启动与注册结果状态 :param error_info: 启动与注册错误信息,若成功则不记录 :return: data: True/False """ try: value = json.dumps( { "server_ip": server_ip, "date": startup_date, "flag": startup_flag, "error_info": error_info, }, ensure_ascii=False ) redis_pool.lpush(self.key, value) except Exception as e: msg = "startUpLog程序启动日志入库失败:" + repr(e) app_logger.error(msg) return False else: msg = "startUpLog程序启动日志入库成功" app_logger.debug(msg) return True
def init_vusers(self): # 首先初始化出来一颗原始的插件树用以基本检查 self.init_plugin_tree(self.plugin_data[0]) # 如果基本初始化失败则不操作协程池 if self.flow_init_result: # 初始化协程池 try: self.gevent_pool = GeventPool(self.base_vuser_num) except Exception as e: msg = '测试任务虚拟用户并发池创建失败:%s' % repr(e) self.flow_init_result = False app_logger.error(msg) self.trans_init_log(msg) else: msg = '测试任务虚拟用户并发池创建成功' app_logger.debug(msg) self.trans_init_log(msg) vuser_index = 1 free_count = self.gevent_pool.free_count() while free_count > 0: # 每个虚拟用户拥有属于自己的插件树,互不干扰 plugin_tree = self.init_plugin_tree( self.plugin_data[0], vuser_index) self.gevent_pool.spawn(self.vuser_excute, plugin_tree) self.trans_init_log("虚拟用户%d准备完毕" % vuser_index) vuser_index += 1 free_count -= 1
def trans_many(self, c_name, logs): try: collection = self.log_pool_table[c_name] collection.insert_many(logs) except Exception as e: app_logger.error('mongodb写入日志失败,原因:%s' % repr(e)) return False else: app_logger.debug('mongodb写入日志成功') return True
def insert_test_task(self, base_data): # 准备向redis中插入新增task数据 insert_result = model_redis_test_task.set( base_data['task_id'], json.dumps(base_data, ensure_ascii=False)) if insert_result: return True else: app_logger.error('测试任务数据写入redis失败') self.request.send(str.encode('Failure')) self.request.close()
def new_test_task_job(base_data): app_logger.debug('准备新增测试任务') app_logger.debug('准备从redis中获取测试任务定时任务数据') # 从redis中获取测试任务定时任务数据,包含add_task新增测试任务定时任务/kill_task强制终止测试任务定时任务 jobs = model_redis_task_job.query(base_data['task_id']) task_info = model_redis_test_task.query(base_data['task_id']) if jobs: app_logger.debug('从redis中获取测试任务定时任务数据成功') try: jobs_dict = json.loads(jobs) task_info_dict = json.loads(task_info) except Exception as e: app_logger.error('测试任务定时任务数据反序列化失败:%s' % repr(e)) http_tell_test_task_status(task_id=base_data['task_id'], status=-2) else: app_logger.debug('测试任务定时任务数据反序列化成功') # 将测试任务的定时任务状态中run字段值变更为True,代表任务已发起 jobs_dict['add_task']['run'] = True set_flag = model_redis_task_job.set(base_data['task_id'], json.dumps(jobs_dict)) if set_flag: app_logger.debug('测试任务定时任务数据重写成功') # 获取插件数据 json_file_path = os.path.join(task_info_dict['file_path'], 'task.json') if os.path.exists(json_file_path) and os.path.isfile( json_file_path): try: # 读取测试插件数据列表,并传递给新增测试任务方法 with open(os.path.join(base_data["file_path"], 'task.json'), encoding='utf-8') as json_file: task_json = json.load(json_file) except Exception as e: app_logger.error('测试任务插件数据文件内容反序列化失败:%s' % repr(e)) http_tell_test_task_status( task_id=base_data['task_id'], status=-2) else: # 新增进程-一个测试任务一个进程 p = Process(target=create_task_run_flow_controller, args=[base_data, task_json, os.getpid()]) p.start() else: app_logger.error('测试任务插件数据文件读取失败,文件路径:%s' % json_file_path) http_tell_test_task_status(task_id=base_data['task_id'], status=-2) else: app_logger.error('测试任务定时任务数据重写失败') http_tell_test_task_status(task_id=base_data['task_id'], status=-2) else: app_logger.error('从redis中获取测试任务定时任务数据失败,任务新增失败') http_tell_test_task_status(task_id=base_data['task_id'], status=-2)
def query(self, task_id): try: data = redis_pool.hget(self.key, task_id).decode() except Exception as e: msg = "redis|" + self.key + " query failed:" + repr(e) app_logger.error(msg) return None else: msg = "redis|" + self.key + " query succeed" app_logger.debug(msg) return data
def check_task_id(package): """ :param package: 包内需包含task_id测试任务id :return: True, {}/False, None """ # 接收的struct数据中,s需要解码,i不需要解码 try: task_id = struct.unpack(dataFormat['stopTestTask'], package) except Exception as e: app_logger.error('解包异常:' + repr(e)) return False, None else: return True, {'task_id': task_id[0]}
def cancel(self): # 要判断定时线程的有无与状态 try: self._continue = False self.log_trans_timer and self.log_trans_timer.cancel() except Exception as e: app_logger.error('取消日志失败,原因:%s' % (repr(e))) else: app_logger.debug('取消日志成功') finally: # 最后一次传输日志内容 self.trans_many('task%d%s' % (self.task_id, self.key_word), self.log_temporary_storage)
def set(self, log): """ 支持传入单条log,将其添加入storage :param log: 单条log的内容 :return: 本方法无返回 """ try: if type(log) is dict: self.log_temporary_storage.append(log) elif type(log) is list: self.log_temporary_storage += log except Exception as e: app_logger.error('暂存日志失败,原因:%s' % repr(e)) else: app_logger.debug('暂存日志成功')
def set(self, task_id, value): """ 记录定时任务基础数据 :param task_id: 测试任务id :param value: 任务定时执行信息 """ try: redis_pool.hset(self.key, task_id, value) except Exception as e: msg = "redis|" + self.key + " insert failed:" + repr(e) app_logger.error(msg) return False else: msg = "redis|" + self.key + " insert succeed" app_logger.debug(msg) return True
def set(self, task_id, ppid, pid): """ 记录测试任务基础数据 :param task_id: 测试任务id :param ppid: 父进程id :param pid: 子进程id :return: data: True/False """ try: redis_pool.hset(self.key, task_id, str(ppid) + ':' + str(pid)) except Exception as e: msg = "redis|" + self.key + " insert failed:" + repr(e) app_logger.error(msg) return False else: msg = "redis|" + self.key + " insert succeed" app_logger.debug(msg) return True
def make_log_pool(self): try: self.log_pool = MongoClient( host=database_config.get("logMongodb", "host"), port=int(database_config.get("logMongodb", "port")), maxPoolSize=100) self.log_pool_table = self.log_pool[self.log_table] self.log_pool_table.authenticate( database_config.get("logMongodb", "username"), database_config.get("logMongodb", "password")) except Exception as e: msg = "mongodb连接池初始化失败,失败原因:" + repr(e) app_logger.error(msg) return False, repr(e) else: msg = "mongodb连接池初始化成功" app_logger.debug(msg) return True, None
def __init__(self, base_data, plugin_data): # self.flow_init_result = True # 将测试任务基础数据转换为多个变量 self.base_data = base_data self.base_task_id = base_data['task_id'] self.base_exc_times = base_data['exc_times'] self.base_vuser_num = base_data['v_user'] self.plugin_data = plugin_data self.worker_info_id = app_config.getint("worker", "id") self.worker_info = {"id": self.worker_info_id} self.gevent_pool = None http_tell_test_task_status(task_id=self.base_task_id, status=2) self.parameters_storage = ParametersStorage() # 实例化日志控制器 self.init_log_controller = SyncLogController('tasklog', self.base_task_id, '_init') if self.init_log_controller.log_pool_make_result: app_logger.debug('测试任务ID:%d基础日志控制器初始化成功' % self.base_task_id) self.trans_init_log('基础日志控制器初始化成功') else: app_logger.error('测试任务ID:%d基础日志控制器初始化失败') self.flow_init_result = False self.run_log_controller = AsyncLogController('tasklog', self.base_task_id, '_run') if self.run_log_controller.log_pool_make_result: app_logger.debug('测试任务ID:%d运行日志控制器初始化成功' % self.base_task_id) self.trans_init_log('运行日志控制器初始化成功') else: app_logger.error('测试任务ID:%d运行日志控制器初始化失败') self.flow_init_result = False if self.flow_init_result: # 写一些环境信息 self.trans_init_log("启动测试任务") # 递归原始数据 self.trans_init_log("准备初始化各虚拟用户的插件树") # self.recurse_plugin_tree(plugin_data[0]) # self.trans_init_log("插件及流程控制器初始化结束") else: http_tell_test_task_status(task_id=self.base_task_id, status=-2)
def trans(self): """ 根据配置文件中的行数限制,传递日志至日志存储服务中 :return: 本方法无返回 """ # 判断行数是否满足要求,不满足直接pass # 计算要传输的数据行数 actual_data_rownum = len(self.log_temporary_storage) trans_data_rownum = self.log_send_item_num if actual_data_rownum >= self.log_send_item_num else actual_data_rownum self.trans_many('task%d%s' % (self.task_id, self.key_word), self.log_temporary_storage[:trans_data_rownum]) try: del (self.log_temporary_storage[:trans_data_rownum]) except Exception as e: app_logger.error('清除日志失败,原因:%s' % (repr(e))) else: app_logger.debug('暂存日志成功') if self._continue: self.log_trans_timer = threading.Timer( self.log_check_time_interval, self.trans) self.log_trans_timer.start()
def http_tell_test_task_status(task_id, status): # 准备参数 # 用户界面的执行应用注册接口地址 server_ip = app_config.get('server', 'host') server_port = app_config.get('server', 'port') try: api_response = urllib3.PoolManager(1).request( method='post', url='http://' + server_ip + ':' + str(server_port) + '/api/task/testTaskFinished.json', headers={'Content-Type': 'application/json;charset=UTF-8'}, body=json.dumps({ 'taskId': task_id, 'uuid': app_config.get('worker', 'uuid'), 'status': status })) except Exception as e: app_logger.error('回传测试任务结束时间失败,失败原因:' + repr(e)) return False else: if api_response.status == 200: # 如果返回码为200则成功,否则失败 api_response_dict = json.loads(api_response.data.decode('utf-8')) if api_response_dict['error_code'] != 200: app_logger.error('回传测试任务结束时间失败,失败原因:' + api_response_dict['error_msg']) else: app_logger.debug('回传测试任务结束时间成功') else: app_logger.error('回传测试任务结束时间失败,失败原因:用户界面服务异常') return False
def file_dir_handle(_data): """ :return: True, 创建成功的测试任务目录路径(str)/False, None """ # 检查文件存放路径 the_now = datetime.datetime.now() the_year_path = '%s/%d' % (app_config.get('file', 'path'), the_now.year) the_month = the_now.month the_day = the_now.day if not os.path.exists(the_year_path): app_logger.debug('年份文件夹不存在,尝试创建...') try: os.makedirs(the_year_path) except Exception as e: app_logger.error('年份文件夹创建失败:%s' % repr(e)) return False, None else: app_logger.debug('年份文件夹创建成功') if not os.path.exists('%s/%d' % (the_year_path, the_month)): app_logger.debug('月份文件夹不存在,尝试创建...') try: os.makedirs('%s/%d' % (the_year_path, the_month)) except Exception as e: app_logger.error('月份文件夹创建失败:' + repr(e)) return False, None else: app_logger.debug('月份文件夹创建失败') if not os.path.exists('%s/%d/%d' % (the_year_path, the_month, the_day)): app_logger.debug('日子文件夹不存在,尝试创建...') try: os.makedirs('%s/%d/%d' % (the_year_path, the_month, the_day)) except Exception as e: app_logger.error('日子文件夹创建失败:' + repr(e)) return False, None else: app_logger.debug('日子文件夹创建成功') task_dir_path = '%s/%d/%d/task_%d_%s' % ( the_year_path, the_month, the_day, _data['task_id'], the_now.strftime('%Y%m%d%H%M%S')) try: os.makedirs(task_dir_path) except Exception as e: app_logger.error('测试任务文件夹创建失败:' + repr(e)) return False, None else: app_logger.debug('测试任务文件夹创建成功') return True, task_dir_path
def http_register_user_ui_server(): # 准备参数 # 用户界面的执行应用注册接口地址 server_ip = app_config.get('server', 'host') server_port = app_config.get('server', 'port') api_url = 'http://' + server_ip + ':' + str( server_port) + '/api/task/workerRegister.json' api_headers = {'Content-Type': 'application/json;charset=UTF-8'} # 生成uuid/ip/port worker_uuid = app_config.get('worker', 'uuid') worker_host = app_config.get('worker', 'host') worker_port = int(app_config.get('worker', 'port')) api_json = json.dumps({ 'uuid': worker_uuid, 'ip': worker_host, 'port': worker_port }) try: api_response = requests.post(api_url, data=api_json, headers=api_headers, timeout=10) except Exception as e: app_logger.error('服务注册失败,失败原因:' + repr(e)) return False else: if api_response.status_code == 200: # 如果返回码为200则成功,否则失败 api_response_dict = json.loads(api_response.text) if api_response_dict['error_code'] != 200: app_logger.error('服务注册失败,失败原因:' + api_response_dict['error_msg']) return False else: app_logger.debug('服务注册成功') return {'worker_id': api_response_dict['data']['worker_id']} else: app_logger.error('服务注册失败,失败原因:用户界面服务异常') return False
def handle(self): # --- client发起connet请求,handler检测到后开始执行下面代码 app_logger.debug('收到来自IP:%s的请求' % self.client_address[0]) # --- connet请求代码末 # --- 第1次send/recv开始 app_logger.debug('准备接收第1次数据传输...') # --- recv方法阻塞handler,程序进入监听数据状态 # 根据内容长度接受内容 action_struct_data = self.request.recv( struct.calcsize(dataFormat['action'])) # --- clent发起第1次send,handler检测到后开始执行下面代码 app_logger.debug('接收到第1次数据传输') # 第1次recv接收的数据为请求类型action # 预备数据接收到内容不为空才可继续执行 if action_struct_data: # 解包内容 request_action = self.struct_unpack(action_struct_data) """ 当前支持action 1.newTestTask 新测试任务 2.stopTestTask stopTestTask 其余字符串一律返回错误信息 """ if request_action == 'newTestTask': app_logger.debug('action为新测试任务') # 回传成功状态 # 检查系统当前资源 # 如果内存剩余不足500MB,则禁止创建测试任务 if int(psutil.virtual_memory().available / 1024 / 1024) < 500: app_logger.warn('测试机剩余内存不足,无法创建测试任务') self.request.send(str.encode('Failure.测试机剩余内存不足,无法创建测试任务')) self.request.close() else: self.request.send(str.encode('Success')) # --- 第1次send/recv结束 app_logger.debug('准备接收第2次数据传输...') # --- recv方法阻塞handler,程序进入监听数据状态 # 根据内容长度接受内容 base_struct_data = self.request.recv( struct.calcsize(dataFormat[request_action])) app_logger.debug('接收到第2次数据传输') # --- clent发起第2次send,handler检测到后开始执行下面代码 # 检查新增测试任务数据基础信息 check_result, base_data = self.check_test_task( base_struct_data) if check_result: # 第2次测试任务基础数据检查通过后,新增测试任务目录,用以存放相关文件,然后再返回状态数据 handle_flag, task_dir_path = self.file_dir_handle( base_data) if handle_flag: # 回传成功状态 app_logger.debug('测试任务文件夹处理成功') self.request.send(str.encode('Success')) # --- 第2次send/recv结束 # 准备接收task文件 recvd_size = 0 file = open(task_dir_path + '.zip', 'wb') app_logger.debug('准备接收第3次数据传输...') while not recvd_size == base_data['file_size']: if base_data['file_size'] - recvd_size > 1024: # --- recv方法阻塞handler,程序进入监听数据状态 rdata = self.request.recv(1024) recvd_size += len(rdata) else: rdata = self.request.recv( base_data['file_size'] - recvd_size) recvd_size = base_data['file_size'] file.write(rdata) file.flush() file.close() app_logger.debug('接收到第3次数据传输') # 解压缩文件 try: with zipfile.ZipFile(task_dir_path + '.zip') as zfile: zfile.extractall(path=task_dir_path) except Exception as e: app_logger.error('压缩包处理失败:' + repr(e)) self.request.send( str.encode('Failure.测试任务压缩包处理失败,测试任务创建失败')) self.request.close() else: app_logger.debug('压缩包处理成功') # 新增测试任务数据 base_data['file_path'] = task_dir_path # 0未启动/1运行中/2已结束 base_data['run_status'] = 0 self.insert_test_task(base_data) # 新增定时测试任务 add_task = test_task_scheduler.add_job( func=new_test_task_job, args=[base_data], trigger='date', jobstore='redis', next_run_time=datetime.datetime.strptime( base_data['start_time'], '%Y-%m-%d %H:%M:%S') if base_data['start_time'] else datetime.datetime.now(), misfire_grace_time=3000) # 如果有结束时间,则还需创建kill的job kill_task = None if base_data['end_time']: kill_task = test_task_scheduler.add_job( func=kill_test_task_job, args=[base_data], trigger='date', jobstore='redis', next_run_time=datetime.datetime. strptime(base_data['end_time'], '%Y-%m-%d %H:%M:%S'), misfire_grace_time=3000) # 定时任务信息存入redis # run默认为false job_value = { 'add_task': { '_jobstore_alias': add_task._jobstore_alias, 'id': add_task.id, 'next_run_time': add_task.next_run_time.strftime( '%Y-%m-%d %H:%M:%S'), 'run': False, 'remove': False } } if kill_task: job_value['kill_task'] = { '_jobstore_alias': kill_task._jobstore_alias, 'id': kill_task.id, 'next_run_time': kill_task.next_run_time.strftime( '%Y-%m-%d %H:%M:%S'), 'run': False, 'remove': False } set_result = model_redis_task_job.set( base_data['task_id'], json.dumps(job_value, ensure_ascii=False)) if set_result: self.request.send(str.encode('Success')) # --- 第三次send/recv结束 self.request.close() else: self.request.send( str.encode( 'Failure.测试任务入库失败,无法创建测试任务')) self.request.close() else: app_logger.warn('测试任务文件夹处理失败') self.request.send( str.encode('Failure.测试任务文件夹处理失败,无法创建测试任务')) self.request.close() else: app_logger.warn('测试任务数据检查失败') self.request.send( str.encode('Failure.测试任务基础数据检查失败,无法创建测试任务')) self.request.close() elif request_action == 'stopTestTask': app_logger.debug('action为强制停止测试任务') self.request.send(str.encode('Success')) # --- 第一次send/recv结束 # --- handler进入监听状态,程序阻塞 app_logger.debug('listen second send from client...') # 根据action选择内容长度并接受内容 base_info_size = struct.calcsize(dataFormat[request_action]) base_struct_data = self.request.recv(base_info_size) # --- clent发起第二次send,handler检测到后开始执行下面代码 # 检查新增测试任务数据基础信息 check_result, base_data = self.check_task_id(base_struct_data) if check_result: # 调用终止进程方法 kill_test_task_job(base_data) self.request.send(str.encode('Success')) # --- 第二次send/recv结束 self.request.close() else: self.request.send( str.encode('Failure.测试任务ID检查失败,无法终止测试任务')) self.request.close() elif request_action == 'test': app_logger.debug('action为测试连接状态') self.request.send(str.encode('Success')) self.request.close() else: app_logger.warn('action内容为空或非法') self.request.send(str.encode('Failure.操作暂不支持')) self.request.close() else: app_logger.warn('首次数据传输内容为空') self.request.send(str.encode('Failure.接收到空数据')) self.request.close()