Merge lp:~zyga/checkbox/plainbox-dbus into lp:checkbox

Proposed by Zygmunt Krynicki
Status: Work in progress
Proposed branch: lp:~zyga/checkbox/plainbox-dbus
Merge into: lp:checkbox
Diff against target: 1176 lines (+903/-20)
11 files modified
checkbox-ng/checkbox_ng/test_main.py (+5/-4)
plainbox/plainbox/impl/box.py (+20/-2)
plainbox/plainbox/impl/checkbox.py (+22/-4)
plainbox/plainbox/impl/commands/service.py (+136/-0)
plainbox/plainbox/impl/dbus/__init__.py (+30/-0)
plainbox/plainbox/impl/dbus/service.py (+351/-0)
plainbox/plainbox/impl/logging.py (+47/-2)
plainbox/plainbox/impl/plugins.py (+1/-1)
plainbox/plainbox/impl/provider.py (+13/-0)
plainbox/plainbox/impl/service.py (+269/-0)
plainbox/plainbox/impl/test_box.py (+9/-7)
To merge this branch: bzr merge lp:~zyga/checkbox/plainbox-dbus
Reviewer Review Type Date Requested Status
Checkbox Developers Pending
Review via email: mp+169865@code.launchpad.net

Description of the change

Work in progress on the DBus service for PlainBox

To post a comment you must log in.
lp:~zyga/checkbox/plainbox-dbus updated
2197. By Zygmunt Krynicki

plainbox:logging: honour debug_console when trace_list is specified

Previously adjust_logging(debug_console=True) would be used only when
level=DEBUG was passed as well. This makes it impossible to use it with
selective logging possible with trace_list. This patch corrects this
issue.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2198. By Zygmunt Krynicki

plainbox:logging: adjust formatting of console_info

Signed-off-by: Zygmunt Krynicki <email address hidden>

2199. By Zygmunt Krynicki

plainbox:box: add -C --debug-console argument

This argument switches on the handler that displays DEBUG messages on
the console. It can be useful when logging selectively.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2200. By Zygmunt Krynicki

plainbox:provider: add IProvider1.name and DummyProvider.name

Signed-off-by: Zygmunt Krynicki <email address hidden>

2201. By Zygmunt Krynicki

plainbox:checkbox: fix get_builtin_whitelists()

This method incorrectly would try to load whitelists from the jobs
directory after properly enumerating them in the whitelists directory.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2202. By Zygmunt Krynicki

plainbox:checkbox: add a name property to CheckBox

This property is required by the IProvider1 interface. While CheckBox
does not yet implement it it's good to add it now.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2203. By Zygmunt Krynicki

plainbox:checkbox: add a name property to WhiteList

This property is inferred from the name of the file a whitelist was
loaded from. It is useful in DBus service where each whitelist can
be exported as a distinct object on the bus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2204. By Zygmunt Krynicki

plainbox:checkbox: fix PEP8 issue

Signed-off-by: Zygmunt Krynicki <email address hidden>

2205. By Zygmunt Krynicki

plainbox:dbus: add DBus utility module

This module holds code that aids in implementation of standard DBus
interfaces such as Properties or ObjectManager. It mirrors the
dbus.service package and providers an enhanced Object class and the
dbus-specific property decorator.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2206. By Zygmunt Krynicki

plainbox:dbus: add support for property introspection

This branch as a bit of other code but it's mostly designed to be squashed
anyway so it should not matter much.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2207. By Zygmunt Krynicki

plainbox:dbus: add some extra imports from vanilla dbus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2208. By Zygmunt Krynicki

plainbox:service: add DBus service for PlainBox

This service is in initial stages of development. It has a version, can
expose provider objects, each of which has a name, and not much else.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2209. By Zygmunt Krynicki

plainbox:service: export JobDefinition and WhiteList with many properties

Work in progress on exporting many properties of both jobs and whitelists.
Jobs are now exported by name, not by checksum, but this is not imperfect
and probably will revert back later on. Whitelists have just the name
property (exporting actual patterns is probably not going to be needed
as we shall later see when session code gets written). The idea is
to use both whitelists and jobs as data sources to drive a bit of the GUI
and as identifiers (particular job, particular whitelist) when interacting
with Session methods (select those jobs, select that whitelist).

Signed-off-by: Zygmunt Krynicki <email address hidden>

2210. By Zygmunt Krynicki

plainbox:commands:service: add 'service' command to spawn DBus service

The service command can be used to start a PlainBox service on the
session bus. It is currently intended as a development helper for GUI
developers.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2211. By Zygmunt Krynicki

plainbox:commands:service: export checkbox object on the bus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2212. By Zygmunt Krynicki

plainbox:commands:service: claim bus name after exporting all objects

Claiming bus name equals to saying 'this thing is ready to use' so let's
claim our well-known-name after adding all objects to the bus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2213. By Zygmunt Krynicki

plainbox:commands:service: export a dummy job definition if requested

Signed-off-by: Zygmunt Krynicki <email address hidden>

2214. By Zygmunt Krynicki

plainbox:box: enable 'service' command

Signed-off-by: Zygmunt Krynicki <email address hidden>

2215. By Zygmunt Krynicki

plainbox:box: ignore KeyboardInterrupt around main()

This is orthogonal to the existing support for handling KeyboardInterrupt
while a command is being executed. Instead the main focus here is to
allow user to interrupt some of the startup code without causing
a backtrace to be printed.

Signed-off-by: Zygmunt Krynicki <email address hidden>

Revision history for this message
Daniel Manrique (roadmr) wrote :

Looks like use of variant properties isn't too widespread. I wasn't able to find any examples in the default Ubuntu services (there are *so* many though, that I may be missing it). This was using d-feet, so using a GUI to click around is somewhat cumbersome.

Anyway, the point of this comment is as an introduction to this bug:

https://bugs.launchpad.net/d-feet/+bug/1192296

To be fair, the bug is in d-feet itself, as the underlying infrastructure seems to handle variants just fine, and the d-feet patch is trivial. Just a reminder for people trying to debug this and running into trouble when looking at a job's "origin" property.

Other than that, this looks fine, the only thing I want to comment on is the jobs, the way we expose them looks fine in principle since there's access to methods related to each job (whereas, if we presented them as an array or dictionary it would be just the data with no methods and no 2-way interactivity), but the amount of jobs is huge which looks a bit overwhelming.

I wouldn't reverse a sound technical decision for aesthetic concerns though, so I don't think there's anything to fix here.

Revision history for this message
Zygmunt Krynicki (zyga) wrote :

I'm not sure we'll keep the current design. It remains to be seen.
18 cze 2013 22:36, "Daniel Manrique" <email address hidden>
napisał(a):

