~landscape/landscape-client/production

« back to all changes in this revision

Viewing changes to landscape/service.py

  • Committer: Simon Poirier
  • Date: 2017-12-07 19:47:27 UTC
  • mfrom: (2.2.245 staging)
  • Revision ID: simon.poirier@canonical.com-20171207194727-5uc2lnn3oza1q7zh
Merge of lp:landscape-client/staging r247 for production rollout of release-79.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import logging
 
2
import signal
 
3
 
 
4
from twisted.application.service import Application, Service
 
5
from twisted.application.app import startApplication
 
6
 
 
7
from landscape.log import rotate_logs
 
8
from landscape.reactor import LandscapeReactor
 
9
from landscape.deployment import get_versioned_persist, init_logging
 
10
 
 
11
 
 
12
class LandscapeService(Service, object):
 
13
    """Utility superclass for defining Landscape services.
 
14
 
 
15
    This sets up the reactor and L{Persist} object.
 
16
 
 
17
    @cvar service_name: The lower-case name of the service. This is used to
 
18
        generate the bpickle and the Unix socket filenames.
 
19
    @ivar config: A L{Configuration} object.
 
20
    @ivar reactor: A L{LandscapeReactor} object.
 
21
    @ivar persist: A L{Persist} object, if C{persist_filename} is defined.
 
22
    @ivar factory: A L{LandscapeComponentProtocolFactory}, it must be provided
 
23
        by instances of sub-classes.
 
24
    """
 
25
    reactor_factory = LandscapeReactor
 
26
    persist_filename = None
 
27
 
 
28
    def __init__(self, config):
 
29
        self.config = config
 
30
        self.reactor = self.reactor_factory()
 
31
        if self.persist_filename:
 
32
            self.persist = get_versioned_persist(self)
 
33
        if not (self.config is not None and self.config.ignore_sigusr1):
 
34
            from twisted.internet import reactor
 
35
            signal.signal(
 
36
                signal.SIGUSR1,
 
37
                lambda signal, frame: reactor.callFromThread(rotate_logs))
 
38
 
 
39
    def startService(self):
 
40
        Service.startService(self)
 
41
        logging.info("%s started with config %s" % (
 
42
            self.service_name.capitalize(), self.config.get_config_filename()))
 
43
 
 
44
    def stopService(self):
 
45
        # We don't need to call port.stopListening(), because the reactor
 
46
        # shutdown sequence will do that for us.
 
47
        Service.stopService(self)
 
48
        logging.info("%s stopped with config %s" % (
 
49
            self.service_name.capitalize(), self.config.get_config_filename()))
 
50
 
 
51
 
 
52
def run_landscape_service(configuration_class, service_class, args):
 
53
    """Run a Landscape service.
 
54
 
 
55
    This function instantiates the specified L{LandscapeService} subclass and
 
56
    attaches the resulting service object to a Twisted C{Application}.  After
 
57
    that it starts the Twisted L{Application} and calls the
 
58
    L{LandscapeReactor.run} method of the L{LandscapeService}'s reactor.
 
59
 
 
60
    @param configuration_class: The service-specific subclass of
 
61
        L{Configuration} used to parse C{args} and build the C{service_class}
 
62
        object.
 
63
    @param service_class: The L{LandscapeService} subclass to create and start.
 
64
    @param args: Command line arguments used to initialize the configuration.
 
65
    """
 
66
    # Let's consider adding this:
 
67
    # from twisted.python.log import (
 
68
    #     startLoggingWithObserver, PythonLoggingObserver)
 
69
    # startLoggingWithObserver(PythonLoggingObserver().emit, setStdout=False)
 
70
 
 
71
    configuration = configuration_class()
 
72
    configuration.load(args)
 
73
    init_logging(configuration, service_class.service_name)
 
74
    application = Application("landscape-%s" % (service_class.service_name,))
 
75
    service = service_class(configuration)
 
76
    service.setServiceParent(application)
 
77
 
 
78
    if configuration.clones > 0:
 
79
 
 
80
        # Increase the timeout of AMP's MethodCalls
 
81
        # XXX: we should find a better way to expose this knot, and
 
82
        # not set it globally on the class
 
83
        from landscape.lib.amp import MethodCallSender
 
84
        MethodCallSender.timeout = 300
 
85
 
 
86
        # Create clones here because LandscapeReactor.__init__ would otherwise
 
87
        # cancel all scheduled delayed calls
 
88
        clones = []
 
89
        for i in range(configuration.clones):
 
90
            clone_config = configuration.clone()
 
91
            clone_config.computer_title += " Clone %d" % i
 
92
            clone_config.master_data_path = configuration.data_path
 
93
            clone_config.data_path += "-clone-%d" % i
 
94
            clone_config.log_dir += "-clone-%d" % i
 
95
            clone_config.is_clone = True
 
96
            clones.append(service_class(clone_config))
 
97
 
 
98
        configuration.is_clone = False
 
99
 
 
100
        def start_clones():
 
101
            # Spawn instances over the given time window
 
102
            start_clones_over = float(configuration.start_clones_over)
 
103
            delay = start_clones_over / configuration.clones
 
104
 
 
105
            for i, clone in enumerate(clones):
 
106
 
 
107
                def start(clone):
 
108
                    clone.setServiceParent(application)
 
109
                    clone.reactor.fire("run")
 
110
 
 
111
                service.reactor.call_later(delay * (i + 1), start, clone=clone)
 
112
 
 
113
        service.reactor.call_when_running(start_clones)
 
114
 
 
115
    startApplication(application, False)
 
116
    if configuration.ignore_sigint:
 
117
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
118
 
 
119
    service.reactor.run()