Merge lp:~townsend/libertine/release-1.4.1 into lp:libertine/trunk

Proposed by Christopher Townsend
Status: Merged
Approved by: Larry Price
Approved revision: 162
Merged at revision: 162
Proposed branch: lp:~townsend/libertine/release-1.4.1
Merge into: lp:libertine/trunk
Diff against target: 1848 lines (+846/-451)
32 files modified
CMakeLists.txt (+1/-1)
data/CMakeLists.txt (+3/-1)
data/com.canonical.libertine.LxcManager.service (+3/-0)
data/libertine-lxc-manager.conf (+0/-13)
debian/changelog (+31/-0)
debian/control (+1/-1)
debian/libertine-tools.install (+0/-1)
debian/python3-libertine-lxc.install (+1/-1)
libertine/ContainerAppsList.cpp (+2/-7)
libertine/ContainerAppsList.h (+2/-1)
libertine/ContainerArchivesList.cpp (+2/-7)
libertine/ContainerArchivesList.h (+2/-1)
libertine/ContainerConfigList.cpp (+2/-2)
libertine/qml/ContainerOptionsDialog.qml (+3/-0)
libertine/qml/ContainersView.qml (+15/-9)
libertine/qml/WelcomeView.qml (+4/-3)
python/libertine/ChrootContainer.py (+4/-1)
python/libertine/ContainersConfig.py (+21/-2)
python/libertine/Libertine.py (+228/-18)
python/libertine/LxcContainer.py (+43/-24)
python/libertine/__init__.py (+13/-2)
python/libertine/utils.py (+27/-4)
tests/unit/CMakeLists.txt (+24/-0)
tests/unit/libertine_logger_tests.py (+49/-0)
tests/unit/libertine_session_bridge_tests.py (+106/-0)
tests/unit/libertine_socket_tests.py (+72/-0)
tools/CMakeLists.txt (+2/-2)
tools/libertine-container-manager (+15/-14)
tools/libertine-launch (+64/-9)
tools/libertine-lxc-manager (+106/-109)
tools/libertine-session-bridge (+0/-205)
tools/libertine-session-bridge.1 (+0/-13)
To merge this branch: bzr merge lp:~townsend/libertine/release-1.4.1
Reviewer Review Type Date Requested Status
Larry Price Approve
Review via email: mp+305645@code.launchpad.net

Commit message

