# encoding: utf-8 class MyshixunsController < ApplicationController layout 'base_myshixun' skip_before_filter :verify_authenticity_token, :only => [:training_task_status, :close_webssh, :code_runinng_message, :vnc] before_filter :require_login, :except => [:training_task_status, :close_webssh, :code_runinng_message, :vnc] before_filter :check_authentication, :except => [:training_task_status, :close_webssh, :mul_test_home, :mul_test_user, :mul_test_myshixun, :mul_test_shixun, :mul_test_start, :code_runinng_message, :vnc] before_filter :find_myshixun, :only => [:show, :myshixun_reset, :open_webssh, :sync_reset_time, :destroy, :search_file_list, :vnc] DCODES = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z) include ApplicationHelper def archive end def forbidden render_403 return end def not_found render_404 return end def search_file_list path = params[:path] @path = to_path_param(path) g = Gitlab.client @dir = g.trees(@myshixun.gpid, :path => @path).map{|tree|[tree.type, (@path.blank? ? tree.name : "#{@path}"+"/"+tree.name )]} logger.info("dir is ##{@dir}") @page = (params['page'] || 1).to_i @dir_count = @dir.count @limit = 5 @is_remote = true @dir_pages = Paginator.new @dir_count, @limit, params['page'] || 1 @offset ||= @dir_pages.offset @dir = paginateHelper @dir, @limit respond_to do |format| format.js end end # 并发测试:主页 # http://localhost:3000/myshixuns/mul_test_home?num=5 def mul_test_home num = params[:num].to_i uri = Setting.protocol + "://" + Setting.host_name num.times do Thread.new{get_url_exec(uri)} end end # 并发测试:个人主页 def mul_test_user num = params[:num].to_i login = params[:login] uri = Setting.protocol + "://" + Setting.host_name + "/users/#{login}" num.times do Thread.new{get_url_exec(uri)} end end #并发测试:tpi def mul_test_myshixun num = params[:num].to_i identifier = params[:identifier] uri = Setting.protocol + "://" + Setting.host_name + "/tasks/#{identifier}" num.times do Thread.new{get_url_exec(uri)} end end #并发测试:tpm def mul_test_shixun num = params[:num].to_i identifier = params[:identifier] uri = Setting.protocol + "://" + Setting.host_name + "/shixuns/#{identifier}" num.times do Thread.new{get_url_exec(uri)} end end # 并发测试 # num为进程数 # 分三种JAVA/PYTHON/C++ # 方式模拟请求 # 实训ID: p - 122,178,120; J - 571,895,50; c++ - 101,70,118 # http://localhost:3000/myshixuns/mul_test?shixun_id=122,178,120&num=10 def mul_test unless User.current.admin? render_403 return end codes = %W(1 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z) begin num = params[:num].to_i @myshixuns = Myshixun.where(:shixun_id => params[:shixun_id].split(",")).first(num) logger.warn("###1mul test game_build start : myshixuns count is #{@myshixuns.count}") @myshixuns.each do |myshixun| game = myshixun.games.last logger.warn("###2mul test game_build start ") identifier = game.try(:identifier) if game.status == 2 code = codes.sample(8).join resubmit = "#{code}_#{myshixun.id}" end logger.warn("###3mul test game_build start ...") uri = Setting.protocol + "://" + Setting.host_name + "/api/v1/games/#{identifier}/game_build?resubmit=#{resubmit}&content_modified=0&first=1" Thread.new{get_url_exec(uri)} end rescue Exception => e logger.error("mul test failed ===> #{e.message}") end render :json => {:status => "start"} end # 最新可以用的并发测试接口 def sigle_mul_test codes = %W(1 2 3 4 5 6 7 8 9 A B C D E F G H J K L N M O P Q R S T U V W X Y Z) begin identifiers = Myshixun.where(:shixun_id => params[:shixun_id].split(",")).pluck(:identifier) ide = identifiers[rand(identifiers.length)] myshixun = Myshixun.where(:identifier => ide).first game = myshixun.games.last logger.warn("###2mul test game_build start ") identifier = game.try(:identifier) if game.status == 2 code = codes.sample(8).join resubmit = "#{code}_#{myshixun.id}" end logger.warn("###3mul test game_build start ...") EvaluateRecord.create!(:user_id => myshixun.user_id, :shixun_id => myshixun.shixun.id, :game_id => game.id) redirect_to "/api/v1/games/#{identifier}/game_build?resubmit=#{resubmit}&content_modified=0&first=1" rescue Exception => e logger.error("mul test failed ===> #{e.message}") end end # 慎用,只能用于测试版 def sigle_update_myshixun shixun = Shixun.find_by_identifier(params[:shixun_id]) user = User.find(rand(50000)) code = down_generate_identifier("myshixun") ActiveRecord::Base.transaction do begin Myshixun.create!(:shixun_id => shixun.id, :user_id => user.id, :identifier => code, :modify_time => shixun.modify_time, :reset_time => shixun.reset_time, :onclick_time => Time.now, :gpid => -1, :git_url => "", :commit_id => "123123") render :json => {:status => 200} rescue Exception => e logger.error("sigle test #{e.message}") render :json => {:error => "#{e}"} raise ActiveRecord::Rollback end end end # 开启实训 def mul_test_start unless User.current.admin? render_403 return end num = params[:num].to_i shixuns = params[:shixun_identifier].split(",") num.times do |n| lastnames = [] 100.times do |n| lastnames << "educoder#{sprintf("%04d", n)}" end lastname = lastnames[rand(lastnames.count)] shixun = Shixun.where(:identifier => shixuns[rand(shixuns.count)]).first current_user = User.where(:lastname => lastname).first Thread.new{start_shixun(shixun, current_user)} end end def start_shixun shixun, current_user ActiveRecord::Base.transaction do begin # fork版本库,如果用户没有同步,则先同步用户 g = Gitlab.client if current_user.gid.nil? s = Trustie::Gitlab::Sync.new s.sync_user(current_user) end gshixun = g.fork(shixun.gpid, current_user.gid) shixun_tomcat = Redmine::Configuration['shixun_tomcat'] code = down_generate_identifier("myshixun") # 一般通过默认分支是否存在来判断一个项目是否fork成功 if gshixun.try(:id).present? commit_id = g.commits(shixun.gpid).first.try(:id) # educoder 加入到myshixun中 myshixun_admin_gid = User.where(:login => "educoder").first.try(:gid) g.add_team_member(gshixun.id, myshixun_admin_gid, 40) # 40代表角色master myshixun = Myshixun.create!(:shixun_id => shixun.id, :user_id => current_user.id, :identifier => code, :modify_time => @shixun.modify_time, :reset_time => shixun.reset_time, :onclick_time => Time.now, :gpid => gshixun.id, :git_url => gshixun.try(:path_with_namespace), :commit_id => commit_id) # tpm 不需要目录的 # rep = Repository.create!(:myshixun_id => myshixun.id, :identifier => gshixun.name,:project_id => -1, :shixun_id => -2) # rep.update_column(:type, "Repository::Gitlab") rep_url = Base64.urlsafe_encode64(gitlab_url shixun) # 注意:educoder为默认给实训创建版本库的用户,如果换成别的用户,名字要相应的修改 logger.info("start openGameInstance") uri = "#{shixun_tomcat}/bridge/game/openGameInstance" logger.info("end openGameInstance") params = {tpiID: "#{myshixun.id}", tpmGitURL:rep_url, tpiRepoName: gshixun.try(:name)} logger.info("openGameInstance params is #{params}") res = uri_exec uri, params if (res && res['code'].to_i != 0) raise("实训云平台繁忙(繁忙等级:83)") end # 其它创建关卡等操作 challenges = shixun.challenges # 之所以增加user_id是为了方便统计查询性能 challenges.each_with_index do |challenge, index| status = (index == 0 ? 0 : 3) code = down_generate_identifier("game") Game.create!(:challenge_id => challenge.id, :myshixun_id => myshixun.id, :status => status, :user_id => myshixun.user_id, :open_time => Time.now, :identifier => code, :modify_time => challenge.modify_time) end else raise("实训云平台繁忙(繁忙等级:81)") end # unlock logger.info("myshixun id is #{myshixun.try(:identifier)} and current_task id is#{myshixun.try(:current_task).try(:id)}") # 开启实训时更新关联作品的状态 update_myshixun_work_status myshixun render :json => {:status => 1} rescue Exception => e if e.message == "shixun error" flash[:error] = "正在后台执行,请稍后重试" elsif e.message.include?("Mysql2::Error") flash[:error] = "正在后台执行,请稍后重试" else flash[:error] = e.message end logger.info("failed to exec shixun: current task id is #{e}") g.delete_project(gshixun.id) if gshixun.try(:id).present? render :json => {:status => -1} raise ActiveRecord::Rollback end end end def down_generate_identifier type if type == "game" code = DCODES.sample(12).join return down_generate_identifier(type) if Game.where(identifier: code).present? elsif type == "myshixun" code = DCODES.sample(10).join return down_generate_identifier(type) if Myshixun.where(identifier: code).present? end code end def game_build myshixun_id, game_id, gitUrl, step, language, mul_test, testcases, podtype, tpmScript, mirror_repositories, time_limit, file, port logger.info("############### game_build") shixun_tomcat = Redmine::Configuration['shixun_tomcat'] # 注意:这个地方的参数写的时候不能换行 params = {:tpiID => "#{myshixun_id}", :tpiGitURL => "#{gitUrl}", :buildID => "#{game_id}",:instanceChallenge => "#{step}", :testCases => "#{testcases}", :resubmit => "", :times => 1, :podType => podtype, :containers => "#{Base64.urlsafe_encode64(container_limit(mirror_repositories))}", :tpmScript => "#{tpmScript}", :timeLimit => "#{time_limit}", :file => "#{file}", :needPortMapping => "#{port}"} logger.info("############### start gameEvaluate") uri = "#{shixun_tomcat}/bridge/game/gameEvaluate" res = uri_exec uri, params logger.info("############### end gameEvaluate") if (res && res['code'] != 0) @message = "实训云平台繁忙(繁忙等级:86)" raise("实训云平台繁忙(繁忙等级:86)") mul_test.update_attributes(:time => (Time.now - mul_test.updated_at), :status => 3) else mul_test.update_attribute(:time, res['costTime'].to_i) end end # 临时使用 def destroy if User.current.admin? user = @myshixun.owner @myshixun.games.each do |game| ex =Experience.where(:container_type => "Game", :container_id => game.id).first ex.destroy if ex.present? gr = Grade.where(:container_type => "Game", :container_id => game.id).first gr.destroy if gr.present? grade = user.try(:grade).to_i - game.try(:final_score).to_i grade = grade > 0 ? grade : 0 user.update_column(:grade, grade) end @myshixun.destroy # 删除积分和经验值 #g = Gitlab.client #g.delete_project(@myshixun.gpid) if @myshixun.gpid.present? end end def sync_reset_time @myshixun.update_column(:modify_time, @myshixun.shixun.try(:modify_time)) render :json => {:data => "success"} end # 将代码重置到开启状态 # 方法:使用文件更新,非Git重置 def code_reset end def code_reset_success end # 连接webssh def open_webssh username = Redmine::Configuration['webssh_username'] password = Redmine::Configuration['webssh_password'] old_time = Time.now.to_i begin shixun_tomcat = Redmine::Configuration['shixun_tomcat'] uri = "#{shixun_tomcat}/bridge/webssh/getConnectInfo" user_id = User.current.id params = {tpiID:@myshixun.id, podType:@myshixun.shixun.try(:webssh), containers:(Base64.urlsafe_encode64(container_limit @myshixun.shixun.mirror_repositories))} res = uri_exec uri, params if res && res['code'].to_i != 0 raise("实训云平台繁忙(繁忙等级:92)") end render :json => {:host => res['address'], :port => res['port'], :ws_url => res['ws_address'], :username => username, :password => password, :game_id => @myshixun.id, :webssh_url => Redmine::Configuration['tomcat_webssh']} rescue Exception => e logger.error(e) render :json => {:error => e.try(:message)} # @message = e.try(:message) ensure use_time = Time.now.to_i - old_time logger.info "open_webssh tpiID #{@myshixun.id} use time #{use_time}" end end def vnc vnc_password = Redmine::Configuration['vnc'] shixun_tomcat = Redmine::Configuration['shixun_tomcat'] host = Redmine::Configuration['vnc_url'] begin uri = "#{shixun_tomcat}/bridge/vnc/getvnc" shixun = @myshixun.shixun params = {tpiID: @myshixun.id, :containers => "#{Base64.urlsafe_encode64(container_limit(shixun.mirror_repositories))}"} res = uri_exec uri, params if res && res['code'].to_i != 0 raise("实训云平台繁忙(繁忙等级:99)") end # url = host + ":" + res['port'] + "?password=" + vnc_password url = "https://#{res['port']}.#{host}/vnc.html" render :json => {:url => url} rescue Exception => e logger.error(e) end end # 断开webssh连接 def close_webssh username = Redmine::Configuration['webssh_username'] password = Redmine::Configuration['webssh_password'] begin shixun_tomcat = Redmine::Configuration['shixun_tomcat'] uri = "#{shixun_tomcat}/bridge/webssh/deleteSSH" user_id = User.current.id logger.info("#############################") logger.info("game_id:" + @_params[:id]) params = {tpiID:@_params[:id]} # @_params[:id] 是webssh传过来的gameId res = uri_exec uri, params if res && res['code'].to_i != 0 raise("实训云平台繁忙(繁忙等级:93)") end render :jsonp => {:host => res['address'], :port => res['port'], :username => username, :password => password} rescue Exception => e logger.error(e) @message = e.try(:message) end end # 实训重置只更新脚本 def myshixun_reset shixun = @myshixun.shixun # ActiveRecord::Base.transaction do begin g = Gitlab.client # shixun_tomcat = Redmine::Configuration['shixun_tomcat'] # @challenges = shixun.challenges # # 删除选择题用户记录 # unless @challenges.blank? # @challenges.each do |challenge| # if challenge.st != 0 # challenge.challenge_chooses.each do |choose| # user_output = choose.choose_outputs # unless user_output.blank? # user_output.delete # end # end # end # end # end #end StudentWork.where(:myshixun_id => @myshixun.id).update_all(:myshixun_id => nil, :work_status => 0) # 数据不关联删除,以后的数据可能要做行为分析 # @myshixun.destroy @myshixun.delete # 主从复制出现脏读的情况 if @myshixun.gpid g.delete_project(@myshixun.gpid) end sleep(1) redirect_to shixun_exec_shixun_path(shixun, :type => 1, :is_subject => params[:is_subject]) rescue Exception => e flash[:error] = "实训云平台繁忙(繁忙等级:89)" redirect_to shixun_challenges_path(@shixun) logger.error("myshixun reset failed #{e}") # raise ActiveRecord::Rollback end # end end # 代码运行中的信息接口 def code_runinng_message begin logger.info("######################params: #{params}") jsonTestDetails = JSON.parse(params[:jsonTestDetails]) game_id = jsonTestDetails['buildID'] message = jsonTestDetails['textMsg'] logger.info("##################code_runinng_message:#{jsonTestDetails}") logger.info("##################buildID: #{game_id}") logger.info("##################textMsg: #{message}") if game_id.present? && message.present? game = Game.find game_id msg = game.run_code_message # 只有评测中的game才会创建和更新代码评测中的信息 logger.info("##################game: #{game.status}") logger.info("##################retry_status: #{game.retry_status}") if game.status == 1 || game.status == 2 if msg.blank? RunCodeMessage.create!(:game_id => game_id, :status => 1, :message => message) else msg.update_attributes(:status => (msg.status + 1), :message => message) end end render :json => {:data => "success"} end rescue Exception => e render :json => {:data => "failed, exception_message: #{e}"} end end # taskId 即返回的game id # 返回结果:params [:stauts] 0 表示运行结果成功,其它则失败 # compile_success 1 表示成功; 0表示编译失败; -1 表示创建pod失败; -2 表示克隆代码失败 # msg 错误信息 # output 为测试用户编译输出结果 # myshixun:status 1为完成实训 # @jenkins: caseId对应test_set的position,passed: 1表示成功,0表示失败 # resubmit 1:表示已通关后重新评测;0:表示非重新评测 # retry_status 0:初始值;1:重新评测失败;2:重新评测成功 # tpiRepoPath 中间层图片的workspace路径 # params[:jsonTestDetails] = '{"buildID":"19284","compileSuccess":"1","createPodStatus": "1", "downloadStatus": "1", # "msg":[{"caseId":"1","expectedOutput":"MSAyIDMNCg","input":"MiAzIDE","output":"MSAyIDMNCg","passed":"1"}, # {"caseId":"2","expectedOutput":"LTMgMSA2DQo","input":"LTMgNiAx","output":"LTMgMSA2DQo","passed":"1"}, # {"caseId":"3","expectedOutput":"LTcgLTUgLTMNCg","input":"LTcgLTMgLTU","output":"LTcgLTUgLTMNCg","passed":"1"}], # "outPut":"Y29tcGlsZSBzdWNjZXNzZnVsbHk","resubmit":"","status":"0"}' # params[:timeCost] = '{"evaluateEnd":"2017-11-24 11:04:37","pull":"0.086", # "createPod":"1.610","evaluateAllTime":2820,"evaluateStart":"2017-11-24 11:04:35","execute":"0.294"}' # params[:pics] = "a.png,b.png,c.png" def training_task_status ActiveRecord::Base.transaction do begin t1 = Time.now jsonTestDetails = JSON.parse(params[:jsonTestDetails]) timeCost = JSON.parse(params[:timeCost]) brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present? return_back_time = format("%.3f", ( t1.to_f - brige_end_time.to_f)).to_f status = jsonTestDetails['status'] game_id = jsonTestDetails['buildID'] sec_key = jsonTestDetails['sec_key'] logger.info("training_task_status start#1**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") resubmit = jsonTestDetails['resubmit'] outPut = tran_base64_decode64(jsonTestDetails['outPut']) jenkins_testsets = jsonTestDetails['msg'] compile_success = jsonTestDetails['compileSuccess'] # # 创建pod的状态 0 失败 1 成功 # create_pod_status = jsonTestDetails['createPodStatus'] # # 克隆代码的装填 0 失败 1 成功 # clone_code_status = jsonTestDetails['downloadStatus'] # # 1表示编译成功;0 表示运行异常; -1 表示克隆代码失败; -2 表示创建pod失败 # compile_success = if clone_code_status == "0" # -1 # elsif create_pod_status == "0" # -2 # else # jsonTestDetails['compileSuccess'] # end # message = Base64.decode64(params[:msg]) unless params[:msg].blank? logger.info(outPut) game = Game.find(game_id) myshixun = game.myshixun challenge = game.challenge # test_sets = challenge.test_sets if challenge.picture_path.present? #pics = params[:files] pics = params[:tpiRepoPath] game.update_column(:picture_path, pics) end logger.info("training_task_status start#2**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") unless jenkins_testsets.blank? jenkins_testsets.each_with_index do |j_test_set, i| logger.info("j_test_set: ############## #{j_test_set}") actual_output = tran_base64_decode64(j_test_set['output']) # is_public = test_sets.where(:position => j_test_set['caseId']).first.try(:is_public) logger.info "actual_output:################################################# #{actual_output}" Output.create!(:code => status, :game_id => game_id, :out_put => outPut, :test_set_position => j_test_set['caseId'], :actual_output => actual_output, :result => j_test_set['passed'].to_i, :query_index => game.query_index, :compile_success => compile_success.to_i) end end logger.info("#############status: #{status}") logger.info("#############resubmit: #{resubmit}") logger.info("sec_key is#############resubmit: #{sec_key}") record = EvaluateRecord.where(:identifier => sec_key).first logger.info("training_task_status start#3**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") # status:0表示评测成功 if status == "0" if !resubmit.blank? game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit) #if game.had_done == 1 challenge.path.split(";").each do |path| game_passed_code(game.id, path.try(:strip), myshixun.try(:gpid), 1) end #end else game.update_attributes!(:status => 2, :end_time => Time.now, :accuracy => format("%.4f", 1.0 / game.query_index)) myshixun.update_attributes!(:status => 1) if game.had_done == 1 challenge.path.split(";").each do |path| game_passed_code(game.id, path, myshixun.try(:gpid), 1) end if game.answer_open == 0 && (challenge.shixun.try(:status) > 1) # 如果没有查看答案,则获得该关卡得分 reward_grade(game.user, game.id, 'Game', challenge.score) reward_experience(game.user, game.id, 'Game', challenge.score) game.update_attributes!(:final_score => challenge.score) end # 更新实训关联的作品分数 update_myshixun_work_score myshixun end # 如果过关了,下一关的状态是3(为开启),则需要把状态改成1(已开启) # next_game = game.next_game next_game = Game.next_game(myshixun.shixun_id, game.myshixun_id, challenge.position) next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3 else # status == "-1" 表示返回结果错误 if !resubmit.blank? game.update_attributes!(:retry_status => 1, :resubmit_identifier => resubmit) else game.update_attributes!(:status => 0) end end test_cases_time = format("%.3f", (Time.now.to_f - t1.to_f)).to_f if record.present? consume_time = format("%.3f", (Time.now - record.created_at)).to_f record.update_attributes!(:consume_time => consume_time, :git_pull => timeCost['pull'] , :create_pod => timeCost['createPod'], :pod_execute => timeCost['execute'], :test_cases => test_cases_time, :brige => timeCost['evaluateAllTime'], :return_back => return_back_time) end logger.info("training_task_status start#4**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") render :json => {:data => "success"} rescue Exception => e render :json => {:data => "failed, errer_message:#{e}"} logger.error("training_task_status error: #{e}") raise ActiveRecord::Rollback end end end def show respond_to do |format| format.html{redirect_to myshixun_game_path(@myshixun.current_task, :myshixun_id => @myshixun)} end end private # Find myshixun of id params[:id] def find_myshixun @myshixun = Myshixun.find_by_identifier(params[:id]) render_404 unless @myshixun.present? end end