BIG-IPのiRuleで関数（プロシジャ）を作る、使う

# はじめに
iRuleでも、一般的なプログラム言語のように自前の関数（Tclで言うプロシジャ）を使いたくなりませんか。BIG-IPのiRuleで、プロシジャを使う方法について確認してみました。

Tclのprocというコマンドを使用するだけなので、Tclに詳しい方には、全く低レベルな内容かと思います。

# 参考情報
 - [DevCentral: Advanced iRules: Getting Started with iRules Procedures](https://devcentral.f5.com/articles/getting-started-with-irules-procedures)
 - [DevCentral: iRule Execution Tracing and Performance Profiling, Part 3 ](https://devcentral.f5.com/articles/irule-execution-tracing-and-performance-profiling-part-3-29020)
 - [DevCentral: iRules API:call](https://devcentral.f5.com/wiki/iRules.call.ashx)
 - [K13033: Constructing CMP-compatible iRules](https://support.f5.com/csp/article/K13033)
 - [wiki.tcl.tk: proc](http://wiki.tcl.tk/463)

# 環境
このページに記載のiRuleはv12.1.2で動作を確認してます。

# どうでもいいこと
このページをわざわざ見てる人は、BIG-IPやiRuleについては当然ご存知かと思います。iRuleは、Tcl言語を拡張して作られています。だから、Tcl言語では当たり前に存在するprocが、iRuleでも使用できて当然と思われるかもしれません。

しかし実際のところ、procがiRuleに実装されたのは、(それなりに)長いBIG-IPの歴史（大体20年近く？）のなかでも、わりと最近（2013年。バージョンv11.4）です。

iRuleにprocが実装された時は、それは[嬉しい大発表](https://devcentral.f5.com/articles/getting-started-with-irules-procedures#.Uii7YBu3bAk)だったようです。

> **"Ladies and gentlemen, procs are now supported in iRules!"**

[https://devcentral.f5.com/wiki/iRules.procs.ashx](https://devcentral.f5.com/wiki/iRules.procs.ashx)

> Procedures are one of the most exciting, largest leaps forward in iRules functionality in quite some time, and certainly the most user requested. 

これほどにprocの実装の要望がありながらなかなか実装されなかった理由を想像してみました。

BIG-IPのiRuleは、Tclで書かれてはいますが、逐次実行のスクリプトではありません。高速に実行するために、iRuleがVirtual Serverに適用された時にコンパイルされ、実行を開始します。（すでに適用されてるiRuleが更新された時は？）

一度実行を開始したiRuleは常に動作し続け、特定のイベントにマッチしたタイミングで、任意のコードを実行します。

後述しますが、iRuleにおけるプロシジャ（proc）は「別のiRule内のプロシジャをcall」できるようになっています。これは非常に便利ですが、iRuleの仕組みからすると、仮にprocを組み込んだ場合「Virtual ServerにiRuleを適用した際、外部プロシジャをcallしている場合、どこまでコンパイルしなければならないか」また「既にコンパイル済みのコードの一部が更新される可能性」といった、様々な問題が発生するわけで、おそらくはこのことが、iRuleにprocを実装することが難しかった理由ではないかと思います。

上記は完全に空想ですが、iRuleにおけるprocが、相当待ち望まれていた産物であることは、いくつかの記述から、間違いありません。

# iRuleで関数（プロシジャ）を作るには
さておき、使い方ですが、基本的には次のとおりです。
 - **proc**で関数を作る
 - **return**を使って戻り値を指定
 - 関数を呼び出すときは**call**

```tcl
when CLIENT_ACCEPTED {
    set var2 [call test_proc "TEST"]
    log local0. "Return variable: $var2"
}

proc test_proc{str} {
    append str "TEST"
    return str
}
```

```text:/var/log/ltmの出力
info tmm[11496]: Rule /Common/proc_sample_2 <CLIENT_ACCEPTED>: Return variable: TESTTEST
```

# 複数の値を返すには
iRuleというよりTclの知識ですが、listを使えば、複数の値を返すこともできます。以下では、array（連想配列）を、一旦listに変換してreturnして、受け取った側で再度arrayに戻しています。

```tcl
when CLIENT_ACCEPTED {
    array set var3 [call test_proc]
    log local0. "Return variable: [array names var3]"
    log local0. "Return variable: [array get var3]"
}

proc test_proc { } {
    array set arr1 { ABC 123 DEF 456 }
    return [array get arr1]
}
```

```text:/var/log/ltmの出力
info tmm[11496]: Rule /Common/proc_sample_3 <CLIENT_ACCEPTED>: Return variable: ABC DEF
info tmm[11496]: Rule /Common/proc_sample_3 <CLIENT_ACCEPTED>: Return variable: ABC 123 DEF 456
```

# 外部のiRule内に含まれている関数をcallする方法
外部といっても、呼び出すiRuleと同じBIG-IPに入ってる必要はありますが、同一機器内の別のiRule内で作成されているプロシジャは **call irule名::プロシジャ名** で利用することができます。

例えば、以下のようなiRule "MyLib" というiRuleがあり、関数名 "test_proc"が定義されているとします。

```tcl:bigip.conf
ltm rule /Common/MyLib {
    proc test_proc {str} {
    append str "TEST"
    return  $str
}
}
```

この関数"test_proc"は、他のiRuleから"**MyLib::test_proc**" という名前で呼び出すことができます。

```tcl
when CLIENT_ACCEPTED {
    set var [call MyLib::test_proc "SAMPLE4"]
    log local0. "Return variable: $var"
}
```

```text:/var/log/ltmの出力
info tmm[11496]: Rule /Common/proc_sample_4 <CLIENT_ACCEPTED>: Return variable: SAMPLE4TEST
```

これを使えば、自前のiRuleライブラリを作ることができるので、コードの再利用の観点でとてもメリットが大きくなります。

# procと変数のスコープについて
これはあまり説明が見つかりませんでした。
とりあえず次のようなiRuleで確認したところ、プロシジャ内は通常のiRuleとは別のスコープになってるようです（一般的なTclと同じ？）。プロシジャの外とは変数を共有しないので、変数はcall時に渡す必要があります。

```tcl
when CLIENT_ACCEPTED {
    set var1 "HOGE"

    if {[info exists var1]} { log local0. "var1 is defined: $var1."}
    else { log local0. "var1 is not defind."}

    call proc_1
}

when CLIENT_CLOSED {
    if {[info exists var1]} { log local0. "var1 is defined: $var1."}
    else { log local0. "var1 is not defind."}
}

proc proc_1 { } {
    if {[info exists var1]} { log local0. "var1 is defined: $var1."}
    else { log local0. "var1 is not defind."}
}
```

```text:/var/log/ltmの出力
info tmm[11496]: Rule /Common/proc_sample_5 <CLIENT_ACCEPTED>: var1 is defined: HOGE.
info tmm[11496]: Rule /Common/proc_sample_5 <CLIENT_ACCEPTED>: var1 is not defind.
info tmm[11496]: Rule /Common/proc_sample_5 <CLIENT_CLOSED>: var1 is defined: HOGE.
```

ちなみに「一般的なTclと一緒なら、globalを使えばいいのでは」という考えが浮かびますが、iRuleでglobalを使うと、CMPが利用できなくなるので、使わないべきかと思います。K13033によれば「自動的に(CMP無効化に)降格する」と書いてあります。

[K13033: Constructing CMP-compatible iRules](https://support.f5.com/csp/article/K13033)

> Because the virtual server is automatically demoted from CMP, F5 recommends that you restrict the use of Tcl global variables to iRules to virtual servers that do not depend on CMP processing.

# 免責事項
本ページの内容に誤り等があり、参考にされた方がなんらかの損害を被った場合、一切の責任は負いません。