~allenap/maas/rpc-give-shared-secret-to-accepted-cluster

« back to all changes in this revision

Viewing changes to src/provisioningserver/plugin.py

  • Committer: Tarmac
  • Author(s): Julian Edwards
  • Date: 2012-01-26 15:00:29 UTC
  • mfrom: (36.1.16 twisted-provisioning)
  • Revision ID: ed@carob-20120126150029-ah7vuh0jxgx77uxd
[r=allenap][bug=][author=julian-edwards] Add the basic TAP framework for the provisioning server.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2012 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Twisted Application Plugin code for the MaaS provisioning server"""
 
5
 
 
6
from __future__ import (
 
7
    print_function,
 
8
    unicode_literals,
 
9
    )
 
10
 
 
11
import setproctitle
 
12
import signal
 
13
import sys
 
14
 
 
15
import oops
 
16
from oops_datedir_repo import DateDirRepo
 
17
from oops_twisted import (
 
18
    Config as oops_config,
 
19
    defer_publisher,
 
20
    OOPSObserver,
 
21
    )
 
22
from twisted.application.internet import TCPClient
 
23
from twisted.application.service import (
 
24
    IServiceMaker,
 
25
    MultiService,
 
26
    )
 
27
from twisted.internet import reactor
 
28
from twisted.plugin import IPlugin
 
29
from twisted.python import (
 
30
    log,
 
31
    usage,
 
32
    )
 
33
from twisted.python.logfile import LogFile
 
34
from twisted.python.log import (
 
35
    addObserver,
 
36
    FileLogObserver,
 
37
    )
 
38
from zope.interface import implements
 
39
 
 
40
from amqpclient import AMQFactory
 
41
 
 
42
__metaclass__ = type
 
43
__all__ = []
 
44
 
 
45
 
 
46
def getRotatableLogFileObserver(filename):
 
47
    """Setup a L{LogFile} for the given application."""
 
48
    if filename != '-':
 
49
        logfile = LogFile.fromFullPath(
 
50
            filename, rotateLength=None, defaultMode=0644)
 
51
        def signal_handler(sig, frame):
 
52
            reactor.callFromThread(logfile.reopen)
 
53
        signal.signal(signal.SIGUSR1, signal_handler)
 
54
    else:
 
55
        logfile = sys.stdout
 
56
    return FileLogObserver(logfile)
 
57
 
 
58
 
 
59
def setUpOOPSHandler(options, logfile):
 
60
    """Add OOPS handling based on the passed command line options."""
 
61
    config = oops_config()
 
62
 
 
63
    # Add the oops publisher that writes files in the configured place
 
64
    # if the command line option was set.
 
65
 
 
66
    if options["oops-dir"]:
 
67
        repo = DateDirRepo(options["oops-dir"])
 
68
        config.publishers.append(
 
69
            defer_publisher(oops.publish_new_only(repo.publish)))
 
70
 
 
71
    if options["oops-reporter"]:
 
72
        config.template['reporter'] = options["oops-reporter"]
 
73
 
 
74
    observer = OOPSObserver(config, logfile.emit)
 
75
    addObserver(observer.emit)
 
76
    return observer
 
77
 
 
78
 
 
79
class Options(usage.Options):
 
80
    """Command line options for the provisioning server."""
 
81
 
 
82
    optParameters = [
 
83
        ["logfile", "l", "provisioningserver.log", "Logfile name."],
 
84
        ["brokerport", "p", 5672, "Broker port"],
 
85
        ["brokerhost", "h", '127.0.0.1', "Broker host"],
 
86
        ["brokeruser", "u", None, "Broker user"],
 
87
        ["brokerpassword", "a", None, "Broker password"],
 
88
        ["brokervhost", "v", '/', "Broker vhost"],
 
89
        ["oops-dir", "r", None, "Where to write OOPS reports"],
 
90
        ["oops-reporter", "o", "MAAS-PS", "String identifying this service."],
 
91
        ]
 
92
 
 
93
    def postOptions(self):
 
94
        for arg in ('brokeruser', 'brokerpassword'):
 
95
            if not self[arg]:
 
96
                raise usage.UsageError("--%s must be specified." % arg)
 
97
        for int_arg in ('brokerport',):
 
98
            try:
 
99
                self[int_arg] = int(self[int_arg])
 
100
            except (TypeError, ValueError):
 
101
                raise usage.UsageError("--%s must be an integer." % int_arg)
 
102
        if not self["oops-reporter"] and self["oops-dir"]:
 
103
            raise usage.UsageError(
 
104
                "A reporter must be supplied to identify reports "
 
105
                "from this service from other OOPS reports.")
 
106
 
 
107
class ProvisioningServiceMaker(object):
 
108
    """Create a service for the Twisted plugin."""
 
109
 
 
110
    implements(IServiceMaker, IPlugin)
 
111
 
 
112
    def __init__(self, name, description):
 
113
        self.tapname = name
 
114
        self.description = description
 
115
 
 
116
    def makeService(self, options):
 
117
        """Construct a service."""
 
118
        # Required to hide the command line options that include a password.
 
119
        # There is a small window where it can be seen though, between
 
120
        # invocation and when this code runs.
 
121
        setproctitle.setproctitle("maas provisioning service")
 
122
 
 
123
        logfile = getRotatableLogFileObserver(options["logfile"])
 
124
        setUpOOPSHandler(options, logfile)
 
125
 
 
126
        broker_port = options["brokerport"]
 
127
        broker_host = options["brokerhost"]
 
128
        broker_user = options["brokeruser"]
 
129
        broker_password = options["brokerpassword"]
 
130
        broker_vhost = options["brokervhost"]
 
131
 
 
132
        # TODO: define callbacks for Rabbit connectivity.
 
133
        # e.g. construct a manager object.
 
134
        client_factory = AMQFactory(
 
135
            broker_user, broker_password, broker_vhost,
 
136
            CONNECTED_CALLBACK, DISCONNECTED_CALLBACK,
 
137
            lambda (connector, reason): log.err(reason, "Connection failed"))
 
138
 
 
139
        # TODO: Create services here, e.g.
 
140
        # service1 = thing
 
141
        # service2 = thing2
 
142
        # services = MultiService()
 
143
        # services.addService(service1)
 
144
        # services.addService(service2)
 
145
        # return services
 
146
 
 
147
        client_service = TCPClient(broker_host, broker_port, client_factory)
 
148
        services = MultiService()
 
149
        services.addService(client_service)
 
150
        return services