An initial Approach to deliver an X11 Application as a Web Service

Florian Held
omi-uulm
Published in
8 min readJun 8, 2021
Photo by Markus Spiske on Unsplash

In my previous blog post, I talked about bringing a complex hydromechanical simulation onto a laptop for the purpose of presenting it to pupils visiting 11th grade in a technical grammar school in Germany. Though I came up with a technically feasible solution, it was clear from the beginning that this wasn’t the best fit for distributing this software to pupils willing to experiment and conducting their own simulations. The option to redundantly install this software “bare-metal” on machines used by the pupils cannot be seen as best practice since at least a decade due to various problems like incompatible library dependencies or an a priori impossibility to port it to a certain operating system. Another option, to install a container runtime, bundle this application and its dependencies into an image and then somehow connecting the graphical display to the running container also seemed more like a bulky hack than an elegant solution. Clearly, a better solution would be to provide this simulation application as some sort of an easily deployable and manageable web service, accessible from a webbrowser and rolled out at e.g. a webhoster or cloud.

However, implementing such a solution, is far away from being straight-forward and comes with various challenges. In the following, I want to elaborate on first ideas and steps to overcome those and present an initial solution.

Challenges

The biggest challenge was to find a tool to port a rather complex X11 application, which:

  • I forked and customized
  • is based on the cmake build-system
  • uses specialized libraries like gomp (i.e. an OpenMP implementation for C++)

to a web service. This raised the question to go for a typical solution with underlying webserver technology like nginx and wrapping the simulation code into e.g. webassembly bytecode for delivery in a webbrowser or simply go for some sort of remote-display server. Another indirect restriction was, that development effort should not be too big, as I worked on this project on my own and only part time.

Another point to consider was, that this solution should be scalable to some degree. As a rough estimator, 15 pupils at the same time should be able to use this application without disturbance.

The solution should also be constructed in such a way that it would be usable by the said pupils without presuming some deep technical background.

Eventually, for me as the operator, ease of deployment and maintainability of the solution should be strived for.

Used Tools

As a technology for porting the application, webassembly and the associated emscripten toolchain seemed like a promising fit at first, as native c++ code could be provided as binary-code to run in modern webbrowsers overcoming javascript limitations for cpu-intensive tasks like performing a fluid simulation. And with emscripten it is principally possible to execute the associated cmake build chain, the whole application is based on. However, the foundation of webassembly was not built for rendering X11 applications in webbrowsers, though there seem to be some hacky workarounds possible, copying X11 headers and manually linking X11 libraries. However, building such a “solution” for my fluid simulation was everything but straight-forward as additional conflicts like the unability to cross-compile OpenMP libraries occurred which eventually led to the conclusion that this approach was too tedious and not expedient. And what was more, a lacking first impression of the rendering experience in the browser and the prospect of work for creating a fancy front-end strengthened this initial rejection for my use case.
So I went on looking for a solution that relieved me off those obstacles and provide some sort of a “cloned experience” of the terminal controlled simulation approach used on my laptop. Eventually, my research brought me to a solution providing a remote display (and thus the ability to interact with the application with a console like xterm) called xpra. On its web page it is defined as: Xpra is an open-source multi-platform persistent remote display server and client for forwarding applications and desktop screens. Together with the tool xpra-html5 it was possible to forward the xterm console running on a machine on the web to a webbrowser connected to it. Summarized, this approach gave me a one-to-one match with my original case developed for use on my laptop.

To tackle challenges like providing a scalable solution, ease of deployment and maintainability, I went for the cloud solution openstack. Reasons for this were, that cloud solutions per-se support those concepts by means of virtualization or providing APIs to smoothly interact with deployments and also because of an openstack infrastructure being available at our institute. For automated provisioning of the xpra server instances I went for ansible due to own experiences with this tool and its tight integration with openstack. Moreover due to the automation capabilities of ansible it was possible to provide resources on the xpra servers to give the pupils a better user experience and make the application easier to handle and comprehend.

