Redmine 2.3.0にXMPP Notifications Pluginを入れた話

Redmine 2.3.0にXMPP Notifications Pluginを入れました。いくつかハマリポイントがあったのでメモしておきます。

なお、私はRubyは全く詳しくありません。

まず、xmpp4r-simpleを入れるのですが、普通にgem install xmpp4r-simpleするとRuby 1.9以上に対応できなかったので、gem install xmpp4r-simple-19としました。

続いて、エンコード云々によって正規表現云々で落ちていたので plugins/redmine_xmpp_notifications/init.rb に書き足しています。
具体的には、require_dependencyの塊に上からに"project" "principal" "user"の3つを追加してやります。
更に、

  # Encoding patch
  require 'socket'
  class TCPSocket
      def external_encoding
          Encoding::BINARY
      end
  end

  require 'rexml/source'
  class REXML::IOSource
      alias_method :encoding_assign, :encoding=
      def encoding=(value)
          encoding_assign(value) if value
      end
  end

  begin
      # OpenSSL is optional and can be missing
      require 'openssl'
      class OpenSSL::SSL::SSLSocket
          def external_encoding
              Encoding::BINARY
          end
      end
  rescue
  end

Redmine::Plugin.register :redmine_xmpp_notifications doブロックの末尾に追加します。

標準手順に加えて上記をすることでとりあえずXMPPのメッセージは飛ぶようになりました。
が、「ウォッチまたは関係している事柄のみ」メール通知をする設定になっている人にはこれでは飛ばなかったので、app/models/user.rb にパッチを当てます。

notify_about?(object)メソッドの when 'selected', 'only_my_events' の下をこんな感じに書き換えます(私は1行で書きましたが複数行に分割して大丈夫?)。

object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was) || 
object.watcher_recipients.include?(self.mail)


やっていることは、Issueのwacher_recipients(メールアドレスが入っていた)にUserのmailが含まれるか調べているだけです。

とりあえずは以上でなんとなく使えると思います。
なにか問題がありましたら教えてください。

CentOS6.4にGitLab5.0をインストールしてハマった

CentOS6.4にGitLab5.0をインストールしたのですが、主に2つのことでハマりましたのでメモしておきます。

GitLab5.0 + unicorn + nginxという、最近よくある構成です。
URLは http://host/gitlab/ という形で、サブディレクトリでつなぐ様にしました。

まず、インストールの時に参考にしたサイトは、公式のinstallation.md。あとは適当にアレンジしました。

とりあえずインストールを終えて、プロジェクトを作成し、SSHキーを追加しました。
そして、remote repositoryへのpush/pullをしようとした時に問題が発覚というわけです。

SSH経由でgitユーザーのパスワードを聞かれる

登録していた公開鍵にはパスワードを付けていませんでした。
それなのにパスワードを聞かれるので、あれ?と思いました。

comolog: CentOS 6.4 に GitLab 5.0.0 をインストールしてはまったことを参考に以下で解決。

chmod 700 /home/git/.ssh
chmod 600 /home/git/.ssh/authorized_keys

今度はCould no read from remote repositoryと言われる

git push -u origin masterをすると、

fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

と言われてしまいました。
ググっていたら、
https://github.com/gitlabhq/gitlabhq/issues/3384
が見つかったのですが、よくわからず、とりあえず単純にsshで接続したところ、

