summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/BuiltinCore.py
blob: 29beb35d5ad614e71111ea2c1c3c855f9f200efa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
""" The core of the builtin Bcfg2 server. """

import os
import sys
import time
import socket
import daemon
import Bcfg2.Statistics
from Bcfg2.Server.Core import BaseCore, NoExposedMethod
from Bcfg2.Compat import xmlrpclib, urlparse
from Bcfg2.SSLServer import XMLRPCServer

from lockfile import LockFailed, LockTimeout
# pylint: disable=E0611
try:
    from daemon.pidfile import TimeoutPIDLockFile
except ImportError:
    from daemon.pidlockfile import TimeoutPIDLockFile
# pylint: enable=E0611


class Core(BaseCore):
    """ The built-in server core """
    name = 'bcfg2-server'

    def __init__(self, setup):
        BaseCore.__init__(self, setup)

        #: The :class:`Bcfg2.SSLServer.XMLRPCServer` instance powering
        #: this server core
        self.server = None

        daemon_args = dict(uid=self.setup['daemon_uid'],
                           gid=self.setup['daemon_gid'],
                           umask=int(self.setup['umask'], 8),
                           detach_process=True)
        if self.setup['daemon']:
            daemon_args['pidfile'] = TimeoutPIDLockFile(self.setup['daemon'],
                                                        acquire_timeout=5)
        #: The :class:`daemon.DaemonContext` used to drop
        #: privileges, write the PID file (with :class:`PidFile`),
        #: and daemonize this core.
        self.context = daemon.DaemonContext(**daemon_args)
    __init__.__doc__ = BaseCore.__init__.__doc__.split('.. -----')[0]

    def _dispatch(self, method, args, dispatch_dict):
        """ Dispatch XML-RPC method calls

        :param method: XML-RPC method name
        :type method: string
        :param args: Paramaters to pass to the method
        :type args: tuple
        :param dispatch_dict: A dict of method name -> function that
                              can be used to provide custom mappings
        :type dispatch_dict: dict
        :returns: The return value of the method call
        :raises: :exc:`xmlrpclib.Fault`
        """
        if method in dispatch_dict:
            method_func = dispatch_dict[method]
        else:
            try:
                method_func = self._resolve_exposed_method(method)
            except NoExposedMethod:
                self.logger.error("Unknown method %s" % (method))
                raise xmlrpclib.Fault(xmlrpclib.METHOD_NOT_FOUND,
                                      "Unknown method %s" % method)

        try:
            method_start = time.time()
            try:
                return method_func(*args)
            finally:
                Bcfg2.Statistics.stats.add_value(method,
                                                 time.time() - method_start)
        except xmlrpclib.Fault:
            raise
        except Exception:
            err = sys.exc_info()[1]
            if getattr(err, "log", True):
                self.logger.error(err, exc_info=True)
            raise xmlrpclib.Fault(getattr(err, "fault_code", 1), str(err))

    def _daemonize(self):
        """ Open :attr:`context` to drop privileges, write the PID
        file, and daemonize the server core. """
        # Attempt to ensure lockfile is able to be created and not stale
        try:
            self.context.pidfile.acquire()
        except LockFailed:
            err = sys.exc_info()[1]
            self.logger.error("Failed to daemonize %s: %s" % (self.name, err))
            return False
        except LockTimeout:
            try:  # attempt to break the lock
                os.kill(self.context.pidfile.read_pid(), 0)
            except (OSError, TypeError):  # No process with locked PID
                self.context.pidfile.break_lock()
            else:
                err = sys.exc_info()[1]
                self.logger.error("Failed to daemonize %s: Failed to acquire"
                                  "lock on %s" % (self.name,
                                                  self.setup['daemon']))
                return False
        else:
            self.context.pidfile.release()

        self.context.open()
        self.logger.info("%s daemonized" % self.name)
        return True

    def _run(self):
        """ Create :attr:`server` to start the server listening. """
        hostname, port = urlparse(self.setup['location'])[1].split(':')
        server_address = socket.getaddrinfo(hostname,
                                            port,
                                            socket.AF_UNSPEC,
                                            socket.SOCK_STREAM)[0][4]
        try:
            self.server = XMLRPCServer(self.setup['listen_all'],
                                       server_address,
                                       keyfile=self.setup['key'],
                                       certfile=self.setup['cert'],
                                       register=False,
                                       ca=self.setup['ca'],
                                       protocol=self.setup['protocol'])
        except:  # pylint: disable=W0702
            err = sys.exc_info()[1]
            self.logger.error("Server startup failed: %s" % err)
            self.context.close()
            return False
        return True

    def _block(self):
        """ Enter the blocking infinite loop. """
        self.server.register_instance(self)
        try:
            self.server.serve_forever()
        finally:
            self.server.server_close()
            self.context.close()
        self.shutdown()