Plugin tutorial: How to create a simple trigger

  Visit at GitHub

In this tutorial we will develop a simple trigger plugin that checks if a given web page is accessible and returns an 200 OK code.

To do this we will have to develop a pure backend plugin.

Directory structure

A basic plugin is just a directory with a manifest.yaml file. This plugin must be accessible during development to the Serverboards Core, so look for your plugins directory and create a 200ok directory.

Serverboards auto reloads the plugin directory on every change, so you don’t need to restart Serverboards for it to realize there is a new plugin nor changes on it.

Manifest file

Every plugin at Serverboards requires a manifest.yaml file that defines what this plugin has.

It has some required fields:

id: tutorial.200ok
name: Checks HTTP server returns 200 OK
description: |
  This definition is for the full plugin.

components:
  - id: 200daemon
    type: cmd
    command: 200ok.py
  - id: check
    type: trigger
    name: 200 OK Trigger
    description: |
      Checks every 5 seconds if the given server returns
      something different to 200 OK.
    command: 200daemon
    states: ok nok
    start:
      method: start_200ok
      params:
        - name: url
    #stop:
    #  method: stop_200ok

Every plugin have one or several components. In the case of triggers we need an active command that will receive the start call, and the trigger definition itself.

Command components (type cmd) are quite simple and just indicate some command to run at the plugin directory. It may have an associates strategy, but here we will use the default one_for_one.

The Trigger component is a bit more complex.

It requires some common data with other components as user facing information (name and description) and the command to execute via a component id, which can be just a component id (200daemon), or a full qualified component id (plugin_id/component_id).

Specific to triggers are the states, start and stop sections.

states indicates in which states can be this trigger. It will trigger whenever there is a state change.

start indicates the method to call at the command to start the watching. It may have some params to know what to watch.

stop is an optional method name to call to stop the watcher.

How this methods works is explained better below.

An optional service component

For this plugin we will not need any special component as we will use built in ones, specifically the ones with the url trait.

But, would we like to add a new service component, it would just be adding this component, actually taken from the core services.

  - name: Web Server
    type: service
    traits: url
    id: web_server
    icon: world
    fields:
      - label: URL
        name: url
        type: text
        validation: empty
        card: true

Command and test

We will use the Serverboards CLI through this tutorial. It can be used as communication tool with the core, or with plugins. It eases the use of the communication protocol (JSON-RPC), and have some goodies as line completion, variables, and some error rescue support. It is distributed at cli/serverboards.py. Find it and have it ready in a terminal.

We will also use the Python bindings, distributed at plugins/bindings/python/serverboards.py. Copy it to your plugin directory.

The python bindings are based on decorators to allow any function to be exported via JSON-RPC. It uses stdin and stdout to communicate so its very important not to use them for other purposes. If there is need for debug, use stderr. The Python bindings have a serverboards.debug function that can be used.

We will first try a simple example:

#!/usr/bin/python
import serverboards

@serverboards.rpc_method
def hello(name='world'):
  return "Hello %s!"%name

serverboards.loop()

First we import the serverboards module. It provides many functions and an rpc object that we will use for advanced usage.

Next we define our function, hello, that just returns a string with “Hello world!” if no parameter is passed, or “Hello “ and the name if any is passed.

To try it, we will use the serverboards CLI.

Ensure your 200ok.py file has executable permissions.

./serverboards.py --command ../../serverboards/plugins/200ok/200ok.py -
> dir
[
  "hello",
  "dir"
]
> hello
"Hello world!"
> hello David
"Hello David!"
> hello name:David
"Hello David!"
> hello name:David,Moreno
"Hello [u'David', u'Moreno']!"

Works as expected.

As you can see we can use positional parameters, and named parameters. In the parameters we can also use more complex structures as lists.

So next lets create the trigger method, modify your 200ok.py file to contain this:

#!/usr/bin/python
import serverboards, requests, time

@serverboards.rpc_method
def start_200ok(id=None, url=None):
    serverboards.rpc.reply("some-id")

    last_ok=None
    while True: # will never exit
        try:
            r = requests.get(url)
            ok = r.ok
        except:
            ok = False
        if ok != last_ok: # state change
            if ok: # send ok
                serverboards.rpc.event("trigger", id=id, state="ok")
            else: # send nok
                serverboards.rpc.event("trigger", id=id, state="nok")
            last_ok=ok
        time.sleep(5)

serverboards.loop()

So there it is. In this code we start with an unknown ok code at last_ok, then we will loop forever, and every 5 seconds (time.sleep(5) at the end of the loop) we will try to get the page.

It can fail with an exception (for example bad URL), or with some code. The r.ok stores a boolean with the result.

Then we use this boolean to compare with the last ok code last_ok (which started with None, so it will always fail on the first run of the loop). If different it will check if it should send a state of ok or nok.

This trigger does not perform stop at all. But the strategy is one_for_one so we are sure nobody will call this trigger except for stopping it, and it will kill it if the stop does not work (as per one_for_one requirements).

This makes trigger development extremely simple, as we can have just one program that speaks very basic JSON-RPC and prints very basic JSON-RPC. It could be done even with bash.

Testing

We are now ready to test. Go to your Serverboards and check there is a new trigger when you create a new rule. It will appear only on services that have the url trait.

Fill out all the data, for example setting as actions set the tags of a service to UP -DOWN on status ok and -UP DOWN on status nok.

If you activate the rule your plugin will be started, and if you deactivate it, it will be stopped.

Advanced triggers

We can evolve our trigger to be a singleton that shares many watchers. To do that we need several changes: First we need to change our manifest.yaml to properly indicate that the daemon is a singleton now, and then change the code to properly manage many watchers in one command.

If you are interested on the details, check the core triggers as they are developed using this strategy.