6
4
from logging import (getLevelName, getLogger,
7
FileHandler, StreamHandler, Formatter, info)
5
FileHandler, StreamHandler, Formatter)
9
7
from optparse import OptionParser
10
8
from ConfigParser import ConfigParser, NoSectionError
12
import dbus.glib # Side-effects rule!
14
from twisted.application.service import Application, Service
15
from twisted.application.app import startApplication
17
10
from landscape import VERSION
18
11
from landscape.lib.persist import Persist
19
from landscape.lib.dbus_util import get_bus
20
from landscape.lib import bpickle_dbus
21
from landscape.log import rotate_logs
22
from landscape.reactor import TwistedReactor
24
13
from landscape.upgraders import UPGRADE_MANAGERS
234
220
@return: An L{OptionParser} preset with options that all
235
221
landscape-related programs accept. These include
236
222
- C{config} (C{None})
239
224
parser = OptionParser(version=VERSION)
240
225
parser.add_option("-c", "--config", metavar="FILE",
241
226
help="Use config from this file (any command line "
242
227
"options override settings from the file) "
243
228
"(default: '/etc/landscape/client.conf').")
244
parser.add_option("--bus", default="system",
245
help="Which DBUS bus to use. One of 'session' "
246
"or 'system' (default: 'system').")
249
231
def get_config_filename(self):
308
286
parser.add_option("--log-level", default="info",
309
287
help="One of debug, info, warning, error or "
311
parser.add_option("--ignore-sigint", action="store_true", default=False,
312
help="Ignore interrupt signals.")
313
parser.add_option("--ignore-sigusr1", action="store_true", default=False,
314
help="Ignore SIGUSR1 signal to rotate logs.")
289
parser.add_option("--ignore-sigint", action="store_true",
290
default=False, help="Ignore interrupt signals.")
291
parser.add_option("--ignore-sigusr1", action="store_true",
292
default=False, help="Ignore SIGUSR1 signal to "
298
def sockets_path(self):
299
"""Return the path to the directory where Unix sockets are created."""
300
return os.path.join(self.data_path, "sockets")
319
303
def get_versioned_persist(service):
320
304
"""Get a L{Persist} database with upgrade rules applied.
330
314
upgrade_manager.initialize(persist)
331
315
persist.save(service.persist_filename)
335
class LandscapeService(Service, object):
336
"""Utility superclass for defining Landscape services.
338
This sets up the reactor, bpickle/dbus integration, a Persist object, and
339
connects to the bus when started.
341
@ivar reactor: a L{TwistedReactor} object.
342
@cvar service_name: The lower-case name of the service. This is used to
343
generate the bpickle filename.
345
reactor_factory = TwistedReactor
346
persist_filename = None
348
def __init__(self, config):
350
bpickle_dbus.install()
351
self.reactor = self.reactor_factory()
352
if self.persist_filename:
353
self.persist = get_versioned_persist(self)
354
if not (self.config is not None and self.config.ignore_sigusr1):
355
signal.signal(signal.SIGUSR1, lambda signal, frame: rotate_logs())
357
def startService(self):
358
"""Extend L{twisted.application.service.IService.startService}.
360
Create a a new DBus connection (normally using a C{SystemBus}) and
361
save it in the public L{self.bus} instance variable.
363
Service.startService(self)
364
self.bus = get_bus(self.config.bus)
365
info("%s started on '%s' bus with config %s" % (
366
self.service_name.capitalize(), self.config.bus,
367
self.config.get_config_filename()))
369
def stopService(self):
370
Service.stopService(self)
371
info("%s stopped on '%s' bus with config %s" % (
372
self.service_name.capitalize(), self.config.bus,
373
self.config.get_config_filename()))
376
def assert_unowned_bus_name(bus, bus_name):
377
dbus_object = bus.get_object("org.freedesktop.DBus",
378
"/org/freedesktop/DBus")
379
if dbus_object.NameHasOwner(bus_name,
380
dbus_interface="org.freedesktop.DBus"):
381
sys.exit("error: DBus name %s is owned. "
382
"Is the process already running?" % bus_name)
386
"broker": "landscape",
387
"monitor": "landscape",
391
def run_landscape_service(configuration_class, service_class, args, bus_name):
392
"""Run a Landscape service.
394
The function will instantiate the given L{LandscapeService} subclass
395
and attach the resulting service object to a Twisted C{Application}.
397
After that it will start the Twisted L{Application} and call the
398
L{TwistedReactor.run} method of the L{LandscapeService}'s reactor.
400
@param configuration_class: The service-specific subclass of L{Configuration} used
401
to parse C{args} and build the C{service_class} object.
402
@param service_class: The L{LandscapeService} subclass to create and start.
403
@param args: Command line arguments.
404
@param bus_name: A bus name used to verify if the service is already
407
from landscape.reactor import install
410
# Let's consider adding this:
411
# from twisted.python.log import startLoggingWithObserver, PythonLoggingObserver
412
# startLoggingWithObserver(PythonLoggingObserver().emit, setStdout=False)
414
configuration = configuration_class()
415
configuration.load(args)
417
if configuration.bus == "system":
418
required_user = _required_users[service_class.service_name]
419
if required_user != pwd.getpwuid(os.getuid())[0]:
421
"When using the system bus, landscape-%s must be run as %s."
422
% (service_class.service_name, required_user))
424
init_logging(configuration, service_class.service_name)
426
assert_unowned_bus_name(get_bus(configuration.bus), bus_name)
428
application = Application("landscape-%s" % (service_class.service_name,))
429
service = service_class(configuration)
430
service.setServiceParent(application)
432
startApplication(application, False)
434
if configuration.ignore_sigint:
435
signal.signal(signal.SIGINT, signal.SIG_IGN)
437
service.reactor.run()