2012/08/04

予約・キャンセルをredmine-plugin-hookで即時実行

チケット操作で予約・キャンセルの即時実行がやっと出来るようになった。
今までは1時間に1回実行している予約スクリプトでチケットトラッカーとステートを確認して録画ジョブ登録や削除をしていたけど、リアルタイム性がなかった。
いろいろ調べてredmineプラグインのフック機能を使えば、チケット操作した時点でジョブ登録・削除を行えるんじゃないかと。
実際にやってみたらいい感じだったので、その辺を書き記しておく。

使用したフック機能は2つ
1. controller_issues_edit_before_save
2. controller_issues_bulk_edit_before_save
1は、普通にチケット更新ページで色々変更した時に呼ばれるフック関数
2は、チケットリストビュー上で複数チケットを選択して右クリックポップアップメニューから属性1つを変更した時にチケットごとに呼ばれるフック関数。この方法が断然便利。

どんなフックがあるかは、Redmine plugin hooks listを参照した。
$ rake redmine:plugins:hook_list
ってやれば確認できるっていうことだけど、そんなビルドタスクはないよって言われた。redmine2.0だとやり方違うのかもね。

クラス継承するものが以前とちょっと違うんだね。以前は
Redmine::Hook::Listenerを継承するんだったはずだけど、新しいredmineでは
Redmine::Hook::ViewListenerを継承するのだそうだ。微妙な違いだけどちょっと悩んだよ。


lib/video_hooks.rb
フックスクリプトは以前視聴ビュー生成のために作ったredmine_videoプラグイン内に追加することにした。フックするリプとはプラグイン内のlibに入れておけばいいようだ。

そして、init.rbの先頭に
require 'video_hooks'
って記述しておくとフックがかかるようになる。

使用するGEMモジュール
hudson-remote-api
前回記事でも登場したJenkinsにREST-APIアクセスする便利ラッパーを使用する。
rexml/document
これもJenkinsジョブ設定XMLを操作するために使用する。

フックスクリプトに、require 'hudson-remote-api'って書いたら見つからないって怒られた!
??パスが通っていないってことか?と思って、直接パスで
$LOAD_PATH << "/var/lib/gems/1.9.1/gems/hudson-remote-api-0.6.0/lib"
require 'hudson-remote-api'
ってやってみたら、読んでくれるようになったが・・・うーむ。こうじゃないよねぇ。

調べてみてもこの辺のことを明確に説明している記事とかが見つからない。
redmine/config/enviromnent.rbで$LOAD_PATHに追加しろとか、redmine/Gemfileに追加しろとか・・・ん?Gemfile?
redmine/Gemfileを覗いてみたら、最後のところでプラグイン内のGemfileがあったらそれも読むよって感じになってるぞ?なるほど。そういうことか。

自分のプラグインにGemfileを用意して
gem 'hudson-remote-api'
って1行記述。そうしたら、スクリプト内でrequireする必要なく使えるようになった。
今更ながらGemfileの存在意味が少し理解できた気がする。でも分かりにくいなぁ。

因みにrexmlは、requireもGemfile記述も必要なく使えた。

これでやっと中身が作れる。出来上がったフックスクリプトがこちら
video_hooks.rb
# -*- coding: cp932 -*-

Hudson.settings = {:url => 'http://localhost/jenkins', :crumb => false }

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
      do_cancel issue if issue.tracker_id == 1 || issue.status_id == 5
      do_reserve issue if issue.tracker_id == 2
    end
  end

  # キャンセル
  def do_cancel issue
    job = Hudson::Job.get "record_#{issue.id}"
    if !job.nil?
      # 予約されていたらジョブ削除してチケットを番組キャンセルに更新
      job.delete
      issue.status_id = 5
      issue.tracker_id = 1
    end
  end

  # 予約?
  def do_reserve issue
    config = configure issue
    if !config.nil?
      # Jenkinsジョブ登録してチケットを実行中に更新
      job_name = "record_#{issue.id}"
      job = Hudson::Job.get job_name
      job = Hudson::Job.create job_name if job.nil?
      job.update config
      job.enable
      issue.status_id = 2
    end
  end

  def configure issue
    return nil if issue.custom_field_values.nil?

    # Configure
    job0 = Hudson::Job.get("record_0")
    return nil if job0.nil?

    config = REXML::Document.new job0.config
    return nil if config.nil?

    time = issue.custom_field_values[0].to_s
    channel = issue.custom_field_values[1].to_s
    duration = issue.estimated_hours.to_i * 60
    datetime = issue.start_date.strftime("%Y%m%d") + format("%04d",time)
    output = "/opt/videos/#{datetime}"

    # コマンド定義
    commands = [
                "/opt/task/record2.sh %d %d %s" % [
                                                   channel.to_i,
                                                   duration,
                                                   output
                                                  ],
                "/opt/task/record2.rb %d" % issue.id
               ]
    trigger = "%s %s %s %s *" % [
                                 datetime[10..11],
                                 datetime[8..9],
                                 datetime[6..7],
                                 datetime[4..5]
                                ]
    # 時間設定
    config.elements["/project/triggers/hudson.triggers.TimerTrigger/spec"].text = trigger

    # コマンド登録
    config.elements['/project/builders/hudson.tasks.Shell[1]/command'].text = commands[0]
    config.elements['/project/builders/hudson.tasks.Shell[2]/command'].text = commands[1]

    return config.to_s
  end
end

フックの口は2つだけど、やることは同じなので、何方からも'edit_hook'メソッドへ飛ばす。
キャンセルなのか予約なのか判断して、それぞれのメソッドに飛ばす。

do_cancel
チケットに対応した録画ジョブがあればhudson-remote-api使って削除して、チケットを番組チケットにする。ステータスはキャンセル状態にする。理由は自動予約チケットだったら自動で再予約されないようにロックしたいから。

do_reserve
こっちは前記事のreserve2.rbから移植したもので、Jenkinsジョブを登録してチケットを実行中にする処理を行う部分。カスタムフィールドへのアクセス方法が外部タスクとやり方が違う。
何がどう変化したのか?というトリガーではなくどういう状態?というステートでしか判断できないので予約チケットを更新すると実行されてしまうけど、手動で録画時間などを変更したりする場合もこれで行けるだろうから、よしかな。

プラグインと外部タスクで処理モジュール共有したいけど、チケットへのアクセス方法とかが微妙に違うんで難しいな。Jenkinsジョブ処理部分だけでも整理しようかな。やる気になったらかな。

とにかく、これで即時実行が出きるようになったので、1時間毎に動かしていた予約スクリプトはクエリー予約だけ1日1回動けばいいだけになったので、その部分も変更しなくては。
次回はそのあたり。

0 件のコメント:

コメントを投稿