~townsend/libertine/remove-X-umount

« back to all changes in this revision

Viewing changes to python/libertine/LxdContainer.py

  • Committer: Tarmac
  • Author(s): Larry Price
  • Date: 2017-01-05 18:38:37 UTC
  • mfrom: (302.30.14 lxd-manager)
  • Revision ID: tarmac-20170105183837-zlcaa8xncukr51n5
Create d-bus service for lxd container management.

Approved by Christopher Townsend, Libertine CI Bot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
# with this program.  If not, see <http://www.gnu.org/licenses/>.
14
14
 
15
15
import crypt
 
16
import dbus
16
17
import os
17
18
import psutil
18
19
import pylxd
19
20
import shlex
20
21
import subprocess
21
22
import time
 
23
 
 
24
from .lifecycle import LifecycleResult
22
25
from libertine import Libertine, utils, HostInfo
23
26
 
24
27
 
 
28
LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxdManager"
 
29
LIBERTINE_LXC_MANAGER_PATH = "/LxdManager"
 
30
 
 
31
 
 
32
def get_lxd_manager_dbus_name():
 
33
    return LIBERTINE_LXC_MANAGER_NAME
 
34
 
 
35
 
 
36
def get_lxd_manager_dbus_path():
 
37
    return LIBERTINE_LXC_MANAGER_PATH
 
38
 
 
39
 