PTY allocation request failed on channel 0
/usr/lib64/ruby/1.9.1/json/common.rb:148:in `parse': 746: unexpected token 
at 'Not Found: /api/v3/internal/discover' (JSON::ParserError)
from /usr/lib64/ruby/1.9.1/json/common.rb:148:in `parse'
from /home/git/gitlab-shell/lib/gitlab_net.rb:24:in `discover'
from /home/git/gitlab-shell/lib/gitlab_shell.rb:28:in `exec'
from /home/git/gitlab-shell/bin/gitlab-shell:16:in `
' Connection to 192.168.xxx.xxx closed.

と返って来ましたので、gitlab-shell絡みだろうとあたりをつけて、設定を見返すことに。
いろいろ悩んだのですが、結果から言えばgitlab-shell/config.ymlの次のところでした。

gitlab_url: "http://127.0.0.1:8080/gitlab/"

8080というポートはunicorn.rbで私が勝手に指定しているポートです。
考えてみれば当然で、サブディレクトリで動かしているのだから、そこを指定してやらなきゃいけないわけです。
ハマっているときはこうでした。

gitlab_url: "http://127.0.0.1:8080/"

まとめ

私もまだまだです...

openSIPSの変数

公式ドキュメントの中身を要約して書いていきます。

変数は4種類の観点から分類できます。

  1. 変数の視認性(スコープのこと?)
  2. 変数が何にくっついているのか
  3. 読み書き可能かどうか
  4. どれだけの複数の値を持つか(同じ変数として処理)

変数は$記号がついているため、一発で識別可能です。

擬似変数の構文

$(name(subname)[index]{transformation})

name

必須項目です。擬似変数の名前(種類)です。

subname

指定された型の特定の擬似変数の識別子です。例えば$hdr(From)であれば、hdrという種類の擬似変数のうちFromが参照されるし、$avp(i:25)であればAVPの整数型25が参照されます。

index

配列の要素の番号です。0から始まる整数値です。
ただし、負の値を与えることもでき、-1は配列の最後尾、-2はさらにその前を意味します。

transformation

擬似変数を加工することができます。様々な処理が用意されており、例えば文字数を数えるものやURIからホスト部を取り出すものがあります。
加工した結果は別の処理の入力として利用でき、処理を数珠つなぎにすることができます。

context

擬似変数が評価されるコンテクストを指定することができます。今のところreplyとrequestが存在しています。

使用例

nameだけ

$ru

nameとsubname

$hdr(Contact)

nameとindex

$(ct[0])

nameとsubnameとindex

$(avp(i:10)[2])

context

$(ru) reply routeからrequestのRequest-URIを取得
$(hdr(Contact)) replyの情報にfailure routeからアクセスする

AVP (Attribute-Value Pair)

今回はAVPについて読んでみました。勘違いしているかもしれませんが、だいたいこんなかんじでしょうか。

AVPとは

AVPは動的な変数で、ひとつのメッセージもしくはトランザクションに連動します(もしステートフル処理を使用していれば)。
メッセージまたはトランザクションは、初期の状態(受信した時もしくは作成された時)でそれに連動する空っぽのAVPリストを持ちます。
ルーティングスクリプトの中では、スクリプトで直接、もしくはスクリプトからの関数呼び出しによって、メッセージやトランザクションに自動的に連動する新しいAVPが作成されます。
AVPは、トランザクションのメッセージ(ReplyまたはRequest)が処理されるすべてのルート branch_route, failure_route, onreply_route (onreply_routeについてはTMモジュールのパラーメータonreply_avp_modeをenableにする *1 必要がある) で可視(visible)です。

AVPは読み書き可能で、更にはすでにあるAVPを削除することも可能です。

AVPは複数の値を持つことができます。新規割り当て(もしくは書き込み)はAVPに新しい値を追加します。値は「後入れ先出し」順 (スタック)に則り保持されます。
値をひとつ持つAVPに、値を更に追加した場合、逆順に格納されます。例えば、"one"という値をもつAVPに"two"を追加、更に"three"を追加すると、Index 0から順番に"three","two","one"と並びます。

$avp(17) = "one";
# we have a single value
$avp(17) = "two";
# we have two values ("two","one")
$avp(17) = "three";
# we have three values ("three","two","one")

名前付けルール

$avp(name) もしくは $(avp(name)[N])

Nは0から始まる要素番号で、省略するとN=0の要素(つまり最後に追加された要素)が指定されたことになります。

AVPの寿命

AVPは明示的に削除しなければトランザクション中で値が有効です。
局所的な場面で変数を扱う場合にはAVPよりもスクリプト変数(Script variable)のほうが処理が高速です。

次はFLAGについて調べてみます。

*1: modparam("tm", "onreply_avp_mode", 1)

スクリプト変数

名前の通り、寿命がスクリプトに拘束される変数で、トランザクションには無関係です。
正しくはopenSIPSのプロセスに関係し、プロセス間では値の共有ができません。
後述のAVPトランザクションの中で値を持ち回れるのに対し、スクリプト変数はルーティングスクリプトが動作するプロセスの中でのみ有効ですが、AVPより動作は高速です。

読み書きが可能で、整数と文字列のみ格納できます。
複数の値を持つことができず、変数に書き込みを行うと古い値は新しい値で上書きされます。

構文

$var(name)

注意

同一プロセスで値が保持されるため、使用前には必ず初期化を行わなければなりません。
初期化しないと古い値を参照してしまいます。
また、NULLは整数の0とみなされます。NULLを格納することはできません。

openSIPSに少なくとも必要なモジュール

前回はopenSIPSのパッケージを一つだけインストールしたと書いたのですが、configのサンプルのパッケージをインストールしようとしたらいろいろと依存関係があったので、それらもインストールしてみました。

で、configファイル/etc/opensips/opensips.cfgを開いてみました。
loadmoduleでmoduleの読み込みをしているのですが、一体何を読み込んでいるのかなー?と思ったので、調べてみました。
grep loadmodule /etc/opensips/opensips.cfgした結果です。

#loadmodule "db_mysql.so"
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "mi_fifo.so"
loadmodule "uri_db.so"
loadmodule "uri.so"
loadmodule "xlog.so"
loadmodule "acc.so"
#loadmodule "auth.so"
#loadmodule "auth_db.so"
#loadmodule "alias_db.so"
#loadmodule "domain.so"
#loadmodule "presence.so"
#loadmodule "presence_xml.so"

#で始まる行はコメントですね。言わずもがな。

signaling.so

公式ドキュメントより。

The SIGNALING module comes as a wrapper over tm and sl modules and offers one function to be called by the modules that want to send a reply.

SIGNALINGモジュールはtmとslモジュールのラッパー。replyを送信したいモジュールから呼び出される機能を一つ提供している。

何を言っているのかいまいちピンときませんが、調べることは3つ?
tmモジュール、slモジュールが何者か?提供される一つの機能とは?

ドキュメントには確かに機能(function)は一つでした。
send_reply(code, reason)
あぁ、なるほど。
send_reply("404", "Not Found");
などのように使うみたいです。

モジュールについては次とその次に出てきます。

sl.so

slというのはStatelessのことだそうです。
状態を持たないやり取り、つまり、質問に答えてハイおしまい、な会話ですね。

function名にはsl_がくっついていました。

tm.so

slに対してこちらはTransaction moduleのことだそうで、SIPのやり取りの情報をある程度覚えて、そのやり取りが完結するまで忘れないという性質のやつですね。

function名にはt_がくっつくようです。

rr.so

RRとはRecord-Routingのことらしいです。
SIPヘッダにRecord-Routeフィールドを設けることでUA同士がやり取りするSIPメッセージがすべてProxy経由になります。
Call statefullなProxyには必須になりますね。

maxfwd.so

こちらはSIPのMax-Forwardsフィールドのことですね。
Loopの検出に必要です。値としては70が良いようです。
もしも、目的地に到着する前にMax-Forwardsの値が0になったら"483 Too Many Hops"を返す決まりになっています。

ちなみに、RFC3261にはTo, From, CSeq, Call-ID, Max-Forwards, Viaフィールドが必須だと書かれています。

usrloc.so

User Locationの中心となるモジュールのようです。公式ドキュメントには「user location tableを保持し、他のモジュールからテーブルにアクセスする手段を提供する。scriptから直接アクセスするためのfunctionは提供されない。」とあります。
つまり、私にできるのは、moduleを読み込むときにパラメータを与えてあげるくらいということですね。

registrar.so

REGISTRARに関するモジュールなのは見たまんまですね。
PathヘッダフィールドとGRUUのことが書かれていました。
PathフィールドはUAに隣接していないREGISTRARにREGISTERするときに使うもので(私は実は詳しくないのですが)Record-Routeのように経由するProxyを(Proxy自身が)次々と記録していって、登録するもののようです。読み出されるときにはRouteヘッダフィールドに名を連ねる、という解釈でよいのでしょうか。

GRUUはGlobally Routable User Agent (UA) URIsの略だと書いてありました(RFC5627)が、なんのことやらさっぱりわかりません。
一意のURIを生成するのでしょうか?UUIDとかがちらっと見えた気がします。

textops.so

これは文字列処理に特化したモジュールのようです。SIPがテキストベースのプロトコルである以上、文字列処理は必須ですよね。

mi_fifo.so

FIFO経由でManagement Interfaceへアクセスするモジュールだそうです。

uri.so, uri_db.so

SIP URIに対していろいろなチェックができるそうです。
URI_DBはURIに統合されたようです。

xlog.so

COREモジュールに統合されたようですが、logに書き出す機能を提供してくれてます。

acc.so

Accounting、つまり利用事実の記録を行うためのモジュールでsyslogやRDBMSなどに記録を行えるようです。

ひとまずは

デフォルトで有効になっていたモジュールだけをざっと調べてみましたが、とりあえずこれだけあれば何とかなりそうですし、これらは最低限必要だと思います。

次回は擬似変数とかについて調べてみようと思います。

openSIPSはじめました

わけあってOpenSIPSを使うことになりましたので、記録を残していこうと思います。
勉強しながらその都度書いていきますので、まとまらないし間違った記事が多く上がると思います。気づいたらその都度訂正記事を上げていくつもりでいますが、ご指摘いただければ幸いです。

特殊な環境ですが、作業環境はOpenWRTです。本番環境はOpenWRT及びRHEL6互換ディストリビューションを想定しています。
インストールもとりあえず基本的なopenSIPSのパッケージを一つだけインストールしました。

openSIPSの設定ファイルで難解に感じる部分はrouting scriptでした。
これはAsteriskの設定を行うことに比べて(不慣れなせいもあってか)かなり難解です。

いきなり設定作業をやるよりも、まず敵を知らなくてはということで、公式サイトにあるopenSIPSのFeatures(http://www.opensips.org/Resources/Features)を読んで見ることにしました。

ざっと眺めてみます。

  • robust and performant SIP (RFC3261) Registrar server, Location server, Proxy server and Redirect server

つまりopenSIPSはこれらの役割ができるということですね。
Registrar serverというのは端末の登録を受け付けるサーバーで、後述のProxy serverやRedirect serverと一緒に配置します。
Location serverは端末の場所(IP addressとか)を蓄積するデータベースです。通信にはSIPを使いません(LDAPとか)。
Proxy serverはUA(User Agent)が直接UAとやり取りする代わりに、UAUAの間に入って問い合わせをしたり返事をしたりするサーバーです。
Redirect serverはUAに対して探しているUAが見つかりそうな場所を案内するサーバーで、自分で誰かにリクエストを出すことはしません。

幾つか飛ばして

  • stateless and transactional statefull SIP Proxy processing

つまりSIP Proxyの動作としてはstatelessとstatefullの両方ができるものの、call statefullではないということですね。

さらに幾つか飛ばしますが、

  • variables support in script - script variables, pseudo-variables (access to the SIP messages), AVPs (values persistent per SIP transactions)

これは覚えておいたほうがよさそうですね。変数をスクリプトの中で使えるようです。pseudo-variablesはSIPメッセージの中身を操作するもので、AVPSIPトランザクション単位で値を持ち回るためのものらしいですね。

  • authentication, authorization and accounting (AAA) via database (MySQL, Postgress, text files), RADIUS and DIAMETER

認証・承認・アカウンティングをDatabaseを使ってできるようです。これはできないと不便なので具備すべき機能でしょう。さらにRADIUSやDIAMETERをつかうこともできるようです。これらは私自身が使ったことがないので便利なのかどうかわかりません。

  • Presence Agent support (many additional integration features)

プレゼンスに関わる用語としては、Presence User Agent (PUA), Presence Agent (PA), Presence server, Edge Presence serverといったものがありますね。
一応自分自身の確認のために書いておきます。
- PUA - Presence Agentに情報を送るもの(SUBSCRIBEする)
- PA - PUAからのSUBSCRIBEを受け取り、SUBSCRIBE応答を返すもの
- Presence Server - PUAがSUBSCRIBEを送る物理エンティティで実態はPAもしくはProxy
- Edge Presence server - PUAと同じ場所に置かれるPA
ここで、Presence Agent supportと言っているので、Presence Serverになることができるということです。

ひとまずはここまで。