Containers

In the application manager context, a container describes an execution environment for an executable: either an application's binary or its runtime binary, in multi-process mode. The container does not have to be something sophisticated like a Docker container, but can be as simple as a Unix process.

Predefined Containers

The application manager comes with one built-in container type: the process container, that spawns a new Unix process to execute the requested binary.

In addition, you can find a basic integration of Pelagicore's SoftwareContainer in examples/applicationmanager/softwarecontainer-plugin. For more information, see SoftwareContainer Plugin Example. This example can be used as a blueprint to either create a customer-specific production version of a SoftwareContainer plugin, or to integrate another container solution.

Extend with Container Plugins

Custom container solutions can be added via plugins. These plugins need not to be built as part of the application manager, but they need to be built against a private Qt module to get the interface definition:

TEMPLATE = lib
CONFIG += plugin
TARGET = mycontainer-plugin

QT += appman_plugininterfaces-private

Then, you only have to implement two classes that derive from ContainerInterface and from ContainerManagerInterface respectively:

#include <QtAppManPluginInterfaces/containerinterface.h>

class SoftwareContainer : public ContainerInterface
{
    // ...
};

class SoftwareContainerManager : public QObject, public ContainerManagerInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID AM_ContainerManagerInterface_iid)
    Q_INTERFACES(ContainerManagerInterface)

    // ....
};

Be aware that your container plugin has to support a few basic requirements to support UI clients in multi-process mode:

  1. The plugin has to be able to forward Unix local sockets into the container. This is needed for both the Wayland socket as well as for the private peer-to-peer D-Bus connection. If the plugin cannot map these sockets to the correct location within the container, the plugin then needs to modify the environment variables for the respective locations before passing them on to the container. The table below lists the relevant environment variables.
  2. To support hardware OpenGL acceleration, the container needs to have access to the necessary devices. For GPUs that follow Linux standards, such as Intel, make sure to have /dev/dri/* available within the container.
  3. You have to implement PID mapping in your plugin; unless your container solution shares its PID namespace with the rest of the ssytem. This is necessary if you want to make use of the application manager's security features. Each connection coming into the application manager via the Wayland or D-Bus Unix local sockets is queried for the PID of the application that requests the connection. The application manager verifies these PIDs against the PIDs of all running applications via ContainerInterface::processId(). Connections that don't match a PID are not accepted. However, you can disable this behavior via the --no-security command line option.

The application manager uses the following environment variables to communicate various settings to the application. A custom container plugin must forward these variables or adjust them accordingly:

NameDescription
WAYLAND_DISPLAYThe path to the Wayland server socket. If your container uses its own filesystem namespace, make sure that this socket is forwarded accordingly.
QT_QPA_PLATFORMAlways set to wayland.
QT_IM_MODULENot set, but explicitly unset by the application manager. Make sure to leave it unset, to use the automatic Wayland input method implementation.
QT_SCALE_FACTOREmpty (unset), to prevent scaling of wayland clients relative to the compositor. Otherwise running the application manager on a 4K desktop with scaling would result in double-scaled applications within the application manager.
QT_WAYLAND_SHELL_INTEGRATIONSet to xdg-shell. This is the preferred wayland shell integration.
DBUS_SESSION_BUS_ADDRESSThe standard D-Bus session bus.
AM_CONFIGA YAML, UTF-8 string encoded version of the amConfig map.
AM_NO_DLT_LOGGINGTells the application to not use DLT for logging.

Configuration

A container configuration has three parts:

  1. Configure which containers are available when loading the container plugins
  2. Add specific settings for each container integration available
  3. Configure which container solution to select to run a specific application

Load Container Plugins

To configure an existing container plugin for use in the application manager, you need to add its full path to the list of plugins to load in the application manager's config file:

plugins:
  container: [ "/full/path/to/softwarecontainers.so", "/another/plugin.so" ]

Note that the application manager does not load plugins automatically if they are placed in a specific directory, since container plugins control the central security mechanism for separating applications.

Add Container Integration Settings

Each container integration has a unique ID, which can be used to add settings to the application manager's config file, such as:

containers:
  process:
    defaultControlGroup: "foo"
  softwarecontainers:
    bar: [ 1, 2, 3 ]

The process container accepts the following configuration settings:

Settings NameTypeDescription
controlGroupsobjectA two-stage mapping object to allow for more readable code when dealing with cgroups from the System UI via Container::controlGroup. The top-level keys are readable group names that are used to interface with Container::controlGroup. The values themselves are maps between multiple low-level cgroup sub-system names and the actual cgroup names within those sub-systems, such as:
controlGroups:
  foreGround:
    memory: mem1
    cpu: cpu_full
  backGround:
    memory: mem2
    cpu: cpu_minimal
defaultControlGroupstringThe default control group for an application when it is first launched.

For other container plugins, refer to their respective documentation.

Container Selection Configuration

When you start an application from within the application manager, there are multiple ways to control which container integration is used:

  1. If the config file does not contain the containers/selection key, the container integration ID defaults to process.
  2. If the containers/selection key exists, its contents are parsed as a list of maps, where each map has a single mapping only. While this single mapping is awkward, it is necessary to preserve the order of the mappings. Each key is interpreted as a standard Unix wildcard expression that is matched against the application ID. The first match stops the algorithm and the mapping's value is used as the container integration ID. If no matches are found, the resulting containter integration ID is an empty string.
    containers:
      selection:
      - com.pelagicore.*: "process"
      - com.navigation: "special-container"
      - '*': "softwarecontainers"  # a single asterisk needs to be quoted
  3. Afterwards, if the System UI did set the ApplicationManager::containerSelectionFunction property to a valid JavaScript function, this function is called with the first parameter set to the application's ID and the second parameter set to the container integration ID that resulted from step 1 and 2.
    ApplicationManager.containerSelectionFunction = function(appId, containerId) {
        var app = ApplicationManager.application(appId)
        if (app.capabilities.indexOf("non-secure") != -1)
            return "process"
        else
            return containerId
    }

© 2019 Luxoft Sweden AB. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.