agent.py 5.41 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 6
import platform
from shutil import copy
7 8 9
import subprocess
import sys

10 11
system = platform.system()

12
if system == "Linux" or system == "FreeBSD" :
Guba Sándor committed
13 14 15
    try:
        chdir(sys.path[0])
        subprocess.call(('pip', 'install', '-r', 'requirements.txt'))
16
        copy("/root/agent/misc/vm_renewal", "/usr/local/bin/")
Guba Sándor committed
17 18
    except:
        pass  # hope it works
19 20


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

Bach Dániel committed
24 25

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

from utils import SerialLineReceiverBase

Guba Sándor committed
31 32 33
# 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
34 35

Context = get_context()
Bach Dániel committed
36

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

Őry Máté committed
42

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

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

Bach Dániel committed
52 53 54 55 56 57 58 59 60 61 62 63
        def shutdown():
            self.connectionLost2('shutdown')
            d = defer.Deferred()
            reactor.callLater(0.3, d.callback, "1")
            return d
        reactor.addSystemEventTrigger("before", "shutdown", shutdown)

    def connectionLost2(self, reason):
        self.send_command(command='agent_stopped',
                          args={})

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

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

89 90 91 92 93
    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__))
94

95 96 97 98 99
        # 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:
100 101
                raise TypeError(
                    "Command %s got unexpected keyword arguments: %s" % (
102
                        self._pretty_fun(func), ", ".join(unexpected_kwargs)))
103

104 105 106 107 108 109
        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" % (
110
                self._pretty_fun(func), ", ".join(missing_kwargs)))
111

112 113 114 115 116 117 118 119 120 121 122 123 124
    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)
125 126
        return func

127 128 129 130 131 132 133 134 135 136 137 138 139
    @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__

140 141 142 143 144 145
    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
146 147 148 149 150 151

    def handle_response(self, response, args):
        pass


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

166

Bach Dániel committed
167 168
if __name__ == '__main__':
    main()