* Refactor the libertine-session-bus to be a class, so we will be on the same process and we can actually test LSB more then just running it and checking exit code.
* Add a get_logger function to the libertine utils. This function will get the logger __libertiner_logger__ which will only be setup once, with one handler.
* Switch libertine-lxc-manager to be a DBus service and activate it on demand via DBus. (LP: #1591350)
* Add check for special LIBERTINE_JENKAAS_TESTING environment variable for the smoke testing harness.
* Bump version to 1.4.1.
* Create bind-mount directories based on information from xdg-user-dir, and only mount on container startup. (LP: #1610123)
* Return user to homepage when container has been destroyed from under them. (LP: #1604015)
* Introduce a method in ContainersConfig to refresh the database depending on an md5 checksum.
* Creating the first container moves user to ContainersView screen. (LP: #1615697)
* Inject a ContainersConfig instance when creating containers.
* Fix crash in ContainersConfig when getting host arch by using HostInfo object.
* Create a signal to indicate that container creation has begun.

To post a comment you must log in.
Revision history for this message
Larry Price (larryprice) wrote :

lgtm

review: Approve
lp:~townsend/libertine/release-1.4.1 updated
163. By Christopher Townsend

Remove debian/changelog entry that was already included in another landing.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2016-09-08 19:47:19 +0000
3+++ CMakeLists.txt 2016-09-14 14:44:20 +0000
4@@ -2,7 +2,7 @@
5 cmake_policy(SET CMP0048 NEW)
6
7 project(libertine
8- VERSION 1.4)
9+ VERSION 1.4.1)
10
11 set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
12
13
14=== modified file 'data/CMakeLists.txt'
15--- data/CMakeLists.txt 2016-08-12 16:11:57 +0000
16+++ data/CMakeLists.txt 2016-09-14 14:44:20 +0000
17@@ -4,10 +4,12 @@
18 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
19 install(FILES libertine_64.png libertine-lxc.conf
20 DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME})
21-install(FILES libertine-lxc-manager.conf libertine-xmir.conf
22+install(FILES libertine-xmir.conf
23 DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions)
24 install(FILES libertine-lxc-sudo
25 DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sudoers.d)
26+install(FILES com.canonical.libertine.LxcManager.service
27+ DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services)
28
29 configure_file("python3-libertine-chroot.click-hook.in"
30 "${CMAKE_SOURCE_DIR}/debian/python3-libertine-chroot.click-hook"
31
32=== added file 'data/com.canonical.libertine.LxcManager.service'
33--- data/com.canonical.libertine.LxcManager.service 1970-01-01 00:00:00 +0000
34+++ data/com.canonical.libertine.LxcManager.service 2016-09-14 14:44:20 +0000
35@@ -0,0 +1,3 @@
36+[D-BUS Service]
37+Name=com.canonical.libertine.LxcManager
38+Exec=/usr/bin/libertine-lxc-manager
39
40=== removed file 'data/libertine-lxc-manager.conf'
41--- data/libertine-lxc-manager.conf 2015-12-01 18:17:43 +0000
42+++ data/libertine-lxc-manager.conf 1970-01-01 00:00:00 +0000
43@@ -1,13 +0,0 @@
44-description "Service to manage Libertine LXC's"
45-author "Christopher Townsend <christopher.townsend@canonical.com>"
46-
47-start on started unity8
48-stop on desktop-end
49-
50-respawn
51-
52-pre-start script
53- dpkg -s python3-libertine-lxc > /dev/null 2>&1 || { stop; exit 0; }
54-end script
55-
56-exec /usr/bin/libertine-lxc-manager
57
58=== modified file 'debian/changelog'
59--- debian/changelog 2016-09-08 19:54:48 +0000
60+++ debian/changelog 2016-09-14 14:44:20 +0000
61@@ -1,3 +1,34 @@
62+libertine (1.4.1-0ubuntu1) UNRELEASED; urgency=medium
63+
64+ [ Brandon Schaefer ]
65+ * Refactor the libertine-session-bus to be a class, so we will be on the
66+ same process and we can actually test LSB more then just running it and
67+ checking exit code.
68+ * Add a get_logger function to the libertine utils. This function will get
69+ the logger __libertiner_logger__ which will only be setup once, with one
70+ handler.
71+
72+ [ Chris Townsend ]
73+ * Switch libertine-lxc-manager to be a DBus service and activate it on demand
74+ via DBus. (LP: #1591350)
75+ * Add check for special LIBERTINE_JENKAAS_TESTING environment variable for
76+ the smoke testing harness.
77+ * Bump version to 1.4.1.
78+
79+ [ Larry Price ]
80+ * Return user to homepage when container has been destroyed from under them.
81+ (LP: #1604015)
82+ * Introduce a method in ContainersConfig to refresh the database depending
83+ on an md5 checksum.
84+ * Creating the first container moves user to ContainersView screen.
85+ (LP: #1615697)
86+ * Inject a ContainersConfig instance when creating containers.
87+ * Fix crash in ContainersConfig when getting host arch by using HostInfo
88+ object.
89+ * Create a signal to indicate that container creation has begun.
90+
91+ -- Chris Townsend <christopher.townsend@canonical.com> Tue, 13 Sep 2016 14:03:29 -0400
92+
93 libertine (1.4+16.10.20160908-0ubuntu1) yakkety; urgency=medium
94
95 [ Chris Townsend ]
96
97=== modified file 'debian/control'
98--- debian/control 2016-09-08 19:47:19 +0000
99+++ debian/control 2016-09-14 14:44:20 +0000
100@@ -34,7 +34,6 @@
101 python3-libertine-lxc,
102 qml-module-qtquick2,
103 qtdeclarative5-ubuntu-ui-toolkit-plugin,
104- python3-dbus,
105 ${misc:Depends},
106 ${shlibs:Depends}
107 Description: sandbox for running deb-packaged X11 apps on Ubuntu Personal
108@@ -47,6 +46,7 @@
109 Depends: libglib2.0-bin,
110 lsb-release,
111 python3-apt,
112+ python3-dbus,
113 python3-distro-info,
114 python3-libertine,
115 ${misc:Depends},
116
117=== modified file 'debian/libertine-tools.install'
118--- debian/libertine-tools.install 2016-08-19 20:07:22 +0000
119+++ debian/libertine-tools.install 2016-09-14 14:44:20 +0000
120@@ -1,5 +1,4 @@
121 usr/bin/libertine-launch
122-usr/bin/libertine-session-bridge
123 usr/bin/libertine-container-manager
124 usr/bin/libertine-xmir
125 usr/bin/pasted
126
127=== modified file 'debian/python3-libertine-lxc.install'
128--- debian/python3-libertine-lxc.install 2016-07-15 18:39:58 +0000
129+++ debian/python3-libertine-lxc.install 2016-09-14 14:44:20 +0000
130@@ -1,6 +1,6 @@
131 usr/lib/python*/*/libertine/LxcContainer.py
132 usr/bin/libertine-lxc-manager
133+usr/share/dbus-1/services/com.canonical.libertine.LxcManager.service
134 usr/share/libertine/libertine-lxc.conf
135-usr/share/upstart/sessions/libertine-lxc-manager.conf
136 etc/sudoers.d/libertine-lxc-sudo
137 usr/bin/libertine-lxc-setup
138
139=== modified file 'libertine/ContainerAppsList.cpp'
140--- libertine/ContainerAppsList.cpp 2016-03-24 14:15:26 +0000
141+++ libertine/ContainerAppsList.cpp 2016-09-14 14:44:20 +0000
142@@ -27,11 +27,6 @@
143 { }
144
145
146-ContainerAppsList::
147-~ContainerAppsList()
148-{ }
149-
150-
151 void ContainerAppsList::
152 setContainerApps(QString const& container_id)
153 {
154@@ -51,12 +46,12 @@
155
156 bool ContainerAppsList::
157 empty() const noexcept
158-{ return apps_->empty(); }
159+{ return apps_ == nullptr || apps_->empty(); }
160
161
162 ContainerAppsList::size_type ContainerAppsList::
163 size() const noexcept
164-{ return apps_->count(); }
165+{ return apps_ != nullptr ? apps_->count() : 0;}
166
167
168 int ContainerAppsList::
169
170=== modified file 'libertine/ContainerAppsList.h'
171--- libertine/ContainerAppsList.h 2016-03-24 14:15:26 +0000
172+++ libertine/ContainerAppsList.h 2016-09-14 14:44:20 +0000
173@@ -53,7 +53,8 @@
174 ContainerAppsList(ContainerConfigList* container_config_list,
175 QObject* parent = nullptr);
176
177- ~ContainerAppsList();
178+ virtual
179+ ~ContainerAppsList() = default;
180
181 Q_INVOKABLE void
182 setContainerApps(QString const& container_id);
183
184=== modified file 'libertine/ContainerArchivesList.cpp'
185--- libertine/ContainerArchivesList.cpp 2016-03-10 21:33:25 +0000
186+++ libertine/ContainerArchivesList.cpp 2016-09-14 14:44:20 +0000
187@@ -27,11 +27,6 @@
188 { }
189
190
191-ContainerArchivesList::
192-~ContainerArchivesList()
193-{ }
194-
195-
196 void ContainerArchivesList::
197 setContainerArchives(QString const& container_id)
198 {
199@@ -44,12 +39,12 @@
200
201 bool ContainerArchivesList::
202 empty() const noexcept
203-{ return archives_->empty(); }
204+{ return archives_ == nullptr || archives_->empty(); }
205
206
207 ContainerArchivesList::size_type ContainerArchivesList::
208 size() const noexcept
209-{ return archives_->count(); }
210+{ return archives_ != nullptr ? archives_->count() : 0; }
211
212
213 int ContainerArchivesList::
214
215=== modified file 'libertine/ContainerArchivesList.h'
216--- libertine/ContainerArchivesList.h 2016-03-10 21:33:25 +0000
217+++ libertine/ContainerArchivesList.h 2016-09-14 14:44:20 +0000
218@@ -53,7 +53,8 @@
219 ContainerArchivesList(ContainerConfigList* container_config_list,
220 QObject* parent = nullptr);
221
222- ~ContainerArchivesList();
223+ virtual
224+ ~ContainerArchivesList() = default;
225
226 Q_INVOKABLE void
227 setContainerArchives(QString const& container_id);
228
229=== modified file 'libertine/ContainerConfigList.cpp'
230--- libertine/ContainerConfigList.cpp 2016-06-07 18:45:30 +0000
231+++ libertine/ContainerConfigList.cpp 2016-09-14 14:44:20 +0000
232@@ -506,6 +506,7 @@
233 load_config()
234 {
235 QFile config_file(config_->containers_config_file_name());
236+ flock(config_file.handle(), LOCK_EX);
237
238 if (config_file.exists())
239 {
240@@ -517,9 +518,7 @@
241 {
242 QJsonParseError parse_error;
243
244- flock(config_file.handle(), LOCK_EX);
245 QJsonDocument json = QJsonDocument::fromJson(config_file.readAll(), &parse_error);
246- flock(config_file.handle(), LOCK_UN);
247
248 if (parse_error.error)
249 {
250@@ -543,4 +542,5 @@
251 }
252 }
253 }
254+ flock(config_file.handle(), LOCK_UN);
255 }
256
257=== modified file 'libertine/qml/ContainerOptionsDialog.qml'
258--- libertine/qml/ContainerOptionsDialog.qml 2016-07-15 18:39:58 +0000
259+++ libertine/qml/ContainerOptionsDialog.qml 2016-09-14 14:44:20 +0000
260@@ -27,6 +27,8 @@
261 title: i18n.tr("Container Options")
262 text: i18n.tr("Configure options for container creation.")
263
264+ signal onCreateInitialized()
265+
266 Row {
267 visible: containerConfigList.getHostArchitecture() == 'x86_64' ? true : false
268 spacing: units.gu(1)
269@@ -79,6 +81,7 @@
270 width: (parent.width - parent.spacing) / 2
271 onClicked: {
272 createContainer()
273+ onCreateInitialized()
274 PopupUtils.close(containerOptionsDialog)
275 }
276 }
277
278=== modified file 'libertine/qml/ContainersView.qml'
279--- libertine/qml/ContainersView.qml 2016-07-15 18:39:58 +0000
280+++ libertine/qml/ContainersView.qml 2016-09-14 14:44:20 +0000
281@@ -55,8 +55,12 @@
282 }
283 model: containerConfigList
284
285- function edit(containerId) {
286- mainView.currentContainer = containerId
287+ function edit(id, status) {
288+ if (status === "removing") {
289+ mainView.error(i18n.tr("Container Unavailable"), i18n.tr("Container is being destroyed and is no longer editable."))
290+ return
291+ }
292+ mainView.currentContainer = id
293 containerAppsList.setContainerApps(mainView.currentContainer)
294 pageStack.push(Qt.resolvedUrl("HomeView.qml"), {"currentContainer": mainView.currentContainer})
295 }
296@@ -82,7 +86,7 @@
297 running: containerActivity.visible
298 }
299
300- onClicked: { containersList.edit(containerId) }
301+ onClicked: { containersList.edit(containerId, installStatus) }
302
303 leadingActions: ListItemActions {
304 actions: [
305@@ -92,8 +96,8 @@
306 description: i18n.tr("Delete Container")
307 onTriggered: {
308 var worker = Qt.createComponent("ContainerManager.qml").createObject(mainView)
309+ worker.error.connect(mainView.error)
310 worker.destroyContainer(containerId)
311- mainView.currentContainer = containerId
312 }
313 }
314 ]
315@@ -117,7 +121,7 @@
316 visible: (installStatus === i18n.tr("ready") ||
317 installStatus === i18n.tr("updating")) ? true : false
318 onTriggered: {
319- containersList.edit(containerId)
320+ containersList.edit(containerId, installStatus)
321 }
322 }
323 ]
324@@ -128,16 +132,18 @@
325 Component.onCompleted: {
326 containerConfigList.configChanged.connect(updateContainerList)
327 }
328-
329+
330 Component.onDestruction: {
331 containerConfigList.configChanged.disconnect(updateContainerList)
332 }
333
334 function updateContainerList() {
335 containerConfigList.reloadContainerList()
336- }
337
338- function showPasswordDialog(enableMultiarch, containerName) {
339- PopupUtils.open(Qt.resolvedUrl("ContainerPasswordDialog.qml"), null, {"enableMultiarch": enableMultiarch, "containerName": containerName})
340+ if (mainView.currentContainer && !containerConfigList.getContainerStatus(mainView.currentContainer) && pageStack.currentPage !== containersView) {
341+ pageStack.pop()
342+ mainView.currentContainer = ""
343+ mainView.error(i18n.tr("Container Unavailable"), i18n.tr("This container has been destroyed and is no longer valid. You have been returned to the containers overview."))
344+ }
345 }
346 }
347
348=== modified file 'libertine/qml/WelcomeView.qml'
349--- libertine/qml/WelcomeView.qml 2016-07-15 18:39:58 +0000
350+++ libertine/qml/WelcomeView.qml 2016-09-14 14:44:20 +0000
351@@ -67,12 +67,13 @@
352 color: UbuntuColors.green
353
354 onClicked: {
355- PopupUtils.open(Qt.resolvedUrl("ContainerOptionsDialog.qml"))
356+ var dialog = PopupUtils.open(Qt.resolvedUrl("ContainerOptionsDialog.qml"))
357+ dialog.onCreateInitialized.connect(createInitialized)
358 }
359 }
360 }
361
362- function showPasswordDialog(enableMultiarch, containerName) {
363- PopupUtils.open(Qt.resolvedUrl("ContainerPasswordDialog.qml"), null, {"enableMultiarch": enableMultiarch, "containerName": containerName})
364+ function createInitialized() {
365+ pageStack.push(Qt.resolvedUrl("ContainersView.qml"))
366 }
367 }
368
369=== modified file 'python/libertine/ChrootContainer.py'
370--- python/libertine/ChrootContainer.py 2016-08-12 16:11:57 +0000
371+++ python/libertine/ChrootContainer.py 2016-09-14 14:44:20 +0000
372@@ -61,7 +61,10 @@
373
374 def destroy_libertine_container(self):
375 container_root = os.path.join(utils.get_libertine_containers_dir_path(), self.container_id)
376- shutil.rmtree(container_root)
377+ try:
378+ shutil.rmtree(container_root)
379+ except Exception as e:
380+ print("%s" % e)
381
382 def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
383 # Create the actual chroot
384
385=== modified file 'python/libertine/ContainersConfig.py'
386--- python/libertine/ContainersConfig.py 2016-07-07 18:46:21 +0000
387+++ python/libertine/ContainersConfig.py 2016-09-14 14:44:20 +0000
388@@ -17,6 +17,8 @@
389 import libertine.utils
390 import os
391 import sys
392+from hashlib import md5
393+from libertine.HostInfo import HostInfo
394
395
396 def read_container_config_file():
397@@ -41,10 +43,21 @@
398 fcntl.lockf(fd, fcntl.LOCK_UN)
399
400
401+def container_config_hash():
402+ checksum = md5()
403+ container_config_file = libertine.utils.get_libertine_database_file_path()
404+ if (os.path.exists(container_config_file) and os.path.getsize(container_config_file) != 0):
405+ with open(container_config_file, "rb") as f:
406+ for chunk in iter(lambda: f.read(128 * checksum.block_size), b""):
407+ checksum.update(chunk)
408+ return checksum.hexdigest()
409+
410+
411 class ContainersConfig(object):
412
413 def __init__(self):
414- self.container_list = read_container_config_file()
415+ self.checksum = None
416+ self.refresh_database()
417
418 if "defaultContainer" in self.container_list:
419 self.default_container_id = self.container_list['defaultContainer']
420@@ -144,6 +157,12 @@
421 """
422 Miscellaneous ContainersConfig.json operations
423 """
424+ def refresh_database(self):
425+ checksum = container_config_hash()
426+ if checksum != self.checksum:
427+ self.container_list = read_container_config_file()
428+ self.checksum = checksum
429+
430 def _find_duplicate_container_entry(self, container_list, container_id):
431 for container in container_list['containerList']:
432 if container['id'] == container_id:
433@@ -243,7 +262,7 @@
434 return self._test_key_value_exists(container_id, 'id')
435
436 def update_container_multiarch_support(self, container_id, multiarch):
437- if multiarch == 'enabled' and libertine.utils.get_host_architecture() == 'i386':
438+ if multiarch == 'enabled' and HostInfo().get_host_architecture() == 'i386':
439 multiarch = 'disabled'
440
441 self._set_value_by_key(container_id, 'multiarch', multiarch)
442
443=== modified file 'python/libertine/Libertine.py'
444--- python/libertine/Libertine.py 2016-08-10 12:45:13 +0000
445+++ python/libertine/Libertine.py 2016-09-14 14:44:20 +0000
446@@ -14,13 +14,19 @@
447
448 from .AppDiscovery import AppLauncherCache
449 from gi.repository import Libertine
450+from multiprocessing import Process, active_children
451+from socket import *
452 import abc
453 import contextlib
454 import libertine.utils
455 import os
456 import psutil
457+import select
458 import shutil
459 import shlex
460+import signal
461+import sys
462+import time
463
464 from libertine.ContainersConfig import ContainersConfig
465 from libertine.HostInfo import HostInfo
466@@ -260,7 +266,7 @@
467 A sandbox for DEB-packaged X11-based applications.
468 """
469
470- def __init__(self, container_id):
471+ def __init__(self, container_id, containers_config=None):
472 """
473 Initializes the container object.
474
475@@ -268,7 +274,9 @@
476 """
477 super().__init__()
478
479- self.containers_config = ContainersConfig()
480+ if containers_config is None:
481+ containers_config = ContainersConfig()
482+ self.containers_config = containers_config
483
484 container_type = self.containers_config.get_container_type(container_id)
485
486@@ -435,18 +443,227 @@
487 return handle_runtime_error(e)
488
489
490+class Socket(object):
491+ """
492+ A simple socket wrapper class. This will wrap a socket
493+
494+ :param socket: A python socket to be wrapped
495+ """
496+ def __init__(self, sock):
497+ if not isinstance(sock, socket):
498+ raise TypeError("expected socket to be a python socket class instead found: '{}'".format(sock.__class__))
499+
500+ self.socket = sock
501+
502+ """
503+ Implement equality checking for other instances of this class or ints only.
504+
505+ :param other: Either a Socket class or an int
506+ """
507+ def __eq__(self, other):
508+ if isinstance(other, Socket):
509+ return self.socket == other.socket
510+ elif isinstance(other, socket):
511+ return self.socket == other
512+ elif isinstance(other, int):
513+ return self.socket.fileno() == other
514+ else:
515+ raise TypeError("unsupported operand type(s) for ==: '{}' and '{}'".format(self.__class__, type(other)))
516+
517+ def __ne__(self, other):
518+ return not self == other
519+
520+ def __hash__(self):
521+ return self.socket.fileno()
522+
523+ def socket(self):
524+ return self.socket
525+
526+class SessionSocket(Socket):
527+ """
528+ Creates a AF_UNIX socket from a path to be used as the session socket.
529+ This is used for RAII and to take ownership of the socket
530+
531+ :param path: The path the socket will be binded with
532+ """
533+ def __init__(self, session_path):
534+ try:
535+ sock = socket(AF_UNIX, SOCK_STREAM)
536+ except:
537+ sock = None
538+ raise
539+ else:
540+ try:
541+ sock.bind(session_path)
542+ sock.listen(5)
543+ except:
544+ sock.close()
545+ sock = None
546+ raise
547+ else:
548+ super().__init__(sock)
549+ self.session_path = session_path
550+
551+ def __del__(self):
552+ self.socket.shutdown(SHUT_RDWR)
553+ self.socket.close()
554+ os.remove(self.session_path)
555+
556+
557+class HostSessionSocketPair():
558+ def __init__(self, host_socket_path, session_socket_path):
559+ self.host_socket_path = host_socket_path
560+ self.session_socket_path = session_socket_path
561+
562+
563+class LibertineSessionBridge(object):
564+ """
565+ Creates a session bridge which will pair host and session sockets to proxy the info
566+ from the session to the host and vice versa.
567+
568+ :param host_session_socket_paths: A list of pairs container valid sockets to proxy {host:session}
569+ """
570+ def __init__(self, host_session_socket_paths):
571+ self.descriptors = []
572+ self.host_session_socket_path_map = {}
573+ self.socket_pairs = {}
574+
575+ for host_session_socket_pair in host_session_socket_paths:
576+ host_path = host_session_socket_pair.host_socket_path
577+ session_path = host_session_socket_pair.session_socket_path
578+
579+ socket = SessionSocket(session_path)
580+
581+ self.descriptors.append(socket)
582+ self.host_session_socket_path_map.update({socket:host_path})
583+
584+ """
585+ If we end up going out of scope/error let's make sure we clean up sockets and paths.
586+ """
587+ def __del__(self):
588+ self.socket_pairs = None
589+ self.descriptors = None
590+ self.host_session_socket_path_map = None
591+
592+ """
593+ When a new connection is made on one of the main sockets we have to create a new
594+ socket pairing with the container socket.
595+
596+ :param host_path: The raw path to the container socket
597+ :param container_sock: The socket which received a new connection
598+ """
599+ def accept_new_connection(self, host_path, container_sock):
600+ newconn = Socket(container_sock.accept()[0])
601+ self.descriptors.append(newconn)
602+
603+ host_sock = Socket(socket(AF_UNIX, SOCK_STREAM))
604+ host_sock.socket.connect(host_path)
605+ self.descriptors.append(host_sock)
606+
607+ self.socket_pairs.update({newconn:host_sock})
608+ self.socket_pairs.update({host_sock:newconn})
609+
610+ """
611+ Cleans up a socket that has had its connection closed.
612+
613+ :param socket_to_remove: The socket that had its connection closed
614+ """
615+ def close_connections(self, socket_to_remove):
616+ partner_socket = self.socket_pairs[socket_to_remove.fileno()]
617+
618+ self.socket_pairs.pop(socket_to_remove.fileno())
619+ self.socket_pairs.pop(partner_socket.socket.fileno())
620+
621+ self.descriptors.remove(socket_to_remove)
622+ self.descriptors.remove(partner_socket)
623+
624+ if socket_to_remove in self.host_session_socket_path_map:
625+ self.host_session_socket_path_map.pop(socket_to_remove)
626+
627+ """
628+ The main loop which uses select to block until one of the sockets we are listening to becomes readable.
629+ It is advised this be started in its own process or thread, as this function blocks!
630+ """
631+ def main_loop(self):
632+ while 1:
633+ try:
634+ raw_sockets = list(map(lambda x : x.socket, self.descriptors))
635+ rlist, wlist, elist = select.select(raw_sockets, [], [])
636+ except InterruptedError as e:
637+ continue
638+ except Exception as e:
639+ libertine.utils.get_logger().exception(e)
640+ break
641+
642+ for sock in rlist:
643+ if sock.fileno() == -1:
644+ continue
645+
646+ # Its possible to have multiple socket reads that are pairs. If this happens and we remove a pair we
647+ # need to ignore the other pair since it no longer has a complete pair
648+ if sock.fileno() not in self.host_session_socket_path_map and sock.fileno() not in self.socket_pairs:
649+ continue
650+
651+ if sock.fileno() in self.host_session_socket_path_map:
652+ self.accept_new_connection(self.host_session_socket_path_map[sock.fileno()], sock)
653+
654+ else:
655+ try:
656+ data = sock.recv(4096)
657+ except Exception as e:
658+ libertine.utils.get_logger().exception(e)
659+ self.close_connections(sock)
660+ continue
661+ else:
662+ if len(data) == 0:
663+ self.close_connections(sock)
664+ continue
665+
666+ send_sock = self.socket_pairs[sock.fileno()].socket
667+
668+ if send_sock.fileno() < 0:
669+ continue
670+
671+ totalsent = 0
672+ while totalsent < len(data):
673+ try:
674+ sent = send_sock.send(data)
675+ except BrokenPipeError as e:
676+ libertine.utils.get_logger().exception(e)
677+ self.close_connections(sock)
678+ break
679+ else:
680+ if sent == 0:
681+ close_connections(sock)
682+ break
683+ totalsent = totalsent + sent
684+
685+
686 class LibertineApplication(object):
687 """
688 Launches a libertine container with a session bridge for sockets such as dbus
689
690 :param container_id: The container id.
691- "param app_exec_line: The exec line used to start the app in the container.
692+ :param app_exec_line: The exec line used to start the app in the container.
693 """
694 def __init__(self, container_id, app_exec_line):
695- self.container_id = container_id
696- self.app_exec_line = app_exec_line
697- self.session_bridge = None
698- self.pasted = None
699+ signal.signal(signal.SIGTERM, self.cleanup_lsb)
700+ signal.signal(signal.SIGINT, self.cleanup_lsb)
701+
702+ self.container_id = container_id
703+ self.app_exec_line = app_exec_line
704+ self.lsb = None
705+ self.pasted = None
706+
707+ def cleanup_lsb(self, signum, frame):
708+ self.close_lsb()
709+
710+ def close_lsb(self):
711+ if self.lsb is not None:
712+ self.lsb_process.terminate()
713+
714+ while active_children():
715+ time.sleep(1)
716
717 """
718 Launches the libertine session bridge. This creates a proxy socket to read to and from
719@@ -455,14 +672,9 @@
720 :param session_socket_paths: A list of socket paths the session will create.
721 """
722 def launch_session_bridge(self, session_socket_paths):
723- session_bridge_arguments = ''
724- for paths in session_socket_paths:
725- session_bridge_arguments += paths + ' '
726-
727- libertine_session_bridge_cmd = "libertine-session-bridge " + session_bridge_arguments
728-
729- args = shlex.split(libertine_session_bridge_cmd)
730- self.session_bridge = psutil.Popen(args)
731+ self.lsb = LibertineSessionBridge(session_socket_paths)
732+ self.lsb_process = Process(target=self.lsb.main_loop)
733+ self.lsb_process.start()
734
735 """
736 Launches the pasted process for allowing copy and paste to work with X apps and
737@@ -479,14 +691,12 @@
738 raise RuntimeError("Container ID %s does not exist." % self.container_id)
739
740 container = LibertineContainer(self.container_id)
741-
742 try:
743 container.launch_application(self.app_exec_line)
744 except:
745 raise
746 finally:
747- if self.session_bridge is not None:
748- self.session_bridge.terminate()
749+ self.close_lsb()
750
751 if self.pasted is not None:
752 self.pasted.terminate()
753
754=== modified file 'python/libertine/LxcContainer.py'
755--- python/libertine/LxcContainer.py 2016-08-12 16:11:57 +0000
756+++ python/libertine/LxcContainer.py 2016-09-14 14:44:20 +0000
757@@ -12,7 +12,9 @@
758 # You should have received a copy of the GNU General Public License along
759 # with this program. If not, see <http://www.gnu.org/licenses/>.
760
761+import contextlib
762 import crypt
763+import dbus
764 import lxc
765 import os
766 import psutil
767@@ -23,11 +25,13 @@
768
769 from .Libertine import BaseContainer
770 from . import utils
771-from socket import *
772
773
774 home_path = os.environ['HOME']
775
776+LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxcManager"
777+LIBERTINE_LXC_MANAGER_PATH = "/LxcManager"
778+
779
780 def check_lxc_net_entry(entry):
781 lxc_net_file = open('/etc/lxc/lxc-usernet')
782@@ -69,6 +73,31 @@
783 return host_timezone
784
785
786+class EnvLxcSettings(contextlib.ExitStack):
787+ """
788+ Helper object providing a way to set the proxies for testing
789+
790+ When running smoke tests on Jenkins, LXC create operations need the proxies
791+ set, but subsequent apt operations cannot have the proxies set. This will
792+ set the proxies when called and unset the proxies when the context is
793+ destroyed.
794+ """
795+ def __init__(self):
796+ super().__init__()
797+ self.set_proxy_env()
798+ self.callback(lambda: self.del_proxy_env())
799+
800+ def set_proxy_env(self):
801+ if 'LIBERTINE_JENKAAS_TESTING' in os.environ:
802+ os.environ['http_proxy'] = 'http://squid.internal:3128'
803+ os.environ['https_proxy'] = 'https://squid.internal:3128'
804+
805+ def del_proxy_env(self):
806+ if 'LIBERTINE_JENKAAS_TESTING' in os.environ:
807+ del os.environ['http_proxy']
808+ del os.environ['https_proxy']
809+
810+
811 class LibertineLXC(BaseContainer):
812 """
813 A concrete container type implemented using an LXC container.
814@@ -178,12 +207,13 @@
815
816 utils.create_libertine_user_data_dir(self.container_id)
817
818- if not self.container.create("download", 0,
819- {"dist": "ubuntu",
820- "release": self.installed_release,
821- "arch": self.architecture}):
822- print("Failed to create container")
823- return False
824+ with EnvLxcSettings():
825+ if not self.container.create("download", 0,
826+ {"dist": "ubuntu",
827+ "release": self.installed_release,
828+ "arch": self.architecture}):
829+ print("Failed to create container")
830+ return False
831
832 self.create_libertine_config()
833
834@@ -253,17 +283,11 @@
835 self.container.save_config()
836
837 def launch_application(self, app_exec_line):
838- libertine_lxc_mgr_sock = socket(AF_UNIX, SOCK_STREAM)
839- libertine_lxc_mgr_sock.connect(utils.get_libertine_lxc_socket())
840-
841- # Tell libertine-lxc-manager that we are starting a new app
842- message = "start " + self.container_id
843- libertine_lxc_mgr_sock.send(message.encode())
844-
845- # Receive the reply from libertine-lxc-manager
846- data = libertine_lxc_mgr_sock.recv(1024)
847-
848- if data.decode() == 'OK':
849+ bus = dbus.SessionBus()
850+ lxc_mgr_service = bus.get_object(LIBERTINE_LXC_MANAGER_NAME, LIBERTINE_LXC_MANAGER_PATH)
851+ lxc_manager_interface = dbus.Interface(lxc_mgr_service, LIBERTINE_LXC_MANAGER_NAME)
852+
853+ if lxc_manager_interface.app_start(self.container_id):
854 if not self.container.wait("RUNNING", 10):
855 print("Container failed to enter the RUNNING state")
856 return
857@@ -290,12 +314,7 @@
858 utils.terminate_window_manager(psutil.Process(window_manager))
859
860 # Tell libertine-lxc-manager that the app has stopped.
861- message = "stop " + self.container_id
862- libertine_lxc_mgr_sock.send(message.encode())
863-
864- # Receive the reply from libertine-lxc-manager (ignore it for now).
865- data = libertine_lxc_mgr_sock.recv(1024)
866- libertine_lxc_mgr_sock.close()
867+ lxc_manager_interface.app_stop(self.container_id)
868
869 def _set_lxc_log(self):
870 self.lxc_log_file = os.path.join(tempfile.mkdtemp(), 'lxc-start.log')
871
872=== modified file 'python/libertine/__init__.py'
873--- python/libertine/__init__.py 2016-06-27 23:15:13 +0000
874+++ python/libertine/__init__.py 2016-09-14 14:44:20 +0000
875@@ -22,10 +22,21 @@
876
877 __all__ = [
878 # from Libertine
879- 'LibertineContainer', 'utils', 'LibertineApplication'
880+ 'LibertineApplication',
881+ 'LibertineContainer',
882+ 'LibertineSessionBridge',
883+ 'LibertineSessionBridge',
884+ 'HostSessionSocketPair',
885+ 'SessionSocket',
886+ 'Socket',
887+ 'utils',
888 ]
889
890 __docformat__ = "restructuredtext en"
891
892+from libertine.Libertine import LibertineApplication
893 from libertine.Libertine import LibertineContainer
894-from libertine.Libertine import LibertineApplication
895+from libertine.Libertine import LibertineSessionBridge
896+from libertine.Libertine import HostSessionSocketPair
897+from libertine.Libertine import SessionSocket
898+from libertine.Libertine import Socket
899
900=== modified file 'python/libertine/utils.py'
901--- python/libertine/utils.py 2016-08-12 16:11:57 +0000
902+++ python/libertine/utils.py 2016-09-14 14:44:20 +0000
903@@ -16,6 +16,7 @@
904 # You should have received a copy of the GNU General Public License
905 # along with this program. If not, see <http://www.gnu.org/licenses/>.
906
907+import logging
908 import os
909 import shlex
910 import subprocess
911@@ -25,6 +26,32 @@
912 require_version('Libertine', '1')
913 from gi.repository import Libertine
914
915+def get_logger():
916+ logger = logging.getLogger('__libertine_logger__')
917+
918+ # If someone else sets a handler before this, we wont run this!
919+ if not logger.hasHandlers():
920+ logger.setLevel(logging.DEBUG)
921+ logger.disabled = True
922+
923+ stream_handler = logging.StreamHandler()
924+ stream_handler.setLevel(logging.DEBUG)
925+
926+ formatter = logging.Formatter('%(filename)s:'
927+ '%(lineno)d: '
928+ '%(levelname)s: '
929+ '%(funcName)s():\t'
930+ '%(message)s')
931+
932+ stream_handler.setFormatter(formatter)
933+ logger.addHandler(stream_handler)
934+
935+ # Only enable the logger if we set this
936+ if os.getenv('LIBERTINE_DEBUG') is '1':
937+ logger.disabled = False
938+
939+ return logger
940+
941
942 def get_libertine_container_rootfs_path(container_id):
943 path = Libertine.container_path(container_id)
944@@ -113,10 +140,6 @@
945 os.makedirs(xdg_path)
946
947
948-def get_libertine_lxc_socket():
949- return '\0libertine_lxc_socket'
950-
951-
952 def get_libertine_lxc_pulse_socket_path():
953 return os.path.join(get_libertine_runtime_dir(), 'pulse_socket')
954
955
956=== modified file 'tests/unit/CMakeLists.txt'
957--- tests/unit/CMakeLists.txt 2016-06-27 23:15:13 +0000
958+++ tests/unit/CMakeLists.txt 2016-09-14 14:44:20 +0000
959@@ -51,3 +51,27 @@
960 PROPERTIES
961 ENVIRONMENT
962 "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
963+
964+add_test(test_libertine_session_bridge
965+ "/usr/bin/python3" "-m" "testtools.run" "libertine_session_bridge_tests"
966+)
967+set_tests_properties(test_libertine_session_bridge
968+ PROPERTIES
969+ ENVIRONMENT
970+ "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
971+
972+add_test(test_libertine_socket
973+ "/usr/bin/python3" "-m" "testtools.run" "libertine_socket_tests"
974+)
975+set_tests_properties(test_libertine_socket
976+ PROPERTIES
977+ ENVIRONMENT
978+ "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
979+
980+add_test(test_logger
981+ "/usr/bin/python3" "-m" "testtools.run" "libertine_logger_tests"
982+)
983+set_tests_properties(test_logger
984+ PROPERTIES
985+ ENVIRONMENT
986+ "GI_TYPELIB_PATH=${CMAKE_BINARY_DIR}/liblibertine;LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/liblibertine:${LD_LIBRARY_PATH};CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR};PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_SOURCE_DIR}/python;CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
987
988=== added file 'tests/unit/libertine_logger_tests.py'
989--- tests/unit/libertine_logger_tests.py 1970-01-01 00:00:00 +0000
990+++ tests/unit/libertine_logger_tests.py 2016-09-14 14:44:20 +0000
991@@ -0,0 +1,49 @@
992+# Copyright 2016 Canonical Ltd.
993+#
994+# This program is free software: you can redistribute it and/or modify it
995+# under the terms of the GNU General Public License version 3, as published
996+# by the Free Software Foundation.
997+#
998+# This program is distributed in the hope that it will be useful, but
999+# WITHOUT ANY WARRANTY; without even the implied warranties of
1000+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1001+# PURPOSE. See the GNU General Public License for more details.
1002+#
1003+# You should have received a copy of the GNU General Public License along
1004+# with this program. If not, see <http://www.gnu.org/licenses/>.
1005+
1006+import os
1007+import shutil
1008+
1009+import libertine.utils
1010+
1011+from testtools import TestCase
1012+from testtools.matchers import Equals, NotEquals
1013+
1014+class TestLogger(TestCase):
1015+ def setUp(self):
1016+ super(TestLogger, self).setUp()
1017+ os.environ['LIBERTINE_DEBUG'] = '1'
1018+ self.addCleanup(self.cleanup)
1019+
1020+ def cleanup(self):
1021+ if os.getenv('LIBERTINE_DEBUG', None) is not None:
1022+ del os.environ['LIBERTINE_DEBUG']
1023+
1024+ def test_logger_off_by_default(self):
1025+ # Need to turn off for this test only!
1026+ self.cleanup()
1027+ os.unsetenv('LIBERTINE_DEBUG')
1028+ l = libertine.utils.get_logger()
1029+ self.assertTrue(l.disabled)
1030+
1031+ def test_logger_on_with_env_var(self):
1032+ l = libertine.utils.get_logger()
1033+ self.assertFalse(l.disabled)
1034+
1035+ def test_logger_only_inits_once(self):
1036+ l1 = libertine.utils.get_logger()
1037+ l2 = libertine.utils.get_logger()
1038+ l3 = libertine.utils.get_logger()
1039+ self.assertThat(len(l3.handlers), Equals(1))
1040+
1041
1042=== added file 'tests/unit/libertine_session_bridge_tests.py'
1043--- tests/unit/libertine_session_bridge_tests.py 1970-01-01 00:00:00 +0000
1044+++ tests/unit/libertine_session_bridge_tests.py 2016-09-14 14:44:20 +0000
1045@@ -0,0 +1,106 @@
1046+# Copyright 2016 Canonical Ltd.
1047+#
1048+# This program is free software: you can redistribute it and/or modify it
1049+# under the terms of the GNU General Public License version 3, as published
1050+# by the Free Software Foundation.
1051+#
1052+# This program is distributed in the hope that it will be useful, but
1053+# WITHOUT ANY WARRANTY; without even the implied warranties of
1054+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1055+# PURPOSE. See the GNU General Public License for more details.
1056+#
1057+# You should have received a copy of the GNU General Public License along
1058+# with this program. If not, see <http://www.gnu.org/licenses/>.
1059+
1060+import os
1061+import tempfile
1062+from socket import *
1063+import shutil
1064+
1065+from libertine import LibertineSessionBridge, HostSessionSocketPair, SessionSocket, LibertineApplication
1066+from libertine import Socket
1067+from multiprocessing import Process
1068+
1069+from testtools import TestCase
1070+from testtools.matchers import Equals, NotEquals
1071+
1072+import time
1073+import threading
1074+
1075+class TestLibertineSessionBridge(TestCase):
1076+ def setUp(self):
1077+ super(TestLibertineSessionBridge, self).setUp()
1078+
1079+ xdg_runtime_path = tempfile.mkdtemp()
1080+
1081+ self.addCleanup(self.cleanup)
1082+
1083+ # Set necessary enviroment variables
1084+ os.environ['XDG_RUNTIME_DIR'] = xdg_runtime_path
1085+
1086+ # Create r/w fake temp sockets, the session will be created by the lsb
1087+ self.host_path = os.path.join(xdg_runtime_path, 'HOST')
1088+ self.session_path = os.path.join(xdg_runtime_path, 'SESSION')
1089+
1090+ self.host_socket = SessionSocket(self.host_path)
1091+ self.assertTrue(os.path.exists(self.host_path))
1092+
1093+ """
1094+ Make sure we assert out socket is cleaned up. If we are failing here RAII
1095+ is broken.
1096+ """
1097+ def cleanup(self):
1098+ self.host_socket = None
1099+
1100+ self.assertFalse(os.path.exists(self.session_path))
1101+ self.assertFalse(os.path.exists(self.host_path))
1102+ shutil.rmtree(os.environ['XDG_RUNTIME_DIR'])
1103+
1104+ """
1105+ A function used to just read from the host socket. In a different thread
1106+ """
1107+ def host_read(self, expected_bytes):
1108+ conn, addr = self.host_socket.socket.accept()
1109+ data = conn.recv(1024)
1110+ conn.close()
1111+ self.assertThat(data, Equals(expected_bytes))
1112+
1113+ def test_creates_socket_file(self):
1114+ """
1115+ We assert when we create a lsb we create the session socket
1116+ """
1117+ lsb = LibertineSessionBridge([HostSessionSocketPair(self.host_path, self.session_path)])
1118+ self.assertTrue(os.path.exists(self.session_path))
1119+
1120+
1121+ def test_data_read_from_host_to_session(self):
1122+ """
1123+ This test shows we are able to proxy data from a host socket to a session client.
1124+ We do this by:
1125+ 1) Create a valid host socket
1126+ 2) Start a thread which waits to asserts out host socket recv the expected data
1127+ 3) Start the lsb thread to create a proxy session socket
1128+ 4) We create a fake session client (socket) and connect to the session path
1129+ 5) We send the expected bytes to the fake session client socket
1130+ 6) We join on the host sock loop, if we never get the expected bytes in the host socket loop we fail
1131+ 7) Clean up, and assert all our threads are not alive and have been cleaned up
1132+ """
1133+ expected_bytes = b'Five exclamation marks, the sure sign of an insane mind.'
1134+ host_sock_loop = threading.Thread(target=self.host_read, args=(expected_bytes,))
1135+ host_sock_loop.start()
1136+
1137+ lsb = LibertineSessionBridge([HostSessionSocketPair(self.host_path, self.session_path)])
1138+ lsb_process = Process(target=lsb.main_loop)
1139+ lsb_process.start()
1140+
1141+ fake_session_client = socket(AF_UNIX, SOCK_STREAM)
1142+ fake_session_client.connect(self.session_path)
1143+ fake_session_client.sendall(expected_bytes)
1144+
1145+ host_sock_loop.join(timeout=1)
1146+ fake_session_client.close()
1147+ lsb_process.terminate()
1148+ lsb_process.join(timeout=1)
1149+
1150+ self.assertFalse(host_sock_loop.is_alive())
1151+ self.assertFalse(lsb_process.is_alive())
1152
1153=== added file 'tests/unit/libertine_socket_tests.py'
1154--- tests/unit/libertine_socket_tests.py 1970-01-01 00:00:00 +0000
1155+++ tests/unit/libertine_socket_tests.py 2016-09-14 14:44:20 +0000
1156@@ -0,0 +1,72 @@
1157+# Copyright 2016 Canonical Ltd.
1158+#
1159+# This program is free software: you can redistribute it and/or modify it
1160+# under the terms of the GNU General Public License version 3, as published
1161+# by the Free Software Foundation.
1162+#
1163+# This program is distributed in the hope that it will be useful, but
1164+# WITHOUT ANY WARRANTY; without even the implied warranties of
1165+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1166+# PURPOSE. See the GNU General Public License for more details.
1167+#
1168+# You should have received a copy of the GNU General Public License along
1169+# with this program. If not, see <http://www.gnu.org/licenses/>.
1170+
1171+from libertine import Socket
1172+
1173+from socket import socket
1174+
1175+from testtools import TestCase
1176+from testtools.matchers import Equals, NotEquals
1177+
1178+class FakeSocket(Socket):
1179+ """
1180+ We are making things up here... soo lets not attempt to remove anything!
1181+ """
1182+ def __del__(self):
1183+ pass
1184+
1185+
1186+class TestSocket(TestCase):
1187+ def setUp(self):
1188+ super(TestSocket, self).setUp()
1189+ self.socket1 = socket()
1190+ self.socket2 = socket()
1191+
1192+ """
1193+ Test we can wrap a python socket.socket in our Socket class
1194+ """
1195+ def test_socket_warp(self):
1196+ s = FakeSocket(self.socket1)
1197+ self.assertThat(s, Equals(self.socket1))
1198+
1199+ """
1200+ Test our equivalence operator works on three types:
1201+ 1: Other Socket classes
1202+ 2: Python socket classes
1203+ 3: The fd/socket raw Int value
1204+ """
1205+ def test_socket_eq_op(self):
1206+ s1 = FakeSocket(self.socket1)
1207+ s2 = FakeSocket(self.socket1)
1208+ self.assertThat(s1, Equals(s2))
1209+ self.assertThat(s2, Equals(self.socket1))
1210+ self.assertThat(s2, Equals(self.socket1.fileno()))
1211+
1212+ """
1213+ Test our not equivalence works on the same three types
1214+ """
1215+ def test_socket_not_eq_op(self):
1216+ s1 = FakeSocket(self.socket1)
1217+ s2 = FakeSocket(self.socket2)
1218+ self.assertThat(s1, NotEquals(s2))
1219+ self.assertThat(s2, NotEquals(self.socket1))
1220+ self.assertThat(s2, NotEquals(self.socket1.fileno()))
1221+
1222+ """
1223+ Test our Socket wrapper class is also hashable
1224+ """
1225+ def test_socket_is_hashable(self):
1226+ s = FakeSocket(self.socket1)
1227+ dic = {s: 17}
1228+ self.assertThat(dic[s], Equals(17))
1229
1230=== modified file 'tools/CMakeLists.txt'
1231--- tools/CMakeLists.txt 2016-07-15 18:39:58 +0000
1232+++ tools/CMakeLists.txt 2016-09-14 14:44:20 +0000
1233@@ -1,6 +1,6 @@
1234-install(PROGRAMS libertine-container-manager libertine-launch libertine-session-bridge libertine-lxc-manager libertine-xmir libertine-lxc-setup
1235+install(PROGRAMS libertine-container-manager libertine-launch libertine-lxc-manager libertine-xmir libertine-lxc-setup
1236 DESTINATION ${CMAKE_INSTALL_BINDIR})
1237-install(FILES libertine-launch.1 libertine-container-manager.1 libertine-session-bridge.1 libertine-lxc-manager.1 libertine-xmir.1
1238+install(FILES libertine-launch.1 libertine-container-manager.1 libertine-lxc-manager.1 libertine-xmir.1
1239 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
1240 COMPONENT doc)
1241 install(PROGRAMS update-puritine-containers
1242
1243=== modified file 'tools/libertine-container-manager'
1244--- tools/libertine-container-manager 2016-08-12 16:11:57 +0000
1245+++ tools/libertine-container-manager 2016-09-14 14:44:20 +0000
1246@@ -274,20 +274,21 @@
1247 self.containers_config.merge_container_config_files(args.file)
1248
1249 def fix_integrity(self, args):
1250- for container in self.containers_config.container_list['containerList']:
1251- if 'installStatus' not in container or container['installStatus'] != 'ready':
1252- self.destroy_container_by_id(container['id'])
1253- continue
1254- LibertineContainer(container['id']).exec_command('dpkg --configure -a')
1255-
1256- for package in container['installedApps']:
1257- if package['appStatus'] != 'installed':
1258- self.remove_package_by_name(container['id'], package['packageName'])
1259-
1260- if 'extraArchives' in container:
1261- for archive in container['extraArchives']:
1262- if archive['archiveStatus'] != 'installed':
1263- self.delete_archive_by_name(container['id'], archive['archiveName'])
1264+ if 'containerList' in self.containers_config.container_list:
1265+ for container in self.containers_config.container_list['containerList']:
1266+ if 'installStatus' not in container or container['installStatus'] != 'ready':
1267+ self.destroy_container_by_id(container['id'])
1268+ continue
1269+ LibertineContainer(container['id']).exec_command('dpkg --configure -a')
1270+
1271+ for package in container['installedApps']:
1272+ if package['appStatus'] != 'installed':
1273+ self.remove_package_by_name(container['id'], package['packageName'])
1274+
1275+ if 'extraArchives' in container:
1276+ for archive in container['extraArchives']:
1277+ if archive['archiveStatus'] != 'installed':
1278+ self.delete_archive_by_name(container['id'], archive['archiveName'])
1279
1280 def set_default(self, args):
1281 if args.clear:
1282
1283=== modified file 'tools/libertine-launch'
1284--- tools/libertine-launch 2016-08-10 12:45:13 +0000
1285+++ tools/libertine-launch 2016-09-14 14:44:20 +0000
1286@@ -17,13 +17,56 @@
1287 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1288
1289 import argparse
1290+import dbus
1291 import os
1292 import random
1293 import string
1294 import libertine.utils
1295 import shlex
1296 import time
1297-from libertine import LibertineApplication
1298+from libertine import LibertineApplication, HostSessionSocketPair
1299+
1300+
1301+def get_host_maliit_socket():
1302+ address_bus_name = 'org.maliit.server'
1303+ address_object_path = '/org/maliit/server/address'
1304+ address_interface = 'org.maliit.Server.Address'
1305+ address_property = 'address'
1306+ address = ''
1307+
1308+ try:
1309+ session_bus = dbus.SessionBus()
1310+ maliit_object = session_bus.get_object('org.maliit.server', '/org/maliit/server/address')
1311+
1312+ interface = dbus.Interface(maliit_object, dbus.PROPERTIES_IFACE)
1313+ address = interface.Get('org.maliit.Server.Address', 'address')
1314+
1315+ partition_key = 'unix:abstract='
1316+ address = address.split(',')[0]
1317+ address = address.partition(partition_key)[2]
1318+ address = "\0%s" % address
1319+ except:
1320+ pass
1321+
1322+ return address
1323+
1324+
1325+def get_host_dbus_socket():
1326+ host_dbus_socket = ''
1327+
1328+ with open(os.path.join(libertine.utils.get_user_runtime_dir(), 'dbus-session'), 'r') as fd:
1329+ dbus_session_str = fd.read()
1330+
1331+ fd.close()
1332+
1333+ dbus_session_split = dbus_session_str.rsplit('=', 1)
1334+ if len(dbus_session_split) > 1:
1335+ host_dbus_socket = dbus_session_split[1].rstrip('\n')
1336+ # We need to add a \0 to the start of an abstract socket path to connect to it
1337+ if dbus_session_str.find('abstract') >= 0:
1338+ host_dbus_socket = "\0%s" % host_dbus_socket
1339+
1340+ return host_dbus_socket
1341
1342
1343 def get_session_socket_path(session_socket_name):
1344@@ -85,16 +128,28 @@
1345 if e in os.environ:
1346 del os.environ[e]
1347
1348- dbus_socket_path = get_dbus_session_socket_path()
1349- maliit_socket_path = get_maliit_session_socket_path()
1350-
1351- la.launch_session_bridge([dbus_socket_path, maliit_socket_path])
1352-
1353- set_dbus_env_socket_path(dbus_socket_path)
1354- set_maliit_env_socket_path(maliit_socket_path)
1355+ socket_paths = []
1356+
1357+ maliit_host_path = get_host_maliit_socket()
1358+
1359+ # Maliit needs to check with the real session dbus, so it needs to go before setting
1360+ # the DBUS_SESSION_ADDRESS to the session socket path
1361+ if maliit_host_path:
1362+ maliit_session_path = get_maliit_session_socket_path()
1363+ socket_paths.append(HostSessionSocketPair(maliit_host_path, maliit_session_path))
1364+ set_maliit_env_socket_path(maliit_session_path)
1365+
1366+ dbus_host_path = get_host_dbus_socket()
1367+
1368+ if dbus_host_path:
1369+ dbus_session_path = get_dbus_session_socket_path()
1370+ socket_paths.append(HostSessionSocketPair(dbus_host_path, dbus_session_path))
1371+ set_dbus_env_socket_path(dbus_session_path)
1372+
1373+ la.launch_session_bridge(socket_paths)
1374
1375 # should detect the maliit socket, but dont know if its around or not here.
1376- detect_session_bridge_socket(dbus_socket_path)
1377+ detect_session_bridge_socket(dbus_session_path)
1378
1379 la.launch_pasted()
1380
1381
1382=== modified file 'tools/libertine-lxc-manager'
1383--- tools/libertine-lxc-manager 2016-08-12 16:11:57 +0000
1384+++ tools/libertine-lxc-manager 2016-09-14 14:44:20 +0000
1385@@ -1,7 +1,7 @@
1386 #!/usr/bin/python3
1387 # -*- coding: utf-8 -*-
1388
1389-# Copyright (C) 2015 Canonical Ltd.
1390+# Copyright (C) 2016 Canonical Ltd.
1391 # Author: Christopher Townsend <christopher.townsend@canonical.com>
1392
1393 # This program is free software: you can redistribute it and/or modify
1394@@ -16,121 +16,118 @@
1395 # You should have received a copy of the GNU General Public License
1396 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1397
1398+import dbus
1399+import dbus.service
1400 import libertine.LxcContainer
1401 import libertine.utils
1402 import os
1403-import select
1404+import shlex
1405 import signal
1406-import shlex
1407 import subprocess
1408
1409 from collections import Counter
1410-from socket import *
1411-
1412-
1413-def lxc_setup_pulse():
1414- pulse_socket_path = os.path.join(libertine.utils.get_libertine_runtime_dir(), 'pulse_socket')
1415-
1416- lsof_cmd = 'lsof -n %s' % pulse_socket_path
1417- args = shlex.split(lsof_cmd)
1418- lsof = subprocess.Popen(args, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
1419- lsof.wait()
1420-
1421- if not os.path.exists(pulse_socket_path) or lsof.returncode == 1:
1422- pactl_cmd = (
1423- 'pactl load-module module-native-protocol-unix auth-anonymous=1 socket=%s'
1424- % pulse_socket_path)
1425- args = shlex.split(pactl_cmd)
1426- subprocess.Popen(args).wait()
1427-
1428-
1429-def launch_lxc_container(container_id):
1430- container = libertine.LxcContainer.LibertineLXC(container_id)
1431- lxc_setup_pulse()
1432-
1433- if not container.is_running():
1434- try:
1435- container.start_container(True)
1436- except RuntimeError as e:
1437- print("%s" % e)
1438- return False
1439-
1440- return True
1441-
1442-
1443-def stop_lxc_container(container_id):
1444- container = libertine.LxcContainer.LibertineLXC(container_id)
1445- if container.is_running():
1446- container.stop_container()
1447-
1448-
1449-def socket_cleanup(signum, frame):
1450- libertine_lxc_mgr_socket.close()
1451-
1452-
1453-def process_data(data):
1454- msg = data.decode().split(' ')
1455- op_code = msg[0]
1456- container_id = msg[1]
1457-
1458- if op_code == 'start':
1459- if not launch_lxc_container(container_id):
1460- return 'FAILED'
1461- app_counter[container_id] += 1
1462-
1463- elif op_code == 'stop':
1464- app_counter[container_id] -= 1
1465-
1466- if app_counter[container_id] == 0:
1467- stop_lxc_container(container_id)
1468-
1469- return 'OK'
1470-
1471-
1472-def main_loop():
1473- while 1:
1474- try:
1475- rlist, wlist, elist = select.select(descriptors, [], [])
1476- except InterruptedError:
1477- continue
1478- except:
1479- break
1480-
1481- for sock in rlist:
1482- if sock.fileno() == -1:
1483- continue
1484-
1485- if sock == libertine_lxc_mgr_socket:
1486- sock_fd = libertine_lxc_mgr_socket.accept()[0]
1487- descriptors.append(sock_fd)
1488-
1489- else:
1490- data = sock.recv(1024)
1491-
1492- if len(data) == 0:
1493- descriptors.remove(sock)
1494- sock.shutdown(SHUT_RDWR)
1495- sock.close()
1496- continue
1497-
1498-
1499- message = process_data(data)
1500- sock.send(message.encode())
1501+from dbus.mainloop.glib import DBusGMainLoop
1502+from gi.repository import GLib
1503+
1504+
1505+LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxcManager"
1506+LIBERTINE_LXC_MANAGER_PATH = "/LxcManager"
1507+
1508+
1509+class Service(dbus.service.Object):
1510+
1511+ def __init__(self):
1512+ self.is_pulse_setup = False
1513+ self.app_counter = Counter()
1514+
1515+ DBusGMainLoop(set_as_default=True)
1516+ try:
1517+ bus_name = dbus.service.BusName(LIBERTINE_LXC_MANAGER_NAME,
1518+ bus=dbus.SessionBus(),
1519+ do_not_queue=True)
1520+ except dbus.exceptions.NameExistsException:
1521+ print("service is already running")
1522+ raise
1523+ super().__init__(bus_name, LIBERTINE_LXC_MANAGER_PATH)
1524+
1525+ @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
1526+ in_signature='s',
1527+ out_signature='b')
1528+ def app_start(self, container_id):
1529+ started = self._launch_lxc_container(container_id)
1530+
1531+ if started:
1532+ self.app_counter[container_id] += 1
1533+
1534+ return started
1535+
1536+ @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
1537+ in_signature='s')
1538+ def app_stop(self, container_id):
1539+ self.app_counter[container_id] -= 1
1540+
1541+ if self.app_counter[container_id] == 0:
1542+ self._stop_lxc_container(container_id)
1543+ del self.app_counter[container_id]
1544+
1545+ def _setup_pulse(self):
1546+ pulse_socket_path = os.path.join(libertine.utils.get_libertine_runtime_dir(), 'pulse_socket')
1547+
1548+ lsof_cmd = 'lsof -n %s' % pulse_socket_path
1549+ args = shlex.split(lsof_cmd)
1550+ lsof = subprocess.Popen(args, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
1551+ lsof.wait()
1552+
1553+ if not os.path.exists(pulse_socket_path) or lsof.returncode == 1:
1554+ pactl_cmd = (
1555+ 'pactl load-module module-native-protocol-unix auth-anonymous=1 socket=%s'
1556+ % pulse_socket_path)
1557+ args = shlex.split(pactl_cmd)
1558+ subprocess.Popen(args).wait()
1559+
1560+ self.is_pulse_setup = True
1561+
1562+ def _launch_lxc_container(self, container_id):
1563+ container = libertine.LxcContainer.LibertineLXC(container_id)
1564+
1565+ if not self.is_pulse_setup:
1566+ self._setup_pulse()
1567+
1568+ if not container.is_running():
1569+ try:
1570+ container.start_container(True)
1571+ except RuntimeError as e:
1572+ print("%s" % e)
1573+ return False
1574+
1575+ return True
1576+
1577+ def _stop_lxc_container(self, container_id):
1578+ container = libertine.LxcContainer.LibertineLXC(container_id)
1579+ if container.is_running():
1580+ container.stop_container()
1581+
1582+
1583+def sigterm(self):
1584+ shutdown()
1585+
1586+
1587+def shutdown():
1588+ GLib.MainLoop().quit()
1589+
1590+
1591+def main():
1592+ service = Service()
1593+ GLib.unix_signal_add(GLib.PRIORITY_HIGH,
1594+ signal.SIGTERM,
1595+ sigterm,
1596+ None)
1597+
1598+ try:
1599+ GLib.MainLoop().run()
1600+ except KeyboardInterrupt:
1601+ shutdown()
1602
1603
1604 if __name__ == '__main__':
1605- signal.signal(signal.SIGTERM, socket_cleanup)
1606- signal.signal(signal.SIGINT, socket_cleanup)
1607-
1608- if not os.path.exists(libertine.utils.get_libertine_runtime_dir()):
1609- os.makedirs(libertine.utils.get_libertine_runtime_dir())
1610-
1611- app_counter = Counter()
1612-
1613- libertine_lxc_mgr_socket = socket(AF_UNIX, SOCK_STREAM)
1614- libertine_lxc_mgr_socket.bind(libertine.utils.get_libertine_lxc_socket())
1615- libertine_lxc_mgr_socket.listen(20)
1616-
1617- descriptors = [libertine_lxc_mgr_socket]
1618-
1619- main_loop()
1620+ main()
1621
1622=== removed file 'tools/libertine-session-bridge'
1623--- tools/libertine-session-bridge 2016-08-12 16:11:57 +0000
1624+++ tools/libertine-session-bridge 1970-01-01 00:00:00 +0000
1625@@ -1,205 +0,0 @@
1626-#!/usr/bin/python3
1627-
1628-import dbus
1629-import libertine.utils
1630-import os
1631-import select
1632-import signal
1633-import sys
1634-
1635-from socket import *
1636-
1637-
1638-def accept_new_connection(host_adder, container_sock):
1639- newconn = container_sock.accept()[0]
1640- descriptors.append(newconn)
1641-
1642- host_sock = socket(AF_UNIX, SOCK_STREAM)
1643- host_sock.connect(host_adder)
1644- descriptors.append(host_sock)
1645-
1646- socket_pairs.append([newconn, host_sock])
1647-
1648-
1649-def get_socket_pair(socket):
1650- for i in range(len(socket_pairs)):
1651- if socket in socket_pairs[i]:
1652- return socket_pairs[i]
1653-
1654-
1655-def get_socket_partner(socket):
1656- socket_pair = get_socket_pair(socket)
1657-
1658- for i in range(len(socket_pair)):
1659- if socket != socket_pair[i]:
1660- return socket_pair[i]
1661-
1662-
1663-def close_connections(remove_socket):
1664- partner_socket = get_socket_partner(remove_socket)
1665-
1666- socket_pair = get_socket_pair(remove_socket)
1667- socket_pairs.remove(socket_pair)
1668-
1669- descriptors.remove(remove_socket)
1670- remove_socket.shutdown(SHUT_RDWR)
1671- remove_socket.close()
1672-
1673- descriptors.remove(partner_socket)
1674- partner_socket.shutdown(SHUT_RDWR)
1675- partner_socket.close()
1676-
1677-
1678-def close_all_connections():
1679- for i, j in socket_pairs:
1680- i.shutdown(SHUT_RDWR)
1681- i.close()
1682- j.shutdown(SHUT_RDWR)
1683- j.close()
1684-
1685-
1686-def get_host_maliit_socket():
1687- address_bus_name = "org.maliit.server"
1688- address_object_path = "/org/maliit/server/address"
1689- address_interface = "org.maliit.Server.Address"
1690- address_property = "address"
1691-
1692- session_bus = dbus.SessionBus()
1693- maliit_object = session_bus.get_object('org.maliit.server', '/org/maliit/server/address')
1694-
1695- interface = dbus.Interface(maliit_object, dbus.PROPERTIES_IFACE)
1696- address = interface.Get('org.maliit.Server.Address', 'address')
1697-
1698- partition_key = 'unix:abstract='
1699- address = address.split(',')[0]
1700- address = address.partition(partition_key)[2]
1701- address = "\0%s" % address
1702-
1703- return address
1704-
1705-
1706-def get_host_dbus_socket():
1707- host_dbus_socket = ''
1708-
1709- with open(os.path.join(libertine.utils.get_user_runtime_dir(), 'dbus-session'), 'r') as fd:
1710- dbus_session_str = fd.read()
1711-
1712- fd.close()
1713-
1714- dbus_session_split = dbus_session_str.rsplit('=', 1)
1715- if len(dbus_session_split) > 1:
1716- host_dbus_socket = dbus_session_split[1].rstrip('\n')
1717- # We need to add a \0 to the start of an abstract socket path to connect to it
1718- if dbus_session_str.find('abstract') >= 0:
1719- host_dbus_socket = "\0%s" % host_dbus_socket
1720-
1721- return host_dbus_socket
1722-
1723-
1724-def socket_cleanup(signum, frame):
1725- for socket in descriptors:
1726- socket.close()
1727-
1728- close_all_connections()
1729-
1730- for socket_path in session_socket_paths:
1731- os.remove(socket_path)
1732-
1733-
1734-def main_loop():
1735- signal.signal(signal.SIGTERM, socket_cleanup)
1736- signal.signal(signal.SIGINT, socket_cleanup)
1737-
1738- while 1:
1739- try:
1740- rlist, wlist, elist = select.select(descriptors, [], [])
1741- except InterruptedError:
1742- continue
1743- except:
1744- break
1745-
1746- for sock in rlist:
1747- if sock.fileno() == -1:
1748- continue
1749-
1750- if sock in host_session_socket_path_map:
1751- accept_new_connection(host_session_socket_path_map[sock], sock)
1752-
1753- else:
1754- try:
1755- data = sock.recv(4096)
1756- except:
1757- close_connections(sock)
1758- continue
1759-
1760- if len(data) == 0:
1761- close_connections(sock)
1762- continue
1763-
1764- send_sock = get_socket_partner(sock)
1765-
1766- if send_sock.fileno() < 0:
1767- continue
1768-
1769- totalsent = 0
1770- while totalsent < len(data):
1771- sent = send_sock.send(data)
1772-
1773- if sent == 0:
1774- close_connections(sock)
1775- break
1776- totalsent = totalsent + sent
1777-
1778-
1779-def create_socket(session_socket_path):
1780- try:
1781- sock = socket(AF_UNIX, SOCK_STREAM)
1782- except:
1783- sock = None
1784- else:
1785- try:
1786- sock.bind(session_socket_path)
1787- sock.listen(5)
1788- except:
1789- sock.close()
1790- sock = None
1791- else:
1792- return sock
1793-
1794- return None
1795-
1796-
1797-def create_container_socket(session_socket_path, get_host_session_path_function):
1798- container_session_sock = create_socket(session_socket_path)
1799-
1800- if container_session_sock is not None:
1801- try:
1802- host_session_path = get_host_session_path_function()
1803- except:
1804- container_session_sock.close()
1805- container_session_sock = None
1806- os.remove(session_socket_path)
1807- raise
1808- else:
1809- host_session_socket_path_map.update({container_session_sock:host_session_path})
1810-
1811- session_socket_paths.append(session_socket_path)
1812- descriptors.append(container_session_sock)
1813-
1814-
1815-descriptors = []
1816-host_session_socket_path_map = {}
1817-session_socket_paths = []
1818-
1819-# Required sockets:
1820-create_container_socket(sys.argv[1], get_host_dbus_socket)
1821-
1822-# Optional sockets:
1823-try:
1824- create_container_socket(sys.argv[2], get_host_maliit_socket)
1825-except:
1826- pass
1827-
1828-socket_pairs = []
1829-
1830-main_loop()
1831
1832=== removed file 'tools/libertine-session-bridge.1'
1833--- tools/libertine-session-bridge.1 2016-04-06 12:31:26 +0000
1834+++ tools/libertine-session-bridge.1 1970-01-01 00:00:00 +0000
1835@@ -1,13 +0,0 @@
1836-.TH libertine-session-bridge "1" "April 2016" "libertine-session-bridge 0.99" "User Commands"
1837-
1838-.SH NAME
1839-libertine-session-bridge \- listen for data from a DBUS session
1840-
1841-.SH DESCRIPTION
1842-usage: libertine\-session-bridge DBUS_SOCKET
1843-.PP
1844-listen for data from a DBUS session on the given socket path
1845-.SS "positional arguments:"
1846-.TP
1847-DBUS_SOCKET
1848-path to DBUS socket

Subscribers

People subscribed via source and target branches