Implementation Approach

To be able to provision xpra server instances, first some prerequisites had to be met. First, authorized access to relevant resources was needed, that is the source code of the forked original hydromechanical fluid simulation at the moment residing at our institute’s gitlab instance and also resources helping pupils to understand and execute the simulation (simulation-manual, theoretical background, plots, …) also residing at the institute’s gitlab.

To map the requirements for application delivery to ansible’s ecosystem, following project structure was chosen:

ansible/ 
├── ansible.cfg
├── ansible-secrets
│ └── secrets.yml
├── inventories
│ ├── development
│ │ ├── group_vars
│ │ │ └── all
│ │ └── hosts
│ ├── integration
│ │ ...
│ │
│ └── production
│ ...

├── pa-token
├── playbooks
│ ├── delete_full_stack.yml
│ ├── deploy_full_stack.yml
│ ├── roles
│ │ └── requirements.yml
│ └── tasks
│ ├── add_sim_folder_to_sim_group.yml
│ ├── create_meta_content_for_sim_user.yml
│ ├── install_app_apt_part.yml
│ ├── install_app_raw_part.yml
│ ├── make_sim_folder_accessible_to_sim_user.yml
│ ├── setup_vm.yml
│ ├── start_xpra.yml
│ ├── stop_server.yml
│ ├── uninstall_app_apt.yml
│ └── uninstall_app_raw.yml
├── requirements.txt
├── roles
│ ├── clone_repo
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── README.md
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── tests
│ │ │ ├── inventory
│ │ │ └── test.yml
│ │ └── vars
│ │ └── main.yml
│ ├── create_group
│ │ │ ...
│ │
│ ├── create_network
│ │ │ ...
│ │
│ ├── create_router
│ │ │ ...
│ │
│ ├── create_rule
│ │ │ ...
│ │
│ ├── create_security_group
│ │ │ ...
│ │
│ ├── create_subnet
│ │ │ ...
│ │
│ ├── create_user
│ │ │ ...
│ │
│ ├── delete_vm
│ │ │ ...
│ │
│ ├── deploy_vm
│ │ │ ...
│ │
│ └── wait_for_port1_ready
│ │ ...

└── site.yml

The provisioning flow is controlled in the deploy_full_stack.yml playbook:

--- 
- hosts: localhost
gather_facts: false
vars:
register_upstream: []
tasks:
- name: create network
vars:
nw_name: "{{ nw_name_par }}"
include_role:
name: create_network
- name: create subnet
vars:
subnet_name: "{{ subnet_name_par }}"
nw_id: "{{ hostvars['localhost']['bodensee_private_net'].id }}"
include_role:
name: create_subnet
- name: create router
vars:
router_name: "{{ router_name_par }}"
include_role:
name: create_router
- name: create security group
vars:
sec_group_name: "{{ sec_group_name_bodensee }}"
include_role:
name: create_security_group
- name: create security group rule
vars:
sec_group_name: "{{ sec_group_name_bodensee }}"
port_nmbr: "{{ port_nmbr_ssh }}"
include_role:
name: create_rule
- name: Deploy vm
vars:
sec_group_list: ["{{ sec_group_1 }}","{{ sec_group_2 }}"]
include_role:
name: deploy_vm
register: app_host
- name: Add vm to inventory
add_host: name="{{ item.server.name }}" groups=bodensee_hosts ansible_ssh_host="{{ item.server.accessIPv4 }}" ansible_all_ipv4_addresses=["{{ item.server.private_v4 }}"] ansible_ssh_private_key_file="{{ key_file_path }}"
with_items:
- "{{ register_upstream }}"

