From 2a0972653c2298d762cd5ab764c47ac82e64c839 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Thu, 21 Jul 2016 11:52:57 +0200 Subject: [PATCH] Rewrite plugin readme Give a bit more information about how to write a plugin. --- examples/plugins/README.md | 256 ++++++++++++++++++++++++++++++++----- 1 file changed, 224 insertions(+), 32 deletions(-) diff --git a/examples/plugins/README.md b/examples/plugins/README.md index 4b425c5be..0dc0e2467 100644 --- a/examples/plugins/README.md +++ b/examples/plugins/README.md @@ -1,45 +1,71 @@ # Scope Probe Plugins -Scope probe plugins let you insert your own custom metrics into Scope and get them displayed in the UI. +Scope probe plugins let you insert your own custom metrics into Scope +and get them displayed in the UI. Scope Probe plugin screenshot -You can find some examples at the -[the example plugins](https://github.com/weaveworks/scope/tree/master/examples/plugins) +You can find some examples at the [the example +plugins](https://github.com/weaveworks/scope/tree/master/examples/plugins) directory. We currently provide two examples: -* A - [Python plugin](https://github.com/weaveworks/scope/tree/master/examples/plugins/http-requests) - using [bcc](http://iovisor.github.io/bcc/) to extract incoming HTTP request - rates per process, without any application-level instrumentation requirements and negligible performance toll (metrics are obtained in-kernel without any packet copying to userspace). - **Note:** This plugin needs a [recent kernel version with ebpf support](https://github.com/iovisor/bcc/blob/master/INSTALL.md#kernel-configuration). It will not compile on current [dlite](https://github.com/nlf/dlite) and boot2docker hosts. -* A - [Go plugin](https://github.com/weaveworks/scope/tree/master/examples/plugins/iovisor), - using [iostat](https://en.wikipedia.org/wiki/Iostat) to provide host-level CPU IO wait - metrics. + +* A [Python + plugin](https://github.com/weaveworks/scope/tree/master/examples/plugins/http-requests) + using [bcc](http://iovisor.github.io/bcc/) to extract incoming HTTP + request rates per process, without any application-level + instrumentation requirements and negligible performance toll + (metrics are obtained in-kernel without any packet copying to + userspace). **Note:** This plugin needs a [recent kernel version + with ebpf + support](https://github.com/iovisor/bcc/blob/master/INSTALL.md#kernel-configuration). It + will not compile on current [dlite](https://github.com/nlf/dlite) + and boot2docker hosts. +* A [Go + plugin](https://github.com/weaveworks/scope/tree/master/examples/plugins/iowait), + using [iostat](https://en.wikipedia.org/wiki/Iostat) to provide + host-level CPU IO wait or idle metrics. The example plugins can be run by calling `make` in their directory. This will build the plugin, and immediately run it in the foreground. To run the plugin in the background, see the `Makefile` for examples of the `docker run ...` command. -If the running plugin was picked up by Scope, you will see it in the list of `PLUGINS` -in the bottom right of the UI. +If the running plugin was picked up by Scope, you will see it in the +list of `PLUGINS` in the bottom right of the UI. +## Plugin ID -## Protocol +Each plugin should have an unique ID. It is forbidden to change it +during the plugin's lifetime. The scope probe will get the plugin's ID +from the plugin's socket filename. For example, the socket named +`my-plugin.sock`, the scope probe will deduce the ID as +`my-plugin`. IDs can only contain alphanumeric sequences, optionally +separated with a dash. + +## Plugin registration All plugins should listen for HTTP connections on a unix socket in the -`/var/run/scope/plugins` directory. The scope probe will recursively scan that -directory every 5 seconds, to look for sockets being added (or removed). It is -also valid to put the plugin unix socket in a sub-directory, in case you want -to apply some permissions, or store other information with the socket. +`/var/run/scope/plugins` directory. The scope probe will recursively +scan that directory every 5 seconds, to look for sockets being added +(or removed). It is also valid to put the plugin unix socket in a +sub-directory, in case you want to apply some permissions, or store +other information with the socket. -When a new plugin is detected, the scope probe will begin requesting -reports from it via `GET /report`. +## Protocol -All plugin endpoints are expected to respond within 500ms, and respond in the JSON format. +There are several interfaces a plugin may (or must) implement. Usually +implementing an interface means handling specific requests. These +requests are described below. -### Report +### Reporter interface + +Plugins _must_ implement the reporter interface. Implementing this +interface means listening for HTTP requests at `/report`. + +Add the "reporter" string to the `interfaces` field in the plugin +specification. + +#### Report When the scope probe discovers a new plugin unix socket it will begin periodically making a `GET` request to the `/report` endpoint. The @@ -69,16 +95,182 @@ For example: Note that the `Plugins` section includes exactly one plugin description. The plugin description fields are: -`interfaces` including `reporter`. -The fields are: +* `id` is used to check for duplicate plugins. It is + required. Described in [the Plugin ID section](#plugin-id). +* `label` is a human readable plugin label displayed in the UI. It is + required. +* `description` is displayed in the UI. +* `interfaces` is a list of interfaces which this plugin supports. It + is required, and must contain at least `["reporter"]`. +* `api_version` is used to ensure both the plugin and the scope probe + can speak to each other. It is required, and must match the probe. -* `id` is used to check for duplicate plugins. It is required. -* `label` is a human readable plugin label displayed in the UI. It is required. -* `description` is displayed in the UI -* `interfaces` is a list of interfaces which this plugin supports. It is required, and must equal `["reporter"]`. -* `api_version` is used to ensure both the plugin and the scope probe can speak to each other. It is required, and must match the probe. +You may notice a small chicken and egg problem - the plugin reports to +the scope probe what interfaces it supports, but the scope probe can +learn that only by doing a `GET /report` request which will be handled +by the plugin if it implements the "reporter" interface. This is +solved (or worked around) by requiring the plugin to always implements +the "reporter" interface. -### Interfaces +### Controller interface -Currently the only interface a plugin can fulfill is `reporter`. +Plugins _may_ implement the controller interface. Implementing the +controller interface means that the plugin can react to HTTP `POST` +control requests sent by the app. The plugin will receive them only +for controls it exposed in its reports. The requests will come to the +`/control` endpoint. + +Add the "controller" string to the `interfaces` field in the plugin +specification. + +#### Control + +The `POST` requests will have a JSON-encoded body with the following contents: + +```json +{ + "AppID": "some ID of an app", + "NodeID": "an ID of the node that had the control activated", + "Control": "the name of the activated control" +} +``` + +The body of the response should also be a JSON-encoded data. Usually +the body would be an empty JSON object (so, "{}" after +serialization). If some error happens during handling the control, +then the plugin can send a response with an `error` field set, for +example: + +```json +{ + "error": "An error message here" +} +``` + +Sometimes the control activation can make the control obsolete, so the +plugin may want to hide it (for example, control for stopping the +container should be hidden after the container is stopped). For this +to work, the plugin can send a shortcut report by filling the +`ShortcutReport` field in the response, like for example: + +```json +{ + "ShortcutReport": { body of the report here } +} +``` + +##### How to expose controls + +Each topology in the report (be it host, pod, endpoint and so on) has +a set of available controls a node in the topology may want to +show. The following (rather artificial) example shows a topology with +two controls (`ctrl-one` and `ctrl-two`) and two nodes, each having a +different control from the two: + +```json +{ + "Host": { + "controls": { + "ctrl-one": { + "id": "ctrl-one", + "human": "Ctrl One", + "icon": "fa-futbol-o", + "rank": 1 + }, + "ctrl-two": { + "id": "ctrl-two", + "human": "Ctrl Two", + "icon": "fa-beer", + "rank": 2 + } + }, + "nodes": { + "host1": { + "latestControls": { + "ctrl-one": { + "timestamp": "2016-07-20T15:51:05Z01:00", + "value": { + "dead": false + } + } + } + }, + "host2": { + "latestControls": { + "ctrl-two": { + "timestamp": "2016-07-20T15:51:05Z01:00", + "value": { + "dead": false + } + } + } + } + } + } +} +``` + +When control "ctrl-one" is activated, the plugin will receive a +request like: + +```json +{ + "AppID": "some ID of an app", + "NodeID": "host1", + "Control": "ctrl-one" +} +``` + +A short note about the "icon" field of the topology control - the +value for it can be taken from [Font Awesome +Cheatsheet](http://fontawesome.io/cheatsheet/) + +##### Node naming + +Very often the controller plugin wants to add some controls to already +existing nodes (like controls for network traffic management to nodes +representing the running Docker container). To achieve that, it is +important to make sure that the node ID in the plugin's report matches +the ID of the node created by the probe. The ID is a +semicolon-separated list of strings. + +For containers, images, hosts and others the ID is usually formatted +as `${name};<${tag}>`. The `${name}` variable is usually a name of a +thing the node represents, like an ID of the Docker container or the +hostname. The `${tag}` denotes the type of the node. There is a fixed +set of tags used by the probe: + +- host +- container +- container_image +- pod +- service +- deployment +- replica_set + +The examples of "tagged" node names: + +- The Docker container with full ID + 2299a2ca59dfd821f367e689d5869c4e568272c2305701761888e1d79d7a6f51: + `2299a2ca59dfd821f367e689d5869c4e568272c2305701761888e1d79d7a6f51;` +- The Docker image with name `docker.io/alpine`: + `docker.io/alpine;` +- The host with name `example.com`: `example.com:` + +The fixed set of tags listed above is not a complete set of names a +node can have though. For example, nodes representing processes are +have ID formatted as `${host};${pid}`. Probably the easiest ways to +discover how the nodes are named are: + +- Read the code in + [report/id.go](https://github.com/weaveworks/scope/blob/master/report/id.go). +- Browse the Weave Scope GUI, select some node and search for an `id` + key in the `nodeDetails` array in the address bar. + - For example in the + `http://localhost:4040/#!/state/{"controlPipe":null,"nodeDetails":[{"id":"example.com;","label":"example.com","topologyId":"hosts"}],…` + URL, you can find the `example.com;` which is an ID of the node + representing the host. + - Mentally substitute the `` with `/`. This can appear in + Docker image names, so `docker.io/alpine` in the address bar will + be `docker.ioalpine`.