コード例 #1
0
ファイル: archiver.py プロジェクト: shangjing105/Archery-1
def archive_switch(request):
    """开启关闭归档任务"""
    archive_id = request.POST.get('archive_id')
    state = True if request.POST.get('state') == 'true' else False
    # 更新启用状态
    try:
        ArchiveConfig(id=archive_id, state=state).save(update_fields=['state'])
        return JsonResponse({'status': 0, 'msg': 'ok', 'data': {}})
    except Exception as msg:
        return JsonResponse({'status': 1, 'msg': f'{msg}', 'data': {}})
コード例 #2
0
ファイル: archiver.py プロジェクト: shangjing105/Archery-1
def archive_audit(request):
    """
    审核数据归档申请
    :param request:
    :return:
    """
    # 获取用户信息
    user = request.user
    archive_id = int(request.POST['archive_id'])
    audit_status = int(request.POST['audit_status'])
    audit_remark = request.POST.get('audit_remark')

    if audit_remark is None:
        audit_remark = ''

    if Audit.can_review(request.user, archive_id, 3) is False:
        context = {'errMsg': '你无权操作当前工单!'}
        return render(request, 'error.html', context)

    # 使用事务保持数据一致性
    try:
        with transaction.atomic():
            audit_id = Audit.detail_by_workflow_id(
                workflow_id=archive_id,
                workflow_type=WorkflowDict.workflow_type['archive']).audit_id

            # 调用工作流插入审核信息,更新业务表审核状态
            audit_status = Audit.audit(audit_id, audit_status, user.username,
                                       audit_remark)['data']['workflow_status']
            ArchiveConfig(id=archive_id,
                          status=audit_status,
                          state=True if audit_status
                          == WorkflowDict.workflow_status['audit_success'] else
                          False).save(update_fields=['status', 'state'])
    except Exception as msg:
        logger.error(traceback.format_exc())
        context = {'errMsg': msg}
        return render(request, 'error.html', context)
    else:

        # 消息通知
        async_task(notify_for_audit,
                   audit_id=audit_id,
                   audit_remark=audit_remark,
                   timeout=60,
                   task_name=f'archive-audit-{archive_id}')

    return HttpResponseRedirect(
        reverse('sql:archive_detail', args=(archive_id, )))
