bin/pvl.rrd-interfaces
author Tero Marttila <terom@paivola.fi>
Tue, 19 Feb 2013 22:07:40 +0200
changeset 228 3b1437d4b0a1
parent 195 bin/pvl.verkko-rrd-interfaces@5e22cca68c39
child 235 a34e7260568b
permissions -rwxr-xr-x
rename pvl.syslog-dhcp as pvl.dhcp-syslog, and pvl.verkko-rrd-interfaces as pvl.rrd-interfaces
#!/usr/bin/python

"""
    Setup symlinks for pvl.verkko-rrd -> collectd based on define host/interface names
"""

__version__ = '0.1'

import shlex
import os

import pvl.args

import optparse
import logging; log = logging.getLogger('main')

def hostjoin (*hosts) :
    """
        DNS hostname join.
    """

    return '.'.join(hosts)

def hostreverse (host) :
    """
        Reverse hostname.
    """
    
    return '.'.join(reversed(host.split('.')))

def collectd_interfaces (options, file, collectd_domain, collectd_plugin) :
    """
        Read collectd (host, type-instance, name) items, and yield (collectd-rrd, out-rrd) tuples.
            
            file                - read host/ports from file
            collectd_domain     - append given domain to collectd hostname
            collectd_plugin     - use given collectd plugin's type-instances
    """

    host = None
        
    log.info("scanning %s/<host>.%s/%s/%s-<port>.rrd", options.collectd_rrd, collectd_domain, collectd_plugin, options.collectd_type)

    for idx, line in enumerate(file, 1) :
        line = line.rstrip()

        if not line :
            continue
        
        # comment?
        if line.startswith('#') :
            continue
        
        # line
        parts = shlex.split(line)

        if not line[0].isspace() :
            host = parts.pop(0)
        
        # host-spec?
        if '=' in host :
            collectd_host, interface_host = host.split('=')
        else :
            collectd_host = interface_host = host
        
        # flip from DNS-ordering -> path-ordering
        if options.reverse_host :
            interface_host = hostreverse(interface_host)

        # host has domain in collectd?
        if collectd_domain :
            collectd_host = hostjoin(collectd_host, collectd_domain)

        if not parts :
            # keep host for following lines
            continue

        port = parts.pop(0)
        
        # possibly multiple tags..
        for tag in parts :
            if options.collectd_instance == 'type' :
                type = options.collectd_type + '-' + port
            else :
                type = options.collectd_type

            if options.collectd_instance == 'plugin' :
                plugin = collectd_plugin + '-' + port
            else :
                plugin = collectd_plugin

            collectd_rrd = os.path.join(options.collectd_rrd, collectd_host, plugin, type) + '.rrd'

            if not os.path.exists(collectd_rrd) :
                log.warn("%s: missing collectd rrd: %s", idx, collectd_rrd)
                continue

            # out
            interface_rrd = os.path.join(interface_host, tag + '.rrd')

            log.debug("%s: %s", interface_rrd, collectd_rrd)

            yield collectd_rrd, interface_rrd

def sync_links (options, links, dir) :
    """
        Sync given (collectd, name) symlinks in given dir.
    """

    log.info("%s", dir)

    for path, name in links :
        link = os.path.join(dir, name)

        # sync
        if os.path.exists(link) :
            continue
            
        log.info("%s: %s: %s", dir, name, path)
        
        yield link, path

def apply_links (options, links) :
    """
        Apply given symlinks
    """

    for link, path in links :
        linkdir = os.path.dirname(link)

        # do
        if not os.path.exists(linkdir) :
            log.warn("mkdir: %s", linkdir)
            os.mkdir(linkdir)

        print path

        os.symlink(path, link)

COLLECTD_RRD = '/var/lib/collectd/rrd'
COLLECTD_PLUGIN = 'interfaces'
COLLECTD_TYPE = 'if_octets'

def parse_argv (argv, doc = __doc__) :
    """
        Parse command-line argv, returning (options, args).
    """

    prog = argv.pop(0)
    args = argv

    # optparse
    parser = optparse.OptionParser(
        prog        = prog,
        usage       = '%prog: [options] [<input.txt> [...]]',
        version     = __version__,
        description = doc,
    )

    # common
    parser.add_option_group(pvl.args.parser(parser))

    # options
    parser.add_option('--collectd-rrd',     metavar='PATH',     default=COLLECTD_RRD,
            help="Path to collectd rrd: %default")
    parser.add_option('--collectd-plugin',  metavar='PLUGIN',   default=None,
            help="Collectd plugin to use: <input>-<plugin>.txt")
    parser.add_option('--collectd-type',    metavar='TYPE',     default=COLLECTD_TYPE,
            help="Collectd type to use: %default")
    
    # interface is by plugin, snmp is by type...
    parser.add_option('--collectd-instance-plugin',     action='store_const', dest='collectd_instance', const='plugin',
            help="Collectd by plugin instance")
    parser.add_option('--collectd-instance-type',       action='store_const', dest='collectd_instance', const='type',
            help="Collectd by type instance")
    
    # hostnames
    parser.add_option('--reverse-host',     action='store_true',
            help="Flip host.domain -> domain.host (default)")
    parser.add_option('--no-reverse-host',  action='store_false', dest='reverse_host',
            help="Keep host.domain as host.domain")
    parser.add_option('--domain',           metavar='DOMAIN',  
            help="Append domain to collectd hostnames: <input>.txt -> <input>")

    # output
    parser.add_option('--rrd',              metavar='PATH',
            help="Output directory for .rrd symlinks: <input>.txt -> <input>/")
    parser.add_option('--noop',             action='store_true',
            help="Scan symlinks, but do not update")

    parser.set_defaults(
            collectd_instance   = 'type',
            reverse_host        = True,
    )

    # parse
    options, args = parser.parse_args(args)

    # apply
    pvl.args.apply(options)

    return options, args

def main (argv) :
    options, args = parse_argv(argv)
    
    for txt in args :
        # <path>/<domain>-<plugin>.txt -> <path>/<domain>-<plugin>
        basepath, _ = os.path.splitext(txt)
        
        # <path>/<domain>-<plugin> -> <path>/<domain>, <plugin>
        if '-' in basepath :
            basepath, collectd_plugin = basepath.rsplit('-', 1)
        else :
            collectd_plugin = None

        # <path>/<domain> -> <domain>
        _, basename = os.path.split(basepath)
        
        # domain?
        if options.domain is None :
            # reverse-order hostname
            domain = hostreverse(basename)
        else :
            # may be ''
            domain = options.domain
        
        # output dir?
        if options.rrd :
            rrd_domain = hostreverse(domain) if options.reverse_host else domain
            rrd = os.path.join(options.rrd, rrd_domain)
        else :
            rrd = basepath

        # generate links from spec
        links = list(collectd_interfaces(options, open(txt),
            collectd_domain     = domain,
            collectd_plugin     = options.collectd_plugin or collectd_plugin or COLLECTD_PLUGIN,
        ))

        if not os.path.exists(rrd) :
            log.warn("mkdir: %s", rrd)

            if not options.noop :
                os.mkdir(rrd)
        
        # sync missing links
        links = list(sync_links(options, links,
            dir     = rrd,
        ))
        
        # verbose
        if not options.quiet :
            for link, path in links :
                print link, '->', path
        
        # apply
        if not options.noop :
            apply_links(options, links)

    return 0
    
if __name__ == '__main__':
    import sys

    sys.exit(main(sys.argv))