LoginSignup
0

More than 5 years have passed since last update.

posted at

Write your blackbird plugin #002

今回はBlackbird pluginを書こう第二弾ってことで実際に簡単なpluginの書き方を紹介します!
各メソッドの細かい説明は第三弾に委ねるってことで、第一弾、第二弾、第三弾とそれぞれかぶっちゃうこともあるかもですが、
みなさん生ぬるい目で見守ってくださいまし。

outline

ざっくりした手順としては、

  1. pluginの基底クラスを入手
  2. 具象クラスを作成
  3. Mainプロセスから生成されたThread上で実行されるメソッドの実装
  4. Queueに入れるアイテムの形式
  5. コンフィグのValidation設定

こんな感じです。

1. pluginの基底クラスを入手

基底クラスはblackbird本体に内包されていて、

pip install blackbird

で簡単に入手できます。

  1. 具象クラスを作成

っで、あなたが作成するプラグイン側で


# blackbird.plugins.base moduleをimport
import blackbird.plugins.base


# JobBase抽象クラスを継承
class ConcreteJob(blackbird.plugins.base.JobBase):
    pass

と指定すれば、具象クラスの作成は完了です。
注意するポイントとしては、クラス名はConcreteJobで固定なので、違うクラス名にすると動かないです。

3. 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'
        }

こんな感じになるかと。

4. 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に送信させることができます。

5. コンフィグの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)
    • listはカンマ区切りだよ
  • 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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0