コード例 #3
0
ファイル: archiver.py プロジェクト: shangjing105/Archery-1
def archive(archive_id):
    """
    执行数据库归档
    :return:
    """
    archive_info = ArchiveConfig.objects.get(id=archive_id)
    s_ins = archive_info.src_instance
    src_db_name = archive_info.src_db_name
    src_table_name = archive_info.src_table_name
    condition = archive_info.condition
    no_delete = archive_info.no_delete
    sleep = archive_info.sleep
    mode = archive_info.mode

    # 获取归档表的字符集信息
    s_engine = get_engine(s_ins)
    db = s_engine.schema_object.databases[src_db_name]
    tb = db.tables[src_table_name]
    charset = tb.options['charset'].value
    if charset is None:
        charset = db.options['charset'].value

    pt_archiver = PtArchiver()
    # 准备参数
    source = fr"h={s_ins.host},u={s_ins.user},p={s_ins.password},P={s_ins.port},D={src_db_name},t={src_table_name}"
    args = {
        "no-version-check": True,
        "source": source,
        "where": condition,
        "progress": 5000,
        "statistics": True,
        "charset": charset,
        "limit": 10000,
        "txn-size": 1000,
        "sleep": sleep
    }

    # 归档到目标实例
    if mode == 'dest':
        d_ins = archive_info.dest_instance
        dest_db_name = archive_info.dest_db_name
        dest_table_name = archive_info.dest_table_name
        dest = fr"h={d_ins.host},u={d_ins.user},p={d_ins.password},P={d_ins.port},D={dest_db_name},t={dest_table_name}"
        args['dest'] = dest
        args['bulk-insert'] = True
        if no_delete:
            args['no-delete'] = True
        else:
            args['bulk-delete'] = True
    elif mode == 'file':
        output_directory = os.path.join(settings.BASE_DIR,
                                        'downloads/archiver')
        os.makedirs(output_directory, exist_ok=True)
        args[
            'file'] = f'{output_directory}/{s_ins.instance_name}-{src_db_name}-{src_table_name}.txt'
        if no_delete:
            args['no-delete'] = True
        else:
            args['bulk-delete'] = True
    elif mode == 'purge':
        args['purge'] = True

    # 参数检查
    args_check_result = pt_archiver.check_args(args)
    if args_check_result['status'] == 1:
        return JsonResponse(args_check_result)
    # 参数转换
    cmd_args = pt_archiver.generate_args2cmd(args, shell=True)
    # 执行命令,获取结果
    select_cnt = 0
    insert_cnt = 0
    delete_cnt = 0
    with FuncTimer() as t:
        p = pt_archiver.execute_cmd(cmd_args, shell=True)
        stdout = ''
        for line in iter(p.stdout.readline, ''):
            if re.match(r'^SELECT\s(\d+)$', line, re.I):
                select_cnt = re.findall(r'^SELECT\s(\d+)$', line)
            elif re.match(r'^INSERT\s(\d+)$', line, re.I):
                insert_cnt = re.findall(r'^INSERT\s(\d+)$', line)
            elif re.match(r'^DELETE\s(\d+)$', line, re.I):
                delete_cnt = re.findall(r'^DELETE\s(\d+)$', line)
            stdout += f'{line}\n'
    statistics = stdout
    # 获取异常信息
    stderr = p.stderr.read()
    if stderr:
        statistics = stdout + stderr

    # 判断归档结果
    select_cnt = int(select_cnt[0]) if select_cnt else 0
    insert_cnt = int(insert_cnt[0]) if insert_cnt else 0
    delete_cnt = int(delete_cnt[0]) if delete_cnt else 0
    error_info = ''
    success = True
    if stderr:
        error_info = f'命令执行报错:{stderr}'
        success = False
    if mode == 'dest':
        # 删除源数据,判断删除数量和写入数量
        if not no_delete and (insert_cnt != delete_cnt):
            error_info = f"删除和写入数量不一致:{insert_cnt}!={delete_cnt}"
            success = False
    elif mode == 'file':
        # 删除源数据,判断查询数量和删除数量
        if not no_delete and (select_cnt != delete_cnt):
            error_info = f"查询和删除数量不一致:{select_cnt}!={delete_cnt}"
            success = False
    elif mode == 'purge':
        # 直接删除。判断查询数量和删除数量
        if select_cnt != delete_cnt:
            error_info = f"查询和删除数量不一致:{select_cnt}!={delete_cnt}"
            success = False

    # 执行信息保存到数据库
    if connection.connection and not connection.is_usable():
        close_old_connections()
    # 更新最后归档时间
    ArchiveConfig(
        id=archive_id,
        last_archive_time=t.end).save(update_fields=['last_archive_time'])
    # 替换密码信息后保存
    ArchiveLog.objects.create(
        archive=archive_info,
        cmd=cmd_args.replace(s_ins.password, '***').replace(
            d_ins.password, '***') if mode == 'dest' else cmd_args.replace(
                s_ins.password, '***'),
        condition=condition,
        mode=mode,
        no_delete=no_delete,
        sleep=sleep,
        select_cnt=select_cnt,
        insert_cnt=insert_cnt,
        delete_cnt=delete_cnt,
        statistics=statistics,
        success=success,
        error_info=error_info,
        start_time=t.start,
        end_time=t.end)
    if not success:
        raise Exception(f'{error_info}\n{statistics}')
