今回はBlackbird pluginを書こう第二弾ってことで実際に簡単なpluginの書き方を紹介します!
各メソッドの細かい説明は第三弾に委ねるってことで、第一弾、第二弾、第三弾とそれぞれかぶっちゃうこともあるかもですが、
みなさん生ぬるい目で見守ってくださいまし。
outline
ざっくりした手順としては、
- pluginの基底クラスを入手
- 具象クラスを作成
- Mainプロセスから生成されたThread上で実行されるメソッドの実装
- Queueに入れるアイテムの形式
- コンフィグのValidation設定
こんな感じです。
- pluginの基底クラスを入手
基底クラスはblackbird本体に内包されていて、
pip install blackbird
で簡単に入手できます。
- 具象クラスを作成
っで、あなたが作成するプラグイン側で
# blackbird.plugins.base moduleをimport
import blackbird.plugins.base
# JobBase抽象クラスを継承
class ConcreteJob(blackbird.plugins.base.JobBase):
    pass
と指定すれば、具象クラスの作成は完了です。
注意するポイントとしては、クラス名はConcreteJobで固定なので、違うクラス名にすると動かないです。
- Mainプロセスから生成されたThread上で実行されるメソッドの実装
アイテムをQueueに入れるとき、MainのプロセスはConcreteJob.build_itemsを呼び出せることを期待しています。
コード的には
import YOUR_MODULE
YOUR_MODULE.ConcreteJob.build_items()
が実行できないと実際にアイテムはQueueにはつまれません。そのため必ずbuild_itemsという名前でメソッドを実装してください。
その際、基底クラス側にはenqueueメソッドを実装してあるので、build_itemsの中で親クラスのenqueueメソッドを実行するか、
具象クラス側でenqueueメソッドを実装したい場合は、overrideしちゃうか、__enqueue_メソッドを実装してください。
今までの話をまとめると、
import blackbird.plugins.base
class ConcreteJob(blackbird.plugins.base.JobBase):
    def build_items(self):
        # Queueに入れるアイテム名とQueueにはついては後述
        items = {
            'HOGEHOGE': 'FUGAFUGA'
        }
こんな感じになるかと。
- Queueに入れるアイテムの形式
っで、実際に入れるQueueのアイテムの形式を合わせてやれば概ね機能としては実装完了です。
Queueに入れるアイテムはすごいシンプルでPythonのdict形式を採用してます。具体的には
ITEM = {
    'key': 'ANYTHING_OK',
    'value': '1.0000',
    'host': 'HOSTNAME_ON_ZABBIX_SERVER',
    'clock': datetime.datetime()
}
こんな感じになってます。
key
_key_はzabbixのテンプレート上のkeyで、たとえば使い慣れてるものだとagent.pingみたいなものをイメージしてもらえればと思います。
なので、あまりabstractなkey名を指定しちゃうとかぶっちゃう可能性高いのでちょっと冗長になったり長くなっちゃうかもですが、
識別しやすいものが吉かと。
value
この値はsenderが送り先に(いまんとこzabbix serverだけですが)送る実際の値です。
これは説明する必要ってあんまりないとは思うんですが、1 or 0なflag系の値でもいいですし、
1.234567みたいな小数点でも、文字列でもなんでもいい感じです。ただいろんなインテグレーション考えると、浮動小数にしとくのが無難かと思います。
host
いまんとこzabbix serverが送り先なことを想定してるのでhostはZabbix Server上でのHostの名前です。
これでどのHostに対して値を送りたいかを指定します。
ただ、外から注入するような仕組みを作りにくいと思うので、後述するconfigのValidationと
連携してconfigに指定したhostnameの値がここの値に入ります。
clock
clockはPythonのdatetime.datetimeインスタンスです。
これも、いちいちPlugin側でインテグレーションするのめんどくさかったので、基底クラス側で、
インスタンス生成時の時刻をclockのvalueとして入れるように設定しました。
4.5. 実はItemも自分でインテグレーションしなくていいですwww
_4._のところにも書いてたので、だいたいご察しかと思いますが、Itemはdict objectをreturnするようなItemBaseクラスを定義してあります。
なので、
- clockの実装
- 取得時刻を設定したdatetime.datetimeインスタンスを作成する必要はなし
 
- 取得時刻を設定した
- hostの実装
- 後述しますが、self.configにはdict形式でconfigファイルに書いた内容が格納されてます。
 
