1
# Copyright 2012 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Twisted Application Plugin code for the MaaS provisioning server"""
6
from __future__ import (
16
from oops_datedir_repo import DateDirRepo
17
from oops_twisted import (
18
Config as oops_config,
22
from twisted.application.internet import TCPClient
23
from twisted.application.service import (
27
from twisted.internet import reactor
28
from twisted.plugin import IPlugin
29
from twisted.python import (
33
from twisted.python.logfile import LogFile
34
from twisted.python.log import (
38
from zope.interface import implements
40
from amqpclient import AMQFactory
46
def getRotatableLogFileObserver(filename):
47
"""Setup a L{LogFile} for the given application."""
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)
56
return FileLogObserver(logfile)
59
def setUpOOPSHandler(options, logfile):
60
"""Add OOPS handling based on the passed command line options."""
61
config = oops_config()
63
# Add the oops publisher that writes files in the configured place
64
# if the command line option was set.
66
if options["oops-dir"]:
67
repo = DateDirRepo(options["oops-dir"])
68
config.publishers.append(
69
defer_publisher(oops.publish_new_only(repo.publish)))
71
if options["oops-reporter"]:
72
config.template['reporter'] = options["oops-reporter"]
74
observer = OOPSObserver(config, logfile.emit)
75
addObserver(observer.emit)
79
class Options(usage.Options):
80
"""Command line options for the provisioning server."""
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."],
93
def postOptions(self):
94
for arg in ('brokeruser', 'brokerpassword'):
96
raise usage.UsageError("--%s must be specified." % arg)
97
for int_arg in ('brokerport',):
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.")
107
class ProvisioningServiceMaker(object):
108
"""Create a service for the Twisted plugin."""
110
implements(IServiceMaker, IPlugin)
112
def __init__(self, name, description):
114
self.description = description
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")
123
logfile = getRotatableLogFileObserver(options["logfile"])
124
setUpOOPSHandler(options, logfile)
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"]
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"))
139
# TODO: Create services here, e.g.
142
# services = MultiService()
143
# services.addService(service1)
144
# services.addService(service2)
147
client_service = TCPClient(broker_host, broker_port, client_factory)
148
services = MultiService()
149
services.addService(client_service)