コード例 #4
0
    def post(self, request):
        # 参数验证
        serializer = AuditWorkflowSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        audit_type = request.data['audit_type']
        workflow_type = request.data['workflow_type']
        workflow_id = request.data['workflow_id']
        audit_remark = request.data['audit_remark']
        engineer = request.data['engineer']
        user = Users.objects.get(username=engineer)

        # 审核查询权限申请
        if workflow_type == 1:
            audit_status = 1 if audit_type == 'pass' else 2

            if audit_remark is None:
                audit_remark = ''

            if Audit.can_review(user, workflow_id, workflow_type) is False:
                raise serializers.ValidationError({"errors": "你无权操作当前工单!"})

            # 使用事务保持数据一致性
            try:
                with transaction.atomic():
                    audit_id = Audit.detail_by_workflow_id(
                        workflow_id=workflow_id,
                        workflow_type=WorkflowDict.workflow_type['query']
                    ).audit_id

                    # 调用工作流接口审核
                    audit_result = Audit.audit(audit_id, audit_status,
                                               user.username, audit_remark)

                    # 按照审核结果更新业务表审核状态
                    audit_detail = Audit.detail(audit_id)
                    if audit_detail.workflow_type == WorkflowDict.workflow_type[
                            'query']:
                        # 更新业务表审核状态,插入权限信息
                        _query_apply_audit_call_back(
                            audit_detail.workflow_id,
                            audit_result['data']['workflow_status'])

            except Exception as msg:
                logger.error(traceback.format_exc())
                raise serializers.ValidationError({'errors': msg})
            else:
                # 消息通知
                async_task(notify_for_audit,
                           audit_id=audit_id,
                           audit_remark=audit_remark,
                           timeout=60,
                           task_name=f'query-priv-audit-{workflow_id}')
                return Response({
                    'msg': 'passed'
                }) if audit_type == 'pass' else Response({'msg': 'canceled'})
        # 审核SQL上线申请
        elif workflow_type == 2:
            # SQL上线申请通过
            if audit_type == 'pass':
                # 权限验证
                if Audit.can_review(user, workflow_id, workflow_type) is False:
                    raise serializers.ValidationError({"errors": "你无权操作当前工单!"})

                # 使用事务保持数据一致性
                try:
                    with transaction.atomic():
                        # 调用工作流接口审核
                        audit_id = Audit.detail_by_workflow_id(
                            workflow_id=workflow_id,
                            workflow_type=WorkflowDict.
                            workflow_type['sqlreview']).audit_id
                        audit_result = Audit.audit(
                            audit_id,
                            WorkflowDict.workflow_status['audit_success'],
                            user.username, audit_remark)

                        # 按照审核结果更新业务表审核状态
                        if audit_result['data'][
                                'workflow_status'] == WorkflowDict.workflow_status[
                                    'audit_success']:
                            # 将流程状态修改为审核通过
                            SqlWorkflow(id=workflow_id,
                                        status='workflow_review_pass').save(
                                            update_fields=['status'])
                except Exception as msg:
                    logger.error(traceback.format_exc())
                    raise serializers.ValidationError({'errors': msg})
                else:
                    # 开启了Pass阶段通知参数才发送消息通知
                    sys_config = SysConfig()
                    is_notified = 'Pass' in sys_config.get('notify_phase_control').split(',') \
                        if sys_config.get('notify_phase_control') else True
                    if is_notified:
                        async_task(notify_for_audit,
                                   audit_id=audit_id,
                                   audit_remark=audit_remark,
                                   timeout=60,
                                   task_name=f'sqlreview-pass-{workflow_id}')
                    return Response({'msg': 'passed'})
            # SQL上线申请驳回/取消
            elif audit_type == 'cancel':
                workflow_detail = SqlWorkflow.objects.get(id=workflow_id)

                if audit_remark is None:
                    raise serializers.ValidationError({"errors": "终止原因不能为空"})

                if can_cancel(user, workflow_id) is False:
                    raise serializers.ValidationError({"errors": "你无权操作当前工单!"})

                # 使用事务保持数据一致性
                try:
                    with transaction.atomic():
                        # 调用工作流接口取消或者驳回
                        audit_id = Audit.detail_by_workflow_id(
                            workflow_id=workflow_id,
                            workflow_type=WorkflowDict.
                            workflow_type['sqlreview']).audit_id
                        # 仅待审核的需要调用工作流,审核通过的不需要
                        if workflow_detail.status != 'workflow_manreviewing':
                            # 增加工单日志
                            if user.username == workflow_detail.engineer:
                                Audit.add_log(audit_id=audit_id,
                                              operation_type=3,
                                              operation_type_desc='取消执行',
                                              operation_info="取消原因:{}".format(
                                                  audit_remark),
                                              operator=user.username,
                                              operator_display=user.display)
                            else:
                                Audit.add_log(audit_id=audit_id,
                                              operation_type=2,
                                              operation_type_desc='审批不通过',
                                              operation_info="审批备注:{}".format(
                                                  audit_remark),
                                              operator=user.username,
                                              operator_display=user.display)
                        else:
                            if user.username == workflow_detail.engineer:
                                Audit.audit(
                                    audit_id, WorkflowDict.
                                    workflow_status['audit_abort'],
                                    user.username, audit_remark)
                            # 非提交人需要校验审核权限
                            elif user.has_perm('sql.sql_review'):
                                Audit.audit(
                                    audit_id, WorkflowDict.
                                    workflow_status['audit_reject'],
                                    user.username, audit_remark)
                            else:
                                raise serializers.ValidationError(
                                    {"errors": "Permission Denied"})

                        # 删除定时执行task
                        if workflow_detail.status == 'workflow_timingtask':
                            schedule_name = f"sqlreview-timing-{workflow_id}"
                            del_schedule(schedule_name)
                        # 将流程状态修改为人工终止流程
                        workflow_detail.status = 'workflow_abort'
                        workflow_detail.save()
                except Exception as msg:
                    logger.error(f"取消工单报错,错误信息:{traceback.format_exc()}")
                    raise serializers.ValidationError({'errors': msg})
                else:
                    # 发送取消、驳回通知,开启了Cancel阶段通知参数才发送消息通知
                    sys_config = SysConfig()
                    is_notified = 'Cancel' in sys_config.get('notify_phase_control').split(',') \
                        if sys_config.get('notify_phase_control') else True
                    if is_notified:
                        audit_detail = Audit.detail_by_workflow_id(
                            workflow_id=workflow_id,
                            workflow_type=WorkflowDict.
                            workflow_type['sqlreview'])
                        if audit_detail.current_status in (
                                WorkflowDict.workflow_status['audit_abort'],
                                WorkflowDict.workflow_status['audit_reject']):
                            async_task(
                                notify_for_audit,
                                audit_id=audit_detail.audit_id,
                                audit_remark=audit_remark,
                                timeout=60,
                                task_name=f'sqlreview-cancel-{workflow_id}')
                    return Response({'msg': 'canceled'})
        # 审核数据归档申请
        elif workflow_type == 3:
            audit_status = 1 if audit_type == 'pass' else 2

            if audit_remark is None:
                audit_remark = ''

            if Audit.can_review(user, workflow_id, workflow_type) is False:
                raise serializers.ValidationError({"errors": "你无权操作当前工单!"})

            # 使用事务保持数据一致性
            try:
                with transaction.atomic():
                    audit_id = Audit.detail_by_workflow_id(
                        workflow_id=workflow_id,
                        workflow_type=WorkflowDict.workflow_type['archive']
                    ).audit_id

                    # 调用工作流插入审核信息,更新业务表审核状态
                    audit_status = Audit.audit(
                        audit_id, audit_status, user.username,
                        audit_remark)['data']['workflow_status']
                    ArchiveConfig(
                        id=workflow_id,
                        status=audit_status,
                        state=True if audit_status
                        == WorkflowDict.workflow_status['audit_success'] else
                        False).save(update_fields=['status', 'state'])
            except Exception as msg:
                logger.error(traceback.format_exc())
                raise serializers.ValidationError({'errors': msg})
            else:
                # 消息通知
                async_task(notify_for_audit,
                           audit_id=audit_id,
                           audit_remark=audit_remark,
                           timeout=60,
                           task_name=f'archive-audit-{workflow_id}')
                return Response({
                    'msg': 'passed'
                }) if audit_type == 'pass' else Response({'msg': 'canceled'})