13
13
# with this program. If not, see <http://www.gnu.org/licenses/>.
24
from .lifecycle import LifecycleResult
22
25
from libertine import Libertine, utils, HostInfo
28
LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxdManager"
29
LIBERTINE_LXC_MANAGER_PATH = "/LxdManager"
32
def get_lxd_manager_dbus_name():
33
return LIBERTINE_LXC_MANAGER_NAME
36
def get_lxd_manager_dbus_path():
37
return LIBERTINE_LXC_MANAGER_PATH
25
40
def _get_devices_map():
27
42
'/dev/tty0': {'path': '/dev/tty0', 'type': 'unix-char'},
109
126
[ -n /dev/video0 ] && chgrp video /dev/video0
111
128
container.files.put('/usr/bin/libertine-lxd-mount-update', script.format(uid=uid, username=username).encode('utf-8'))
112
container.execute(shlex.split('chmod +x /usr/bin/libertine-lxd-mount-update'))
129
container.execute(shlex.split('chmod 755 /usr/bin/libertine-lxd-mount-update'))
114
131
utils.get_logger().info("Enabling systemd mount update service")
115
132
container.execute(shlex.split('systemctl enable libertine-lxd-mount-update.service'))
135
def lxd_container(client, container_id):
137
return client.containers.get(container_id)
138
except pylxd.exceptions.LXDAPIException:
142
def _wait_for_network(container):
143
for retries in range(0, 10):
144
out, err = container.execute(shlex.split('ping -c 1 ubuntu.com'))
146
utils.get_logger().info("Network connection active")
152
def lxd_start(container):
153
if container.status != 'Running':
154
container.start(wait=True)
156
container.sync(rollback=True) # required for pylxd=2.0.x
158
if container.status != 'Running':
159
return LifecycleResult("Container {} failed to start".format(container.name))
161
return LifecycleResult()
164
def lxd_stop(container, wait):
165
if container.status == 'Stopped':
166
return LifecycleResult()
168
container.stop(wait=wait)
169
container.sync(rollback=True) # required for pylxd=2.0.x
171
if wait and container.status != 'Stopped':
172
return LifecycleResult("Container {} failed to stop".format(container.name))
174
return LifecycleResult()
177
def update_bind_mounts(container, config):
178
home_path = os.environ['HOME']
180
container.devices.clear()
181
container.devices['root'] = {'type': 'disk', 'path': '/'}
183
if os.path.exists(os.path.join(home_path, '.config', 'dconf')):
184
container.devices['dconf'] = {
186
'source': os.path.join(home_path, '.config', 'dconf'),
187
'path': os.path.join(home_path, '.config', 'dconf')
190
run_user = '/run/user/{}'.format(os.getuid())
191
container.devices[run_user] = {'source': run_user, 'path': '/var/tmp{}'.format(run_user), 'type': 'disk'}
193
mounts = utils.get_common_xdg_user_directories() + \
194
config.get_container_bind_mounts(container.name)
195
for user_dir in utils.generate_binding_directories(mounts, home_path):
196
if not os.path.exists(user_dir[0]):
197
utils.get_logger().warning('Bind-mount path \'{}\' does not exist.'.format(user_dir[0]))
200
if os.path.isabs(user_dir[1]):
203
path = os.path.join(home_path, user_dir[1])
205
utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container.name))
207
container.devices[user_dir[1]] = {
208
'source': _readlink(user_dir[0]),
215
container.save(wait=True)
216
except pylxd.exceptions.LXDAPIException as e:
217
utils.get_logger().warning('Saving bind mounts for container \'{}\' raised: {}'.format(container.name, str(e)))
218
# This is most likely the result of the container currently running
221
def update_libertine_profile(client):
223
profile = client.profiles.get('libertine')
225
utils.get_logger().info('Updating existing lxd profile.')
226
profile.devices = _get_devices_map()
227
profile.config['raw.idmap'] = 'both 1000 1000'
231
except pylxd.exceptions.LXDAPIException as e:
232
utils.get_logger().warning('Saving libertine lxd profile raised: {}'.format(str(e)))
233
# This is most likely the result of an older container currently
234
# running and/or containing a conflicting device entry
235
except pylxd.exceptions.LXDAPIException:
236
utils.get_logger().info('Creating libertine lxd profile.')
237
client.profiles.create('libertine', config={'raw.idmap': 'both 1000 1000'}, devices=_get_devices_map())
118
240
class LibertineLXD(Libertine.BaseContainer):
119
241
def __init__(self, name, config):
120
242
super().__init__(name)
131
253
self._window_manager = None
132
254
self.root_path = '{}/containers/{}/rootfs'.format(os.getenv('LXD_DIR', '/var/lib/lxd'), name)
134
def _update_libertine_profile(self):
256
utils.set_session_dbus_env_var()
136
profile = self._client.profiles.get('libertine')
138
utils.get_logger().info('Updating existing lxd profile.')
139
profile.devices = _get_devices_map()
140
profile.config['raw.idmap'] = 'both 1000 1000'
144
except pylxd.exceptions.LXDAPIException as e:
145
utils.get_logger().warning('Saving libertine lxd profile raised: {}'.format(str(e)))
146
# This is most likely the result of an older container currently
147
# running and/or containing a conflicting device entry
148
except pylxd.exceptions.LXDAPIException:
149
utils.get_logger().info('Creating libertine lxd profile.')
150
self._client.profiles.create('libertine', config={'raw.idmap': 'both 1000 1000'}, devices=_get_devices_map())
152
def create_libertine_container(self, password=None, multiarch=False):
258
bus = dbus.SessionBus()
259
self._manager = bus.get_object(get_lxd_manager_dbus_name(), get_lxd_manager_dbus_path())
260
except dbus.exceptions.DBusException:
261
utils.get_logger().warning("D-Bus Service not found.")
265
def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
153
266
if self._try_get_container():
154
267
utils.get_logger().error("Container already exists")
157
self._update_libertine_profile()
270
update_libertine_profile(self._client)
159
272
utils.get_logger().info("Creating container '%s' with distro '%s'" % (self._id, self.installed_release))
160
273
create = subprocess.Popen(shlex.split('lxc launch ubuntu-daily:{distro} {id} --profile '
243
347
if not self._try_get_container():
246
if self._container.status != 'Running':
247
self._container.start(wait=True)
351
result = LifecycleResult.from_dict(self._manager.operation_start(self._id))
353
result = lxd_start(self._container)
250
if not self._wait_for_network():
251
utils.get_logger().error("Network unavailable in container '{}'".format(self._id))
355
if not result.success:
356
utils.get_logger().error(result.error)
254
self._container.sync(rollback=True) # required for pylxd=2.0.x
359
if not _wait_for_network(self._container):
360
utils.get_logger().warning("Network unavailable in container '{}'".format(self._id))
256
return self._container.status == 'Running'
362
return result.success
258
364
def stop_container(self, wait=False):
259
365
if not self._try_get_container():
262
if self._container.status == 'Stopped':
265
self._container.stop(wait=wait)
267
return not wait or self._container.status == 'Stopped'
269
def _update_bind_mounts(self):
270
home_path = os.environ['HOME']
272
self._container.devices.clear()
273
self._container.devices['root'] = {'type': 'disk', 'path': '/'}
275
if os.path.exists(os.path.join(home_path, '.config', 'dconf')):
276
self._container.devices['dconf'] = {
278
'source': os.path.join(home_path, '.config', 'dconf'),
279
'path': os.path.join(home_path, '.config', 'dconf')
282
run_user = '/run/user/{}'.format(os.getuid())
283
self._container.devices[run_user] = {'source': run_user, 'path': '/var/tmp{}'.format(run_user), 'type': 'disk'}
285
mounts = utils.get_common_xdg_user_directories() + \
286
self._config.get_container_bind_mounts(self._id)
287
for user_dir in utils.generate_binding_directories(mounts, home_path):
288
if not os.path.exists(user_dir[0]):
289
utils.get_logger().warning('Bind-mount path \'{}\' does not exist.'.format(user_dir[0]))
292
if os.path.isabs(user_dir[1]):
295
path = os.path.join(home_path, user_dir[1])
297
utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, self._id))
298
self._container.devices[user_dir[1]] = {
299
'source': _readlink(user_dir[0]),
306
self._container.save(wait=True)
307
except pylxd.exceptions.LXDAPIException as e:
308
utils.get_logger().warning('Saving bind mounts for container \'{}\' raised: {}'.format(self._id, str(e)))
309
# This is most likely the result of the container currently running
369
result = LifecycleResult.from_dict(self._manager.operation_stop(self._id))
371
result = lxd_stop(self._container, wait)
373
if not result.success:
374
utils.get_logger().error(result.error)
376
return result.success
311
378
def _get_matchbox_pids(self):
312
379
p = subprocess.Popen(self._lxc_args('pgrep matchbox'), stdout=subprocess.PIPE)