- 後述しますが、
はplugin開発者側で実装する必要はないです。
blackbird.plugins.base.ItemBase クラスについて
このクラスは先程もちょろっと触れましたが、dict型オブジェクトをreturnするような抽象クラスです。
特に難しいことはやってなくって、__init__で_generateを呼び出してて、その中でdictを生成してます。
_returnするような_と書いたのは、実際にはdataっていう名前のgetterメソッドを``@propertyして、ConcreteItem.data`でdictをreturnするようにしてます。
ちょっと細かく書きすぎましたかね.....この辺の細かすぎる話は次の第三弾でkwsk!!
っでここまでの話をまとめると、
import blackbird.plugins.base
class ConcreteJob(blackbird.plugins.base.JobBase):
    def build_items(self):
        item=ConcreteItem(
           key='isPublished',
           value=1,
           #self.configについてはValidationと一緒に後述しますね。
           host=self.config.get('hostname')
        )
        self.enqueue(
            item=item
        )
class ConcreteItem(blackbird.plugins.base.ItemBase):
    def __init__(self, key, value, host):
        super(ConcreteItem, self).__init__(key, value, host)
        self.__data = dict()
        self._generate()
    @property
    def data(self):
        return self.__data
    def _generate(self):
        """
        例えば、keyに特定のprefixをつけたければ、こんな感じで
        self.__data['key'] = 'YOUR_PREFIX.' + self.key
        ここに書けばおk
        """
        self.__data['key'] = self.key
        self.__data['value'] = self.value
        self.__data['host'] = self.host
        self.__data['clock'] = self.clock
こんな感じでConcreteItemを生成してQueueに入れて、zabbix_sender pluginに送信させることができます。
- コンフィグのValidation設定
Validationは実は設定しなくてもおkなんです。
コンフィグファイルに書いちゃえばそのkeyとvalueがそのままblackbird.plugins.base.JobBase.configに
dict型オブジェクトとして格納されます。たとえば、
[MY_BLACKBIRD_PLUGIN]
hostname = hogehoge.com
module = MODULE_NAME
って書けば、それだけでself.config['hostname']とかすればconfig内の値にアクセスできます。
ただ、必須の値とか欲しいじゃないですか、そこでValidationです。
Validationオブジェクトも基底クラス側でインテグレーションしてありまして、 文字列を使ってValidationの設定を書く感じです。
実際に書いてみると、
class Validator(blackbird.plugins.base.ValidatorBase):
    def __init__(self):
        self.__spec = None
    @property
    def spec(self):
        self.__spec = (
            "[0]".format(__name__),
            "required_parameter = string()",
            "not_required_paramter = string(default='DEFAULT_STRING')"
        )
        return self.__spec
こんな感じです。class名はValidator固定です。ちょっといろいろ詰め込んでてややこしくなっちゃったんですが、
細かいところは第三弾に任せるとしまして、ざっくりとポイントだけ。
- class名はValidator固定
- specはValidator.specでアクセスできる必要がある
- 
__specの[]で囲まれたセクション名はplugin module自身の名前である必要がある- 直接文字列で指定してもいいとは思うんですが、__name__の使用をrecommendです
 
- 直接文字列で指定してもいいとは思うんですが、
- 
string()みたいな指定だとdefault値がないために必須のparameterになる
- 
string(default='HOGEHOGE')みたいな指定だとdefault値がHOGEHOGEっていう文字列になるためoptionedになる
- 型指定はstring、integer、float、listの指定が可能
- integerは最低値と最高値を範囲指定可能
- e.g: port = integer(0, 65535, default=80)
 
- e.g: 
- listはカンマ区切りだよ
 
- integerは最低値と最高値を範囲指定可能
- docstringでも__specを指定できるけど、formatメソッドのintegrationとか考えるとtupleで文字列渡して複数行文字列の方が楽だよ
まとめ
ここまでの内容をそのまま実装すると、実はplugin完成です。
# blackbirdからplugin用の抽象クラスを含んだmoduleをimportする
import blackbird.plugins.base
# JobBase抽象クラスを継承したConcreteJobを作成する
# class名はConcreteJob固定
class ConcreteJob(blackbird.plugins.base.JobBase):
# build_itemsメソッドがMainのプロセスから呼ばれるので、build_itemsは名前固定です
    def build_items(self):
        item = self.generate_items()
        self.enqueue(
            item=item
        )
    def generate_items(self)
        return ConcreteItem(
           key='isPublished',
           value=1,
           host=self.config.get('hostname')
        )
class ConcreteItem(blackbird.plugins.base.ItemBase):
    def __init__(self, key, value, host):
        super(ConcreteItem, self).__init__(key, value, host)
        self.__data = dict()
        self._generate()
    @property
    def data(self):
        return self.__data
    def _generate(self):
        self.__data['key'] = self.key
        self.__data['value'] = self.value
        self.__data['host'] = self.host
        self.__data['clock'] = self.clock
class Validator(blackbird.plugins.base.ValidatorBase):
    def __init__(self):
        self.__spec = None
    @property
    def spec(self):
        self.__spec = (
            "[0]".format(__name__),
            "required_parameter = string()",
            "not_required_paramter = string(default='DEFAULT_STRING')"
        )
        return self.__spec
if __name__ == '__main__':
    OPTIONS = {
        'required_parameter': 'HOGEHOGE'
    }
    JOB = ConcreteJob(options=OPTIONS)
    import json
    print(json.dumps(JOB.generate_items))
まとめるとこんな感じですかね=3