Merge "Add plugin Calico Bird"
diff --git a/collectd/files/plugin/collectd_calico_bird.py b/collectd/files/plugin/collectd_calico_bird.py
new file mode 100644
index 0000000..5b519b8
--- /dev/null
+++ b/collectd/files/plugin/collectd_calico_bird.py
@@ -0,0 +1,153 @@
+#!/usr/bin/python
+# Copyright 2017 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if __name__ == '__main__':
+    import collectd_fake as collectd
+else:
+    import collectd
+import re
+import time
+
+import collectd_base as base
+
+NAME = 'calico_bird'
+# Default sampling interval
+INTERVAL = 60
+BIRDCL_BINARY = '/usr/bin/birdcl'
+
+re_memory = re.compile(r"(?P<attribute>.*):\s+"
+                       r"(?P<quantity>\d+)\s"
+                       r"(?P<multiplier>[kMG ])B\s*")
+
+re_protocol = re.compile(r"(?P<name>\S+)\s+"
+                         r"(?P<protocol>\S+)\s+"
+                         r"(?P<table>\S+)\s+"
+                         r"(?P<state>\S+)\s+"
+                         r"(?P<since>\d{2}:\d{2}:\d{2})"
+                         r"(?P<info>.*)")
+
+
+def protocol_metric(line):
+    re_match = re_protocol.match(line)
+    if re_match:
+        if re_match.group("protocol") == "BGP":
+            return gen_metric(
+                'bgp_up', re_match.group('state') == 'up',
+                {
+                    'bird_protocol_instance': re_match.group('name'),
+                }
+            )
+
+
+def memory_metric(line):
+    re_match = re_memory.match(line)
+    if re_match:
+        quantity = int(re_match.group("quantity"))
+        for m in " kMG":
+            if re_match.group("multiplier") == m:
+                break
+            quantity *= 1024
+        mname = 'memory_' + re_match.group(
+            'attribute').lower().replace(' ', '_')
+        return gen_metric(
+            mname, quantity
+        )
+
+
+def gen_metric(name, val, meta={}):
+    ret_metric = {
+        'type_instance': name,
+        'values': val,
+    }
+    if meta:
+        ret_metric['meta'] = meta
+    return ret_metric
+
+
+class CalicoBirdPlugin(base.Base):
+
+    _checks = {
+        'memory': {
+            'cmd_args': ['show', 'memory'],
+            'func': memory_metric,
+        },
+        'protocol': {
+            'cmd_args': ['show', 'protocols'],
+            'func': protocol_metric,
+        },
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(CalicoBirdPlugin, self).__init__(*args, **kwargs)
+        self.plugin = NAME
+        self._socketfiles = {}
+
+    def config_callback(self, config):
+        super(CalicoBirdPlugin, self).config_callback(config)
+        for node in config.children:
+            m = re.search('^ipv(?P<ip_version>[46])_socket$', node.key.lower())
+            if m:
+                self._socketfiles[m.group('ip_version')] = node.values[0]
+
+    def _run_birdcl_command(self, sockf, args):
+        cmd = [
+            BIRDCL_BINARY,
+            '-s',
+            sockf
+        ] + args
+        retcode, out, err = self.execute(cmd, shell=False)
+        if retcode == 0:
+            return out
+        msg = "Failed to execute {} '{}'".format(cmd, err)
+        raise base.CheckException(msg)
+
+    def itermetrics(self):
+        for ipv, sockf in self._socketfiles.items():
+            for lcname, lcheck in self._checks.items():
+                out = self._run_birdcl_command(sockf, lcheck['cmd_args'])
+                for metric in filter(
+                        None,
+                        [lcheck['func'](line) for line in out.split('\n')]
+                ):
+                    if not metric.get('meta', False):
+                        metric['meta'] = {}
+                    metric['meta'].update({'ip_version': ipv})
+                    yield metric
+
+
+plugin = CalicoBirdPlugin(collectd)
+
+
+def init_callback():
+    plugin.restore_sigchld()
+
+
+def config_callback(conf):
+    plugin.config_callback(conf)
+
+
+def read_callback():
+    plugin.read_callback()
+
+if __name__ == '__main__':
+    collectd.load_configuration(plugin)
+    plugin.read_callback()
+    collectd.info('Sleeping for {}s'.format(INTERVAL))
+    time.sleep(INTERVAL)
+    plugin.read_callback()
+else:
+    collectd.register_init(init_callback)
+    collectd.register_config(config_callback)
+    collectd.register_read(read_callback, INTERVAL)