> Looks like use of variant properties isn't too widespread. I wasn't able
> to find any examples in the default Ubuntu services (there are *so* many
> though, that I may be missing it). This was using d-feet, so using a GUI to
> click around is somewhat cumbersome.
>
> Anyway, the point of this comment is as an introduction to this bug:
>
> https://bugs.launchpad.net/d-feet/+bug/1192296
>
> To be fair, the bug is in d-feet itself, as the underlying infrastructure
> seems to handle variants just fine, and the d-feet patch is trivial. Just a
> reminder for people trying to debug this and running into trouble when
> looking at a job's "origin" property.
>
> Other than that, this looks fine, the only thing I want to comment on is
> the jobs, the way we expose them looks fine in principle since there's
> access to methods related to each job (whereas, if we presented them as an
> array or dictionary it would be just the data with no methods and no 2-way
> interactivity), but the amount of jobs is huge which looks a bit
> overwhelming.
>
> I wouldn't reverse a sound technical decision for aesthetic concerns
> though, so I don't think there's anything to fix here.
>
> --
> https://code.launchpad.net/~zkrynicki/checkbox/plainbox-dbus/+merge/169865
> You are the owner of lp:~zkrynicki/checkbox/plainbox-dbus.
>

Unmerged revisions

2215. By Zygmunt Krynicki

plainbox:box: ignore KeyboardInterrupt around main()

This is orthogonal to the existing support for handling KeyboardInterrupt
while a command is being executed. Instead the main focus here is to
allow user to interrupt some of the startup code without causing
a backtrace to be printed.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2214. By Zygmunt Krynicki

plainbox:box: enable 'service' command

Signed-off-by: Zygmunt Krynicki <email address hidden>

2213. By Zygmunt Krynicki

plainbox:commands:service: export a dummy job definition if requested

Signed-off-by: Zygmunt Krynicki <email address hidden>

2212. By Zygmunt Krynicki

plainbox:commands:service: claim bus name after exporting all objects

Claiming bus name equals to saying 'this thing is ready to use' so let's
claim our well-known-name after adding all objects to the bus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2211. By Zygmunt Krynicki

plainbox:commands:service: export checkbox object on the bus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2210. By Zygmunt Krynicki

plainbox:commands:service: add 'service' command to spawn DBus service

The service command can be used to start a PlainBox service on the
session bus. It is currently intended as a development helper for GUI
developers.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2209. By Zygmunt Krynicki

plainbox:service: export JobDefinition and WhiteList with many properties

Work in progress on exporting many properties of both jobs and whitelists.
Jobs are now exported by name, not by checksum, but this is not imperfect
and probably will revert back later on. Whitelists have just the name
property (exporting actual patterns is probably not going to be needed
as we shall later see when session code gets written). The idea is
to use both whitelists and jobs as data sources to drive a bit of the GUI
and as identifiers (particular job, particular whitelist) when interacting
with Session methods (select those jobs, select that whitelist).

Signed-off-by: Zygmunt Krynicki <email address hidden>

2208. By Zygmunt Krynicki

plainbox:service: add DBus service for PlainBox

This service is in initial stages of development. It has a version, can
expose provider objects, each of which has a name, and not much else.

Signed-off-by: Zygmunt Krynicki <email address hidden>

2207. By Zygmunt Krynicki

plainbox:dbus: add some extra imports from vanilla dbus

Signed-off-by: Zygmunt Krynicki <email address hidden>

2206. By Zygmunt Krynicki

plainbox:dbus: add support for property introspection

This branch as a bit of other code but it's mostly designed to be squashed
anyway so it should not matter much.

