という感じの話と
外部タスクと、Redmineのフック処理でコードを共有化する際に行ったこと
について。
やりたいこと
- 絶対録画したいやつを、自動予約や不意の予約が重なっても保護されること。
- マニュアル操作での予約で重複があることがすぐに分かるようにすること
2は、Redmineのフックスクリプト内で重複を確認して、重複していることをチケットのステータスで知らせるようにすれば良い。
結論から先に言うと、1はまあ出来たかな。2はどうやってもうまく動かない。
もう1歩か2歩先のスキルが必要な感じだ。ということで完全ではないけれど、こうしたいのだ!というところまでは形になったと思うので、そこまでの考えや手順を記録しておく。
(願わくば、どなたか初心者にも分かるようにご教授賜りたい気分です)
思惑
- 重複確認は、前記事で構築した’VideoTimeline’レコードを使えば簡単にできるはず。
- 重複が発生する可能性があるのは、自動予約タスクとRedmine操作での予約の時。
- 重複したチケットのうち負けたチケットは録画が実行されないように。
- 実行されないけれど、予約されており、無効状態。
- 無効/有効はJenkinsジョブのアクティブ・非アクティブと連動する。
- 外部タスクからと、Redmine内部からの処理で重複する部分を共有したい。
video-jobs.rb
# -*- coding: utf-8 -*-
# Jenkins接続
Hudson.settings = {:url => 'http://localhost/jenkins', :crumb => false }
def get_job_name id
"record_#{id}"
end
# ジョブ作成
def create_job info
jobname = get_job_name info[:issue_id]
job = Hudson::Job.get jobname
job.delete if job
job = Hudson::Job.create jobname
# 設定
config = REXML::Document.new(Hudson::Job.get("record_0").config)
# コマンド
commands = [
"/opt/task/ready.rb %d" % info[:issue_id],
"/opt/task/record2.sh %d %d %s" % [
info[:channel],
info[:duration],
info[:output]
],
"/opt/task/record2.rb %d" % info[:issue_id]
]
config.elements.each_with_index('/project/builders/hudson.tasks.Shell') {|shell, i|
if i<commands.size
shell.elements["command"].text = commands[i]
end
}
# 時間
config.elements["/project/triggers/hudson.triggers.TimerTrigger/spec"].text =
"%s %s %s %s *" % [
info[:datetime][10..11],
info[:datetime][8..9],
info[:datetime][6..7],
info[:datetime][4..5]
]
job.update config.to_s
job.enable
return true
end
def entry_job issue, time, channel
# 1分前にジョブがスタートするように
datetime = Time.parse( issue.start_date.strftime("%Y%m%d") + format("%04d",time) ) - 60
duration = issue.estimated_hours.to_i * 60
# 時間調整
nowtime = Time.now
if datetime < nowtime
if datetime + duration < nowtime
# 終わってる・・・
issue.tracker_id = 1
return
else
# 時間が過ぎてる!後半だけでも!
duration = duration - (nowtime - datetime) - 60
datetime = nowtime + 60
end
end
info = {
# チケット番号
:issue_id => issue.id,
# 録画開始日時
:datetime => datetime.strftime("%Y%m%d%H%M"),
# 録画時間
:duration => duration,
# チャンネル
:channel => channel,
# 保存場所
:output => "/opt/videos/%d" % issue.id
}
# ジョブ作成
if create_job info
# 実行中
issue.status_id = 2
end
# 予約トラッカー
issue.tracker_id = 2
# 重複?
if duplicated? issue
disable_job issue
else
issue.save
end
puts "ID: #{issue.id}"
update_timeline issue
end
def delete_job issue
job = Hudson::Job.get get_job_name issue.id
job.delete unless job.nil?
update_timeline issue
end
def disable_job issue
job = Hudson::Job.get get_job_name issue.id
job.disable unless job.nil?
# 無効
issue.status_id = 8
issue.save
end
def enable_job issue
job = Hudson::Job.get get_job_name issue.id
job.enable unless job.nil?
# 実行中に復活
if issue.status_id != 2
issue.status_id = 2
issue.save
end
end
# タイムライン更新
def update_timeline issue
vt = VideoTimeline.where(:issue_id =>issue.id).first
if vt
vt.reserved = issue.tracker_id == 2
vt.save
end
end
def duplicated? issue
vt = VideoTimeline.where(:issue_id =>issue.id).first
vts = VideoTimeline.where("reserved = true and start_on <= '%s' and end_on >= '%s'" % [vt.end_on, vt.start_on])
return vts && vts.size>0
end
def priority vt
# ActiveRecord::Base.lock_optimistically = false
vts = VideoTimeline.where("reserved = true and start_on <= '%s' and end_on >= '%s'" % [vt.end_on, vt.start_on])
if vts
# ソート
issues = Issue.find( vts.map(&:issue_id), :order => "priority_id desc, estimated_hours" )
if issues && issues.size>0
# 有効
enable_job issues.shift
# 無効
issues.each {|issue| disable_job issue } if issues.size>0
end
end
# ActiveRecord::Base.lock_optimistically = true
# Issue変更フック内で別のIssueに対する操作を行おうとすると
# ActiveRecord::StaleObjectError (Attempted to update a stale object: Issue)
# というエラーが出てしまう。このエラーを出ないようにすることが、上記の設定で可能だが
# Issue.find自体が正常動作しないようなので、結局別のIssueに対する操作ができそうにない。
# 従って、こっち側は外部タスク専用とする。
end
ここでのポイントは、処理内容ではなくて外部タスクからとRedmine内部フックからの両方から使えるようにするということ。
requireを使わない。Redmineプラグイン側はGemfileで指定する。
外部タスク側で必要なrequireやロード、初期化を行う。
という感じにすると、共有がしやすいようだ。
Redmineフック video_hooks.rb
# -*- coding: utf-8 -*-
# $: << "/opt/task"
# $: << "."
# require 'video-jobs'
load '/opt/task/video-jobs.rb'
class VideoHooks < Redmine::Hook::ViewListener
# チケット更新
def controller_issues_edit_before_save(context)
edit_hook context[:issue]
end
# バルク更新(チケットごとに呼ばれる)
def controller_issues_bulk_edit_before_save(context)
edit_hook context[:issue]
end
def edit_hook issue
# 番組内のチケット
if issue.project_id == 1
if issue.tracker_id == 1 || issue.status_id == 5
do_cancel issue
elsif issue.tracker_id == 2
time = issue.custom_field_values[0].to_s
channel = issue.custom_field_values[1].to_s
entry_job issue, time.to_i, channel.to_i
end
end
end
# キャンセル
def do_cancel issue
# ジョブ削除して、録画キャンセルチケットに
delete_job issue
issue.status_id = 5 if issue.status_id > 1
issue.tracker_id = 1
end
end
たいした事じゃないけれど、$: << 'パス'ってやってロードパスに追加して、requireするってのをよく見かける。Jenkinsジョブとして動作させる事を考えるとカレント'.'を追加しても意味が無い。フルパスを指定するなら、requireである必要ないし、そもそもロードしたいのはrubyスクリプトなので、直接loadするのが自然だと思う。
無理してrequireを使う例が多い気がするのは、気のせいか?
外部タスク priority.rb
#!/usr/bin/ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'active_record'
require 'hudson-remote-api'
load '/opt/task/video-jobs.rb'
# レコード
class Issue < ActiveRecord::Base;end
class VideoTimeline < ActiveRecord::Base; end
ActiveRecord::Base.establish_connection(
:adapter => 'mysql2',
:host => 'localhost',
:username => 'redmine',
:password => 'redmine',
:database => 'redmine'
)
# clean dust
VideoTimeline.where( :reserved => true ).each {|vt|
issue = Issue.find( vt.issue_id )
vt.reserved = false unless issue.tracker_id == 2
vt.save
}
# main
VideoTimeline.where( :reserved => true ).each {|vt| priority vt }
こちらは思い通りに動作している。
思い通りにいかなかった事
話が戻るけど、Redmineのフックスクリプトで、チケット予約を行った際に重複をチェックして重複していたら、どうのこうのという処理を行いたかった。その辺の処理がvideo-jobs.rb内のduplicated?関数とpriority関数。
priority関数内にもコメントしてあるけど、Redmineのチケット更新処理フック内で別のチケットの更新とかアクセスとかしようとするとロック機構が働いてエラーが出てしまうようなのだ。そのエラーを回避する事はスクリプト内に書いてある通り出来たけれど、チケット検索処理自体が正常に動作しなくなるみたい。
この辺色々やってみたけどどうしても解決しないんで、ちょっとあきらめた。
外部タスクだけで活用する事にした。
仕方ないと思って、今更新中のチケットが重複しているかどうかだけをチェックしてステータス変更するくらいなら出来るだろうと思ったけど、なぜかduplicated?関数が正常動作しない。処理はされているようだけれど、正しい結果が得られない。トレースしてもスルーされてしまう感じ。こっちも原因がつかめていない。
と、不完全な状態ではあるけれど、ハングはしなくなったし、重複確認もリアルタイムは出来てないけどpriority.rbは動作しているので半分くらいは解決したかな。
この辺はかなーり高いスキルとじっくり取り組み時間が必要かも。ちょっと別の事に取り組もうかな。
0 件のコメント:
コメントを投稿