25
40
def _get_devices_map():
26
41
    devices = {
27
42
        '/dev/tty0':   {'path': '/dev/tty0', 'type': 'unix-char'},
95
110
WantedBy=multi-user.target
96
111
'''[1:-1]
97
112
    container.files.put('/etc/systemd/system/libertine-lxd-mount-update.service', service.encode('utf-8'))
 
113
    container.execute(shlex.split('chmod 644 /etc/systemd/system/libertine-lxd-mount-update.service'))
98
114
 
99
115
    utils.get_logger().info("Creating mount update shell script")
100
116
    script = '''
102
118
 
103
119
mkdir -p /run/user/{uid}
104
120
chown {username}:{username} /run/user/{uid}
 
121
chmod 755 /run/user/{uid}
105
122
mount -o bind /var/tmp/run/user/{uid} /run/user/{uid}
106
123
 
107
124
chgrp audio /dev/snd/*
109
126
[ -n /dev/video0 ] && chgrp video /dev/video0
110
127
'''[1:-1]
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'))
113
130
 
114
131
    utils.get_logger().info("Enabling systemd mount update service")
115
132
    container.execute(shlex.split('systemctl enable libertine-lxd-mount-update.service'))
116
133
 
117
134
 
 
135
def lxd_container(client, container_id):
 
136
    try:
 
137
        return client.containers.get(container_id)
 
138
    except pylxd.exceptions.LXDAPIException:
 
139
        return None
 
140
 
 
141
 
 
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'))
 
145
        if out:
 
146
            utils.get_logger().info("Network connection active")
 
147
            return True
 
148
        time.sleep(1)
 
149
    return False
 
150
 
 
151
 
 
152
def lxd_start(container):
 
153
    if container.status != 'Running':
 
154
        container.start(wait=True)
 
155
 
 
156
    container.sync(rollback=True) # required for pylxd=2.0.x
 
157
 
 
158
    if container.status != 'Running':
 
159
        return LifecycleResult("Container {} failed to start".format(container.name))
 
160
 
 
161
    return LifecycleResult()
 
162
 
 
163
 
 
164
def lxd_stop(container, wait):
 
165
    if container.status == 'Stopped':
 
166
        return LifecycleResult()
 
167
 
 
168
    container.stop(wait=wait)
 
169
    container.sync(rollback=True) # required for pylxd=2.0.x
 
170
 
 
171
    if wait and container.status != 'Stopped':
 
172
        return LifecycleResult("Container {} failed to stop".format(container.name))
 
173
 
 
174
    return LifecycleResult()
 
175
 
 
176
 
 
177
def update_bind_mounts(container, config):
 
178
    home_path = os.environ['HOME']
 
179
 
 
180
    container.devices.clear()
 
181
    container.devices['root'] = {'type': 'disk', 'path': '/'}
 
182
 
 
183
    if os.path.exists(os.path.join(home_path, '.config', 'dconf')):
 
184
        container.devices['dconf'] = {
 
185
            'type': 'disk',
 
186
            'source': os.path.join(home_path, '.config', 'dconf'),
 
187
            'path': os.path.join(home_path, '.config', 'dconf')
 
188
        }
 
189
 
 
190
    run_user = '/run/user/{}'.format(os.getuid())
 
191
    container.devices[run_user] = {'source': run_user, 'path': '/var/tmp{}'.format(run_user), 'type': 'disk'}
 
192
 
 
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]))
 
198
            continue
 
199
 
 
200
        if os.path.isabs(user_dir[1]):
 
201
            path = user_dir[1]
 
202
        else:
 
203
            path = os.path.join(home_path, user_dir[1])
 
204
 
 
205
        utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container.name))
 
206
 
 
207
        container.devices[user_dir[1]] = {
 
208
                'source': _readlink(user_dir[0]),
 
209
                'path': path,
 
210
                'optional': 'true',
 
211
                'type': 'disk'
 
212
        }
 
213
 
 
214
    try:
 
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
 
219
 
 
220
 
 
221
def update_libertine_profile(client):
 
222
    try:
 
223
        profile = client.profiles.get('libertine')
 
224
 
 
225
        utils.get_logger().info('Updating existing lxd profile.')
 
226
        profile.devices = _get_devices_map()
 
227
        profile.config['raw.idmap'] = 'both 1000 1000'
 
228
 
 
229
        try:
 
230
            profile.save()
 
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())
 
238
 
 
239
 
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)
133
255
 
134
 
    def _update_libertine_profile(self):
 
256
        utils.set_session_dbus_env_var()
135
257
        try:
136
 
            profile = self._client.profiles.get('libertine')
137
 
 
138
 
            utils.get_logger().info('Updating existing lxd profile.')
139
 
            profile.devices = _get_devices_map()
140
 
            profile.config['raw.idmap'] = 'both 1000 1000'
141
 
 
142
 
            try:
143
 
                profile.save()
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())
151
 
 
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.")
 
262
            self._manager = None
 
263
 
 
264
 
 
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")
155
268
            return False
156
269
 
157
 
        self._update_libertine_profile()
 
270
        update_libertine_profile(self._client)
158
271
 
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 '
194
307
 
195
308
        return True
196
309
 
197
 
    def _wait_for_network(self):
198
 
        for retries in range(0, 10):
199
 
            out, err = self._container.execute(shlex.split('ping -c 1 ubuntu.com'))
200
 
            if out:
201
 
                utils.get_logger().info("Network connection active")
202
 
                return True
203
 
            time.sleep(1)
204
 
        return False
205
 
 
206
310
    def update_packages(self):
207
311
        if not self._timezone_in_sync():
208
312
            utils.get_logger().info("Re-syncing timezones")
243
347
        if not self._try_get_container():
244
348
            return False
245
349
 
246
 
        if self._container.status != 'Running':
247
 
            self._container.start(wait=True)
 
350
        if self._manager:
 
351
            result = LifecycleResult.from_dict(self._manager.operation_start(self._id))
 
352
        else:
 
353
            result = lxd_start(self._container)
248
354
 
249
 
        # Connect to network
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)
252
357
            return False
253
358
 
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))
255
361
 
256
 
        return self._container.status == 'Running'
 
362
        return result.success
257
363
 
258
364
    def stop_container(self, wait=False):
259
365
        if not self._try_get_container():
260
366
            return False
261
367
 
262
 
        if self._container.status == 'Stopped':
263
 
            return True
264
 
 
265
 
        self._container.stop(wait=wait)
266
 
 
267
 
        return not wait or self._container.status == 'Stopped'
268
 
 
269
 
    def _update_bind_mounts(self):
270
 
        home_path = os.environ['HOME']
271
 
 
272
 
        self._container.devices.clear()
273
 
        self._container.devices['root'] = {'type': 'disk', 'path': '/'}
274
 
 
275
 
        if os.path.exists(os.path.join(home_path, '.config', 'dconf')):
276
 
            self._container.devices['dconf'] = {
277
 
                'type': 'disk',
278
 
                'source': os.path.join(home_path, '.config', 'dconf'),
279
 
                'path': os.path.join(home_path, '.config', 'dconf')
280
 
            }
281
 
 
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'}
284
 
 
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]))
290
 
                continue
291
 
 
292
 
            if os.path.isabs(user_dir[1]):
293
 
                path = user_dir[1]
294
 
            else:
295
 
                path = os.path.join(home_path, user_dir[1])
296
 
 
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]),
300
 
                    'path': path,
301
 
                    'optional': 'true',
302
 
                    'type': 'disk'
303
 
            }
304
 
 
305
 
        try:
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
 
368
        if self._manager:
 
369
            result = LifecycleResult.from_dict(self._manager.operation_stop(self._id))
 
370
        else:
 
371
            result = lxd_stop(self._container, wait)
 
372
 
 
373
        if not result.success:
 
374
            utils.get_logger().error(result.error)
 
375
 
 
376
        return result.success
310
377
 
311
378
    def _get_matchbox_pids(self):
312
379
        p = subprocess.Popen(self._lxc_args('pgrep matchbox'), stdout=subprocess.PIPE)
337
404
            utils.get_logger().error("Could not get container '{}'".format(self._id))
338
405
            return None
339
406
 
340
 
        self._update_libertine_profile()
341
 
        self._update_bind_mounts()
342
 
        self.start_container()
 
407
        if self._manager:
 
408
            result = LifecycleResult.from_dict(self._manager.app_start(self._id))
 
409
        else:
 
410
            update_libertine_profile(self._client)
 
411
            update_bind_mounts(self._container, self._config)
 
412
            result = lxd_start(self._container)
 
413
 
 
414
        if not result.success:
 
415
            utils.get_logger().error(result.error)
 
416
            return False
343
417
 
344
418
        args = self._lxc_args("sudo -E -u {} env PATH={}".format(os.environ['USER'], environ['PATH']), environ)
345
419
 
362
436
 
363
437
        app.wait()
364
438
 
 
439
        if self._manager:
 
440
            self._manager.app_stop(self.container_id)
 
441
        else:
 
442
            lxd_stop(self._container, False)
 
443
 
365
444
    def copy_file_to_container(self, source, dest):
366
445
        with open(source, 'rb') as f:
367
446
            return self._container.files.put(dest, f.read())
371
450
 
372
451
    def _try_get_container(self):
373
452
        if self._container is None:
374
 
            try:
375
 
                self._container = self._client.containers.get(self._id)
376
 
            except pylxd.exceptions.LXDAPIException:
377
 
                self._container = None
378
 
                return False
 
453
            self._container = lxd_container(self._client, self._id)
379
454
 
380
 
        return True
 
455
        return self._container is not None