Merge lp:~zyga/checkbox/plainbox-dbus into lp:checkbox
- plainbox-dbus
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Checkbox Developers | Pending | ||
Review via email: mp+169865@code.launchpad.net |
Commit message
Description of the change
Work in progress on the DBus service for PlainBox
- 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 busSigned-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 busSigned-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>
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:/
>
> 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:/
> 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 busSigned-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
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") |
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.