- hosts: bodensee
remote_user: ubuntu
gather_facts: false
tasks:
- name: wait for ssh port to be ready
include_role:
name: wait_for_port1_ready
- hosts: bodensee
remote_user: ubuntu
become: yes
gather_facts: false
tasks:
- import_tasks: tasks/setup_vm.yml
- hosts: bodensee
remote_user: ubuntu
become: yes
gather_facts: false
vars:
personal_access_token: "token"
repo_name: "SPlishSplash"
repo_uri: https://gitlab-ci-token:{{ personal_access_token }}@{{ gitlab-server-uri }}/simulierte-welten/projekte/wasserschicht_dynamik/SPlisHSPlasH_mod_1.git
dest_folder: "{{ sim_folder_su }}"
tasks:
- name: clone SPlishSplash
include_role:
name: clone_repo
- hosts: bodensee
remote_user: ubuntu
become: yes
gather_facts: false
vars:
repo_name: "xpra-html5"
repo_uri: https://github.com/Xpra-org/xpra-html5
dest_folder: "{{ xpra_html5_folder_su }}"
tasks:
- name: clone xpra-html5
include_role:
name: clone_repo
- hosts: bodensee
remote_user: ubuntu
become: yes
gather_facts: false
pre_tasks:
- import_tasks: tasks/install_app_apt_part.yml
tasks:
- import_tasks: tasks/install_app_raw_part.yml
- hosts: bodensee
remote_user: ubuntu
become: yes
gather_facts: false
vars:
append_user: "ubuntu"
tasks:
- name: "create group {{ group_name }}"
include_role:
name: create_group
- name: "create user {{ user_name }} under group {{ group_name }}"
include_role:
name: create_user
- name: "append {{ append_user }} to group {{ group_name }}"
user:
name: "{{ append_user }}"
groups: "{{ group_name }}"
append: yes
- import_tasks: tasks/add_sim_folder_to_sim_group.yml
- import_tasks: tasks/make_sim_folder_accessible_to_sim_user.yml
- hosts: bodensee
remote_user: ubuntu
become: yes
tasks:
- import_tasks: tasks/create_meta_content_for_sim_user.yml
- import_tasks: tasks/start_xpra.yml

What is basically done here is:

  • Setup a tailor-made virtual machine accessible from the public internet and waiting for its ssh-port to become ready.
  • Cloning relevant git-repositories and building and installing them later alongside dependent other software packages
  • Creating a non-privileged sim-user able to access content relevant for simulation execution
  • Copying meta-content having educational purposes for the pupils onto the machine.
  • Starting the xpra server

The provisioning process can be triggered with a command similar to this:

$ export ANSIBLE_HOST_KEY_CHECKING=False && ansible-playbook -vvv site.yml --extra-vars "inventories/development/group_vars/all deploy=1 personal_access_token=xxx key_file_path=xxx"

At the moment, the virtual machine is up and running in openstack, the user can authenticate with valid credentials over a webbrowser (replace localhost with the respective public IP):

Login mask for xpra server

Given access to the machine, the user can now work on an xterm console browsing through the simulation-manual (a pdf readable through the pre-installed reader evince) or looking at temperature plots (png images viewable through the pre-installed viewer qiv) . However, most importantly, a simulation run can be started, shown here exemplarily:

$ whoami
sim-user
$ pwd
/home/sim-user/simulation/bin
$ cat example-run
MESA_GL_VERSION_OVERRIDE=3.3 ./SPHSimulator --stopAt 42.0 ../data/Scenes/simu_bodensee/bodensee_scene_7_succeed.json
$ bash example-run

and should look similar to this screenshot from my personal Firefox 89.0 browser:

Simulation run in a webbrowser

Open Issues

Though this approach yielded a running and usable solution, it is far from having a production-like quality. What is to follow is a small excerpt of open issues still to overcome:

  • Low security quality. Ranging from not having features like Secure Sockets or (AES) encryption enabled to giving each connected user terminal access to a virtual machine and sharing content with a privileged user.
  • No typical web application delivery. That is e.g., multiple sessions on a server instance are not possible, no fancy interactive frontend and no auto-scalability.
  • No integration with DNS so far.

--

--

Florian Held
omi-uulm
Writer for

Researcher with interests in: Energy-Efficient Computing, Middlewares in the IoT