Signed-off-by: Zygmunt Krynicki <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'checkbox-ng/checkbox_ng/test_main.py'
2--- checkbox-ng/checkbox_ng/test_main.py 2013-06-14 16:32:07 +0000
3+++ checkbox-ng/checkbox_ng/test_main.py 2013-06-18 18:07:29 +0000
4@@ -49,8 +49,8 @@
5 self.assertEqual(call.exception.args, (0,))
6 self.maxDiff = None
7 expected = """
8- usage: checkbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-T LOGGER]
9- [-P] [-I]
10+ usage: checkbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-C]
11+ [-T LOGGER] [-P] [-I]
12 {sru,check-config,script,dev} ...
13
14 positional arguments:
15@@ -69,6 +69,7 @@
16 logging and debugging:
17 -v, --verbose be more verbose (same as --log-level=INFO)
18 -D, --debug enable DEBUG messages on the root logger
19+ -C, --debug-console display DEBUG messages in the console
20 -T LOGGER, --trace LOGGER
21 enable DEBUG messages on the specified logger (can be
22 used multiple times)
23@@ -84,8 +85,8 @@
24 main([])
25 self.assertEqual(call.exception.args, (2,))
26 expected = """
27- usage: checkbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-T LOGGER]
28- [-P] [-I]
29+ usage: checkbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-C]
30+ [-T LOGGER] [-P] [-I]
31 {sru,check-config,script,dev} ...
32 checkbox: error: too few arguments
33 """
34
35=== modified file 'plainbox/plainbox/impl/box.py'
36--- plainbox/plainbox/impl/box.py 2013-06-14 16:32:07 +0000
37+++ plainbox/plainbox/impl/box.py 2013-06-18 18:07:29 +0000
38@@ -40,6 +40,7 @@
39 from plainbox.impl.commands.run import RunCommand
40 from plainbox.impl.commands.selftest import SelfTestCommand
41 from plainbox.impl.commands.sru import SRUCommand
42+from plainbox.impl.commands.service import ServiceCommand
43 from plainbox.impl.logging import setup_logging, adjust_logging
44
45
46@@ -112,6 +113,8 @@
47 SRUCommand(self._checkbox, self._config).register_parser(subparsers)
48 CheckConfigCommand(self._config).register_parser(subparsers)
49 DevCommand(self._checkbox, self._config).register_parser(subparsers)
50+ ServiceCommand(self._checkbox, self._config).register_parser(
51+ subparsers)
52
53 def early_init(self):
54 """
55@@ -124,7 +127,9 @@
56 """
57 Initialize with early command line arguments being already parsed
58 """
59- adjust_logging(level=early_ns.log_level, trace_list=early_ns.trace)
60+ adjust_logging(
61+ level=early_ns.log_level, trace_list=early_ns.trace,
62+ debug_console=early_ns.debug_console)
63 # Load plainbox configuration
64 self._config = self.get_config_cls().get()
65 # Load and initialize checkbox provider
66@@ -207,6 +212,11 @@
67 action="store_const",
68 const="DEBUG",
69 help="enable DEBUG messages on the root logger")
70+ # Add the --debug flag
71+ group.add_argument(
72+ "-C", "--debug-console",
73+ action="store_true",
74+ help="display DEBUG messages in the console")
75 # Add the --trace flag
76 group.add_argument(
77 "-T", "--trace",
78@@ -294,7 +304,15 @@
79
80
81 def main(argv=None):
82- raise SystemExit(PlainBox().main(argv))
83+ # Another try/catch block for catching KeyboardInterrupt
84+ # This one is really only meant for the early init abort
85+ # (when someone runs main but bails out before we really
86+ # get to the point when we do something useful and setup
87+ # all the exception handlers).
88+ try:
89+ raise SystemExit(PlainBox().main(argv))
90+ except KeyboardInterrupt:
91+ pass
92
93
94 def get_builtin_jobs():
95
96=== modified file 'plainbox/plainbox/impl/checkbox.py'
97--- plainbox/plainbox/impl/checkbox.py 2013-06-06 11:23:51 +0000
98+++ plainbox/plainbox/impl/checkbox.py 2013-06-18 18:07:29 +0000
99@@ -58,7 +58,7 @@
100 and appended (respectively) to the actual pattern specified in the file.
101 """
102
103- def __init__(self, pattern_list):
104+ def __init__(self, pattern_list, name=None):
105 """
106 Initialize a whitelist object with the specified list of patterns.
107
108@@ -67,6 +67,14 @@
109 inclusive = [RegExpJobQualifier(pattern) for pattern in pattern_list]
110 exclusive = ()
111 super(WhiteList, self).__init__(inclusive, exclusive)
112+ self._name = name
113+
114+ @property
115+ def name(self):
116+ """
117+ name of this WhiteList (might be None)
118+ """
119+ return self._name
120
121 @classmethod
122 def from_file(cls, pathname):
123@@ -77,7 +85,8 @@
124 :returns: a fresh WhiteList object
125 """
126 pattern_list = cls._load_patterns(pathname)
127- return cls(pattern_list)
128+ name = os.path.splitext(os.path.basename(pathname))[0]
129+ return cls(pattern_list, name=name)
130
131 @classmethod
132 def _load_patterns(self, pathname):
133@@ -126,7 +135,8 @@
134
135 Historically plainbox used a git submodule with checkbox tree (converted to
136 git). This ended with the merge of plainbox into the checkbox tree.
137-
138+
139+
140 Now it's the other way around and the checkbox tree can be located two
141 directories "up" from the plainbox module, in a checkbox-old directory.
142 """
143@@ -281,7 +291,8 @@
144 for name in os.listdir(self.whitelists_dir):
145 if name.endswith(".whitelist"):
146 whitelist_list.append(
147- WhiteList.from_file(os.path.join(self.jobs_dir, name)))
148+ WhiteList.from_file(os.path.join(
149+ self.whitelists_dir, name)))
150 return whitelist_list
151
152 def get_builtin_jobs(self):
153@@ -318,3 +329,10 @@
154 raise TypeError(
155 "Unsupported type of 'somewhere': {!r}".format(
156 type(somewhere)))
157+
158+ @property
159+ def name(self):
160+ """
161+ name of this provider (always checkbox)
162+ """
163+ return "checkbox"
164
165=== added file 'plainbox/plainbox/impl/commands/service.py'
166--- plainbox/plainbox/impl/commands/service.py 1970-01-01 00:00:00 +0000
167+++ plainbox/plainbox/impl/commands/service.py 2013-06-18 18:07:29 +0000
168@@ -0,0 +1,136 @@
169+# This file is part of Checkbox.
170+#
171+# Copyright 2013 Canonical Ltd.
172+# Written by:
173+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
174+#
175+# Checkbox is free software: you can redistribute it and/or modify
176+# it under the terms of the GNU General Public License as published by
177+# the Free Software Foundation, either version 3 of the License, or
178+# (at your option) any later version.
179+#
180+# Checkbox is distributed in the hope that it will be useful,
181+# but WITHOUT ANY WARRANTY; without even the implied warranty of
182+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
183+# GNU General Public License for more details.
184+#
185+# You should have received a copy of the GNU General Public License
186+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
187+
188+"""
189+:mod:`plainbox.impl.commands.dev` -- service sub-command
190+========================================================
191+
192+"""
193+
194+import logging
195+
196+from dbus import SessionBus
197+from dbus.mainloop.glib import DBusGMainLoop
198+from dbus.service import BusName
199+from gi.repository import GObject
200+
201+from plainbox.impl.commands import PlainBoxCommand
202+from plainbox.impl.job import JobDefinition
203+from plainbox.impl.provider import DummyProvider1
204+from plainbox.impl.service import Service
205+
206+
207+logger = logging.getLogger("plainbox.commands.service")
208+
209+
210+def connect_to_session_bus():
211+ """
212+ Connect to the session bus properly.
213+
214+ Returns a tuple (session_bus, loop) where loop is a GObject.MainLoop
215+ instance. The loop is there so that you can listen to signals.
216+ """
217+ # We'll need an event loop to observe signals. We will need the instance
218+ # later below so let's keep it. Note that we're not passing it directly
219+ # below as DBus needs specific API. The DBusGMainLoop class that we
220+ # instantiate and pass is going to work with this instance transparently.
221+ #
222+ # NOTE: DBus tutorial suggests that we should create the loop _before_
223+ # connecting to the bus.
224+ logger.debug("Setting up glib-based event loop")
225+ loop = GObject.MainLoop()
226+ # Let's get the system bus object. We need that to access UDisks2 object
227+ logger.debug("Connecting to DBus session bus")
228+ session_bus = SessionBus(mainloop=DBusGMainLoop())
229+ return session_bus, loop
230+
231+
232+class ServiceInvocation:
233+
234+ def __init__(self, checkbox, config, ns):
235+ self.checkbox = checkbox
236+ self.config = config
237+ self.ns = ns
238+
239+ def run(self):
240+ bus, loop = connect_to_session_bus()
241+ logger.info("Setting up DBus objects...")
242+ service_obj = Service(on_exit=lambda: loop.quit(), conn=bus)
243+ if self.ns.dummy:
244+ logger.debug("Adding a DummyProvider")
245+ job_list = [
246+ JobDefinition({
247+ "name": "example-job",
248+ "description": "This is an example dummy job",
249+ "plugin": "manual",
250+ }),
251+ ]
252+ provider = DummyProvider1(job_list, name="dummy")
253+ service_obj.add_provider(provider)
254+ service_obj.add_provider(self.checkbox)
255+ logger.debug("Attempting to claim bus name: %s", self.ns.bus_name)
256+ bus_name = BusName(self.ns.bus_name, bus)
257+ logger.info(
258+ "PlainBox DBus service ready, claimed name: %s",
259+ bus_name.get_name())
260+ try:
261+ loop.run()
262+ except KeyboardInterrupt:
263+ logger.warning((
264+ "Main loop interrupted!"
265+ " It is recommended to call the Exit() method on the"
266+ " exported service object instead"))
267+ finally:
268+ logger.debug("Main loop terminated, exiting...")
269+ # TODO: move cleanup to service, maybe?
270+ for provider_obj in service_obj._provider_list:
271+ logger.debug("Removing %s", provider_obj)
272+ provider_obj.remove_from_connection()
273+ logger.debug("Removing %s", service_obj)
274+ service_obj.remove_from_connection()
275+ logger.debug("Releasing %s", bus_name)
276+ # XXX: ugly but that's how one can reliably release a bus name
277+ del bus_name
278+ logger.debug("Closing %s", bus)
279+ bus.close()
280+
281+
282+class ServiceCommand(PlainBoxCommand):
283+ """
284+ DBus service for PlainBox
285+ """
286+
287+ # XXX: Maybe drop checkbox / config and handle them differently
288+ def __init__(self, checkbox, config):
289+ self.checkbox = checkbox
290+ self.config = config
291+
292+ def invoked(self, ns):
293+ return ServiceInvocation(self.checkbox, self.config, ns).run()
294+
295+ def register_parser(self, subparsers):
296+ parser = subparsers.add_parser("service", help="spawn dbus service")
297+ parser.add_argument(
298+ '--bus-name', action="store",
299+ default="com.canonical.certification.PlainBox",
300+ help="Use the specified DBus bus name")
301+ parser.add_argument(
302+ '--dummy', action="store_true", default=False,
303+ help="Add a dummy job provider")
304+ parser.set_defaults(command=self)
305
306=== added directory 'plainbox/plainbox/impl/dbus'
307=== added file 'plainbox/plainbox/impl/dbus/__init__.py'
308--- plainbox/plainbox/impl/dbus/__init__.py 1970-01-01 00:00:00 +0000
309+++ plainbox/plainbox/impl/dbus/__init__.py 2013-06-18 18:07:29 +0000
310@@ -0,0 +1,30 @@
311+# This file is part of Checkbox.
312+#
313+# Copyright 2013 Canonical Ltd.
314+# Written by:
315+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
316+#
317+# Checkbox is free software: you can redistribute it and/or modify
318+# it under the terms of the GNU General Public License as published by
319+# the Free Software Foundation, either version 3 of the License, or
320+# (at your option) any later version.
321+#
322+# Checkbox is distributed in the hope that it will be useful,
323+# but WITHOUT ANY WARRANTY; without even the implied warranty of
324+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
325+# GNU General Public License for more details.
326+#
327+# You should have received a copy of the GNU General Public License
328+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
329+
330+"""
331+:mod:`plainbox.impl.dbus` -- DBus support code for PlainBox
332+===========================================================
333+"""
334+
335+__all__ = ['service', 'Signature', 'Struct']
336+
337+from dbus import Struct
338+from dbus import Signature
339+
340+from plainbox.impl.dbus import service
341
342=== added file 'plainbox/plainbox/impl/dbus/service.py'
343--- plainbox/plainbox/impl/dbus/service.py 1970-01-01 00:00:00 +0000
344+++ plainbox/plainbox/impl/dbus/service.py 2013-06-18 18:07:29 +0000
345@@ -0,0 +1,351 @@
346+# This file is part of Checkbox.
347+#
348+# Copyright 2013 Canonical Ltd.
349+# Written by:
350+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
351+#
352+# Checkbox is free software: you can redistribute it and/or modify
353+# it under the terms of the GNU General Public License as published by
354+# the Free Software Foundation, either version 3 of the License, or
355+# (at your option) any later version.
356+#
357+# Checkbox is distributed in the hope that it will be useful,
358+# but WITHOUT ANY WARRANTY; without even the implied warranty of
359+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
360+# GNU General Public License for more details.
361+#
362+# You should have received a copy of the GNU General Public License
363+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
364+
365+"""
366+:mod:`plainbox.impl.dbus.service` -- DBus Service support code for PlainBox
367+===========================================================================
368+"""
369+
370+import logging
371+
372+import _dbus_bindings
373+import dbus
374+import dbus.service
375+import dbus.exceptions
376+
377+from dbus.service import method, signal
378+
379+__all__ = ['signal', 'method', 'property', 'Object', 'Interface']
380+
381+logger = logging.getLogger("plainbox.dbus")
382+
383+
384+class InterfaceType(dbus.service.InterfaceType):
385+ """
386+ Subclass of :class:`dbus.service.InterfaceType` that also handles
387+ properties.
388+ """
389+
390+ def _reflect_on_property(cls, func):
391+ reflection_data = (
392+ ' <property name="{}" type="{}" access="{}"/>\n').format(
393+ func._dbus_property, func._dbus_signature,
394+ func.dbus_access_flag)
395+ return reflection_data
396+
397+
398+#Subclass of :class:`dbus.service.Interface` that also handles properties
399+Interface = InterfaceType('Interface', (dbus.service.Interface,), {})
400+
401+
402+class property:
403+ """
404+ property that handles DBus stuff
405+ """
406+
407+ def __init__(self, dbus_signature, dbus_interface, dbus_property=None,
408+ setter=False):
409+ """
410+ Initialize new dbus_property with the given interface name.
411+
412+ If dbus_property is not specified it is set to the name of the
413+ decorated method. In special circumstances you may wish to specify
414+ alternate dbus property name explicitly.
415+
416+ If setter is set to True then the implicit decorated function is a
417+ setter, not the default getter. This allows to define write-only
418+ properties.
419+ """
420+ self.__name__ = None
421+ self.__doc__ = None
422+ self._dbus_signature = dbus_signature
423+ self._dbus_interface = dbus_interface
424+ self._dbus_property = dbus_property
425+ self._getf = None
426+ self._setf = None
427+ self._implicit_setter = setter
428+
429+ @property
430+ def dbus_access_flag(self):
431+ """
432+ access flag of this DBus property
433+
434+ :returns: either "readwrite", "read" or "write"
435+ :raises TypeError: if the property is ill-defined
436+ """
437+ if self._getf and self._setf:
438+ return "readwrite"
439+ elif self._getf:
440+ return "read"
441+ elif self._setf:
442+ return "write"
443+ else:
444+ raise TypeError(
445+ "property provides neither readable nor writable")
446+
447+ @property
448+ def dbus_interface(self):
449+ """
450+ name of the DBus interface of this DBus property
451+ """
452+ return self._dbus_interface
453+
454+ @property
455+ def dbus_property(self):
456+ """
457+ name of this DBus property
458+ """
459+ return self._dbus_property
460+
461+ @property
462+ def dbus_signature(self):
463+ """
464+ signature of this DBus property
465+ """
466+ return self._dbus_signature
467+
468+ @property
469+ def setter(self):
470+ """
471+ decorator for setter functions
472+
473+ This property can be used to decorate additional method that
474+ will be used as a property setter. Otherwise properties cannot
475+ be assigned.
476+ """
477+ def decorator(func):
478+ self._setf = func
479+ return self
480+ return decorator
481+
482+ @property
483+ def getter(self):
484+ """
485+ decorator for getter functions
486+
487+ This property can be used to decorate additional method that
488+ will be used as a property getter. It is only provider for parity
489+ as by default, the @dbus.service.property() decorator designates
490+ a getter function. This behavior can be controlled by passing
491+ setter=True to the property initializer.
492+ """
493+ def decorator(func):
494+ self._getf = func
495+ return self
496+ return decorator
497+
498+ def __call__(self, func):
499+ """
500+ Decorate a getter function and return the property object
501+
502+ This method sets __name__, __doc__ and _dbus_property.
503+ """
504+ self.__name__ = func.__name__
505+ if self.__doc__ is None:
506+ self.__doc__ = func.__doc__
507+ if self._dbus_property is None:
508+ self._dbus_property = func.__name__
509+ if self._implicit_setter:
510+ return self.setter(func)
511+ else:
512+ return self.getter(func)
513+
514+ def __get__(self, instance, owner):
515+ if instance is None:
516+ return self
517+ else:
518+ if self._getf is None:
519+ raise dbus.exceptions.DBusException(
520+ "property is not readable")
521+ return self._getf(instance)
522+
523+ def __set__(self, instance, value):
524+ if self._setf is None:
525+ raise dbus.exceptions.DBusException(
526+ "property is not writable")
527+ self._setf(instance, value)
528+
529+ # This little helper is here is to help :meth:`Object.Introspect()`
530+ # figure out how to handle properties.
531+ _dbus_is_property = True
532+
533+
534+class Object(Interface, dbus.service.Object):
535+ """
536+ dbus.service.Object subclass that provides DBus properties interface.
537+
538+ Implements Get(), Set() and GetAll(). Every actual property needs to
539+ be implemented with the @dbus_property decorator.
540+
541+ This class also overrides Introspect() from the
542+ org.freedesktop.DBus.Introspectable interface so that all of the
543+ properties are properly accounted for.
544+ """
545+
546+ @method(
547+ dbus_interface=dbus.INTROSPECTABLE_IFACE,
548+ in_signature='', out_signature='s',
549+ path_keyword='object_path', connection_keyword='connection')
550+ def Introspect(self, object_path, connection):
551+ """
552+ Return a string of XML encoding this object's supported interfaces,
553+ methods and signals.
554+ """
555+ logger.debug("Introspect(object_path=%r)", object_path)
556+ reflection_data = (
557+ _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE)
558+ reflection_data += '<node name="%s">\n' % object_path
559+ interfaces = self._dbus_class_table[
560+ self.__class__.__module__ + '.' + self.__class__.__name__]
561+ for (name, funcs) in interfaces.items():
562+ reflection_data += ' <interface name="%s">\n' % (name)
563+ for func in funcs.values():
564+ if getattr(func, '_dbus_is_method', False):
565+ reflection_data += self.__class__._reflect_on_method(func)
566+ elif getattr(func, '_dbus_is_signal', False):
567+ reflection_data += self.__class__._reflect_on_signal(func)
568+ elif getattr(func, '_dbus_is_property', False):
569+ reflection_data += (
570+ self.__class__._reflect_on_property(func))
571+ reflection_data += ' </interface>\n'
572+ for name in connection.list_exported_child_objects(object_path):
573+ reflection_data += ' <node name="%s"/>\n' % name
574+ reflection_data += '</node>\n'
575+ logger.debug("Introspect() returns: %s", reflection_data)
576+ return reflection_data
577+
578+ @dbus.service.method(
579+ dbus_interface=dbus.PROPERTIES_IFACE,
580+ in_signature="ss", out_signature="v")
581+ def Get(self, interface_name, property_name):
582+ """
583+ Get the value of a property @property_name on interface
584+ @interface_name.
585+ """
586+ logger.debug("Get(%r, %r)", interface_name, property_name)
587+ property_iface_map = self._get_property_iface_map()
588+ if interface_name not in property_iface_map:
589+ raise dbus.exceptions.DBusException(
590+ dbus.PROPERTIES_IFACE,
591+ "No such interface {}".format(interface_name))
592+ if property_name not in property_iface_map[interface_name]:
593+ raise dbus.exceptions.DBusException(
594+ dbus.PROPERTIES_IFACE,
595+ "No such property {}:{}".format(
596+ interface_name, property_name))
597+ name = property_iface_map[interface_name][property_name]
598+ try:
599+ return getattr(self, name)
600+ except dbus.exceptions.DBusException:
601+ raise
602+ except Exception as exc:
603+ logger.exception(
604+ "runaway exception from Get(%r, %r)",
605+ interface_name, property_name)
606+ raise dbus.exceptions.DBusException(
607+ dbus.PROPERTIES_IFACE,
608+ "Unable to get property interface/property {}:{}: {!r}".format(
609+ interface_name, property_name, exc))
610+
611+ @dbus.service.method(
612+ dbus_interface=dbus.PROPERTIES_IFACE,
613+ in_signature="ssv", out_signature="")
614+ def Set(self, interface_name, property_name, value):
615+ logger.debug("Set(%r, %r, %r)", interface_name, property_name, value)
616+ property_iface_map = self._get_property_iface_map()
617+ if interface_name not in property_iface_map:
618+ raise dbus.exceptions.DBusException(
619+ dbus.PROPERTIES_IFACE,
620+ "No such interface {}".format(interface_name))
621+ if property_name not in property_iface_map[interface_name]:
622+ raise dbus.exceptions.DBusException(
623+ dbus.PROPERTIES_IFACE,
624+ "No such property {}:{}".format(
625+ interface_name, property_name))
626+ name = property_iface_map[interface_name][property_name]
627+ try:
628+ setattr(self, name, value)
629+ except dbus.exceptions.DBusException:
630+ raise
631+ except Exception as exc:
632+ logger.exception(
633+ "runaway exception from Set(%r, %r, %r)",
634+ interface_name, property_name, value)
635+ raise dbus.exceptions.DBusException(
636+ dbus.PROPERTIES_IFACE,
637+ "Unable to set property {}:{}: {!r}".format(
638+ interface_name, property_name, exc))
639+
640+ @dbus.service.method(
641+ dbus_interface=dbus.PROPERTIES_IFACE,
642+ in_signature="s", out_signature="a{sv}")
643+ def GetAll(self, interface_name):
644+ logger.debug("GetAll(%r)", interface_name)
645+ property_iface_map = self._get_property_iface_map()
646+ if interface_name not in property_iface_map:
647+ raise dbus.exceptions.DBusException(
648+ dbus.PROPERTIES_IFACE,
649+ "No such interface {}".format(interface_name))
650+ else:
651+ return {
652+ property_name: self.Get(interface_name, property_name)
653+ for property_name in property_iface_map[interface_name]
654+ }
655+
656+ @dbus.service.signal(
657+ dbus_interface=dbus.PROPERTIES_IFACE,
658+ signature='sa{sv}as')
659+ def PropertiesChanged(
660+ self, interface_name, changed_properties, invalidated_properties):
661+ logger.debug(
662+ "PropertiesChanged(%r, %r, %r)",
663+ interface_name, changed_properties, invalidated_properties)
664+
665+ # None helper that is overriden per-class in _get_property_iface_map()
666+ _property_iface_map = None
667+
668+ @classmethod
669+ def _get_property_iface_map(cls):
670+ """
671+ Get or construct (once) property-interface map out of methods
672+ decorated with @dbus_property decorator.
673+ """
674+ if cls._property_iface_map is None:
675+ property_iface_map = cls._build_property_iface_map()
676+ logger.debug("built property_iface_map: %r", property_iface_map)
677+ cls._property_iface_map = property_iface_map
678+ return cls._property_iface_map
679+
680+ @classmethod
681+ def _build_property_iface_map(cls):
682+ """
683+ Construct property-interface map out of methods
684+ decorated with @dbus_property decorator.
685+ """
686+ pif = {} # property_iface_map
687+ logger.debug("Looking for dbus_property instances in %r", cls)
688+ for name in dir(cls):
689+ obj = cls.__dict__.get(name)
690+ if not isinstance(obj, property):
691+ continue
692+ logger.debug("Found DBus property %r", obj)
693+ if obj._dbus_interface not in pif:
694+ pif[obj._dbus_interface] = {}
695+ pif[obj._dbus_interface][obj._dbus_property] = name
696+ return pif
697
698=== modified file 'plainbox/plainbox/impl/logging.py'
699--- plainbox/plainbox/impl/logging.py 2013-06-14 13:05:27 +0000
700+++ plainbox/plainbox/impl/logging.py 2013-06-18 18:07:29 +0000
701@@ -36,6 +36,8 @@
702 from plainbox.impl.color import ansi_on, ansi_off
703
704
705+logger = logging.getLogger("plainbox.logging")
706+
707 # XXX: enable ansi escape sequences if sys.std{out,err} are both TTYs
708 #
709 # This is a bad place to take this decision (ideally we'd do that per log
710@@ -91,15 +93,20 @@
711 # defined for all of the logging subsystem in this python runtime
712 logging.config.dictConfig(self.DEFAULT_CONFIG)
713
714- def adjust_logging(self, level=None, trace_list=None):
715+ def adjust_logging(self, level=None, trace_list=None, debug_console=False):
716 # Bump logging on the root logger if requested
717 if level is not None:
718 logging.getLogger(None).setLevel(level)
719+ logger.debug("Enabled %r on root logger", level)
720 logging.getLogger("plainbox").setLevel(level)
721 # Enable tracing on specified loggers
722 if trace_list is not None:
723 for name in trace_list:
724 logging.getLogger(name).setLevel(logging.DEBUG)
725+ logger.debug("Enabled debugging on logger %r", name)
726+ if debug_console and (level == 'DEBUG' or trace_list):
727+ # Enable DEBUG logging to console if explicitly requested
728+ logging.config.dictConfig(self.DEBUG_CONSOLE_CONFIG)
729
730 @property
731 def log_dir(self):
732@@ -115,6 +122,22 @@
733 return {
734 "version": 1,
735 "formatters": {
736+ "console_debug": {
737+ "()": "plainbox.impl.logging.ANSIFormatter",
738+ "format": (
739+ "{ansi.f.BLACK}{ansi.s.BRIGHT}"
740+ "%(levelname)s"
741+ "{ansi.s.NORMAL}{ansi.f.RESET}"
742+ " "
743+ "{ansi.f.CYAN}{ansi.s.DIM}"
744+ "%(name)s"
745+ "{ansi.f.RESET}{ansi.s.NORMAL}"
746+ ": "
747+ "{ansi.s.DIM}"
748+ "%(message)s"
749+ "{ansi.s.NORMAL}"
750+ ),
751+ },
752 "console_info": {
753 "()": "plainbox.impl.logging.ANSIFormatter",
754 "format": (
755@@ -122,7 +145,9 @@
756 "%(levelname)s"
757 "{ansi.s.NORMAL}{ansi.f.RESET}"
758 " "
759- "{ansi.f.CYAN}%(name)s{ansi.f.RESET}"
760+ "{ansi.f.CYAN}{ansi.s.BRIGHT}"
761+ "%(name)s"
762+ "{ansi.f.RESET}{ansi.s.NORMAL}"
763 ": "
764 "%(message)s"
765 ),
766@@ -178,6 +203,13 @@
767 },
768 },
769 "handlers": {
770+ "console_debug": {
771+ "class": "logging.StreamHandler",
772+ "stream": "ext://sys.stdout",
773+ "formatter": "console_debug",
774+ "filters": ["only_debug"],
775+ "level": 150,
776+ },
777 "console_info": {
778 "class": "logging.StreamHandler",
779 "stream": "ext://sys.stdout",
780@@ -229,6 +261,7 @@
781 "plainbox": {
782 "level": "WARNING",
783 "handlers": [
784+ "console_debug",
785 "console_info",
786 "console_warning",
787 "console_error",
788@@ -248,6 +281,18 @@
789 "disable_existing_loggers": True,
790 }
791
792+ @property
793+ def DEBUG_CONSOLE_CONFIG(self):
794+ return {
795+ "version": 1,
796+ "handlers": {
797+ "console_debug": {
798+ "level": "DEBUG",
799+ },
800+ },
801+ "incremental": True,
802+ }
803+
804
805 # Instantiate the helper
806 _LoggingHelper = LoggingHelper()
807
808=== modified file 'plainbox/plainbox/impl/plugins.py'
809--- plainbox/plainbox/impl/plugins.py 2013-06-05 07:43:07 +0000
810+++ plainbox/plainbox/impl/plugins.py 2013-06-18 18:07:29 +0000
811@@ -37,7 +37,7 @@
812 such as :meth:`PlugInCollection.get_by_name()` or
813 :meth:`PlugInCollection.get_all_names()`
814
815-The set of loaded plugins can be overriden by mock/patching
816+The set of loaded plugins can be overridden by mock/patching
817 :meth:`PlugInCollection._get_entry_points()`. This is especially useful for
818 testing in isolation from whatever entry points may exist in the system.
819 """
820
821=== modified file 'plainbox/plainbox/impl/provider.py'
822--- plainbox/plainbox/impl/provider.py 2013-05-17 16:14:38 +0000
823+++ plainbox/plainbox/impl/provider.py 2013-06-18 18:07:29 +0000
824@@ -1,3 +1,4 @@
825+
826 # This file is part of Checkbox.
827 #
828 # Copyright 2013 Canonical Ltd.
829@@ -72,6 +73,14 @@
830 """
831
832 @abc.abstractproperty
833+ def name(self):
834+ """
835+ name of this provider
836+
837+ This name should be dbus-friendly. It should not be localizable.
838+ """
839+
840+ @abc.abstractproperty
841 def CHECKBOX_SHARE(self):
842 """
843 Return the required value of CHECKBOX_SHARE environment variable.
844@@ -134,6 +143,10 @@
845 job._checkbox = self
846
847 @property
848+ def name(self):
849+ return self._extras.get('name', "dummy")
850+
851+ @property
852 def CHECKBOX_SHARE(self):
853 return self._extras.get('CHECKBOX_SHARE', "")
854
855
856=== added file 'plainbox/plainbox/impl/service.py'
857--- plainbox/plainbox/impl/service.py 1970-01-01 00:00:00 +0000
858+++ plainbox/plainbox/impl/service.py 2013-06-18 18:07:29 +0000
859@@ -0,0 +1,269 @@
860+# This file is part of Checkbox.
861+#
862+# Copyright 2013 Canonical Ltd.
863+# Written by:
864+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
865+#
866+# Checkbox is free software: you can redistribute it and/or modify
867+# it under the terms of the GNU General Public License as published by
868+# the Free Software Foundation, either version 3 of the License, or
869+# (at your option) any later version.
870+#
871+# Checkbox is distributed in the hope that it will be useful,
872+# but WITHOUT ANY WARRANTY; without even the implied warranty of
873+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
874+# GNU General Public License for more details.
875+#
876+# You should have received a copy of the GNU General Public License
877+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
878+
879+"""
880+:mod:`plainbox.impl.service` -- DBus service for PlainBox
881+=========================================================
882+"""
883+
884+import logging
885+
886+from plainbox import __version__ as plainbox_version
887+from plainbox.impl import dbus
888+
889+
890+logger = logging.getLogger("plainbox.service")
891+
892+
893+PLAINBOX_JOB_IFACE = "com.canonical.certification.PlainBox.JobDefinition"
894+CHECKBOX_JOB_IFACE = "com.canonical.certification.CheckBox.JobDefinition"
895+CHECKBOX_WHITELIST_IFACE = "com.canonical.certification.CheckBox.WhiteList"
896+
897+
898+class JobDefinition(dbus.service.Object):
899+ """
900+ DBus exported JobDefinition defintion object
901+ """
902+
903+ def __init__(self, job,
904+ conn=None, object_path=None, bus_name=None):
905+ self._checksum = job.get_checksum()
906+ self._job = job
907+ if object_path is None:
908+ object_path = (
909+ "/com/canonical/certification/plainbox/"
910+ "job/{}"
911+ ).format(self._get_dbus_friendly_name())
912+ super(JobDefinition, self).__init__(conn, object_path, bus_name)
913+ logger.debug("Created %r", self)
914+
915+ def _get_dbus_friendly_name(self):
916+ # FIXME: this is very sloppy, either use checksum again or
917+ # be better at some sort of defensive coding approach
918+ return self._job.name.replace("-", "_").replace(".", "_")
919+
920+ # PlainBox properties
921+
922+ @dbus.service.property(
923+ dbus_signature="s", dbus_interface=PLAINBOX_JOB_IFACE)
924+ def name(self):
925+ return self._job.name
926+
927+ @dbus.service.property(
928+ dbus_signature="s", dbus_interface=PLAINBOX_JOB_IFACE)
929+ def description(self):
930+ return self._job.description or ""
931+
932+ @dbus.service.property(
933+ dbus_signature="s", dbus_interface=PLAINBOX_JOB_IFACE)
934+ def checksum(self):
935+ return self._checksum
936+
937+ @dbus.service.property(
938+ dbus_signature="s", dbus_interface=PLAINBOX_JOB_IFACE)
939+ def requires(self):
940+ return self._job.requires or ""
941+
942+ @dbus.service.property(
943+ dbus_signature="s", dbus_interface=PLAINBOX_JOB_IFACE)
944+ def depends(self):
945+ return self._job.depends or ""
946+
947+ # PlainBox methods
948+
949+ @dbus.service.method(
950+ dbus_interface=PLAINBOX_JOB_IFACE,
951+ in_signature='', out_signature='as')
952+ def GetDirectDependencies(self):
953+ return self._job.get_direct_dependencies()
954+
955+ @dbus.service.method(
956+ dbus_interface=PLAINBOX_JOB_IFACE,
957+ in_signature='', out_signature='as')
958+ def GetResourceDependencies(self):
959+ return self._job.get_resource_dependencies()
960+
961+ @dbus.service.method(
962+ dbus_interface=CHECKBOX_JOB_IFACE,
963+ in_signature='', out_signature='as')
964+ def GetEnvironSettings(self):
965+ return self._job.get_environ_settings()
966+
967+ # CheckBox properties
968+
969+ @dbus.service.property(
970+ dbus_signature="s", dbus_interface=CHECKBOX_JOB_IFACE)
971+ def plugin(self):
972+ return self._job.plugin
973+
974+ @dbus.service.property(
975+ dbus_signature="s", dbus_interface=CHECKBOX_JOB_IFACE)
976+ def via(self):
977+ return self._job.via or ""
978+
979+ @dbus.service.property(
980+ dbus_signature="v", dbus_interface=CHECKBOX_JOB_IFACE)
981+ def origin(self):
982+ if self._job.origin is not None:
983+ return dbus.Struct([
984+ self._job.origin.filename,
985+ self._job.origin.line_start,
986+ self._job.origin.line_end
987+ ], signature=dbus.Signature("(suu)"))
988+
989+ @dbus.service.property(
990+ dbus_signature="s", dbus_interface=CHECKBOX_JOB_IFACE)
991+ def command(self):
992+ return self._job.command or ""
993+
994+ @dbus.service.property(
995+ dbus_signature="s", dbus_interface=CHECKBOX_JOB_IFACE)
996+ def environ(self):
997+ return self._job.environ or ""
998+
999+ @dbus.service.property(
1000+ dbus_signature="s", dbus_interface=CHECKBOX_JOB_IFACE)
1001+ def user(self):
1002+ return self._job.user or ""
1003+
1004+
1005+class WhiteList(dbus.service.Object):
1006+ """
1007+ DBus exported WhiteList object
1008+ """
1009+
1010+ def __init__(self, whitelist,
1011+ conn=None, object_path=None, bus_name=None):
1012+ self._whitelist = whitelist
1013+ if object_path is None:
1014+ object_path = (
1015+ "/com/canonical/certification/plainbox/"
1016+ "whitelist/{}"
1017+ ).format(self._get_dbus_friendly_name())
1018+ super(WhiteList, self).__init__(conn, object_path, bus_name)
1019+ logger.debug("Created %r", self)
1020+
1021+ def _get_dbus_friendly_name(self):
1022+ # FIXME: this is very sloppy, either use checksum again or
1023+ # be better at some sort of defensive coding approach
1024+ assert self._whitelist.name is not None
1025+ return self._whitelist.name.replace("-", "_").replace(".", "_")
1026+
1027+ @dbus.service.property(
1028+ dbus_signature="s", dbus_interface=CHECKBOX_WHITELIST_IFACE)
1029+ def name(self):
1030+ """
1031+ Name of this whitelist
1032+ """
1033+ return self._whitelist.name
1034+
1035+
1036+class Provider(dbus.service.Object):
1037+ """
1038+ DBus exported Provider object
1039+
1040+ This wrapper exports the plainbox IProvider1 interface over DBus.
1041+ Methods and properties internal to the implementation are *NOT* exported.
1042+ """
1043+
1044+ __dbus_iface__ = "com.canonical.certification.PlainBox.Provider"
1045+
1046+ def __init__(self, provider,
1047+ conn=None, object_path=None, bus_name=None):
1048+ if object_path is None:
1049+ object_path = (
1050+ "/com/canonical/certification/plainbox/"
1051+ "providers/{}"
1052+ ).format(provider.name)
1053+ super(Provider, self).__init__(conn, object_path, bus_name)
1054+ self._provider = provider
1055+ self._job_list = []
1056+ self._whitelist_list = []
1057+ logger.debug("Created %r", self)
1058+ for job in provider.get_builtin_jobs():
1059+ job_obj = JobDefinition(job, conn=self.connection)
1060+ self._job_list.append(job_obj)
1061+ for whitelist in provider.get_builtin_whitelists():
1062+ whitelist_obj = WhiteList(whitelist, conn=self.connection)
1063+ self._whitelist_list.append(whitelist_obj)
1064+
1065+ @dbus.service.property(
1066+ dbus_signature="s", dbus_interface=__dbus_iface__)
1067+ def name(self):
1068+ """
1069+ Name of this provider
1070+ """
1071+ return self._provider.name
1072+
1073+
1074+class Service(dbus.service.Object):
1075+ """
1076+ PlainBox DBus Service
1077+ """
1078+
1079+ __dbus_iface__ = "com.canonical.certification.PlainBox"
1080+
1081+ def __init__(self, on_exit,
1082+ conn=None, object_path=None, bus_name=None):
1083+ if object_path is None:
1084+ object_path = "/com/canonical/certification/plainbox/service"
1085+ super(Service, self).__init__(conn, object_path, bus_name)
1086+ self._provider_list = []
1087+ self._on_exit = on_exit
1088+ logger.debug("Created %r", self)
1089+
1090+ def add_provider(self, provider):
1091+ """
1092+ Add and expose a provider
1093+
1094+ :param provider:
1095+ A provider object to expose over DBus
1096+ """
1097+ provider_obj = Provider(provider, conn=self.connection)
1098+ self._provider_list.append(provider_obj)
1099+
1100+ @dbus.service.property(
1101+ dbus_signature="s", dbus_interface=__dbus_iface__)
1102+ def version(self):
1103+ """
1104+ version string of the PlainBox service
1105+ """
1106+ return "{}.{}.{}".format(*plainbox_version)
1107+
1108+ @dbus.service.method(
1109+ dbus_interface=__dbus_iface__,
1110+ in_signature='', out_signature='o')
1111+ def getCheckBoxProvider(self):
1112+ """
1113+ Return the special CheckBox provider.
1114+
1115+ This provider is currently always guaranteed to exist.
1116+ It is a shorthand for calling :meth:`EnumerateProviders()` and
1117+ filtering the one with the name "checkbox".
1118+ """
1119+ return None
1120+
1121+ @dbus.service.method(
1122+ dbus_interface=__dbus_iface__,
1123+ in_signature='', out_signature='')
1124+ def Exit(self):
1125+ """
1126+ Shut down the service and terminate
1127+ """
1128+ self._on_exit()
1129
1130=== modified file 'plainbox/plainbox/impl/test_box.py'
1131--- plainbox/plainbox/impl/test_box.py 2013-06-14 16:32:07 +0000
1132+++ plainbox/plainbox/impl/test_box.py 2013-06-18 18:07:29 +0000
1133@@ -122,17 +122,18 @@
1134 self.assertEqual(call.exception.args, (0,))
1135 self.maxDiff = None
1136 expected = """
1137- usage: plainbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-T LOGGER]
1138- [-P] [-I]
1139- {run,self-test,sru,check-config,dev} ...
1140+ usage: plainbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-C]
1141+ [-T LOGGER] [-P] [-I]
1142+ {run,self-test,sru,check-config,dev,service} ...
1143
1144 positional arguments:
1145- {run,self-test,sru,check-config,dev}
1146+ {run,self-test,sru,check-config,dev,service}
1147 run run a test job
1148 self-test run integration tests
1149 sru run automated stable release update tests
1150 check-config check and display plainbox configuration
1151 dev development commands
1152+ service spawn dbus service
1153
1154 optional arguments:
1155 -h, --help show this help message and exit
1156@@ -143,6 +144,7 @@
1157 logging and debugging:
1158 -v, --verbose be more verbose (same as --log-level=INFO)
1159 -D, --debug enable DEBUG messages on the root logger
1160+ -C, --debug-console display DEBUG messages in the console
1161 -T LOGGER, --trace LOGGER
1162 enable DEBUG messages on the specified logger (can be
1163 used multiple times)
1164@@ -159,9 +161,9 @@
1165 main([])
1166 self.assertEqual(call.exception.args, (2,))
1167 expected = """
1168- usage: plainbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-T LOGGER]
1169- [-P] [-I]
1170- {run,self-test,sru,check-config,dev} ...
1171+ usage: plainbox [-h] [--version] [-c {src,deb,auto}] [-v] [-D] [-C]
1172+ [-T LOGGER] [-P] [-I]
1173+ {run,self-test,sru,check-config,dev,service} ...
1174 plainbox: error: too few arguments
1175 """
1176 self.assertEqual(io.combined, cleandoc(expected) + "\n")

Subscribers

People subscribed via source and target branches