agent.py 5.46 KB
Newer Older
Bach Dániel committed
1
#!/usr/bin/env python
Bach Dániel committed
2
# -*- coding: utf-8 -*-
Bach Dániel committed
3

Guba Sándor committed
4
from os import environ, chdir
5
import platform
6 7 8
import subprocess
import sys

9 10
system = platform.system()

Bach Dániel committed
11
if system == "Linux" or system == "FreeBSD":
Guba Sándor committed
12 13 14 15 16
    try:
        chdir(sys.path[0])
        subprocess.call(('pip', 'install', '-r', 'requirements.txt'))
    except:
        pass  # hope it works
17 18


19
from twisted.internet import reactor, defer
Bach Dániel committed
20
from twisted.internet.task import LoopingCall
21

Bach Dániel committed
22 23

import uptime
Bach Dániel committed
24
import logging
25
from inspect import getargspec, isfunction
Bach Dániel committed
26 27 28

from utils import SerialLineReceiverBase

Guba Sándor committed
29 30 31
# Note: Import everything because later we need to use the BaseContext
# (relative import error.
from context import BaseContext, get_context, get_serial  # noqa
Guba Sándor committed
32 33

Context = get_context()
Bach Dániel committed
34

Bach Dániel committed
35
logging.basicConfig()
Bach Dániel committed
36
logger = logging.getLogger()
Bach Dániel committed
37 38 39
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)

Őry Máté committed
40

Bach Dániel committed
41
class SerialLineReceiver(SerialLineReceiverBase):
Őry Máté committed
42

Bach Dániel committed
43
    def connectionMade(self):
44
        self.transport.write('\r\n')
Bach Dániel committed
45 46
        self.send_command(
            command='agent_started',
Guba Sándor committed
47 48
            args={'version': Context.get_agent_version(),
                  'system': system})
49

Bach Dániel committed
50 51 52 53 54 55 56
        def shutdown():
            self.connectionLost2('shutdown')
            d = defer.Deferred()
            reactor.callLater(0.3, d.callback, "1")
            return d
        reactor.addSystemEventTrigger("before", "shutdown", shutdown)

Bach Dániel committed
57 58 59
    def connectionLost(self, reason):
        reactor.stop()

Bach Dániel committed
60 61 62 63 64
    def connectionLost2(self, reason):
        self.send_command(command='agent_stopped',
                          args={})

    def tick(self):
65 66 67 68 69
        logger.debug("Sending tick")
        try:
            self.send_status()
        except:
            logger.exception("Twisted hide exception")
Bach Dániel committed
70 71 72 73 74 75 76

    def __init__(self):
        super(SerialLineReceiver, self).__init__()
        self.lc = LoopingCall(self.tick)
        self.lc.start(5, now=False)

    def send_status(self):
Bach Dániel committed
77
        import psutil
78 79 80
        disk_usage = dict((disk.device.replace('/', '_'),
                           psutil.disk_usage(disk.mountpoint).percent)
                          for disk in psutil.disk_partitions())
Őry Máté committed
81 82 83
        args = {"cpu": dict(psutil.cpu_times()._asdict()),
                "ram": dict(psutil.virtual_memory()._asdict()),
                "swap": dict(psutil.swap_memory()._asdict()),
Bach Dániel committed
84
                "uptime": {"seconds": uptime.uptime()},
Bach Dániel committed
85
                "disk": disk_usage,
Bach Dániel committed
86
                "user": {"count": len(psutil.get_users())}}
87 88
        self.send_response(response='status', args=args)
        logger.debug("send_status finished")
Bach Dániel committed
89

90 91 92 93 94
    def _check_args(self, func, args):
        if not isinstance(args, dict):
            raise TypeError("Arguments should be all keyword-arguments in a "
                            "dict for command %s instead of %s." %
                            (self._pretty_fun(func), type(args).__name__))
95

96 97 98 99 100
        # check for unexpected keyword arguments
        argspec = getargspec(func)
        if argspec.keywords is None:  # _operation doesn't take ** args
            unexpected_kwargs = set(args) - set(argspec.args)
            if unexpected_kwargs:
101 102
                raise TypeError(
                    "Command %s got unexpected keyword arguments: %s" % (
103
                        self._pretty_fun(func), ", ".join(unexpected_kwargs)))
104

105 106 107 108 109 110
        mandatory_args = argspec.args
        if argspec.defaults:  # remove those with default value
            mandatory_args = mandatory_args[0:-len(argspec.defaults)]
        missing_kwargs = set(mandatory_args) - set(args)
        if missing_kwargs:
            raise TypeError("Command %s missing arguments: %s" % (
111
                self._pretty_fun(func), ", ".join(missing_kwargs)))
112

113 114 115 116 117 118 119 120 121 122 123 124 125
    def _get_command(self, command, args):
        if not isinstance(command, basestring) or command.startswith('_'):
            raise AttributeError(u'Invalid command: %s' % command)
        try:
            func = getattr(Context, command)
        except AttributeError as e:
            raise AttributeError(u'Command not found: %s (%s)' % (command, e))

        if not isfunction(func):
            raise AttributeError("Command refers to non-static method %s." %
                                 self._pretty_fun(func))

        self._check_args(func, args)
126
        logger.debug("_get_command finished")
127 128
        return func

129 130 131 132 133 134 135 136 137 138 139 140 141
    @staticmethod
    def _pretty_fun(fun):
        try:
            argspec = getargspec(fun)
            args = argspec.args
            if argspec.varargs:
                args.append("*" + argspec.varargs)
            if argspec.keywords:
                args.append("**" + argspec.keywords)
            return "%s(%s)" % (fun.__name__, ",".join(args))
        except:
            return "<%s>" % type(fun).__name__

142 143 144 145 146 147
    def handle_command(self, command, args):
        func = self._get_command(command, args)
        retval = func(**args)
        self.send_response(
            response=func.__name__,
            args={'retval': retval, 'uuid': args.get('uuid', None)})
Bach Dániel committed
148 149 150 151 152 153

    def handle_response(self, response, args):
        pass


def main():
Guba Sándor committed
154 155
    # Get proper serial class and port name
    (serial, port) = get_serial()
Guba Sándor committed
156
    logger.info("Opening port %s", port)
Guba Sándor committed
157 158
    # Open serial connection
    serial(SerialLineReceiver(), port, reactor)
159 160 161 162
    try:
        from notify import register_publisher
        register_publisher(reactor)
    except:
Guba Sándor committed
163
        logger.exception("Could not register notify publisher")
164
    logger.debug("Starting reactor.")
Bach Dániel committed
165
    reactor.run()
Guba Sándor committed
166
    logger.debug("Reactor finished.")
Bach Dániel committed
167

168

Bach Dániel committed
169 170
if __name__ == '__main__':
    main()