Quantcast
Channel: Sander van der Burg's blog
Viewing all 159 articles
Browse latest View live

Seventh annual blog reflection

$
0
0
Today, it is my blog's seventh anniversary. As with previous years, it is a nice opportunity to reflect over last year's writings.

Nix expression generators


As usual, the majority of my work is Nix related. This year, a substantial amount of my time was spend on the development of several kinds of Nix expression generators.

In the first quarter of the year, I improved node2nix's accuracy by implementing a more robust version pinning strategy to prevent NPM from consulting external sources. I also gave a talk about node2nix's state of affairs at FOSDEM 2017, attracting quite a large audience.

In addition to node2nix, I revived a number old PHP projects, modernized them a bit and developed composer2nix that can be used to automatically generate Nix expressions from composer (a package manager for PHP projects) configuration files. Similar to node2nix I have developed PNDP: an internal DSL for Nix in PHP to make the code generation process more robust and reliable.

I later unified some of the generation concepts of node2nix and composer2nix and extended both NiJS (an internal DSL for Nix in JavaScript) and PNDP (an internal DSL for Nix in PHP) to support custom object transformations, making the code of both generators much cleaner and better maintainable.

Finally, I had to make a substantial revision to node2nix to support NPM 5.x's content-addressable cache that conflicts with Nix's purity principles.

Disnix


In addition to the Nix expression generators, I have also released a new major version of Disnix in March: version 0.7.

For this new version, I have developed a new abstraction layer implementing multi-process programming patterns to improve the performance of certain deployment activities and to make the code more readable and better maintainable.

Another major feature addition is a deployment configuration reconstructor. Disnix deployment is centralized and when the coordinator machine disappears, a system can no longer be reliably upgraded. With the reconstructor, it is possible to recover from such failures.

Contrary to 2015 and 2016, I did not do as much Disnix development this year. Apart from these two major feature additions, I only did a couple of maintenance releases.

Other Nix features


I have also developed an NPM package providing an API that can be used to remotely control a Hydra server, a Nix-based continuous integration service, and a command-line client to demonstrate its possibilities.

Web technology


Besides Nix-development, I made a brief journey back in time to a technology area that I used to be very interested in (years ago) before I got involved with anything Nix-related and my research: web technology.

I used to develop a custom web framework around that time. I already published some pieces of it in 2014. In the middle of this year, I have isolated, documented and released the remaining parts. I also developed a number of simple example applications to demonstrate how the framework's features can be used.

In the process, some memories of the past resurfaced and I wrote an essay to reflect over them.

One of my not-so-strong points when it comes to web technology is anything layout and style related. I did a small investigation and wrote a checklist of minimalistic layout considerations for myself.

Mobile app development


I also wrote a blog post about the Model-View-Controller (MVC) paradigm and considerations while extending the company's product with chat functionality.

Blog posts


Like every year, I will publish the top 10 of the most frequently read blog posts so far:

  1. On Nix and GNU Guix. This blog post is still the most popular for five years in a row. It seems that there are still plenty of people out there who want to know the differences. However, it appears that its position can soon be overtaken by the number 2.
  2. Managing private Nix packages outside the Nixpkgs tree. This blog post is a tutorial written for Nix-beginners and seems to have grown quite considerably in popularity. It may soon become my most popular blog post.
  3. An evaluation and comparison of Snappy Ubuntu. Still popular, but gradually dropping. I believe this can be attributed to the fact that Snappy has not been in the news for quite a while.
  4. Setting up a multi-user Nix installation on non-NixOS systems. As with previous blog reflections, this post remains popular and still shows that this is an area open for improvement.
  5. Yet another blog post about Object Oriented Programming and JavaScript. Was in last year's top 10 and remains popular. It appears that I did a fairly good job explaining the prototypes concept.
  6. An alternative explanation of the Nix package manager. This blog post has also been in the top 10 for the last five years. It is gradually dropping in popularity. I still believe that it is important to have a good Nix package manager explanation recipe.
  7. On NixOps, Disnix, service deployment and infrastructure deployment. This blog post was also in last year's top 10 and still popular. It is good to observe that people take interest in both NixOps and Disnix.
  8. Asynchronous programming with JavaScript. A JavaScript-related blog post that remains popular.
  9. The NixOS project and deploying systems declaratively. This is the only blog post that was not in last year's top 10. It seems to have quite some impact, in particular the corresponding presentation slides.
  10. Composing FHS-compatible chroot environments with Nix (or deploying Steam in NixOS). Is still popular, but I expect this one to disappear from the top 10 next year.

Some thoughts


I am quite happy with the blog posts I produced this year, yet I have a few observations and ideas for next year to improve upon.

In the middle of this year, I had a significant drop in my blogging productivity (as may be observed by checking the publishing dates on the panel on the right). This drop was caused by a variety of things I will not elaborate about. It took me quite a bit of effort to get back into my usual rhythm and get another story published. This is something I should look after next year.

Another thing I observed by looking at my overall top 10 is that all blog posts except the 10th (about FHS-compatible chroot environments) were written for educational purposes. This year, I have not published any blog posts with education in mind. This is also something I should focus myself a bit more on next year.

Finally, the fact that I did not do so much Disnix development, does not mean that it is finished or that I am out of ideas. I still have a huge list of things that I would like to explore.

Conclusion


I'm still not out of ideas, so stay tuned! The final thing I'd like to say is:


HAPPY NEW YEAR!!!!!


Syntax highlighting Nix expressions in mcedit

$
0
0
The year 2017 has passed and 2018 has now started. For quite a few people, this is a good moment for reflection (as I have done in my previous blog post) and to think about new year's resolutions. New year's resolutions are typically about adopting good new habits and rejecting old bad ones.

Orthodox file managers



One of my unconventional habits is that I like orthodox file managers and that I extensively use them. Orthodox file managers have a number of interesting properties:

  • They typically display textual lists of files, as opposed to icons or thumbnails.
  • They typically have two panels for displaying files: one source and one destination panel.
  • They may also have third panel (typically placed underneath the source and destination panels) that serves as a command-line prompt.

The first orthodox file manager I ever used was DirectoryOpus on the Commodore Amiga. For nearly all operating systems and desktop environments that I touched ever since, I have been using some kind of a orthodox file manager, such as:


Over the years, I have received many questions from various kinds of people -- they typically ask me what is so appealing about using such a "weird program" and why I have never considered switching to a more "traditional way" of working, because "that would be more efficient".

Aside from the fact that it may probably be mostly inertia, my motivating factors are the following:

  • Lists of files allow me to see more relevant and interesting details. In many traditional file managers, much of the screen space is wasted by icons and the spacing between them. Furthermore, traditional file managers may typically hide properties of files that I also typically want to know about, such as a file's size or modification timestamp.
  • Some file operations involve a source and destination, such as copying or moving files. In an orthodox file manager, these operations can be executed much more intuitively IMO because there is always a source and destination panel present. When I am using a traditional file manager, I typically have to interrupt my workflow to open a second destination window, and use it to browse to my target location.
  • All the orthodox file managers I have mentioned, implement virtual file system support allowing me to browse compressed archives and remote network locations as if they were directories.

    Nowadays, VFS support is not exclusive to orthodox file managers anymore, but they existed in orthodox file managers much longer.

    Moreover, I consider the VFS properties of orthodox file managers to be much more powerful. For example, the Windows file explorer can browse Zip archives, but Total Commander also has first class support for many more kinds of archives, such as RAR, ACE, LhA, 7-zip and tarballs, and can be easily extended to support many other kinds of file systems by an add-on system.
  • They have very powerful search properties. For example, searching for a collection of files having certain kinds of text patterns can be done quite conveniently.

    As with VFS support, this feature is not exclusive to orthodox file managers, but I have noticed that their search functions are still considerably more powerful than most traditional file managers.

From all the orthodox file managers listed above, Midnight Commander is the one I have been using the longest -- it was one of the first programs I used when I started using Linux (in 1999) and I have been using it ever since.

Midnight Commander also includes a text editor named: mcedit that integrates nicely with the search function. Although I have experience with half a dozen editors (such as vim and various IDEs, such as Eclipse and Netbeans), I have been using mcedit, mostly for editing configuration files, shell scripts and simple programs.

Syntax highlighting in mcedit


Earlier in the introduction I mentioned: "new year's resolutions", which may probably suggest that I intend to quit using orthodox file managers and an unconventional editor, such as mcedit. Actually, this is not something I am planning to :-).

In addition to Midnight Commander and mcedit, I have also been using another unconventional program for quite some time, namely: the Nix package manager since late 2007.

What I noticed is that, despite being primitive, mcedit has reasonable syntax highlighting support for a variety of programming languages. Unfortunately, what I still miss is support for the Nix expression language -- the DSL that is used to specify package builds and system configurations.

For quite some time, editing Nix expressions was a primitive process for me. To improve my unconventional way of working a bit, I have decided to address this shortcoming in my Christmas break by creating a Nix syntax configuration file for mcedit.

Implementing a syntax configuration for the Nix expression language


mcedit provides syntax highlighting (the format is described in the manual page) for a number of programming languages. The syntax highlighting configurations seem to follow similar conventions, probably because of the fact that programming languages influence each other a lot.

As with many programming languages, the Nix expression language has its own influences as well, such as Haskell, C, bash, JavaScript (more specifically: the JSON subset) and Perl.

I have decided to adopt similar syntax highlighting conventions in the Nix expression syntax configuration. I started by examining Nix's lexer module (src/libexpr/lexer.l):

  • First, I took the keywords and operators, and configured the syntax highlighter to color them yellow. Yellow keywords is a convention that other syntax highlighting configurations also seem to follow.
  • Then I implemented support for single line and multi-line comments. The context directive turned out to be very helpful -- it makes it possible to color all characters between a start and stop token. Comments in mcedit are typically brown.
  • The next step were the numbers. Unfortunately, the syntax highlighter does not have full support for regular expressions. For example, you cannot specify character ranges, such as [0-9]+. Instead you must enumerate all characters one by one:

    keyword whole \[0123456789\]

    Floating point numbers were a bit trickier to support, but fortunately I could steal them from the JavaScript syntax highlighter, since the formatting Nix uses is exactly the same.
  • Strings were also relatively simple to implement (with the exception of anti-quotations) by using the context directive. I have configured the syntax highlighter to color them green, similar to other programming languages.
  • The Nix expression language also supports objects of the URL or path type. Since there is no other language that I am aware of that has a similar property, I have decided to color them white, with the exception of system paths -- system paths look very similar to the C preprocessor's #include path arguments, so I have decided to color them red, similar to the C syntax highlighter.

    To properly support paths, I implemented an approximation of the regular expression used in Nix's lexer. Without full regular expression support, it is extremely difficult to make a direct translation, but for all my use cases it seems to work fine.

After configuring the above properties, I noticed that there were still some bits missing. The next step was opening the parser configuration (src/libexpr/parser.y) and look for any missing characters.

I discovered that there were still separators that I needed to add (e.g. parenthesis, brackets, semi-colons etc.). I have configured the syntax highlighter to color them bright cyan, with the exception of semi-colons -- I colored them purple, similar to the C and JavaScript syntax highlighter.

I also added syntax highlighting for the builtin functions (e.g. derivation, map and toString) so that they appear in cyan. This convention is similar to bash' syntax highlighting.

The implementation process of the Nix syntax configuration was generally straight forward, except for one thing -- anti-quotations. Because we only have a primitive lexer and no parser, it is impossible to have a configuration that covers all possibilities. For example, anti-quotations in strings that embed strings cannot be properly supported. I ended up with an implementation that only works for simple cases (e.g. a reference to an identifier or a file).

Results


The syntax highlighter works quite well for the majority of expressions in the Nix packages collection. For example, the expression for the Disnix package looks as follows:


The top-level expression that contains the package compositions looks as follows:


Also, most Hydra release.nix configurations seem to work well, such as the one used for node2nix:


Availability


The Nix syntax configuration can be obtained from my GitHub page. It can be used by installing it in a user's personal configuration directory, or by deploying a patched version of Midnight Commander. More details can be found in the README.

Diagnosing problems and running maintenance tasks in a network with services deployed by Disnix

$
0
0
I have been maintaining a production system with Disnix for quite some time. Although deployment works quite conveniently for me (I may probably be a bit biased, since I created Disnix :-) ), you cannot get around unforeseen incidents and problems, such as:

  • Crashing processes due to bugs or excessive load.
  • Database problems, such as inconsistencies in the data.

Errors in distributed systems are typically much more difficult to debug than single machine system failures. For example, tracing the origins of an error in distributed systems is generally hard -- one service's fault may be caused by a message propagated by another service residing on a different machine in the network.

But even if you know the origins of an error (e.g. you can clearly observe that a web application is crashing or a database connection), you may face other kinds of challenges:

  • You have to figure out to which machine in the network a service has been deployed.
  • You have to connect to the machine, e.g. through an SSH connection, to run debugging tasks.
  • You have to know the configuration properties of a service to diagnose it -- in Disnix, as explained in earlier blog posts, services can take any form -- they can be web services, but also web applications, databases and processes.

Because of these challenges, diagnosing errors and running maintenance tasks in a system deployed by Disnix is always unnecessarily time-consuming and inconvenient.

To alleviate this burden, I have developed a small tool and extension that establishes remote shell connections with environments providing all relevant configuration properties. Furthermore, the tool gives suggestions to the end-user explaining what kinds of maintenance tasks he could carry out.

The shell activity of Dysnomia


As explained in previous Disnix-related blog posts, Disnix carries out all activities to deploy a service oriented system to a network machines (i.e. to bring it in a running state), such as building services from source code, distributing their intra-dependency closures to the target machines, and activating or deactivating every service.

For the build and distribution activities, Disnix uses, as its name implies, the Nix package manager because it offers a number of powerful properties, such as strong reproducibility guarantees and atomic upgrades and rollbacks.

For the remaining activities that Nix does not support, e.g. activating or deactivating services, Disnix uses a companion tool called Dysnomia. Because services in a Disnix context could take any form, there is no generic means to activate or deactivate them -- for this reason, Dysnomia provides a plugin system with modules that carry out specific activities for a specific service type.

One of the plugins that Dysnomia provides is the deployment of MySQL databases to a MySQL DBMS server. Dysnomia deployment activities are driven by two kinds of configuration specifications. A component configuration defines the properties of a deployable unit, such as a MySQL database:


create table author
( AUTHOR_ID INTEGER NOT NULL,
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255) NOT NULL,
PRIMARY KEY(AUTHOR_ID)
);

create table books
( ISBN VARCHAR(255) NOT NULL,
Title VARCHAR(255) NOT NULL,
AUTHOR_ID VARCHAR(255) NOT NULL,
PRIMARY KEY(ISBN),
FOREIGN KEY(AUTHOR_ID) references author(AUTHOR_ID) on update cascade on delete cascade
);

The above configuration is a MySQL script (~/testdb) that creates the database schema consisting of two tables.

The container configuration captures properties of the environment in which the component should be hosted, which is in this particular case, a MySQL DBMS server:


type=mysql-database
mysqlUsername=root
mysqlPassword=verysecret

The above component configuration (~/mysql-production) defines the type stating that mysql-database plugin must be used, and provides the authentication credentials required to connect to the DBMS server.

The Dysnomia plugin for MySQL implements various kinds of deployment activities for MySQL databases. For example, the activation activity is implemented as follows:


...

case "$1" in
activate)
# Initalize the given schema if the database does not exists
if [ "$(echo "show databases" | @mysql@ --user=$mysqlUsername --password=$mysqlPassword -N | grep -x $componentName)" = "" ]
then
( echo "create database $componentName;"
echo "use $componentName;"

if [ -d $2/mysql-databases ]
then
cat $2/mysql-databases/*.sql
fi
) | @mysql@ $socketArg --user=$mysqlUsername --password=$mysqlPassword -N
fi
markComponentAsActive
;;

...
esac

The above code fragment checks whether a database with the given schema exists and if it does not, it will create it by running the database initialization script provided by the component configuration. As may also be observed, the above activity uses the container properties (such as the authentication credentials) as environment variables.

Dysnomia activities can be executed by invoking the dysnomia command-line tool. For example, the following command will activate the MySQL database in the MySQL database server:


$ dysnomia --operation activate \
--component ~/testdb --container ~/mysql-production

To make the execution of arbitrary tasks more convenient, I have created a new Dysnomia option called: shell. The shell operation is basically an activity that does not execute anything, but instead spawns a shell session that provides the container configuration properties as environment variables.

Moreover, the shell activity of a Dysnomia plugin typically displays suggestions for shell commands that the user may want to carry out.

For example, when we run the following command:


$ dysnomia --shell \
--component ~/testdb --container ~/mysql-production

Dysnomia spawns a shell session that shows the following:


This is a shell session that can be used to control the 'staff' MySQL database.

Module specific environment variables:
mysqlUsername Username of the account that has the privileges to administer
the database
mysqlPassword Password of the above account
mysqlSocket Path to the UNIX domain socket that is used to connect to the
server (optional)

Some useful commands:
/nix/store/h0kcf5g2ssyancr9m2i8sr09b3wq2zy0-mariadb-10.1.28/bin/mysql --user=$mysqlUsername --password=$mysqlPassword staff Start a MySQL interactive terminal

General environment variables:
this_dysnomia_module Path to the Dysnomia module
this_component Path to the mutable component
this_container Path to the container configuration file

[dysnomia-shell:~]#

By executing the command-line suggestion shown above in the above shell session, we get a MySQL interactive terminal allowing us to execute arbitrary SQL commands. It saves us the burden looking up all the MySQL configuration properties, such as the authentication credentials and the database name.

The Dysnomia shell feature is heavily inspired by nix-shell that works in quite a similar way -- it will take the build dependencies of a package build as inputs (which typically manifest themselves as environment variables) and fetches the sources, but it will not execute the package build procedure. Instead, it spawns an interactive shell session allowing the user to execute arbitrary build tasks. This Nix feature is particularly useful for development projects.

Diagnosing services with Disnix


In addition to extending Dysnomia with the shell feature, I have also extended Disnix to make this feature available in a distributed context.

The following command can be executed to spawn a shell for a particular service of the ridiculous staff tracker example (that happens to be a MySQL database):


$ disnix-diagnose -S staff
[test2]: Connecting to service: /nix/store/yazjd3hcb9ds160cq03z66y5crbxiwq0-staff deployed to container: mysql-database
This is a shell session that can be used to control the 'staff' MySQL database.

Module specific environment variables:
mysqlUsername Username of the account that has the privileges to administer
the database
mysqlPassword Password of the above account
mysqlSocket Path to the UNIX domain socket that is used to connect to the
server (optional)

Some useful commands:
/nix/store/h0kcf5g2ssyancr9m2i8sr09b3wq2zy0-mariadb-10.1.28/bin/mysql --user=$mysqlUsername --password=$mysqlPassword staff Start a MySQL interactive terminal

General environment variables:
this_dysnomia_module Path to the Dysnomia module
this_component Path to the mutable component
this_container Path to the container configuration file

[dysnomia-shell:~]#

The above command-line instruction will lookup the location of the staff database in the configuration of the system that is currently deployed, connects to it (typically through SSH) and spawns a Dysnomia shell for the given service type.

In addition to an interactive shell, you can also directly run shell commands. For example, the following command will query all the staff records:


$ disnix-diagnose -S staff \
--command 'echo "select * from staff" | mysql -u $mysqlUsername -p $mysqlPassword staff'

In most cases, only one instance of a service exists, but Disnix can also deploy redundant instances of the same service. For example, we may want to deploy two redundant instances of the web application front end in the distribution.nix configuration file:


stafftracker = [ infrastructure.test1 infrastructure.test2 ];

When trying to spawn a Dysnomia shell, the tool returns an error because it does not know to which instance to connect to:


$ disnix-diagnose -S stafftracker
Multiple mappings found! Please specify a --target and, optionally, a
--container parameter! Alternatively, you can execute commands for all possible
service mappings by providing a --command parameter.

This service has been mapped to:

container: apache-webapplication, target: test1
container: apache-webapplication, target: test2

In this case, we must refine our query with a --target parameter. For example, the following command connects to the web front-end on the test1 machine:


$ disnix-diagnose -S stafftracker --target test1

It is still possible to execute remote shell commands for redundantly deployed services. For example, the following command gets executed twice, because we have two instances deployed:


$ disnix-diagnose -S stafftracker \
--command 'echo I will see this message two times!'

In some cases, you may want to execute other kinds of maintenance tasks or you simply want to know where a particular service resides. This can be done by running the following command:


$ disnix-diagnose -S stafftracker --show-mappings
This service has been mapped to:

container: apache-webapplication, target: test1
container: apache-webapplication, target: test2

Conclusion


In this blog post, I have described a new feature of Dysnomia and Disnix that spawns interactive shell sessions making problem solving and maintenance tasks more convenient.

disnix-diagnose and the shell extension are part of the development versions of Disnix and Dysnomia and will become available in the next release.

Deploying systems with circular dependencies using Disnix

$
0
0

Some time ago, during my PhD thesis defence, one of my committee members asked me how I would deploy systems with Disnix in which services have circular dependencies.

It was an interesting question because Disnix defines dependencies between services (that typically involve network connections) as inter-dependencies that have two properties:

  • They allow services to find services they depend on by providing their connection properties
  • They ensure that any inter-dependency is activated before the service itself, so that no failures will occur because of missing dependencies -- in Disnix, a service is either available or unavailable, but never in a broken state due to missing inter-dependencies at runtime.

In a system with circular dependencies, the ordering property is problematic -- it is impossible to activate one dependency before another without having broken connections between them.

During the defence, I had to admit that I have never deployed such systems with Disnix before, but that there were a couple of possible solutions to cope with such constraints. For example, you can propagate properties of the distribution model directly to a service, as opposed to declaring circular inter-dependencies. Then the ordering requirement is not enforced.

I also explained that systems should not have any hard cyclic requirements on other services, but instead compose their (potential bidirectional) communication channels at runtime. Furthermore, I explained that circular dependencies are bad from a reuse perspective -- when two services mutually depend on each other, then they should ideally be one service.

Although the answer sufficed (e.g. it provided the answer that it was possible), the solution basically relies on unconventional usage of the deployment tool. Recently, as a personal exercise, I have decided to dig up this question again and explore the possibilities of deploying systems with circular dependencies.

Chord: a peer-to-peer distributed hash table


When thinking of an example system that has a circular dependency structure, the first thing that came up in my mind is Chord: a peer-to-peer distributed hash table (a copy of the research paper written by Stoica et al can be found here). Interesting fact is that I had to implement it many years ago in the lab course of the distributed algorithms course taught by another member of my PhD thesis committee.

A Chord network has circular runtime dependencies because it has a a ring structure -- in a network that has more than one node, each node has a successor and predecessor link, in which no node has the same predecessor or successor and the last successor link refers to the first node:


The Chord nodes (shown in the figure above) constitute a distributed peer-to-peer hash table. In addition to the fact that it can store key and value pairs (all kinds of objects), it also distributes the data over the nodes in the network.

Moreover, its operations are decentralized -- for example, when it is desired to search for an object or to store new objects in the hash table, it is possible to consult any node in the network. The system will redirect the caller to the appropriate node that should host the data.

Various kinds of implementations exist of the Chord protocol. The official reference implementation is a filesystem abstraction layer built on top of it. I experimented with the Java-based OpenChord implementation that is capable of storing arbitrary serializable Java objects.

More details about the implementation details of Chord operations can be found in the research paper.

Deploying a Chord network


One of the challenges I faced during the lab course is that I had deploy a test Chord network with a small collection of nodes. At that time, I had no proper deployment automation. I ended up writing a bash shell script that spawned a collection of processes in parallel.

Because deployment was complicated, I never tried more complex scenarios than running a small collection of processes on a single machine. Because it was not required for the lab course to do more than just that I, for example, never tried any real network communication deployments in which I had to distribute Chord nodes over multiple computer systems. The latter would have introduced even more complexity to the deployment process.

Deploying a Chord network basically works as follows:

  • First, we must deploy an initial node that has no connection to a predecessor or successor node.
  • Then for each additional node, we call the join operation to attach it to the network. As explained earlier, a Chord hash-table is decentralized and as a result, we can consult any node we want in the network for the join process. The join and stabilization procedures decide which predecessor and successor a new node actually gets.

There are various strategies to join additional nodes to the network, but I what I ended up doing is using the initial node as a bootstrap node -- all successive nodes, simply join to the bootstrap node and the network stabilizes to become a ring.

(As a sidenote: you could argue whether this is a good process, since the introduction of a central bootstrap node during the deployment process violates the peer-to-peer contraint, but that is a different story. Obviously, you could also think of other bootstrap strategies but that is beyond the scope of this blog post).

Automating a Chord network deployment with Disnix


To experiment with a Chord network, I have decided to create a simple server process (using the OpenChord API) whose only responsibility is to store data. It can optionally join another node in the network and it has a command-line interface allowing me to conveniently specify the connection parameters.

The deployment strategy using the initial node as a bootstrap node can be easily automated with Disnix. In the Disnix services model, we can define the bootstrap node as follows:


ChordBootstrapNode = rec {
name = "ChordBootstrapNode";
pkg = customPkgs.ChordBootstrapNode { inherit port; };
port = 8001;
portAssign = "private";
type = "process";
};

The above service configuration corresponds to a process that binds the service to a provided TCP port.

Each successive node can be defined as a service that has an inter-dependency on the bootstrap node:


ChordNode1 = rec {
name = "ChordNode1";
pkg = customPkgs.ChordNode { inherit port; };
port = 8002;
portAssign = "private";
type = "process";
dependsOn = {
inherit ChordBootstrapNode;
};
};

As can be seen in the above Nix expression, the dependsOn attribute specifies that the node has an inter-dependency on the bootstrap node. The inter-dependency declaration provides the connection settings of the bootstrap node to the command-line utility that spawns the service and ensures that the bootstrap node is deployed first.

By providing an infrastructure model containing a number of machines and writing a distribution model that maps the node to the machine, such as:


{infrastructure}:

{
ChordBootstrapNode = [ infrastructure.test1 ];
ChordNode1 = [ infrastructure.test1 ];
ChordNode2 = [ infrastructure.test2 ];
ChordNode3 = [ infrastructure.test2 ];
}

we can deploy a Chord network consisting of 4 nodes distributed over two machines by running:


$ disnix-env -s services.nix -i infrastructure.nix -d distribution.nix

This is the resulting deployment architecture of the Chord network that gets deployed:


In the above picture, the light grey colored boxes denote machines, the dark grey colored boxes container environments, the ovals services and the arrows inter-dependency relationships.

By running the OpenChord console, we can join any of our nodes in the network, such as the third node deployed to machine test2:


$ /nix/var/nix/profiles/disnix/default/bin/openchord-console
> joinN -port 9000 -bootstrap test2:8001
Trying to join chord network with boostrap URL ocsocket://test2:8001/
URL of created chord node ocsocket://192.168.56.102:9000/.

we can check the references that the console node has:


> refsN
Node: C1 F0 42 95 , ocsocket://192.168.56.102:9000/
Finger table:
59 E4 86 AC , ocsocket://test2:8001/ (0-159)
Successor List:
59 E4 86 AC , ocsocket://test2:8001/
64 F1 96 B9 , ocsocket://test1:8001/
Predecessor: 9C 51 42 1F , ocsocket://test2:8002/

As may be observed in the output above, our predecessor is the node 3 deployed to machine test2 and our successors are node 3 deployed to machine test2 and node 1 deployed to machine test1.

We can also insert and retrieve the data we want:


> insertN -key test -value test
> entriesN
Entries:
key = A9 4A 8F E5 , value = [( key = A9 4A 8F E5 , value = test)]

Defining services with circular dependencies in Disnix


As shown in the previous paragraph, the ring structure of a Chord hash table is constructed at runtime. As a result, Disnix does not need to manage any circular dependencies. Instead, it only has to know the dependencies of the bootstrap phase which are not cyclic at all.

I was also curious whether I could modify Disnix to properly define circular-dependencies, without any workarounds such as directly propagating properties from the distribution model. As explained in the introduction, inter-dependencies have two properties in which the second property is problematic: the ordering constraint.

To cope with the problematic ordering property, I have introduced a new property in the services model called: connectsTo allowing users to specify inter-dependencies for which the ordering does not matter. The connectsTo property makes it possible for services to define mutual dependencies on each other.

As an example case, I have extended the Disnix composition examples (a set of trivial examples implementing "Hello world" testcases) with a cyclic example case. In this new sub example, I have created a web application that both contains a server returning the "Hello world!" string and a client displaying the string. The result would be the following screen:


(Does it look cool? :p)

A web application instance is capable of connecting to another web service to obtain the "Hello world!" message to display. We can compose two web application instances that refer to each other to accomplish this.

The corresponding services model looks as follows:


{distribution, invDistribution, system, pkgs}:

let customPkgs = import ../top-level/all-packages.nix {
inherit system pkgs;
};
in
rec {
HelloWorldCycle1 = {
name = "HelloWorldCycle1";
pkg = customPkgs.HelloWorldCycle;
connectsTo = {
# Depends on the other cyclic service
HelloWorldCycle = HelloWorldCycle2;
};
type = "tomcat-webapplication";
};

HelloWorldCycle2 = {
name = "HelloWorldCycle2";
pkg = customPkgs.HelloWorldCycle;
connectsTo = {
# Depends on the other cyclic service
HelloWorldCycle = HelloWorldCycle1;
};
type = "tomcat-webapplication";
};
}

As may be observed in the above code fragment, the first service has a dependency on the second, while the second also has a dependency on the first. They are allowed to refer to each other because the connectsTo property disregards ordering.

By mapping the services to a network of machines that have Apache Tomcat hosted:


{infrastructure}:

{
HelloWorldCycle1 = [ infrastructure.test1 ];
HelloWorldCycle2 = [ infrastructure.test2 ];
}

and deploying the system:


$ disnix-env -s services-cyclic.nix \
-i infrastructure.nix \
-d distribution-cyclic.nix

We end-up with a deployment architecture of two services having cyclic dependencies:


To produce the above visualization, I have extended the disnix-visualize tool with support for the connectsTo property that displays inter-dependencies as dashed arrows (as opposed to solid arrows that denote ordinary inter-dependencies).

In addition to the option to specify circular dependencies, the connectsTo property has another interesting use case -- when services have inter-dependencies that may be broken, we can optimize the duration of an upgrade processes.

Normally, when a service gets upgraded, all its inter-dependent services will be reactivated. This is an implication of Disnix's strictness -- a service is either available or unavailable, but never broken because of missing inter-dependencies.

However, all the extra reactivations in the upgrade phase can be quite expensive as a result. If a link is non-critical and it is permitted to be down for a short while, then redeployments can be made faster.

Conclusion


In this blog post, I have described two deployment experiments with Disnix involving systems that have circular dependencies -- a Chord-based distributed hash table (that constructs a ring structure at runtime) and a trivial toy example system in which two services have mutual dependencies on each other.

Availability


The newly introduced connectsTo property is part of the development version of Disnix and will become available in the next release.

The composition example and newly created Chord example can be found on my GitHub page.

A more realistic public Disnix example

$
0
0
It has almost been ten years ago when I started developing Disnix -- February 2008 marked the start of my master's thesis internship at Philips Research that resulted in the first prototype version.

Originally, Disnix was specifically developed for one use case only -- a medical service-oriented system called the "Service Development Support System" (SDS2) that can be used for asset tracking and utilisation analysis for medical devices in a hospital environment. More information about this case study can be found in my master's thesis, some of my research papers and my PhD thesis (all of them can be found on my publications page).

Many developments have happened since the realization of the first prototype -- its feature set has been extended considerably, its architecture has been overhauled several times and the code has evolved significantly. Most notably, I have been maintaining a production system for over three years with it.

In all these years, there is always one recurring question that I regularly receive from various kinds of people:

Why should I use Disnix and why would it be useful?

The answer is that Disnix becomes useful when you have a system that can be decomposed into distributable services, such as web services, RESTful services, web applications or processes.

In addition to the fact that Disnix automates its deployment and offers a number of powerful quality properties (e.g. non-destructive upgrades for the static parts of a system), it also helps componentized systems in reaching their full potential -- for example, when services can be built, deployed, and managed individually you can scale a system up and down (e.g. by distributing services to dedicated machines or consolidating all services on a single machine) and you can anticipate more flexibly to events (e.g. by redeploying services when we encounter a crashing machine).

Although the answer may sound simple, service-oriented systems are complicated -- besides facing all kinds of deployment complexities, properly dividing a system into distributable components is also quite challenging. For all the systems I have seen in the last decade, the requirements and their modularization strategies were all quite different from each other. I have also seen a number of systems for which decomposing into services did not work and unnecessary complexities were introduced.

Moreover, it is hard to find representative public examples that people can use as a reference. I was fortunate that I had access to an industrial case study during my research. Nonetheless, I was suffering from many difficulties because of the lack of any meaningful public case studies. As a countermeasure, I developed a collection of example cases in addition to SDS2, but because of their over-simplicity, proving my point often remained hard.

Roughly half a year ago, I have released most parts of my ancient web framework that I used to actively develop before I started doing research in software deployment and I created a couple of example applications for it.


Although my web framework development predates my deployment research, I was already using it to implement information systems that followed some modularity principles that are beneficial when using Disnix as a deployment system.

Recently, I have extended my web framework's example applications repository (providing a homework assistant, CMS, photo gallery and literature survey assistant) to become another public Disnix example case following the same modularity principles I used for the information systems I used to implement at that time.

Creating a componentized web information system


As mentioned earlier in this blog post, I have already implemented a (fairly simple) componentized web information system before I started working on Disnix using my ancient custom made web framework. The "componentization process" (a term that I had neither learned about yet nor something I was consciously implementing at that time) was partially driven by evolution and partially by non-functional requirements.

Originally, the system started out as just one single web application for one specific purpose and consisted of only two components -- a MySQL database responsible for storing the data and web front-end implemented in PHP, which is quite a common separation pattern for PHP applications.

Later, I was asked to implement another PHP application with similar functionality. Initially, I wrote the application from scratch without any reuse in mind, but at some point I made two important decisions:

  • I decided to keep the databases of each applications separate as opposed to integrating all the tables into one single database. My main motivating factor was that I wanted to prevent another developer's wrong decisions from messing up the other application. Moreover, I realized that for the data that was specific to the application domain that other systems did not have to know about it.
  • In addition to domain specific data, I noticed that both databases also stored the same kind of data, namely: user accounts -- both systems had a user account system to allow users to change the data. This also did not motivate me to integrate both databases into one database. Instead, I created a separate user database and authentication system (as a library API) that was shared among both applications.

After completing the two web applications, I had to implement more functionality. I decided to keep all of these new features for these new problem domains in separate applications with separate databases. The only thing they had in common was a shared user authentication system.

At some point I ended up having many sub applications. As a result, I needed a portal application that redirected users to these sub applications. Essentially, what I implemented became a system of systems.

Deployment with Disnix


The "architectural decisions" that I described earlier resulted in a system composed of several kinds of components:

  • Domain-specific web applications exposing functionality that logically belongs together.
  • Domain-specific databases storing tables that are strongly correlated.
  • A shared user database.
  • A portal application redirecting users to the domain-specific web applications.

The above listed components can be distributed over multiple machines in a network, because they connect to each other through network links (e.g. connecting to a MySQL database can be done with a TCP connection and connecting to a domain specific web application can be done through HTTP). As a result, they can also be modeled as services that can be deployed with Disnix.

To replicate the same patterns for demo purposes, I integrated my framework's example applications into a similar system of sub systems. We can deploy the corresponding example system to one single target machine with Disnix, by running:


$ disnixos-env -s services.nix \
-n network-single.nix \
-d distribution-single.nix --use-nixops

The entire system gets deployed to a single machine because of the distribution model (distribution.nix) that maps all services to one target machine:


{infrastructure}:

{
usersdb = [ infrastructure.test1 ];
cmsdb = [ infrastructure.test1 ];
cmsgallerydb = [ infrastructure.test1 ];
homeworkdb = [ infrastructure.test1 ];
literaturedb = [ infrastructure.test1 ];
portaldb = [ infrastructure.test1 ];

cms = [ infrastructure.test1 ];
cmsgallery = [ infrastructure.test1 ];
homework = [ infrastructure.test1 ];
literature = [ infrastructure.test1 ];
users = [ infrastructure.test1 ];
portal = [ infrastructure.test1 ];
}

The resulting deployment architecture looks as follows:


The above visualization of the deployment architecture shows the following aspects:

  • The surrounding light grey colored box denotes a target machine. In this particular example, we only have one single target machine where services are deployed to.
  • The dark grey colored boxes correspond to container environments. For our example system, we have two of them: mysql-database corresponding to a MySQL DBMS server and apache-webapplication corresponding to an Apache HTTP server.
  • The ovals denote services corresponding to MySQL databases and web applications.
  • The arrows denote inter-dependency links that correspond to network connections. As explained in my previous blog post, solid arrows are dependencies with a strict ordering requirement while dashed arrows are dependencies without an ordering requirement.

Some people may argue that it is not really beneficial to deploy such a system with Disnix -- with NixOps you can define a machine configuration having a MySQL DBMS server and an Apache HTTP server with the corresponding databases and web application components. With Disnix, you must first ensure that the machines, the MySQL and Apache HTTP servers are configured by other means first (that could for example be done with NixOps), and then you have to deploy the system's components with Disnix.

In a single machine deployment scenario, it may indeed not be that beneficial. However, what you get in addition to automated deployment is also more flexibility. Since Disnix manages the services directly, as opposed to entire machine configurations as a whole, you can anticipate better in case of events by redeploying the system.

For example, when the amount of visitors keeps growing, you may run into the problem that a single server can no longer handle all the traffic. In such cases, you can easily add another machine to the network and adjust the distribution model to move (for example) the databases to another machine:


{infrastructure}:

{
usersdb = [ infrastructure.test2 ];
cmsdb = [ infrastructure.test2 ];
cmsgallerydb = [ infrastructure.test2 ];
homeworkdb = [ infrastructure.test2 ];
literaturedb = [ infrastructure.test2 ];
portaldb = [ infrastructure.test2 ];

cms = [ infrastructure.test1 ];
cmsgallery = [ infrastructure.test1 ];
homework = [ infrastructure.test1 ];
literature = [ infrastructure.test1 ];
users = [ infrastructure.test1 ];
portal = [ infrastructure.test1 ];
}

By redeploying the system, we can take advantage of the additional system resources that the new machine provides:


$ disnixos-env -s services.nix \
-n network-separate.nix \
-d distribution-separate.nix --use-nixops

resulting in the following deployment architecture:


Likewise, there are countless of other deployment strategies possible to meet all kinds of non-functional requirements. For example, we can also distribute bundles of domain specific application and database pairs over two machines:


$ disnixos-env -s services.nix \
-n network-bundles.nix \
-d distribution-bundles.nix --use-nixops

resulting in the following deployment architecture:


This approach is even more scalable than simply offloading the databases to another server.

In addition to scalability, there are countless of other reasons to pick a certain distribution strategy. You could also, for example, distribute redundant instances of databases and applications as a failover to improve availability or improve security by deploying the databases with privacy sensitive data to a machine with restrictive network access.

State management


When updating the deployment of systems with Disnix (such as moving a database from one machine to another), there may be a recurring limitation that you could run frequently into -- like Nix, Disnix only manages the static parts of the system, but not any state. This means that a service's deployment can be reproduced elsewhere, but data, such as the content of a database is not migrated.

For example, the sub system of example applications stores two kinds of data -- records in the MySQL database and files, such as images uploaded in the photo gallery or PDF files uploaded to the literature application. When moving these applications around the data is not migrated.

As a possible solution, Disnix also provides simple state management facilities. When enabled, Disnix will take snapshots of the databases and filesets on the source machines, transfers the snapshots to the target machines, and finally restores the snapshots when moving a service one machine to another in the distribution model.

State management can be enabled globally by passing the --deploy-state parameter to (disnix-env or annotating the services with deployState = true; in the services model):


$ disnixos-env -s services.nix \
-n network-bundles.nix \
-d distribution-bundles.nix --use-nixops --deploy-state

We can also directly use the state management system, e.g. for backup purposes. When running the following command:


$ disnix-snapshot

Disnix takes snapshots of all databases and web application state (e.g. the images in the photo gallery and uploaded PDF files) and transfers them to the coordinator machine. With the dysnomia-snapshots tool we can inspect the snapshot store:


$ dysnomia-snapshots --query-all
apache-webapplication/cms/1f9ed847885d2b3e3c67c51231122d958751eb5e2443c281e02e1d7108a505a3
apache-webapplication/cmsgallery/28d17a6941cb195a92e748aae737ccf524747477c6943436b734891d0f36fd53
apache-webapplication/literature/ed5ec4f8b9b4fcdb8b740ad1fa7ecb40b10dece03548f1d6e09a6a82c804131b
apache-webapplication/portal/5bbea499f8f8a4f708bb873ad683dbf088afa4c553f90ab287a9249a7ef02651
mysql-database/cmsdb/aa75992f780991c39a0969dcac5f69b04685c4fa764937476b816e938d6972ba
mysql-database/cmsgallerydb/31ebdaba658ca376123ff6a91a3e275731b383346a07840b1acaa1e44d921b65
mysql-database/homeworkdb/f0fda91545af0cb300afd84592d4914dcd48257053401e232438e34d83af828d
mysql-database/literaturedb/cb881c2200a5f1562f0b66f1394d0902bbb8e2361068fe096faac3bc31f76b5d
mysql-database/portaldb/5d8a5cb952f40ce76f93eb939d0b37eab33736d7b1e1426038322f8a572034ee
mysql-database/usersdb/64d11fc7f8969da5da318276a666f2e00e0a020ba619a1d82ed9b84a7f1c2ca6

and with some shell scripting, the actual contents of the snapshot store:


$ find $(dysnomia-snapshots --resolve $(dysnomia-snapshots --query-all)) -type f
/home/sander/state/snapshots/apache-webapplication/cms/1f9ed847885d2b3e3c67c51231122d958751eb5e2443c281e02e1d7108a505a3/state.tar.xz
/home/sander/state/snapshots/apache-webapplication/cmsgallery/28d17a6941cb195a92e748aae737ccf524747477c6943436b734891d0f36fd53/state.tar.xz
/home/sander/state/snapshots/apache-webapplication/literature/ed5ec4f8b9b4fcdb8b740ad1fa7ecb40b10dece03548f1d6e09a6a82c804131b/state.tar.xz
/home/sander/state/snapshots/apache-webapplication/portal/5bbea499f8f8a4f708bb873ad683dbf088afa4c553f90ab287a9249a7ef02651/state.tar.xz
/home/sander/state/snapshots/mysql-database/cmsdb/aa75992f780991c39a0969dcac5f69b04685c4fa764937476b816e938d6972ba/dump.sql.xz
/home/sander/state/snapshots/mysql-database/cmsgallerydb/31ebdaba658ca376123ff6a91a3e275731b383346a07840b1acaa1e44d921b65/dump.sql.xz
/home/sander/state/snapshots/mysql-database/homeworkdb/f0fda91545af0cb300afd84592d4914dcd48257053401e232438e34d83af828d/dump.sql.xz
/home/sander/state/snapshots/mysql-database/literaturedb/cb881c2200a5f1562f0b66f1394d0902bbb8e2361068fe096faac3bc31f76b5d/dump.sql.xz
/home/sander/state/snapshots/mysql-database/portaldb/5d8a5cb952f40ce76f93eb939d0b37eab33736d7b1e1426038322f8a572034ee/dump.sql.xz
/home/sander/state/snapshots/mysql-database/usersdb/64d11fc7f8969da5da318276a666f2e00e0a020ba619a1d82ed9b84a7f1c2ca6/dump.sql.xz

The above output shows that for each MySQL database, we store a compressed SQL dump of the database and for each stateful web application, a compressed tarball of state files.

Conclusion


In this blog post, I have described a more realistic public Disnix example that is inspired by my web framework developments a long time ago. Aside from automating a system's deployment, the purpose of this blog post is to describe how a system that can be decomposed into distributable services that can be deployed with Disnix. Implementing such a system is all but trivial and driven by various kinds of design decisions.

Availability


The example web application system can be obtained from my GitHub page. The Disnix deployment expressions can be found in the deployment/ sub folder.

In addition, I have created a Dysnomia module named: fileset that can capture the state files of web applications in a compressed tarball.

After the recent developments the Disnix toolset has reached a new stable point. As a result, I have decided to release Disnix 0.8. Consult the Disnix homepage for more information!

A new challenge

$
0
0
It has been quiet on my blog for a while, but don't worry, I'm still alive. In my absence, many things have happened. Most importantly, I have decided to embark on a new challenge.

Almost two months ago, I joined Mendix, a Rotterdam-based company (with its headquarters located in Boston) that ships a low-code application development platform. I will be working on their deployment infrastructure and I will be thinking about software modularity.

Some reflection


I vividly remember the days when I completed my PhD and left academia -- I still had to wait a couple of months for the defence ceremony, mainly because of the availability of the committee members.

Although I was quite happy with some of the work I had done during my research, I also wanted to leave my ivory tower and be more connected to the "real world" and my "research audience": developers. I joined a small startup company named Conference Compass (located in the YES!Delft incubator centre) that consisted of fewer than 10 people around the time I joined.

They had been looking into a setting up a product-line for their mobile conference apps, which sounded like an interesting challenge.

In the years that I was employed at Conference Compass, quite a few things happened. Most notably, the size of the company, the product and the service portfolio have grown considerably. Aside from these developments, I am particularly proud that I made quite a number of impacting open source contributions as part of my daily work.

The app building infrastructure


The biggest contribution contribution I made by far is the mobile app building infrastructure. Most of its components have been part of Nixpkgs (the ecosystem of packages that can be deployed with the Nix package manager) for several years, such as:


To carry out all the builds on a large and timely scale, I installed a Hydra cluster: a Nix-based continuous integration service. I also developed an NPM module and command-line tool that we could use to remotely control a Hydra server from our custom built applications.

Chat functionality


Another interesting development area was enriching the app's product line with chat functionality built around the XMPP protocol/ejabberd service. I have ported the Simple XMPP library from the Node.js package ecosystem to Titanium by using a zero-forking strategy and I made a simple test application that somewhat resembles the Pidgin chat application.

I also ran into a number of practical issues while trying to keep the architecture of the test app clean. I did an in-depth study on the MVC paradigm and wrote a blog post about my findings.

Node.js


I also learned quite a lot about Node.js and its underlying concepts, such as asynchronous programming. Before joining Conference Compass, my JavaScript knowledge was limited to browser usage only, and I was not using JavaScript on a very extensive scale.

My learning experiences resulted in the following blog posts elaborating about various kinds of related concepts:


As of today, some of these blog posts are still in my all-time top 10 of most frequently read blog posts.

As a result of having to work with Node.js and being involved with the Nix project, I became the maintainer of node2nix, a tool that can generate Nix expressions from NPM package configurations after the maintainer of npm2nix decided to hand over the project.

Building a service deployment platform


In the first two years of my employment, my chief responsibility was the app building infrastructure. Another thing I am particularly proud of is the deployment infrastructure for the service platform that I built from scratch, that grew from just a single virtual machine hosted in the Amazon EC2 cloud to a platform managing the data and configuration services for 100+ apps per year, with some events attracting tens of thousands of app users.

I used variety of solutions, such as various Amazon web services, e.g. EC2, Route 53, S3. Most importantly, I used NixOps for infrastructure deployment and Disnix (the tool I created as part of my research) for service deployment.

Although Disnix already supported all the features I needed before I actually started using it at Conference Compass, my company experiences helped to substantially improve Disnix from a usability perspective -- in academia, Disnix was mostly used to validate my research objectives. To make it suitable for using it in a company with non-specialized deployment people, you need iron out many additional issues. I added more helpful error messages, assistance in recovering from errors and additional utilities to make diagnosing problems and carrying out maintenance tasks more convenient.

At the end of 2015, after using Disnix for almost one year in production, I gave a talk about Disnix's deployment concepts at NixCon 2015, the first edition of a conference fully centered around Nix and its related technologies.

Conclusion


I am grateful to my previous employer: Conference Compass who gave me the opportunity to do all the things described in this blog post.

At Mendix, there will be many new interesting challenges for me -- I will be working with different kinds of technologies, a new platform and new people. Stay tuned, for more information...

My introduction to Mendix and low-code application development

$
0
0
As explained in my previous blog post, I started a new challenge two months ago and joined Mendix, a company that develops a low-code application development platform. Because I am receiving many questions about the platform and low-code application development in general, I have decided to write a blog post about it.

Low-code application development


With Mendix, the idea is that instead of writing (textual) code, applications are developed by modeling (and configuring) various kinds of application aspects, such as the data model and graphical user interfaces.

Why would that be useful you might ask? It makes you a developer more productive.

As many seasoned software developers may probably already know, developing applications is typically a costly investment and time consuming process. Aside from implementing all desired functionality and making it work correctly, a developer typically also needs to solve many kinds of secondary issues, such as:

  • Memory management.
  • Interacting with a database for storage.
  • Pagination: Displaying a fixed amount of rows on a page for a collection of data
  • Making screens work on various kinds of displays, e.g. monitors, tablets, phones.
  • Deploying the application to a production environment so that it can be used by (possibly) a large number of end users.

For certain classes of systems, such as information systems, these secondary issues are frequently implemented over and over again, wasting precious development time and increasing the likelihood on errors.

With Mendix, most of these secondary concerns have been addressed in a reasonable manner, and primary functionality can be implemented by modeling and writing none to just a tiny amount of code, providing an incredible boost in developer productivity.

In addition to productivity, another objective of the Mendix platform is to reach a broader audience than just developers, such as business consultants.

A simple development scenario


What does the development process of a very tiny Mendix application look like? Typically, an application development process starts by creating a data model that specifies what kind of data needs to be stored (either in a database or in memory).


As shown in the picture above, in Mendix, a data model is created by drawing an ER (Entity-Relationship) diagram. For example, the example above defines a 'Contact' entity representing a contact person (with properties: first name and last name) and a 'Message' entity representing chat messages. A contact can send zero or more messages, as denoted by the 'Message_Contact' relationship.

After defining a data model, you may want to develop screens containing user interface components allowing users to inspect and change the data. In Mendix, these screens can be generated with just a few simple mouse clicks: creating a blank page, dragging and dropping a data grid to the page, and dragging and dropping the 'Contact' entity to the data grid:


In addition to an overview page displaying a collection of data items, we also want a page allowing us to change a record or to create a new record. This screen can be created by right clicking on the 'New' button and picking the 'Generate page...' option. The result is a page allowing us to edit an individual contact record:


Finally, to make the screen available to end users, we must add it to the navigation layout by creating a button that redirects the user to the overview page.

The result


With just a few simple clicks, we have already constructed a very small, but working application. For example, when clicking on the 'Run locally...' button in the IDE, we can start an instance of our test app and run it in a web browser.

The overview page looks as follows:


As may be observed by looking at the image above, the screen offers all functionality needed to work with a collection of records: navigation buttons, a search function, pagination, and edit functionality.

By clicking on an item in the list or clicking on the 'Edit' button, we can navigate to a page allowing us to edit the properties of a record:


After changing any of the attributes and clicking on the 'Save' button, the changes will be committed to the database.

Implementing all this functionality did not require me to write a single line of code.

Programming additional functionality


In addition to defining the domain model and generating views for all data elements, we may also want to program additional functionality, for example, a method that counts the amount of contacts having a first name that starts with an 'S'.

In Mendix, server-side functionality can be programmed by constructing microflows. Microflows use a graphical notation based on the Business Process Model and Notation (BPMN), a standardized graphical notation.


The above microflow retrieves all contacts from the database, sets an initial counter variable to 0 and iterates over all contacts. When a first name that starts with an 'S' has been encountered, the counter will be increased. Finally, it will return the counter value to the caller.

Microflows can be used to program many kinds of aspects in an application -- for example, they can be launched on startup, attached to buttons as event handlers, or attached as pre- or post commit hooks to data entities.

Other features


Besides the features described in this blog post, the Mendix platform has many additional features to offer, such as access control, internationalization, custom page layouts and collaboration tools (with a Subversion-based team server, and sprintr application managing the development workflow).

Furthermore, the deployment process of a Mendix application is completely automated in the Mendix cloud -- with just a few simple mouse clicks, your application becomes publicly available, without having to worry about managing a database or application server.

Finally, the platform is extensible -- you can implement custom actions in Java (server-side) and your own widgets in JavaScript (client-side) and there is an app store allowing you to download all kinds of pre-built third-party extensions.

Availability


You can sign up for a free Mendix account to start experimenting.

Layered build function abstractions for building Nix packages

$
0
0
I have shown quite a few Nix expression examples on my blog. When it is desired to write a Nix expression for a package, it is a common habit to invoke the stdenv.mkDerivation {} function, or functions that are abstractions built around it.

For example, if we want to build a package, such as the trivial GNU Hello package, we can write the following expression:


with import <nixpkgs> {};

stdenv.mkDerivation {
name = "hello-2.10";

src = fetchurl {
url = mirror://gnu/hello/hello-2.10.tar.gz;
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};

meta = {
description = "A program that produces a familiar, friendly greeting";
longDescription = ''
GNU Hello is a program that prints "Hello, world!" when you run it.
It is fully customizable.
'';
homepage = http://www.gnu.org/software/hello/manual/;
license = "GPLv3+";
};
}

and build it with the Nix package manager as follows:


$ nix-build
/nix/store/188avy0j39h7iiw3y7fazgh7wk43diz1-hello-2.10

The above code fragment does probably not look too complicated and is quite easy to repeat to build other kinds of GNU Autotools/GNU Make-based packages. However, stdenv.mkDerivation {} is a big/complex function abstraction that has many responsibilities.

Its most important responsibility is to compose a so-called pure build environments, in which various restrictions are imposed on the build scripts to provide better guarantees that builds are pure (meaning: that they always produce the same (nearly) bit-identical result if the dependencies are the same), such as:

  • Build scripts can only write to designated output directories and temp directories. They are restricted from writing to any other file system location.
  • All environment variables are cleared and some of them are set to default or dummy values, such as search path environment variables (e.g. PATH).
  • All build results are made immutable by removing the write permission bits and their timestamps are reset to one second after the epoch.
  • Running builds as unprivileged users.
  • Optionally, builds run in a chroot environment and use namespaces to restrict access to the host filesystem and the network as much as possible.

In addition to purity, the stdenv.mkDerivation {} function has many additional responsibilities. For example, it also implements a generic builder that is clever enough to build a GNU Autotools/GNU Make project without specifying any build instructions.

For example, the above Nix expression for GNU Hello does not specify any build instructions. The generic builder automatically unpacks the tarball, opens the resulting directory and invokes ./configure --prefix=$out; make; make install with the appropriate parameters.

Because stdenv.mkDerivation {} has many responsibilities and nearly all packages in Nixpkgs depend on it, its implementation is very complex (e.g. thousands of lines of code) and hard to change.

As a personal exercise, I have developed a function abstraction with similar functionality from scratch. My implementation can be decomposed into layers in which every abstraction layer gradually adds additional responsibilities.

Writing "raw" derivations


stdenv.mkDerivation is a function abstraction, not a feature of the Nix expression language. To compose "pure" build environments, stdenv.mkDerivation invokes a Nix expression language construct -- the derivation {} builtin.

(As a sidenote: derivation is strictly speaking not a builtin, but an abstraction built around the derivationStrict builtin, but this is something internal to the Nix package manager. It does not matter for the scope of this blog post).

Despite the fact that this low level function is not commonly used, it is also possible to directly invoke it and compose low-level "raw" derivations to build packages. For example, we can write the following Nix expression (default.nix):


derivation {
name = "test";
builder = ./test.sh;
system = "x86_64-linux";
person = "Sander";
}

The above expression invokes the derivation builtin function that composes a "pure" build environment:

  • The name attribute specifies the name of the package, that should appear in the resulting Nix store path.
  • The builder attribute specifies that the test.sh executable should be run inside the pure build environment.
  • The system attribute is used to tell Nix that this build should be carried out for x86-64 Linux systems. When Nix is unable to build the package for the requested system architecture, it can also delegate a build to a remote machine that is capable.
  • All attributes (including the attributes described earlier) are converted to environment variables (e.g. strings, numbers and URLs are converted to strings and the boolean value: 'true' is converted to '1') and can be used by the builder process for a variety of reasons.

We can implement the builder process (the test.sh build script) as follows:


#!/bin/sh -e

echo "Hello $person"> $out

The above script generates a greeting message for the provided person (exposed as an environment variable by Nix) and writes it to the Nix store (the output path is provided by the out environment variable).

We can evaluate the Nix expression (and generate the output file with the Hello greeting) by running:


$ nix-build
/nix/store/7j4y5d8rx1vah5v64bpqd5dskhwx5105-test
$ cat result
Hello Sander

The return value of the derivation {} function is a bit confusing. At first sight, it appears to be a string corresponding to the output path in the Nix store. However, some investigation with the nix repl tool reveals that it is much more than that:


$ nix repl
Welcome to Nix version 2.0.4. Type :? for help.

when importing the derivation:


nix-repl> test = import ./default.nix

and describing the result:


nix-repl> :t test
a set

we will see that the result is actually an attribute set, not a string. By requesting the attribute names, we will see the following attributes:


nix-repl> builtins.attrNames test
[ "all""builder""drvAttrs""drvPath""name""out""outPath""outputName""person""system""type" ]

It appears that the resulting attribute set has the same attributes as the parameters that we passed to derivation, augmented by the following additional attributes:

  • The type attribute that refers to the string: "derivation".
  • The drvAttrs attribute refers to an attribute set containing the original parameters passed to derivation {}.
  • drvPath and outPath refer to the Nix store paths of the store derivation file and output of the build. A side effect of requesting these members is that the expression gets evaluated or built.
  • The out attribute is a reference to the derivation producing the out result, all is a list of derivations of all outputs produced (Nix derivations can also produce multiple output paths in the Nix store).
  • In case there are multiple outputs, the outputName determines the name of the output path that is the default.

Providing basic dependencies


Although we can use the low-level derivation {} function to produce a very simple output file in the Nix store, it is not very useful on its own.

One important limitation is that we only have a (Bourne-compatible) shell (/bin/sh), but no other packages in the "pure" build environment. Nix prevents unspecified dependencies from being found to make builds more pure.

Since a pure build environment is almost entirely empty (with the exception of the shell), the amount of things we can do in an environment created by derivation {} is very limited -- most of the commands that build scripts run are provided by executables belonging to external packages, e.g. commands such as cat, ls (GNU Coreutils), grep (GNU Grep) or make (GNU Make) and should be added to the PATH search environment variable in the build environment.

We may also want to configure additional environment variables to make builds more pure -- for example, on Linux systems, we want to set the TZ (timezone) environment variable to UTC to prevent error messages, such as: "Local time zone must be set--see zic manual page".

To make the execution of more complex build scripts more convenient, we can create a setup script that we can include in a every build script that adds basic utilities to the PATH search environment variable, configures these additional environment variables, and sets the SHELL environment variable to the bash shell residing in the Nix store. We can create a package named: stdenv that provides a setup script to accomplish this:


{bash, basePackages, system}:

let
shell = "${bash}/bin/sh";
in
derivation {
name = "stdenv";
inherit shell basePackages system;
builder = shell;
args = [ "-e" ./builder.sh ];
}

The builder script of the stdenv package can be implemented as follows:

set -e

# Setup PATH for base packages
for i in $basePackages
do
basePackagesPath="$basePackagesPath${basePackagesPath:+:}$i/bin"
done

export PATH="$basePackagesPath"

# Create setup script
mkdir $out
cat > $out/setup <<EOF
export SHELL=$shell
export PATH="$basePackagesPath"
EOF

# Allow the user to install stdenv using nix-env and get the packages
# in stdenv.
mkdir $out/nix-support
echo "$basePackages"> $out/nix-support/propagated-user-env-packages

The above script adds all base packages (GNU Coreutils, Findutils, Diffutils, sed, grep, gawk and bash) to the PATH of builder and creates a script in $out/setup that exports the PATH environment variable and the location to the bash shell.

We can use the stdenv (providing this setup script) as a dependency for building a package, such as:


{stdenv}:

derivation {
name = "hello";
inherit stdenv;
builder = ./builder.sh;
system = "x86_64-linux";
}

In the corresponding builder script, we include the setup script in the first line and we, for example, invoke various external commands to generate a shell script that says: "Hello world!":


#!/bin/sh -e
source $stdenv/setup

mkdir -p $out/bin

cat > $out/bin/hello <<EOF
#!$SHELL -e

echo "Hello"
EOF

chmod +x $out/bin/hello

The above script works because the setup script adds GNU Coreutils (that includes cat, mkdir and chmod) to the PATH of the builder.

Writing more simple derivations


Using a setup script makes writing build scripts somewhat practical, but there are still a number inconveniences we have to cope with.

The first inconvenience is the system parameter -- in most cases, we want to build a package for the same architecture as the host system's architecture and preferably we want the same architecture for all other packages that we intend to deploy.

Another issue is the shell. /bin/sh is, in a sandbox-enabled Nix installations, a minimal Bourne-compatible shell provided by Busybox, or a reference to the host system's shell in non-sandboxed installations. The latter case could be considered an impurity, because we do not know what kind of shell (e.g. bash, dash, ash ?) or version of a shell we are using (e.g. 3.2.57, 4.3.30 ?). Ideally, we want to use a shell that is provided as a Nix package in the Nix store, because that version is pure.

(As a sidenote: in Nixpkgs, we use the bash shell to run build commands, but this is not a strict requirement. For example, GNU Guix (a package manager that uses several components of the Nix package manager) uses both Guile as a host and guest language. In theory, we could also launch a different kind of interpreter than bash).

The third issue is the meta parameter -- for every package, it is possible to specify meta-data, such as a description, license and homepage reference as an attribute set. Unfortunately, attribute sets cannot be converted to environment variables. To deal with this problem, the meta attribute needs to be removed before we invoke derivation {} and be readded to the return attribute set. (IMO I believe this ideally should be something the Nix package manager could solve by itself).

We can hide all these inconveniences by creating a simple abstraction function that I will call: stdenv.simpleDerivation that can be implemented as follows:


{stdenv, system, shell}:
{builder, ...}@args:

let
extraArgs = removeAttrs args [ "builder""meta" ];

buildResult = derivation ({
inherit system stdenv;
builder = shell; # Make bash the default builder
args = [ "-e" builder ]; # Pass builder executable as parameter to bash
setupSimpleDerivation = ./setup.sh;
} // extraArgs);
in
buildResult //
# Readd the meta attribute to the resulting attribute set
(if args ? meta then { inherit (args) meta; } else {})

The above Nix expression basically removes the meta argument, then invokes the derivation {} function, sets the system parameter, uses bash as builder and passes the builder executable as an argument to bash. After building the package, the meta attribute gets readded to the result.

With this abstraction, we can reduce the complexity of the previously shown Nix expression to something very simple:


{stdenv}:

stdenv.simpleDerivation {
name = "hello";
builder = ./builder.sh;
meta = {
description = "This is a simple testcase";
};
}

The function abstraction is also sophisticated enough to build something more complex, such as GNU Hello. We can write the following Nix expression that passes all dependencies that it requires as function parameters:


{stdenv, fetchurl, gnumake, gnutar, gzip, gcc, binutils}:

stdenv.simpleDerivation {
name = "hello-2.10";
src = fetchurl {
url = mirror://gnu/hello/hello-2.10.tar.gz;
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};
inherit stdenv gnumake gnutar gzip gcc binutils;
builder = ./builder.sh;
}

We can use the following builder script to build GNU Hello:


source $setupSimpleDerivation

export PATH=$PATH:$gnumake/bin:$gnutar/bin:$gzip/bin:$gcc/bin:$binutils/bin

tar xfv $src
cd hello-2.10
./configure --prefix=$out
make
make install

The above script imports a setup script configuring basic dependencies, then extends the PATH environment variable with additional dependencies, and then executes the commands to build GNU Hello -- unpacking the tarball, running the configure script, building the project, and installing the package.

The run command abstraction


We can still improve a bit upon the function abstraction shown previously -- one particular inconvenience that remains is that you have to write two files to get a package built -- a Nix expression that composes the build environment and a builder script that carries out the build steps.

Another repetitive task is configuring search path environment variables (e.g. PATH, PYTHONPATH, CLASSPATH etc.) to point to the appropriate directories in the Nix store. As may be noticed by looking at the code of the previous builder script, this process is tedious.

To address these inconveniences, I have created another abstraction function called: stdenv.runCommand that extends the previous abstraction function -- when no builder parameter has been provided, this function executes a generic builder that will evaluate the buildCommand environment variable containing a string with shell commands to execute. This feature allows us to rewrite the first example (that generates a shell script) to one file:


{stdenv}:

stdenv.runCommand {
name = "hello";
buildCommand = ''
mkdir -p $out/bin
cat > $out/bin/hello <<EOF
#! ${stdenv.shell} -e

echo "Test"
EOF
chmod +x $out/bin/hello
'';
}

Another feature of the stdenv.runCommand abstraction is to provide a generic mechanism to configure build-time dependencies -- all build-time dependencies that a package needs can be provided as a list of buildInputs. The generic builder carries out all necessary build steps to make them available. For example, when a package provides a bin/ sub folder, then it will be automatically added to the PATH environment variable.

Every package can bundle a setup-hook.sh script that modifies the build environment so that it knows how dependencies for this package can be configured. For example, the following partial expression represents the Perl package that bundles a setup script:


{stdenv, ...}:

stdenv.mkDerivation {
name = "perl";
...
setupHook = ./setup-hook.sh
}

The setup hook can automatically configure the PERL5LIB search path environment variable for all packages that provide Perl modules:


addPerlLibPath()
{
addToSearchPath PERL5LIB $1/lib/perl5/site_perl
}

envHooks+=(addPerlLibPath)

When we add perl as a build input to a package, then its setup hook configures the generic builder in such a way that the PERL5LIB environment variable is automatically configured when we provide a Perl module as a build input.

We can also more conveniently build GNU Hello, by using the buildInputs parameter:


{stdenv, fetchurl, gnumake, gnutar, gzip, gcc, binutils}:

stdenv.runCommand {
name = "hello-2.10";
src = fetchurl {
url = mirror://gnu/hello/hello-2.10.tar.gz;
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};
buildInputs = [ gnumake gnutar gzip gcc binutils ];
buildCommand = ''
tar xfv $srcb
cd hello-2.10
./configure --prefix=$out
make
make install
'';
}

Compared to the previous GNU Hello example, this Nix expression is much simpler and more intuitive to write.

The run phases abstraction


We can improve the ease of use for build processes even further. GNU Hello, and many other GNU packages and other system software used for Linux are GNU Autotools/GNU Make based and follow similar conventions including the build commands you need to carry out. Likewise, many other software projects use standardized build tools that follow conventions.

As a result, when you have to maintain a collection of packages, you probably end up writing the same kinds of build instructions over and over again.

To alleviate this problem, I have created another abstraction layer, named: stdenv.runPhases making it possible to define and execute phases in a specific order. Every phase has a pre and post hook (a script that executes before and after each phase) and can be disabled or reenabled with a do* or dont* flag.

With this abstraction function, we can divide builds into phases, such as:


{stdenv}:

stdenv.runPhases {
name = "hello";
phases = [ "build""install" ];
buildPhase = ''
cat > hello <<EOF
#! ${stdenv.shell} -e
echo "Hello"
EOF
chmod +x hello
'';
installPhase = ''
mkdir -p $out/bin
mv hello $out/bin
'';
}

The above Nix expression executes a build and install phase. In the build phase, we construct a script that echoes "Hello", and in the install phase we move the script into the Nix store and we make it executable.

In addition to environment variables, it is also possible to define the phases in a setup script as shell functions. For example, we can also use a builder script:


{stdenv}:

stdenv.runPhases {
name = "hello2";
builder = ./builder.sh;
}

and define the phases in the builder script:


source $setupRunPhases

phases="build install"

buildPhase()
{
cat > hello <<EOF
#! $SHELL -e
echo "Hello"
EOF
chmod +x hello
}

installPhase()
{
mkdir -p $out/bin
mv hello $out/bin
}

genericBuild

Another feature of this abstraction is that we can also define exitHook and failureHook parameters that will be executed if the builder succeeds or fails.

In the next sections, I will show abstractions built on top of stdenv.runPhases that can be used to hide implementation details of common build procedures.

The generic build abstraction


For many build procedures, we need to carry out the same build steps, such as: unpacking the source archives, applying patches, and stripping debug symbols from the resulting ELF executables.

I have created another build function abstraction named: stdenv.genericBuild that implements a number of common build phases:

  • The unpack phase generically unpacks the provided sources, makes it content writable and opens the source directory. The unpack command is determined by the unpack hook that each potential unpacker provides -- for example, the GNU tar package includes a setup hook that untars the file if it looks like a tarball or compressed tarball:


    _tryUntar()
    {
    case "$1" in
    *.tar|*.tar.gz|*.tar.bz2|*.tar.lzma|*.tar.xz)
    tar xfv "$1"
    ;;
    *)
    return 1
    ;;
    esac
    }

    unpackHooks+=(_tryUntar)
  • The patch phase applies any patch that is provided by the patches parameter uncompressing them when necessary. The uncompress file operation also works with setup hooks -- uncompressor packages (such as gzip and bzip2) provide a setup hook that uncompresses the file if it is of the right filetype.
  • The strip phase processes all sub directories containing ELF binaries (e.g. bin/ and lib/) and strips their debugging symbols. This reduces the size of the binaries and removes non-deterministic timestamps.
  • The patchShebangs phase processes all scripts with a shebang line and changes it to correspond to a path in the Nix store.
  • The compressManPages phase compresses all manual pages with gzip.

We can also add GNU patch as as base package for this abstraction function, since it is required to execute the patch phase. As a result, it does not need to be specified as a build dependency for each package.

This function abstraction alone is not very useful, but it captures all common aspects that most build tools use, such as GNU Make, CMake or SCons projects.

I can reduce the size of the previously shown GNU Hello example Nix expression to the following:


{stdenv, fetchurl, gnumake, gnutar, gzip, gcc, binutils}:

stdenv.genericBuild {
name = "hello-2.10";
src = fetchurl {
url = mirror://gnu/hello/hello-2.10.tar.gz;
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};
buildInputs = [ gnumake gnutar gzip gcc binutils ];
buildCommandPhase = ''
./configure --prefix=$out
make
make install
'';
}

In the above expression, I no longer have to specify how to unpack the download GNU Hello source tarball.

GNU Make/GNU Autotools abstraction


We can extend the previous function abstraction even further with phases that automate a complete GNU Make/GNU Autotools based workflow. This abstraction is what we can call stdenv.mkDerivation and is comparable in terms of features with the implementation in Nixpkgs.

We can adjust the phases to include a configure, build, check and install phase. The configure phase checks whether a configure script exists and executes it. The build, check and install phases will execute: make, make check and make install with appropriate parameters.

We can also add common packages that we need to build these projects as base packages so that they no longer have to be provided as a build input: GNU Tar, gzip, bzip2, xz, GNU Make, Binutils and GCC.

With these additional phases and base packages, we can reduce the GNU Hello example to the following expression:


{stdenv, fetchurl}:

stdenv.mkDerivation {
name = "hello-2.10";
src = fetchurl {
url = mirror://gnu/hello/hello-2.10.tar.gz;
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};
}

The above Nix expression does not contain any installation instructions -- the generic builder is able to figure out all steps on its own.

Composing custom function abstractions


I have shown several build abstraction layers implementing most features that are in the Nixpkgs version of stdenv.mkDerivation. Aside from clarity, another objective of splitting this function in layers is to make the composition of custom build abstractions more convenient.

For example, we can implement the trivial builder named: writeText whose only responsibility is to write a text file into the Nix store, by extending stdenv.runCommand. This abstraction suffices because writeText does not require any build tools, such as GNU Make and GCC, and it also does not need any generic build procedure executing phases:


{stdenv}:

{ name # the name of the derivation
, text
, executable ? false # run chmod +x ?
, destination ? "" # relative path appended to $out eg "/bin/foo"
, checkPhase ? "" # syntax checks, e.g. for scripts
}:

stdenv.runCommand {
inherit name text executable;
passAsFile = [ "text" ];

# Pointless to do this on a remote machine.
preferLocalBuild = true;
allowSubstitutes = false;

buildCommand = ''
target=$out${destination}
mkdir -p "$(dirname "$target")"

if [ -e "$textPath" ]
then
mv "$textPath""$target"
else
echo -n "$text">"$target"
fi

[ "$executable" = "1" ] && chmod +x "$target" || true
'';
}

We can also make a builder for Perl packages, by extending: stdenv.mkDerivation -- Perl packages also use GNU Make as a build system. Its only difference is the configuration step -- it runs Perl's MakeMaker script to generate the Makefile. We can simply replace the configuration phase for GNU Autotools by an implementation that invokes MakeMaker.

When developing custom abstractions, I basically follow this pattern:


{stdenv, foo, bar}:
{name, buildInputs ? [], ...}@args:

let
extraArgs = removeAttrs args [ "name""buildInputs" ];
in
stdenv.someBuildFunction ({
name = "mypackage-"+name;
buildInputs = [ foo bar ] ++ buildInputs;
} // extraArgs)

  • A build function is a nested function in which the first line is a function header that captures the common build-time dependencies required to build a package. For example, when we want to build Perl packages, then perl is such a common dependency.
  • The second line is the inner function header that captures the parameters that should be passed to the build function. The notation allows an arbitrary number of parameters. The parameters in the { } block (name, buildInputs) are considered to have a specific use in the body of the function. The remainder of parameters are non-essential -- they are used as environment variables in the builder environment or they can be propagated to other functions.
  • We compose an extraArgs variable that contains all non-essential arguments that we can propagate to the build function. Basically, all function arguments that are used in the body need to be removed and function arguments that are attribute sets, because they cannot be converted to strings.
  • In the body of the function, we set up important aspects of the build environment, such as the mandatory build parameters, and we propagate the remaining function arguments to the builder abstraction function.

Following this pattern also ensures that the builder is flexible enough to be extended and modified. For example, by extending a function that is based on stdenv.runPhases the builder can be extended with custom phases and build hooks.

Discussion


In this blog post, I have derived my own reimplementation of Nixpkgs's stdenv.mkDerivation function that consists of the following layers each gradually adding functionality to the "raw"derivation {} builtin:

  1. "Raw" derivations
  2. The setup script ($stdenv/setup)
  3. Simple derivation (stdenv.simpleDerivation)
  4. The run command abstraction (stdenv.runCommand)
  5. The run phases abstraction (stdenv.runPhases)
  6. The generic build abstraction (stdenv.genericBuild)
  7. The GNU Make/GNU Autotools abstraction (stdenv.mkDerivation)

The features that the resulting stdenv.mkDerivation provides are very similar to the Nixpkgs version, but not entirely identical. Most notably, cross compiling support is completely absent.

From the experience, I have a number of improvement suggestions that we may want to implement in Nixpkgs version to improve the quality and clarity of the generic builder infrastructure:

  • We could also split the implementation of stdenv.mkDerivation and the corresponding setup.sh script into layered sub functions. Currently, the setup.sh script is huge (e.g. over 1200 LOC) and has many responsibilities (perhaps too many). By splitting the build abstraction functions and their corresponding setup scripts, we can separate concerns better and reduce the size of the script so that it becomes more readable and better maintainable.
  • In the Nixpkgs implementation, the phases that the generic builder executes are built for GNU Make/GNU Autotools specifically. Furthermore, the invocation of pre and post hooks and do and dont flags are all hand coded for every phase (there is no generic mechanism that deals with them). As a result, when you define a new custom phase, you need to reimplement the same aspects over and over again. In my implementation, you only have to define phases -- the generic builder automatically executes the coresponding pre and post hooks and evaluates the do and dont flags.
  • In the Nixpkgs implementation there is no uncompressHook -- as a result, the decompression of patch files is completely handcoded for every uncompressor, e.g. gzip, bzip2, xz etc. In my implementation, we can delegate this responsibility to any potential uncompressor package.
  • In my implementation, I turned some of the phases of the generic builder into command-line tools that can be invoked outside the build environment (e.g. patch-shebangs, compress-man). This makes it easier to experiment with these tools and to make adjustments.

The biggest benefit of having separated concerns is flexibility when composing custom abstractions -- for example, the writeText function in Nixpkgs is built on top of stdenv.mkDerivation that includes GNU Make and GCC as dependencies, but does not depend on it. As a result, when one of these packages get updated all generated text files need to be updated as well, while there is no real dependency on it. When using a more minimalistic function, such as stdenv.runCommand this problem will go away.

Availability


I have created a new GitHub repository called: nix-lowlevel-experiments. It contains the implementation of all function abstractions described in this blog post, including some test cases that demonstrate how these functions can be used.

In the future, I will probably experiment with other low level Nix concepts and add them to this repository as well.


Automating Mendix application deployments with Nix

$
0
0
As explained in a previous blog post, Mendix is a low-code development platform -- the general idea behind low-code application development is that instead of writing (textual) code, you model an application, such as the data structures and the corresponding views. One of the benefits of Mendix is that it makes you more productive as a developer, for certain classes of applications.

Although low-code development is conceptually different from a development perspective compared to more "traditional" development approaches (that require you to write code), there is one particular aspect a Mendix application lifecycle has in common. Eventually, you will have to deploy your app to an environment that makes your application available to end users.

For users of the Mendix cloud portal, deploying an application is quite convenient: with just a few simple mouse clicks your application gets deployed to a test, acceptance or production environment.

However, managing on-premise application deployments or actually managing applications in the cloud is all but a simple job. There all all kinds of challenges you need to cope with, such as:

  • Making sure that all dependencies of an app are present, such as a database for storage.
  • Executing all relevant deployment activities to make an app available for use.
  • Upgrading is risky and difficult -- it may break the application and introduce downtime.

There are a variety of deployment solutions available to manage deployment processes. However, no solution is perfect -- every tool has its strengths and weaknesses and no tool is a perfect fit. As a result, we still have to develop custom solutions that automate missing parts in a deployment process and we have many kinds of additional complexities that we need to cope with.

Recently, I investigated whether it would be possible to deploy Mendix applications, with my favourite class of deployment utilities from the Nix project, and I gave an introduction to the Nix project to the R&D department at Mendix.

Using tools from the Nix project


For readers not familiar with Nix: the tools in the Nix project solve many configuration management problems in their own unique way. The basis of all the tools is the Nix package manager that borrows concepts from purely functional programming languages, such as Haskell.

To summarize Nix in just a few sentences: deploying a package with Nix is the same thing as invoking a pure function that constructs a package from source code and its build-time dependencies (that are provided as function parameters). To accomplish purity, Nix composes so-called "pure build environments", in which various restrictions are imposed on the build script to ensure that the outcome will be (almost) identical if a package is built with the same build inputs.

The purely functional deployment model has all kinds of benefits -- for example, it provides very strong dependency completeness and reproducibility guarantees, and all kinds of optimizations (e.g. a package that has been deployed before does not have to be built again, packages that have no dependency on each other can be built in parallel, builds can be downloaded from a remote location or delegated to another machine).

Another important property that all tools in the Nix project have in common is declarative deployment -- instead of describing the deployment activities that need to be carried out, you describe the structure of your system that want to deploy, e.g. the packages, a system configuration, or a network of machines/services. The deployment tools infer the activities that need to be carried out to get the system deployed.

Automating Mendix application deployments with Nix


As an experiment, I investigated how Mendix application deployments could fit in Nix's vision of declarative deployment -- the objective is to take a Mendix project created by the modeler (essentially the "source code" form of an application), write a declarative deployment specification for it, and use the tools from the Nix project to get a machine running with all required components to make the app run.

To bring a Mendix application in a running state, we require the following ingredients:

  • We must obtain the Mendix runtime that interprets the Mendix models. Packaging the Mendix runtime in Nix is fairly straight forward -- simply unzipping the distribution, and moving the package contents into the Nix store, and adding a wrapper script launches the runtime suffices.
  • We must produce a Mendix Deployment Archive (MDA file) that creates a Zip container with all artifacts required to run a Mendix app by the runtime. An MDA file can be produced from a Mendix project by invoking the MxBuild tool. Since MxBuild is required for this, I had to package it as well. Packaging mxbuild is a bit trickier, as it requires mono and Node.js.

Building an MDA file with Nix


The most interesting part is writing a new function abstraction for building MDA files with Nix -- in a Nix builder environment, (almost) any build tool can be used albeit with restrictions that are imposed on them to make builds more pure.

We can also create a function abstraction that invokes mxbuild in a Nix builder environment:


{stdenv, mxbuild, jdk, nodejs}:
{name, mendixVersion, looseVersionCheck ? false, buildInputs ? [], ...}@args:

let
mxbuildPkg = mxbuild."${mendixVersion}";
extraArgs = removeAttrs args [ "buildInputs" ];
in
stdenv.mkDerivation ({
buildInputs = [ mxbuildPkg nodejs ] ++ buildInputs;
installPhase = ''
mkdir -p $out
mxbuild --target=package \
--output=$out/${name}.mda \
--java-home ${jdk} \
--java-exe-path ${jdk}/bin/java \
${stdenv.lib.optionalString looseVersionCheck "--loose-version-check"} \
"$(echo *.mpr)"
mkdir -p $out/nix-support
echo "file binary-dist \"$(echo $out/*.mda)\""> $out/nix-support/hydra-build-products
'';
} // extraArgs)

The above expression is a function that composes another function that takes common Mendix parameters -- the application name, the version of MxBuild that we want, and whether we want to use a strict or loose version check (it is possible to compile a project developed for a different version of Mendix, if desired).

In the body, we create an output directory in the Nix store, we invoke mxbuild to compile to MDA app and put it in the Nix store, and we generate a configuration file that makes it possible to expose the MDA file as a build product, when Hydra: the Nix-based continuous integration service is being used.

With the build function shown in the code fragment above, we can write a Nix expression for a Mendix project:


{ pkgs ? import { inherit system; }
, system ? builtins.currentSystem
}:

let
mendixPkgs = import ./nixpkgs-mendix/top-level/all-packages.nix {
inherit pkgs system;
};
in
mendixPkgs.packageMendixApp {
name = "conferenceschedule";
src = /home/sander/SharedWindowsFolder/ConferenceSchedule-main;
mendixVersion = "7.13.1";
}

The above expression (conferenceschedule.nix) can be used to build an MDA file for a project named: conferenceschedule, residing in the /home/sander/SharedWindowsFolder/ConferenceSchedule-main directory using Mendix version 7.13.1.

By running the following command-line instruction, we can use Nix to build our MDA:


$ nix-build conferenceschedule.nix
/nix/store/nbaa7fnzi0xw9nkf27mixyr9awnbj16i-conferenceschedule
$ ls /nix/store/nbaa7fnzi0xw9nkf27mixyr9awnbj16i-conferenceschedule
conferenceschedule.mda nix-support

In addition to building an MDA, Nix will also download the dependencies: the Mendix runtime and MxBuild, if they have not been installed yet.

Running a Mendix application


Producing an MDA file is an important ingredient in the deployment lifecycle of a Mendix application, but it is not entirely what we want -- what we really want is a running system. To get a running system, additional steps are required beyond producing an MDA:

  • We must unzip the MDA file into a directory with write permissions.
  • We must create writable state sub directories, e.g. data/tmp, data/files.
  • After starting the runtime, we must configure the admin interface, to send instructions to the runtime to initialize the database and start the app:

    $ export M2EE_ADMIN_PORT=9000
    $ export M2EE_ADMIN_PASS=secret
  • Finally, we must communicate over the admin interface to configure, initialize the database and start the app:

    curlCmd="curl -X POST http://localhost:$M2EE_ADMIN_PORT \
    -H 'Content-Type: application/json' \
    -H 'X-M2EE-Authentication: $(echo -n "$M2EE_ADMIN_PASS" | base64)' \
    -H 'Connection: close'"
    $curlCmd -d '{ "action": "update_appcontainer_configuration", "params": { "runtime_port": 8080 } }'
    $curlCmd -d '{ "action": "update_configuration", "params": { "DatabaseType": "HSQLDB", "DatabaseName": "myappdb", "DTAPMode": "D" } }'
    $curlCmd -d '{ "action": "execute_ddl_commands" }'
    $curlCmd -d '{ "action": "start" }'

These deployment steps cannot be executed by Nix, because Nix's purpose is to manage packages, but not the state of a running process. To automate these remaining parts, we generate scripts that execute the above listed steps.

NixOS integration


NixOS is a Linux distribution that extends Nix's deployment facilities to complete systems. Aside from using the Nix package manage to deploy all packages including the Linux kernel, NixOS' main objective is to deploy an entire system from a single declarative specification capturing the structure of an entire system.

NixOS uses systemd for managing system services. The systemd configuration files are generated by the Nix package manager. We can integrate our Mendix activation scripts with a generated systemd job to fully automate the deployment of a Mendix application.


{pkgs, ...}:

{
...

systemd.services.mendixappcontainer =
let
runScripts = ...
appContainerConfigJSON = ...
configJSON = ...
in {
enable = true;
description = "My Mendix App";
wantedBy = [ "multi-user.target" ];
environment = {
M2EE_ADMIN_PASS = "secret";
M2EE_ADMIN_PORT = "9000";
MENDIX_STATE_DIR = "/home/mendix";
};
serviceConfig = {
ExecStartPre = "${runScripts}/bin/undeploy-app";
ExecStart = "${runScripts}/bin/start-appcontainer";
ExecStartPost = "${runScripts}/bin/configure-appcontainer ${appContainerConfigJSON} ${configJSON}";
};
};

The partial NixOS configuration shown above defines a systemd job that runs three scripts (as shown in the last three lines):

  • The undeploy-app script removes all non-state artefacts from the working directory.
  • The start-appcontainer script starts the Mendix runtime.
  • The configure-appcontainer script configures the runtime, such as the embedded Jetty server and the database, and starts the application.

Writing a systemd job (as shown above) is a bit cumbersome. To make it more convenient to use, I captured all Mendix runtime functionality in a NixOS module, with an interface exposing all relevant configuration properties.

By importing the Mendix NixOS module into a NixOS configuration, we can conveniently define a machine configuration that runs our Mendix application:


{pkgs, ...}:

{
require = [ ../nixpkgs-mendix/nixos/modules/mendixappcontainer.nix ];

services = {
openssh.enable = true;

mendixAppContainer = {
enable = true;
adminPassword = "secret";
databaseType = "HSQLDB";
databaseName = "myappdb";
DTAPMode = "D";
app = import ../../conferenceschedule.nix {
inherit pkgs;
inherit (pkgs.stdenv) system;
};
};
};

networking.firewall.allowedTCPPorts = [ 8080 ];
}

In the above configuration, the mendixAppContainer captures all the properties of the Mendix application that we want to run:

  • The password for communicating over the admin interface.
  • The type of database we want to use (in this particular case an in memory HSQLDB instance) and the name of the database.
  • Whether we want to use the application in development (D), test (T), acceptance (A) or production (P) mode.
  • A reference to the MDA that we want to deploy (deployed by a Nix expression that invokes the Mendix build function abstraction shown earlier).

By writing a NixOS configuration file, storing it in /etc/nixos/configuration.nix and running the following command-line instruction:


$ nixos-rebuild switch

A complete system gets deployed with the Nix package manager that runs our Mendix application.

For production use, HSQLDB and directly exposing the embedded Jetty HTTP is not recommended -- instead a more sophisticated database, such as PostgreSQL should be used. For serving HTTP requests, it is recommended to use nginx as a reverse proxy and use it to serve static data and provide caching.

It is also possible to extend the above configuration with a PostgreSQL and nginx system service. The NixOS module system can be used to retrieve the properties from the Mendix app container to make the configuration process more convenient.

Conclusion


In this blog post, I have investigated how Mendix applications can be deployed by using tools from the Nix project. This resulted in the following deployment functionality:

  • A Nix function that can be used to compile an MDA file from a Mendix project.
  • Generated scripts that configure and launch the runtime and the application.
  • A NixOS module that can be used to deploy a running Mendix app as part of a NixOS machine configuration.

Future work


Currently, only single machine deployments are possible. It may also be desirable to connect a Mendix application to a database that is stored on a remote machine. Furthermore, we may also want to deploy multiple Mendix applications to multiple machines in a network. With Disnix, it is possible to automate such scenarios.

Availability


The Nix function abstractions and NixOS module can be obtained from the Mendix GitHub page and used under the terms and conditions of the Apache Software License version 2.0.

Acknowledgements


The work described in this blog post is the result of the so-called "crafting days", in which Mendix supports its employees to experiment completely freely two full days a month.

Furthermore, I have given a presentation about the functionality described in this blog post and an introduction to the Nix project:


and I have also written an introduction-oriented article about it on the Mendix blog.

Creating Nix build function abstractions for pluggable SDKs

$
0
0
Two months ago, I decomposed the stdenv.mkDerivation {} function abstraction in the Nix packages collection that is basically the de-facto way in the Nix expression language to build software packages from source.

I identified some of its major concerns and developed my own implementation that is composed of layers in which each layer gradually adds a responsibility until it has most of the features that the upstream version also has.

In addition to providing a better separation of concerns, I also identified a pattern that I repeatedly use to create these abstraction layers:


{stdenv, foo, bar}:
{name, buildInputs ? [], ...}@args:

let
extraArgs = removeAttrs args [ "name""buildInputs" ];
in
stdenv.someBuildFunction ({
name = "mypackage-"+name;
buildInputs = [ foo bar ] ++ buildInputs;
} // extraArgs)

Build function abstractions that follow this pattern (as outlined in the code fragment shown above) have the following properties:

  • The outer function header (first line) specifies all common build-time dependencies required to build a project. For example, if we want to build a function abstraction for Python projects, then python is such a common build-time dependency.
  • The inner function header specifies all relevant build parameters and accepts an arbitrary number of arguments. Some arguments have a specific purpose for the kind of software project that we want to build (e.g. name and buildInputs) while other arguments can be passed verbatim to the build function abstraction that we use as a basis.
  • In the body, we invoke a function abstraction (quite frequently stdenv.mkDerivation {}) that builds the project. We use the build parameters that have a specific meaning to configure specialized build properties and we pass all remaining build parameters that are not conflicting verbatim to the build function that we use a basis.

    A subset of these arguments have no specific meaning and are simply exposed as environment variables in the builder environment.

    Because some parameters are already being used for a specific purpose and others may be incompatible with the build function that we invoke in the body, we compose a variable named: extraArgs in which we remove the conflicting arguments.

Aside from having a function that is tailored towards the needs of building a specific software project (such as a Python project), using this pattern provides the following additional benefits:

  • A build procedure is extendable/tweakable -- we can adjust the build procedure by adding or changing the build phases, and tweak them by providing build hooks (that execute arbitrary command-line instructions before or after the execution of a phase). This is particularly useful to build additional abstractions around it for more specialized deployment procedures.
  • Because an arbitrary number of arguments can be propagated (that can be exposed as environment variables in the build environment), we have more configuration flexibility.

The original objective of using this pattern is to create an abstraction function for GNU Make/GNU Autotools projects. However, this pattern can also be useful to create custom abstractions for other kinds of software projects, such as Python, Perl, Node.js etc. projects, that also have (mostly) standardized build procedures.

After completing the blog post about layered build function abstractions, I have been improving the Nix packages/projects that I maintain. In the process, I also identified a new kind of packaging scenario that is not yet covered by the pattern shown above.

Deploying SDKs


In the Nix packages collection, most build-time dependencies are fully functional software packages. Notable exceptions are so-called SDKs, such as the Android SDK -- the Android SDK "package" is only a minimal set of utilities (such as a plugin manager, AVD manager and monitor).

In order to build Android projects from source code and manage Android app installations, you need to install a variety of plugins, such as build-tools, platform-tools, platform SDKs and emulators.

Installing all plugins is typically a much too costly operation -- it requires you to download many gigabytes of data. In most cases, you only want to install a very small subset of them.

I have developed a function abstraction that makes it possible to deploy the Android SDK with a desired set of plugins, such as:


with import <nixpkgs> {};

let
androidComposition = androidenv.composeAndroidPackages {
toolsVersion = "25.2.5";
platformToolsVersion = "27.0.1";
buildToolsVersions = [ "27.0.3" ];
includeEmulator = true;
emulatorVersion = "27.2.0";
};
in
androidComposition.androidsdk

When building the above expression (default.nix) with the following command-line instruction:


$ nix-build
/nix/store/zvailnl4f1261cn87s9n29lhj9i7y7iy-androidsdk

We get an Android SDK installation, with tools plugin version 25.2.5, platform-tools version 27.0.1, one instance of the build-tools (version 27.0.1) and an emulator of version 27.0.2. The Nix package manager will download the required plugins automatically.

Writing build function abstractions for SDKs


If you want to create function abstractions for software projects that depend on an SDK, you not only have to execute a build procedure, but you must also compose the SDK in such a way that all plugins are installed that a project requires. If any of the mandatory plugins are missing, the build will most likely fail.

As a result, the function interface must also provide parameters that allow you to configure the plugins in addition to the build parameters.

A very straight forward approach is to write a function whose interface contains both the plugin and build parameters, and propagates each of the required parameters to the SDK composition function, but manually writing this mapping has a number of drawbacks -- it duplicates functionality of the SDK composition function, it is tedious to write, and makes it very difficult to keep it consistent in case the SDK's functionality changes.

As a solution, I have extended the previously shown pattern with support for SDK deployments:


{composeMySDK, stdenv}:
{foo, bar, ...}@args:

let
mySDKFormalArgs = builtins.functionArgs composeMySDK;
mySDKArgs = builtins.intersectAttrs mySDKFormalArgs args;
mySDK = composeMySDK mySDKArgs;
extraArgs = removeAttrs args ([ "foo""bar" ]
++ builtins.attrNames mySDKFormalArgs);
in
stdenv.mkDerivation ({
buildInputs = [ mySDK ];
buildPhase = ''
${mySDK}/bin/build
'';
} // extraArgs)

In the above code fragment, we have added the following steps:

  • First, we dynamically extract the formal arguments of the function that composes the SDK (mySDKFormalArgs).
  • Then, we compute the intersection of the formal arguments of the composition function and the actual arguments from the build function arguments set (args). The resulting attribute set (mySDKArgs) are the actual arguments we need to propagate to the SDK composition function.
  • The next step is to deploy the SDK with all its plugins by propagating the SDK arguments set as function parameters to the SDK composition function (mySDK).
  • Finally, we remove the arguments that we have passed to the SDK composition function from the extra arguments set (extraArgs), because these parameters have no specific meaning for the build procedure.

With this pattern, the build abstraction function evolves automatically with the SDK composition function without requiring me to make any additional changes.

To build an Android project from source code, I can write an expression such as:


{androidenv}:

androidenv.buildApp {
# Build parameters
name = "MyFirstApp";
src = ../../src/myfirstapp
antFlags = "-Dtarget=android-16";

# SDK composition parameters
platformVersions = [ 16 ];
toolsVersion = "25.2.5";
platformToolsVersion = "27.0.1";
buildToolsVersions = [ "27.0.3" ];
}

The expression shown above has the following properties:

  • The above function invocation propagates three build parameters: name referring to the name of the Nix package, src referring to a filesystem location that contains the source code of an Android project, and antFlags that contains command-line arguments that are passed to the Apache Ant build tool.
  • It propagates four SDK composition parameters: platformVersions referring to the platform SDKs that must be installed, toolsVersion to the version of the tools package, platformToolsVersion to the platform-tools package and buildToolsVersion to the build-tool packages.

By evaluating the above function invocation, the Android SDK with the plugins will be composed, and the corresponding SDK will be passed as a build input to the builder environment.

In the build environment, Apache Ant gets invoked build that builds the project from source code. The android.buildApp implementation will dynamically propagate the SDK composition parameters to the androidenv.composeAndroidPackages function.

Availability


The extended build function abstraction pattern described in this blog post is among the structural improvements I have been implementing in the mobile app building infrastructure in Nixpkgs. Currently, it is used in standalone test versions of the Nix android build environment, iOS build environment and Titanium build environment.

The Titanium SDK build function abstraction (a JavaScript-based cross-platform development framework that can produce Android, iOS, and several other kinds of applications from the same codebase) automatically composes both Xcode wrappers and Android SDKs to make the builds work.

The test repositories can be found on my GitHub page and the changes live in the nextgen branches. At some point, they will be reintegrated into the upstream Nixpkgs repository.

Besides mobile app development SDKs, this pattern is generic enough to be applied to other kinds of projects as well.

Automating Mendix application deployments with Nix (introduction-oriented blog post)

$
0
0

Mendix is a low-code application development platform. Low-code application development offers all kinds of benefits over traditional development approaches involving code, such as a boost in productivity. For some applications, it is possible to develop up to ten times faster compared to traditional coding approaches and frameworks.

However, despite being different from a development perspective, there is one particular activity that all application development approaches have in common – at some point, you need to deploy your application to an environment (e.g. test, acceptance, or production) to make it available to end users.

For users of the Mendix cloud portal, deployment is automated in a convenient way – with just a few simple mouse clicks, you can make your application available to all potential users in the world.

However, managing on-premise deployments or the cloud infrastructure itself is all but a trivial job – for example, there are many complex dependencies that need to be deployed to run a Mendix application, upgrading may introduce unnecessary downtime and break a system, and the infrastructure needs to be scalable so that it can manage thousands of applications.

Fortunately, there are many automated deployment solutions that come to our aid, such as Kubernetes. Although many of them are useful, none of these solutions are perfect -- they all have their strengths and weaknesses. As a result, there are still complexities we need to solve ourselves and incidents that require fixing.

At Mendix R&D, everybody is encouraged to freely experiment two days a month (the so-called “crafting days”). One of my crafting day projects is to experiment with deployment tools from a different and unorthodox solution spectrum: The Nix project. The goal is to fully automate the deployment of a Mendix application from source – the input is a Mendix project created with the modeler and the end-result is a system running the application.

The Nix Project


The Nix project provides a family of tools that solve configuration management problems in a unique way. Some tools that are part of the Nix project are:

  • The Nix package manager
  • The NixOS Linux distribution
  • NixOps: A NixOS-based cloud deployment tool
  • Hydra: The Nix-based continuous integration service
  • Disnix: A Nix-based service deployment tool

The basis of all tools in the Nix project is the Nix package manager. Nix is quite different from almost any conventional package manager (such as RPM, APT, or Homebrew) because it borrows concepts from purely functional programming languages, such as Haskell.

The Nix Package Manager


The Nix package manager implements a purely functional deployment model. In Nix, deploying a package reliably is the same thing as invoking a pure function, without any side effects. To make this possible, Nix provides a purely functional domain-specific language called the Nix expression language.


{ stdenv, fetchurl, acl }:

stdenv.mkDerivation {
name = "gnutar-1.30";
src = fetchurl {
url = http://ftp.gnu.org/tar/tar-1.30.tar.xz;
sha256 = "1lyjyk8z8hdddsxw0ikchrsfg3i0x3fsh7l63a8jgaz1n7dr5gzi";
};
buildCommand = ''
tar xfv $src
cd tar-1.30
./configure --prefix=$out --with-acl=${acl}
make
make install
'';
}

The above code fragment is an example of a Nix expression that describes how to build GNU tar from source code and its build-time dependencies:

  • The entire expression is a function definition. The first line corresponds to a function header in which every argument is a build-time dependency:
    • stdenv is an environment providing standard UNIX utilities, such as cat, ls and make.
    • fetchurl is a function that is used to download files from an external location.
    • acl is a library dependency of GNU tar that provides access control list support.
  • In the body of the function, we invoke the mkDerivation {} function that composes so-called “pure build environments” in which arbitrary build commands can be executed.
  • As function arguments to mkDerivation, we specify the name of the package (name), how the source can be obtained (src) and the shell commands (buildCommand) that need to be executed to build the package.

The above expression is a function definition describing how to build something from sources, but the expression does not specify which version or variants of the sources that are supposed to be used. Function definitions alone are not useful. Instead, functions must be invoked with all the required function arguments. In Nix, they need to correspond to the versions or variants of the build-time dependencies that we want to use.

Packages are composed in a second Nix expression that has the following structure:


rec {
stdenv = import ...

fetchurl = import ...

acl = import ../pkgs/os-specific/linux/acl {
inherit stdenv fetchurl …;
};

gnutar = import ../pkgs/tools/archivers/gnutar {
inherit stdenv fetchurl acl;
};

...
}

The above partial Nix expression is an attribute set (a language construct conceptually similar to objects in JSON) in which every key represents a package name and every value refers to a function invocation that builds the package from source code. The GNU tar expression (shown in the previous code fragment) is imported in this expression and invoked with function arguments referring to the keys in the same attribute set, such as stdenv, fetchurl, and acl.

In addition to GNU tar, all build-time dependencies are composed in the same Nix expression. These dependencies are also constructed by following the same convention – invoking a function that builds the package from source code and its build-time dependencies.

In a Nix build environment, you can execute (almost) any build tool. In the GNU tar example, we run a standard GNU Autotools build procedure, but it is also possible to run Apache Ant (for Java software), Python setup tools, Perl’s MakeMaker or CMake and many other tools.

The only catch is that Nix imposes restrictions on what the tools are permitted to do to provide better guarantees that builds are pure, such as:

  • Every package is stored in an isolated directory, not in global directories, such as /lib, /bin or C:\Windows\System32
  • Files are made read-only after build completion
  • Timestamps are reset to 1 second after the epoch
  • Search environment variables are cleared and configured explicitly, e.g. PATH
  • Private temp folders and designated output directories
  • Network access is restricted (except when an output hash is given)
  • Running builds as unprivileged users
  • Chroot environments, namespaces, bind-mounting dependency packages

The most important restriction is the first – in Nix, all packages are stored in a so-called Nix store, in which every package is prefixed by a cryptographic hash code derived from all build inputs, such as: /nix/store/fjh974kzdcab7yp0ibmwwymmgbi6cg59-gnutar-1.30. Because hash prefixes are unique, no package shares the same name and as a result, we can safely store multiple versions and variants of the same package alongside each other in the store.

The result of complementing build tools with these restrictions is that when you build a package with Nix with certain build-time dependencies and you perform the build with the same inputs on another machine, the result will be the exact same (nearly bit-identical) build.

Purity offers many kinds of benefits, such as:

  • Strong dependency completeness guarantees
  • Strong reproducibility guarantees
  • Build only the packages and dependencies that you need
  • Packages that don’t depend on each other can be safely built in parallel
  • Ability to download substitutes from a remote machine (e.g. build server) if the hash prefix is identical
  • Ability to delegate builds to remote machines and be sure that the result is identical if it were built locally

By taking the composition expression (shown earlier) and running nix-build, we can build GNU tar, including all of its build-time dependencies:


$ nix-build all-packages.nix -A gnutar
/nix/store/fjh974kzdcab7yp0ibmwwymmgbi6cg59-gnutar-1.30

The result of the Nix-build instruction is a Nix store path that contains a hash code that has been derived from all build inputs.

Building Mendix Deployment Archives (MDAs) with Nix


As explained earlier, in Nix build environments any kind of build tool can be used albeit with purity restrictions.

For Mendix applications, there is also an important artifact that needs to be produced in the deployment lifecycle – the Mendix Deployment Archive (MDA) that captures all relevant files that an application needs to run in production.

MDA files can be produced by running the MxBuild tool. We can also package MxBuild and the Mendix runtime as Nix packages and write our own Nix function abstraction that builds MDA files from Mendix projects:


{stdenv, mxbuild, jdk, nodejs}:
{name, mendixVersion, looseVersionCheck ? false, ...}@args:

let mxbuildPkg = mxbuild."${mendixVersion}";
in
stdenv.mkDerivation ({
buildInputs = [ mxbuildPkg nodejs ];
installPhase = ''
mkdir -p $out
mxbuild --target=package --output=$out/${name}.mda \
--java-home ${jdk} --java-exe-path ${jdk}/bin/java \
${stdenv.lib.optionalString looseVersionCheck "--loose-version-check"} \
"$(echo *.mpr)"
'';
} // args)

The above function returns another function taking Mendix-specific parameters (e.g. the name of the project, Mendix version), invokes MxBuild, and stores the resulting MDA file in the Nix store.

By using the function abstraction and a Mendix project created by the modeler, we can build the Mendix project by writing the following Nix expression:


{packageMendixApp}:

packageMendixApp {
name = "conferenceschedule";
src = /home/sander/ConferenceSchedule-main;
mendixVersion = "7.13.1";
}

The above expression specifies that we want to build a project named: conferenceschedule, we want to use the Mendix project that is stored in the directory: /home/sander/ConferenceSchedule-main and we want to use Mendix version 7.13.1.

Using NixOS: A Nix-Based Linux Distribution


One of the common objectives that all tools in the Nix project have in common is declarative deployment, meaning that you can express the structure of your system, and the tools infer all the activities that need to be carried out to deploy it.

As a Mendix developer, generating an MDA archive is not entirely what we want – what we really want is a system running a Mendix application. To accomplish this, additional deployment activities need to be carried out beyond producing an MDA file.

NixOS is a Linux distribution that extends Nix’s deployment features to complete systems. In addition to the fact that the Nix package manager is being used to deploy all packages (including the Linux kernel) and configuration files, it also deploys entire machine configurations from a single declarative specification:


{pkgs, ...}:

{
boot.loader.grub.device = "/dev/sda";
fileSystems."/".device = "/dev/sda1";

services = {
openssh.enable = true;

xserver = {
enable = true;
displayManager.sddm.enable = true;
desktopManager.plasma5.enable = true;
};
};

environment.systemPackages = [
pkgs.firefox
];
}

The above code fragment is an example of a NixOS configuration file that captures the following properties:

  • The GRUB bootloader should be installed on the Master Boot Record of the first harddrive (/dev/sda)
  • The first partition of the first harddrive (/dev/sda1) should be mounted as a root partition
  • We want to run OpenSSH and the X Window System as system services
  • We configure the X Window Server to use SDDM as a login manager and the KDE Plasma Desktop as desktop manager.
  • We want to install Mozilla Firefox as an end-user package.

By running a single command-line instruction, we can deploy an entire system configuration with the Nix package manager:


$ nixos-rebuild switch

The result is a running system implementing the configuration described above.

Creating a NixOS Module for Mendix App Containers


To automate the remaining Mendix deployment activities (that need to be carried out after composing an MDA file), we can create a systemd job (the service manager that NixOS uses) that unpacks the MDA file into a writable directory, creates additional state directories for storing temp files, and configure the runtime by communicating over the admin interface to start the embedded Jetty HTTP service, configure the database and start the app.

Composing a systemd job can be done by adding a systemd configuration setting to a NixOS configuration. The following partial Nix expression shows the overall structure of a systemd job for a Mendix app container:


{pkgs, ...}:

{
systemd.services.mendixappcontainer =
let
mendixPkgs = import ../nixpkgs-mendix/top-level/all-packages.nix { inherit pkgs; };
appContainerConfigJSON = pkgs.writeTextFile { ... };
configJSON = pkgs.writeTextFile {
name = "config.json";
text = builtins.toJSON {
DatabaseType = "HSQLDB";
DatabaseName = "myappdb";
DTAPMode = "D";
};
};
runScripts = mendixPkgs.runMendixApp {
app = import ../conferenceschedule.nix { inherit (mendixPkgs) packageMendixApp; };
};
in {
enable = true;
description = "My Mendix App";
wantedBy = [ "multi-user.target" ];
environment = {
M2EE_ADMIN_PASS = "secret";
M2EE_ADMIN_PORT = "9000";
MENDIX_STATE_DIR = "/home/mendix";
};
serviceConfig = {
ExecStartPre = "${runScripts}/bin/undeploy-app";
ExecStart = "${runScripts}/bin/start-appcontainer";
ExecStartPost = "${runScripts}/bin/configure-appcontainer ${appContainerConfigJSON} ${configJSON}";
};
};
}

The above systemd job declaration does the following:

  • It generates JSON configuration files with app container and database settings
  • It composes an environment with environment variables configuring the admin interface
  • It launches scripts: one script before startup that cleans the old state, a start script that starts the app container and a script that runs after startup that configures the app container settings, such as the database

Writing a systemd job as a Nix expression is quite cumbersome and a bit impractical when it is desired to compose NixOS configurations that should run Mendix applications. Fortunately, we can hide all these implementation details behind a more convenient interface by wrapping all Mendix app container properties in a NixOS module.

By importing this NixOS module in a NixOS configuration, we can more concisely express the properties of a system running a Mendix app container:


{pkgs, ...}:

{
require = [ ../nixpkgs-mendix/nixos/modules/mendixappcontainer.nix ];

services = {
openssh.enable = true;

mendixAppContainer = {
enable = true;
adminPassword = "secret";
databaseType = "HSQLDB";
databaseName = "myappdb";
DTAPMode = "D";
app = import ../../conferenceschedule.nix {
inherit pkgs;
inherit (pkgs.stdenv) system;
};
};
};

networking.firewall.allowedTCPPorts = [ 8080 ];
}

The above code fragment is another NixOS configuration that imports the Mendix app container NixOS module. It defines a Mendix app container system service that connects to an in-memory HSQL database, runs the app in development mode, and deploys the MDA file that is the result of building one of our test projects, by invoking the Nix build abstraction function that builds MDAs.

By running a single command-line instruction, we can deploy a machine configuration running our Mendix application:


$ nixos-rebuild switch

After the deployment has succeeded, we should able to open a web browser and test our app.

In production scenarios, only deploying an app container is not enough to make an application reliably available to end users. We must also deploy a more robust database service, such as PostgreSQL, and use a reverse proxy, such as nginx, to more efficiently serve static files and cache common requests to improve the performance of the application.

It is also possible to extend the NixOS configuration with a PostgreSQL and nginx system service and use the NixOS module system to refer to the relevant properties of a Mendix app container.

Conclusion


This blog post covers tools from the Nix project implementing deployment concepts inspired by purely functional programming languages and declarative programming. These tools offer a number of unique advantages over more traditional deployment tools. Furthermore, we have demonstrated that Mendix application deployments could fit into such a deployment model.

Availability


The Nix build abstraction function for Mendix projects and the NixOS module for running app containers can be obtained from the nixpkgs-mendix repository on GitHub. The functionality should be considered experimental – it is not yet recommended for production usage.

The Nix package manager and NixOS Linux distribution can be obtained from the NixOS website.

This blog post originally appeared on: https://www.mendix.com/blog/automating-mendix-application-deployments-with-nix/)

Auto patching prebuilt binary software packages for deployment with the Nix package manager

$
0
0
As explained in many previous blog posts, most of the quality properties of the Nix package manager (such as reliable deployment) stem from the fact that all packages are stored in a so-called Nix store, in which every package resides in its own isolated folder with a hash prefix that is derived from all build inputs (such as: /nix/store/gf00m2nz8079di7ihc6fj75v5jbh8p8v-zlib-1.2.11).

This unorthodox naming convention makes it possible to safely store multiple versions and variants of the same package next to each other.

Although isolating packages in the Nix store provides all kinds of benefits, it also has a big drawback -- common components, such as shared libraries, can no longer be found in their "usual locations", such as /lib.

For packages that are built from source with the Nix package manager this is typically not a problem:

  • The Nix expression language computes the Nix store paths for the required packages. By simply referring to the variable that contains the build result, you can obtain the Nix store path of the package, without having to remember them yourself.
  • Nix statically binds shared libraries to ELF binaries by modifying the binary's RPATH field. As a result, binaries no longer rely on the presence of their library dependencies in global locations (such as /lib), but use the libraries stored in isolation in the Nix store.
  • The GNU linker (the ld command) has been wrapped to transparently add the paths of all the library package to the RPATH field of the ELF binary, whenever a dynamic library is provided.

As a result, you can build most packages from source code by simply executing their standardized build procedures in a Nix builder environment, such as: ./configure --prefix=$out; make; make install.

When it is desired to deploy prebuilt binary packages with Nix then you may probably run into various kinds of challenges:

  • ELF executables require the presence of an ELF interpreter in /lib/ld-linux.so.2 (on x86) and /lib/ld-linux-x86-64.so.2 (on x86-64), which is impure and does not exist in NixOS.
  • ELF binaries produced by conventional means typically have no RPATH configured. As a result, they expect libraries to be present in global namespaces, such as /lib. Since these directories do not exist in NixOS an executable will typically fail to work.

To make prebuilt binaries work in NixOS, there are basically two solutions -- it is possible to compose so-called FHS user environments from a set of Nix packages in which shared components can be found in their "usual locations". The drawback is that it requires special privileges and additional work to compose such environments.

The preferred solution is to patch prebuilt ELF binaries with patchelf (e.g. appending the library dependencies to the RPATH of the executable) so that their dependencies are loaded from the Nix store. I wrote a guide that demonstrates how to do this for a number of relatively simple packages.

Although it is possible to patch prebuilt ELF binaries to make them run work from the Nix store, such a process is typically tedious and time consuming -- you must dissect a package, search for all relevant ELF binaries, figure out which libraries a binary requires, find the corresponding packages that provide them and then update the deployment instructions to patch the ELF binaries.

For small projects, a manual binary patching process is still somewhat manageable, but for a complex project such as the Android SDK, that provides a large collection of plugins containing a mix of many 32-bit and 64-bit executables, manual patching is quite labourious, in particular when it is desired to keep all plugins up to date -- plugin packages are updated quite frequently forcing the packager to re-examine all binaries over and over again.

To make the Android SDK patching process easier, I wrote a small tool that can mostly automate it. The tool can also be used for other kinds of binary packages.

Automatic searching for library locations


In order to make ELF binaries work, they must be patched in such a way that they use an ELF interpreter from the Nix store and their RPATH fields should contain all paths to the libraries that they require.

We can gather a list of required libraries for an executable, by running:


$ patchelf --print-needed ./zipmix
libm.so.6
libc.so.6

Instead of manually patching the executable with this provided information, we can also create a function that searches for the corresponding libraries in a list of search paths. The tool could take the first path that provides the required libraries.

For example, by setting the following colon-separated seach environment variable:


$ export libs=/nix/store/7y10kn6791h88vmykdrddb178pjid5bv-glibc-2.27/lib:/nix/store/xh42vn6irgl1cwhyzyq1a0jyd9aiwqnf-zlib-1.2.11/lib

The tool can automatically discover that the path: /nix/store/7y10kn6791h88vmykdrddb178pjid5bv-glibc-2.27/lib provides both libm.so.6 and libc.so.6.

We can also run into situations in which we cannot find any valid path to a required library -- in such cases, we can throw an error and notify the user.

It is also possible extend the searching approach to the ELF interpreter. The following command provides the path to the required ELF interpreter:


$ patchelf --print-interpreter ./zipmix
/lib64/ld-linux-x86-64.so.2

We can search in the list of library packages for the ELF interpreter as well so that we no longer have to explicitly specify it.

Dealing with multiple architectures


Another problem with the Android SDK is that plugin packages may provide both x86 and x86-64 binaries. You cannot link libraries compiled for x86 against an x86-64 executable and vice versa. This restriction could introduce a new kind of risk in the automatic patching process.

Fortunately, it is also possible to figure out for what kind of architecture a binary was compiled:


$ readelf -h ./zipmix
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64

The above command-line instruction shows that we have a 64-bit binary (Class: ELF64) compiled for the x86-64 architecture (Machine: Advanced Micro Devices X86-64)

I have also added a check that ensures that the tool will only add a library path to the RPATH if the architecture of the library is compatible with the binary. As a result, it is not possible to accidentally link a library with an incompatible architecture to a binary.

Patching collections of binaries


Another inconvenience is the fact that Android SDK plugins typically provide more than one binary that needs to be patched. We can also recursively search an entire directory for ELF binaries:


$ autopatchelf ./bin

The above command-line instruction recursively searches for binaries in the bin/ sub directory and automatically patches them.

Sometimes recursively patching executables in a directory hierarchy could have undesired side effects. For example, the Android SDK also provides emulators having their own set of ELF binaries that need to run in the emulator. Patching these binaries typically breaks the software running in the emulator. We can also disable recursion if this is desired:


$ autopatchelf --no-recurse ./bin

or revert to patching individual executables:


$ autopatchelf ./zipmix

The result


The result of having most aspects automated of a binary patching process results in a substantial reduction in code size for the Nix expressions that need to deploy prebuilt packages.

In my previous blog post, I have shown two example cases for which I manually derived the patchelf instructions that I need to run. By using the autopatchelf tool I can significantly decrease the size of the corresponding Nix expressions.

For example, the following expression deploys kzipmix:


{stdenv, fetchurl, autopatchelf, glibc}:

stdenv.mkDerivation {
name = "kzipmix-20150319";
src = fetchurl {
url = http://static.jonof.id.au/dl/kenutils/kzipmix-20150319-linux.tar.gz;
sha256 = "0fv3zxhmwc3p34larp2d6rwmf4cxxwi71nif4qm96firawzzsf94";
};
buildInputs = [ autopatchelf ];
libs = stdenv.lib.makeLibraryPath [ glibc ];
installPhase = ''
${if stdenv.system == "i686-linux" then "cd i686"
else if stdenv.system == "x86_64-linux" then "cd x86_64"
else throw "Unsupported system architecture: ${stdenv.system}"}
mkdir -p $out/bin
cp zipmix kzip $out/bin
autopatchelf $out/bin
'';
}

In the expression shown above, it suffices to simply move the executable to $out/bin and running autopatchelf.

I have also shown a more complicated example demonstrating how to patch the Quake 4 demo. I can significantly reduce the amount of code by substituting all the patchelf instructions by a single autopatchelf invocation:


{stdenv, fetchurl, glibc, SDL, xlibs}:

stdenv.mkDerivation {
name = "quake4-demo-1.0";
src = fetchurl {
url = ftp://ftp.idsoftware.com/idstuff/quake4/demo/quake4-linux-1.0-demo.x86.run;
sha256 = "0wxw2iw84x92qxjbl2kp5rn52p6k8kr67p4qrimlkl9dna69xrk9";
};
buildInputs = [ autopatchelf ];
libs = stdenv.lib.makeLibraryPath [ glibc SDL xlibs.libX11 xlibs.libXext ];

buildCommand = ''
# Extract files from the installer
cp $src quake4-linux-1.0-demo.x86.run
bash ./quake4-linux-1.0-demo.x86.run --noexec --keep
# Move extracted files into the Nix store
mkdir -p $out/libexec
mv quake4-linux-1.0-demo $out/libexec
cd $out/libexec/quake4-linux-1.0-demo
# Remove obsolete setup files
rm -rf setup.data
# Patch ELF binaries
autopatchelf .
# Remove libgcc_s.so.1 that conflicts with Mesa3D's libGL.so
rm ./bin/Linux/x86/libgcc_s.so.1
# Create wrappers for the executables and ensure that they are executable
for i in q4ded quake4
do
mkdir -p $out/bin
cat > $out/bin/$i <<EOF
#! ${stdenv.shell} -e
cd $out/libexec/quake4-linux-1.0-demo
./bin/Linux/x86/$i.x86 "\$@"
EOF
chmod +x $out/libexec/quake4-linux-1.0-demo/bin/Linux/x86/$i.x86
chmod +x $out/bin/$i
done
'';
}

For the Android SDK, there is even a more substantial win in code size reductions. The following Nix expression is used to patch the Android build-tools plugin package:


{deployAndroidPackage, lib, package, os, autopatchelf, makeWrapper, pkgs, pkgs_i686}:

deployAndroidPackage {
inherit package os;
buildInputs = [ autopatchelf makeWrapper ];

libs_x86_64 = lib.optionalString (os == "linux")
(lib.makeLibraryPath [ pkgs.glibc pkgs.zlib pkgs.ncurses5 ]);
libs_i386 = lib.optionalString (os == "linux")
(lib.makeLibraryPath [ pkgs_i686.glibc pkgs_i686.zlib pkgs_i686.ncurses5 ]);

patchInstructions = ''
${lib.optionalString (os == "linux") ''
export libs_i386=$packageBaseDir/lib:$libs_i386
export libs_x86_64=$packageBaseDir/lib64:$libs_x86_64
autopatchelf $packageBaseDir/lib64 libs --no-recurse
autopatchelf $packageBaseDir libs --no-recurse
''}

wrapProgram $PWD/mainDexClasses \
--prefix PATH : ${pkgs.jdk8}/bin
'';
noAuditTmpdir = true;
}

The above expression specifies the search libraries per architecture for x86 (i386) and x86_64 and automatically patches the binaries in the lib64/ sub folder and base directories. The autopatchelf tool ensures that no library of an incompatible architecture gets linked to a binary.

Discussion


The automated patching approach described in this blog post is not entirely a new idea -- in Nixpkgs, Aszlig Neusepoff created an autopatchelf hook that is integrated into the fixup phase of the stdenv.mkDerivation {} function. It shares a number of similar features -- it accepts a list of library packages (the runtimeDependencies environment variable) and automatically adds the provided runtime dependencies to the RPATH of all executables residing in the bin/ output.

There are also a number of differences -- my approach provides an autopatchelf command-line tool that can be invoked in any stage of a build process and provides full control over the patching process. It can also be used outside a Nix builder environment, which is useful for experimentation purposes. This increased level of flexibility is required for more complex prebuilt binary packages, such as the Android SDK and its plugins -- for some plugins, you cannot generalize the patching process and you typically require more control.

It also offers better support to cope with repositories providing binaries of multiple architectures -- while the Nixpkgs version has a check that prevents incompatible libraries from being linked, it does not allow you to have fine grained control over library paths to consider for each architecture.

The main difference between my and the Nixpkgs implementation is the rationale -- the autopatchelf hook was primarily developed for source compiled projects whose executables may need to dynamically load dependencies via the dlopen() function call.

Dynamically loaded libraries are not known at link time (because they are not provided to the Nix-wrapped ld command), and as a result, they are not added to the RPATH of an executable. The Nixpkgs autopatchelf hook allows you to easily supplement the library paths of these dynamically loaded libraries after the build process completes.

Availability


The autopatchelf command-line tool can be found in the nix-patchtools repository. The goal of this repository to provide a collection of tools that help making the patching processes of complex prebuilt packages more convenient. In the future, I may identify more patterns and provide additional tooling to automate them.

autopatchelf is prominently used in my refactored version of the Android SDK to automatically patch all ELF binaries. I have the intention to integrate this new Android SDK implementation into Nixpkgs soon.

8th yearly blog reflection

$
0
0
Similar to previous years, I will reflect over last year's writings. Again the occasion is my blog's anniversary -- today, it has been exactly 8 years ago that I started this blog.

Disnix


In the first two months of this year, most of my work was focused on Disnix. I added a number of extra features to Disnix that are particularly useful to diagnose errors in an environment in which services are distributed over a collection of machines in a network.

I also revised the internals of Disnix in such a way so that it has become possible to deploy systems with circular dependencies, by dropping the ordering requirement on inter-dependencies, when this is desired.

Finally, I extended my custom web application framework's example applications repository (that I released last year) and made it a public Disnix example case, to provide a more realistic/meaningful public example in addition to my trivial/hypothetical examples.

The new features described in these three blog posts are part of Disnix 0.8, released in March this year.

Mendix


The biggest news of the year is probably the fact that I embarked on a new challenge. In April, I joined Mendix, a company that provides a low-code application development platform.

Since the moment I joined, there have been many things I learned and quite a few new people I got to know.

From my learning experiences, I wrote an introduction blog post to the Mendix platform, specifically aimed at people with an advanced programming background (as a sidenote: the Mendix platform targets various kinds of audiences, including users with a limited programming background).

At Mendix, the Nix project and its tools are still largely unknown technology. In the company, sharing learning experiences within the entire R&D team is generally encouraged.

Mendix already uses rivalling technologies to automate application deployments. As an exercise to learn the technical architecture of running applications better, I automated the deployment process of Mendix applications with the Nix package manager and NixOS making it possible to automatically deploy a Mendix application by writing a simple NixOS configuration file and running only a single command-line instruction.

I also presented the Nix automation process to the entire R&D department and wrote an article about it on the public Mendix blog. (I kept a transcription on my own blog for archiving purposes).

Nix


In addition to Disnix, and joining Mendix (where I gave an introduction to Nix), I did some general Nix-related work as well.

To improve my (somewhat unorthodox) way of working, I created a syntax highlighter for the Nix expression language for one of my favourite tools: the editor the comes with the Midnight Commander.

As a personal experiment and proposal to tidy up some internals in the Nix packages repository, I developed my own build function abstractions to mimic Nix's stdenv.mkDerivation {} function abstraction with the purpose to clearly identify and separate its concerns.

Later, I extended this function abstraction approach to tidy up the deployment automation of pluggable SDKs, such as the Android SDK, to make it easier to automatically compose SDKs with plugins and the corresponding applications that they build.

Finally, I experimented with an approach that can automatically patch all ELF binaries in the Android SDK (and other binary only software projects), so that they will run from the Nix store without any problems.

Some of the function abstraction techniques described in the blog posts listed above as well as the auto patching strategy are used in the revised version of the Android build infrastructure that I merged into the master version of Nixpkgs last week. Aside from upgrading the Android SDK to the latest version, these improvements make maintaining the Android SDK and its plugins much easier.

In addition to writing Nix-related blog posts, I did much more Nix related stuff. I also gave a talk at NixCon 2018 (the third conference held about Nix and its related technologies) about Dysnomia's current state of affairs and I released a new major version of node2nix that adds Node.js 10.x support.

Overall top 10 of my blog posts


As with previous years, I will publish my overall top 10 of most popular blog posts:

  1. Managing private Nix packages outside the Nixpkgs tree. As I predicted in my blog reflection last year, this blog post is going to overtake the popularity of the blog post about GNU Guix. It seems that this blog post proves that we should provide more practical/hands-on information to people who just want to start using the Nix package manager.
  2. On Nix and GNU Guix. This has been my most popular blog post since 2012 and has now dropped to the second place. I think this is caused by the fact that GNU Guix is not as controversial as it used to be around the time it was first announced.
  3. An evaluation and comparison of Snappy Ubuntu. Remains my third most popular blog post but is gradually dropping in popularity. I have not heard much about the Snappy package manager developments in the last two years.
  4. Setting up a multi-user Nix installation on non-NixOS systems. Is still my fourth most popular blog post. I believe this blog post should drop in popularity soon, thanks to a number of improvements made to the Nix package manager. There is now a Nix installer for non-NixOS systems that supports multi-user and single-user installations on any Linux and macOS system.
  5. Yet another blog post about Object Oriented Programming and JavaScript. Still at the same place compared to last year. I noticed that still quite a few people use this blog post as a resource to learn more about prototypes in JavaScript, despite the fact that newer implementations of the ECMAScript standard have better functions (such as: Object.create) to manage objects with prototypes.
  6. An alternative explanation of the Nix package manager. Remains at exactly the same spot compared to last year. It probably remains popular due to the fact that this blog post is my preferred explanation recipe.
  7. On NixOps, Disnix, service deployment and infrastructure deployment. No change compared to last year. It still seems to clear up confusion to groups of people.
  8. Asynchronous programming with JavaScript. Maintains the same position compared to last year. I have no idea why it remains so popular, despite many improvements to the Node.js runtime and JavaScript language.
  9. Composing FHS-compatible chroot environments with Nix (or deploying Steam in NixOS). The popularity of this blog post has been gradually dropping in the last few years, but all of sudden it increased again. As a result, it now rose to the 9th place.
  10. A more realistic public Disnix example. This is the only blog post I wrote in 2018. It seems that this public example case is helpful for people to understand the kind of systems and requirements you have to meet in order to use Disnix to its full potential.

Discussion


What you may have probably noticed is that there is a drop in some of my more technical blog posts this year. This drop is caused by the fact that I am basically in a transition period -- I still have familiarize myself with my new environment and it is probably going to take a bit of time before I am back in my usual rythm.

But do not be worried. As usual, I have plenty of ideas and there will be more interesting stuff coming next year!

The final thing I would like to say is:

HAPPY NEW YEAR!!!!

A minimalistic discovery and architecture documentation process

$
0
0
In a blog post written a couple of years ago, I have described how to set up a basic configuration management process in a small organization that is based on the process framework described in the IEEE 828-2012 configuration management standard. The most important prerequisite for setting up such a process is identifying all configuration items (CIs) and storing them in a well-organized repository.

There are many ways to organize configuration items ranging from simple to very sophisticated solutions. I used a very small set of free and open source tools, and a couple of simple conventions to set up a CI repository:

  • A Git repository with an hierarchical directory structure referring to configurations items. Each path component in the directory structure serves a specific purpose to group configuration items. The overall strategy was to use a directory structure with a maximum of three levels: environment/machine/application. Using Git makes it possible to version configuration items and share the repository with team members.
  • Using markdown to write down the purposes of the configuration items and descriptions how they can be reproduced. Markdown works well for two reasons: it can be nicely formatted in a browser, but also read from a terminal when logged in to remote servers via SSH.
  • Using Dia for drawing diagrams of systems consisting of more complex applications components. Dia is not the most elegant program around, but it works well enough, it is free and open source, and supported on Linux, Windows and macOS.

My main motivation to formalize configuration management (but only lightly), despite being in a small organization, is to prevent errors and minimize delays and disruptions while remaining flexible by not being bound to all kinds of complex management procedures.

I wrote this blog post while I was still employed at a small-sized startup company with only one development team. In the meantime, I have joined a much bigger organization (Mendix) that has many cross-disciplinary development teams that work concurrently on various aspects of our service and product portfolio.

About microservices


When I just joined, the amount of information I had to absorb was quite overwhelming. I also learned that we heavily adopted the microservices architecture paradigm for our entire online service platform.

According to Martin Fowler's blog post on microservices, using microservices offers the following benefits:

  • Strong module boundaries. You can divide the functionality of a system into microservices and make separate teams responsible for the development of each service. This makes it possible to iterate faster and offer better quality because teams can focus on themselves on a subset of features only.
  • Independent deployment. Microservices can be deployed independently making it possible to ship features when they are done, without having complex integration cycles.
  • Technology diversity. Microservices are language and technology agnostic. You can pick almost any programming language (e.g. Java, Python, Mendix, Go), data storage solution (e.g. PostgreSQL, MongoDB, InfluxDB) or operating system (e.g. Linux, FreeBSD, Windows) to implement a microservice making it possible pick the most suitable combination of technologies and use them at their full advantage.

However, decomposing a system into a collection of collaborating services also comes at a (sometimes substantial!) price:

  • There is typically much more operational complexity. Because there are many components and typically a large infrastructure to manage, activities such as deploying, upgrading, and monitoring the condition of a system is much more time consuming and complex. Furthermore, because of technology diversity, there are also many kinds of specialized deployment procedures that you need to carry out.
  • Data is eventually consistent. You have to live with the fact that (temporary) inconsistencies could end up in your data, and you must invest in implementing facilities that keep your data is consistent.
  • Because of distribution development is harder in general -- it is more difficult to diagnose errors (e.g. a failure in one service could trigger a chain reaction of errors, without having proper error traces), it is harder to test a system because of additional deployment complexity. The network links between services may be slow and subject to failure, causing all kinds of unpredictable problems. Also machines that host critical services may crash.

Studying the architecture


When applied properly, e.g. functionality is well separated and there is strong cohesion and weak coupling between services, while investing in solutions to cope with the challenges listed above, the benefits of microservices can be reaped, resulting in a scalable systems that can be developed my multiple teams working on features concurrently.

However, an important prerequisite for making changes in such an environment, and maintaining or improving the quality properties of a system, requires discipline and a relatively good understanding of the environment -- in the beginning, I faced all kinds of practical problems when I wanted to make even a subtle change -- some areas of our platform where documented, while others were not. Some documentation was also outdated, slightly incomplete and sometimes inconsistent with the actual implementation.

Certain areas of our platform were also highly complex resulting in very complex architectural views, with many boxes and arrows. Furthermore, information was also scattered around many different places.

As part of my on-boarding process, and as a means to cope with some of my practical problems, I have created a documentation repository of the platform that our team develops by extending the (minimalistic) principles for configuration management described in the earlier blog post.

I realized that simply identifying the service components of which the system consists, is not enough to get an understanding of the system -- there are many items and complex details that need to be captured.

In addition to the identification of all configuration items, I also want:

  • Proper guidance. To understand a particular piece of functionality, I should not need to study every component in detail. Instead, I want to know the full context and only the details of the relevant components.
  • Completeness. I want all service components to be visible. I do not want any details to be covered up. For example, I have also seen quite a few diagrams that hide complex implementation details. I much rather want flaws to be visible so that they can be resolved at a later point in time.
  • Clear boundaries. Our platform is not self contained, but relies on services provided by other teams. I want to know what components are our responsibility and what is managed by external teams.
  • Clarity. I want to know what the purpose of a component is. Their names may not always necessarily reflect or explain what they do.
  • Consistency. No matter how nicely a diagram is drawn, it should match the actual implementation or it is of very little use.
  • References to the actual implementation. I also want to know where I can find the implementation of a component, such as its Git repository.

Documenting the architecture


To visualize the architecture of our platform and organize all relevant information, I followed a strategy:

  • I took the components (typically their source code repositories) as the basis for everything else -- every component translates to a box in the architecture diagram.
  • I analyzed the dependency relationships between the components and denoted them as arrows. When a box points to another box by means of an arrow, this means that the other box is a dependency that should be deployed first. When a dependency is absent, the service will (most likely) not work.
  • I also discovered that the platform diagram easily gets cluttered by the sheer amount of components -- I decided to combine components that have very strongly correlated functionality in feature groups (that have dashed borders). Every feature group in architecture diagrams refers to another sub architecture diagram that provides a more specialized view of the feature group.
  • To clearly illustrate the difference between components that are our responsibility and those that are maintained by others teams, I make all external dependencies visible in the top-level architecture diagram.

The notation I used for these diagrams is not something I have entirely invented from scratch -- it is inspired by graph theory, package management and service management concepts. Disnix, for example, can visualize deployment architectures by using a similar notation.

To find all relevant information to create the diagrams, I consulted various sources:

  • I studied existing documents and diagrams to get a basic understanding of the system and an idea of the details I should look at.
  • I talked to a variety of people from various teams.
  • I looked inside the configuration settings of all deployment solutions used, e.g. the Amazon AWS console, Docker, CloudFoundry, Kubernetes, Nix configuration files.
  • Peek inside the source code repositories and look for settings that are references to other systems, such as configuration values that store URLs.
  • When I am in doubt: I consider the deployment configuration files and source code the "ultimate source of truth", because no matter how nice a diagram looks, it is useless if it is implemented differently.

Finally, just drawing diagrams will not completely suffice when the goal is provide clarity. I also observed that I need to document some leftover details.

Foremost, having a diagram without the semantics not explained will typically leave too many details open to interpretation to the user, so you need to explain the notation.

Second, you need to provide additional details about the services. I typically enumerate the following properties in a table for every component:

  • The name of the component.
  • A one line description stating its purpose.
  • The type of project (e.g. a Python/Java/Go project, Docker container, AWS Lambda function, etc.). This is useful to determine the kind of deployment procedure for the component.
  • A reference to the source code repository, e.g. a Git repository. The README of the corresponding repository should provide more detailed information about the project.

Benefits


Although it is quite a bit of work to set up, having a well documented architecture provides us the following benefits:

  • More effective deployment. Because of the feature groups and dividing the architecture into multiple layers, general concepts and details are separated. This makes it easier for developers to focus and absorb the right detailed knowledge to change a service.
  • More consensus in the team about the structure of the system and general quality attributes, such as scalability and security.
  • Better on-boarding for new team members.

Discussion


Writing architecture documentation IMO is not rocket science, just discipline. Obviously, there are much more sophisticated tools available to organize and visualize architectures (even tools that can generate code and reverse engineer code), but this is IMO not a hard requirement to start documenting.

However, you can not take all confusion away -- even if you have the best possible architecture documentation, people's thinking habits are shaped by the concepts they know and there will always be a slight mismatch (which is documented in academic research: 'Why Is It So Hard to Define Software Architecture?' written by Jason Baragry and Karl Reed).

Finally, architecture documentation is only a first good step to improve the quality of service-oriented systems. To make it a success, much more is needed, such as:

  • Automated (and reproducible) deployment processes.
  • More documentation (such as the APIs, end-user documentation, project documentation).
  • Automated unit, integration and acceptance testing.
  • Monitoring.
  • Measuring and improving code quality, test coverage, etc.
  • Using design patterns, architectural patterns, good programming abstractions.
  • And many more aspects.

But to do these things properly, having proper architecture documentation is an important prerequisite.

Generating functional architecture documentation from Disnix service models

$
0
0
In my previous blog post, I have described a minimalistic architecture documentation approach for service-oriented systems based on my earlier experiences with setting up basic configuration management repositories. I used this approach to construct a documentation catalog for the platform I have been developing at Mendix.

I also explained my motivation -- it improves developer effectiveness, team consensus and the on-boarding of new team members. Moreover, it is a crucial ingredient in improving the quality of a system.

Although we are quite happy with the documentation, my biggest inconvenience is that I had to derive it entirely by hand -- I consulted various kinds of sources, but since existing documentation and information provided by people may be incomplete or inconsistent, I considered the source code and deployment configuration files the ultimate source of truth, because no matter how elegantly a diagram is drawn, it is useless if it does not match the actual implementation.

Because a manual documentation process is very costly and time consuming, a more ideal situation would be to have an automated approach that automatically derives architecture documentation from deployment specifications.

Since I am developing a deployment framework for service-oriented systems myself (Disnix), I have decided to extend it with a generator that can derive architecture diagrams and supplemental descriptions from the deployment models using the conventions I have described in my previous blog post.

Visualizing deployment architectures in Disnix


As explained in my previous blog post, the notation that I used for the diagrams was not something I invented from scratch, but something I borrowed from Disnix.

Disnix already has a feature (for quite some time) that can visualize deployment architectures referring to a description that shows how the functional parts (the services/components) are mapped to physical resources (e.g. machines/containers) in a network.

For example, after deploying a service-oriented system, such as my example web application system, by running:


$ disnix-env -s services.nix -i infrastructure.nix \
-d distribution-bundles.nix

You can visualize the corresponding deployment architecture of the system, by running:


$ disnix-visualize > out.dot

The above command-line instruction generates a directed graph in the DOT language. The resulting dot file can be converted into a displayable image (such as a PNG or SVG file) by running:


$ dot -Tpng out.dot > out.png

Resulting in a diagram of the deployment architecture that may look as follows:


The above diagram uses the following notation:

  • The light grey boxes denote machines in a network. In the above deployment scenario, we have two them.
  • The ovals denote services (more specifically: in a Disnix-context, they reflect any kind of distributable deployment unit). Services can have almost any shape, such as web services, web applications, and databases. Disnix uses a plugin system called Dysnomia to make sure that the appropriate deployment steps are carried out for a particular type of service.
  • The arrows denote inter-dependencies. When a service points to another service means that the latter is an inter-dependency of the former service. Inter-dependency relationships ensure that the dependent service gets all configuration properties so that it knows how to reach the dependency and the deployment system makes sure that inter-dependencies of a specific service are deployed first.

    In some cases, enforcing the right activation order of activation may be expensive. It is also possible to drop the ordering requirement, as denoted by the dashed arrows. This is acceptable for redirects from the portal application, but not acceptable for database connections.
  • The dark grey boxes denote containers. Containers can be any kind of runtime environment that hosts zero or more distributable deployment units. For example, the container service of a MySQL database is a MySQL DBMS, whereas the container service of a Java web application archive can be a Java Servlet container, such as Apache Tomcat.

Visualizing the functional architecture of service-oriented systems


The services of which a service-oriented systems is composed are flexible -- they can be deployed to various kinds of environments, such a test environment, a second fail-over production environment or a local machine.

Because services can be deployed to a variety of targets, it may also be desired to get an architectural view of the functional parts only.

I created a new tool called: dydisnix-visualize-services that can be used to generate functional architecture diagrams by visualizing the services in the Disnix services model:


The above diagram is a visual representation of the services model of the example web application system, using a similar notation as the deployment architecture without showing any environment characteristics:

  • Ovals denote services and arrows denote inter-dependency relationships.
  • Every service is annotated with its type, so that it becomes clear what kind of a shape a service has and what kind of deployment procedures need to be carried out.

Despite the fact that the above diagram is focused on the functional parts, it may still look quite detailed, even from a functional point of view.

Essentially, the architecture of my example web application system is a "system of sub systems" -- each sub system provides an isolated piece of functionality consisting of a database backend and web application front-end bundle. The portal sub system is the entry point and responsible for guiding the users to the sub systems implementing the functionality that they want to use.

It is also possible to annotate services in the Disnix services model with a group and description property:


{distribution, invDistribution, pkgs, system}:

let
customPkgs = import ../top-level/all-packages.nix {
inherit pkgs system;
};

groups = {
homework = "Homework";
literature = "Literature";
...
};
in
{
homeworkdb = {
name = "homeworkdb";
pkg = customPkgs.homeworkdb;
type = "mysql-database";
group = groups.homework;
description = "Database backend of the Homework subsystem";
};

homework = {
name = "homework";
pkg = customPkgs.homework;
dependsOn = {
inherit usersdb homeworkdb;
};
type = "apache-webapplication";
appName = "Homework";
group = groups.homework;
description = "Front-end of the Homework subsystem";
};

...
}

In the above services model, I have grouped every database and web application front-end bundle in a group that represents a sub system (such as Homework). By adding the --group-subservices parameter to the dydisnix-visualize-services command invocation, we can simplify the diagram to only show the sub systems and how these sub systems are inter-connected:


$ dydisnix-visualize-services -s services.nix -f png \
--group-subservices

resulting in the following functional architecture diagram:


As may be observed in the picture above, all services have been grouped. The service groups are denoted by ovals with dashed borders.

We can also query sub architecture diagrams of every group/sub system. For example, the following command generates a sub architecture diagram for the Homework group:


$ dydisnix-visualize-services -s services.nix -f png \
--group Homework --group-subservices

resulting in the following diagram:


The above diagram will only show the the services in the Homework group and their context -- i.e. non-transitive dependencies and services that have a dependency on any service in the requested group.

Services that exactly fit the group or any of its parent groups will be displayed verbatim (e.g. the homework database back-end and front-end). The other services will be categorized into in the lowest common sub group (the Users and Portal sub systems).

For more complex architectures consisting of many layers, you may probably want to generate all available architecture diagrams in one command invocation. It is also possible to run the visualization tool in batch mode. In batch mode, it will recursively generate diagrams for the top-level architecture and every possible sub group and stores them in a specified output folder:


$ dydisnix-visualize-services --batch -s services.nix -f svg \
--output-dir out

Generating supplemental documentation


Another thing I have explained in my previous blog post is that providing diagrams is useful, but they cannot clear up all confusion -- you also need to document and clarify additional details, such as the purposes of the services.

It also possible to generate a documentation page for each group showing a table of services with their descriptions and types:

The following command generates a documentation page for the Homework group:


$ dydisnix-document-services -s services.nix --group Homework

It is also possible to adjust the generation process by providing a documentation configuration file (by using the --docs parameter):


$ dydisnix-document-services -f services.nix --docs docs.nix \
--group Homework

The are a variety of settings that can be provided in a documentation configuration file:


{
groups = {
Homework = "Homework subsystem";
Literature = "Literature subsystem";
...
};

fields = [ "description""type" ];

descriptions = {
type = "Type";
description = "Description";
};
}

The above configuration file specifies the following properties:

  • The descriptions for every group.
  • Which fields should be displayed in the overview table. It is possible to display any property of a service.
  • A description of every field in the services model.

Like the visualization tool, the documentation tool can also be used in batch mode to generate pages for all possible groups and sub groups.

Generating a documentation catalog


In addition to generating architecture diagrams and descriptions, it is also possible to combine both tools to automatically generate a complete documentation catalog for a service-oriented system, such as the web application example system:


$ dydisnix-generate-services-docs -s services.nix --docs docs.nix \
-f svg --output-dir out

By opening the entry page in the output folder, you will get an overview of the top-level architecture, with a description of the groups.


By clicking on a group hyperlink, you can inspect the sub architecture of the corresponding group, such as the 'Homework' sub system:


The above page displays the sub architecture diagram of the 'Homework' subsystem and a description of all services belonging to that group.

Another particularly interesting aspect is the 'Portal' sub system:


The portal's purpose is to redirect users to functionality provided by the other sub systems. The above architecture diagram displays all the sub systems in grouped form to illustrate that there is a dependency relationship, but without revealing all their internal details that clutters the diagram with unnecessary implementation details.

Other features


The tools support more use cases than those described in this blog post -- it is also possible, for example, to create arbitrary layers of sub groups by using the '/' character as a delimiter in the group identifier. I also used the company platform as an example case, that can be decomposed into four layers.

Availability


The tools described in this blog post are part of the latest development version of Dynamic Disnix -- a very experimental extension framework built on top of Disnix that can be used to make service-oriented systems self-adaptive by redeploying their services in case of events.

The reason why I have added these tools to Dynamic Disnix (and not the core Disnix toolset) is because the extension toolset has an infrastructure to parse and reflect over individual Disnix models.

Although I promised to make an official release of Dynamic Disnix a very long time ago, this still has not happened yet. However, the documentation feature is a compelling reason to stabilize the code and make the framework more usable.

A Nix-friendly XML-based data exchange library for the Disnix toolset

$
0
0
In the last few months, I have been intensively working on a variety of internal improvements to the Disnix toolset.

One of the more increasingly complex and tedious aspects in the Disnix toolset is data exchange -- Disnix implements declarative deployment in the sense that it takes three specifications written in the Nix expression language as inputs: a services model that specifies the deployable units, their properties and how they depend on each other, an infrastructure model specifies the available target machines and their properties, and a distribution model that specifies the mapping between services in the services models and target machines in the infrastructure model.

From these three declarative models, Disnix derives all the activities that need to be carried out to get a system in a running state: building services from source, distributing services (and their dependencies) to target machines, activating the services, and (optionally) restoring state snapshots.

Using the Nix expression language for these input models is useful for a variety of reasons:

  • We can use the Nix package manager's build infrastructure to reliably build services from source code, including all their required dependencies, and store them in isolation in the Nix store. The Nix store ensures that multiple variants and versions can co-exist and that we can always roll back to previous versions.
  • Because the Nix expression language is a purely functional domain-specific language (that in addition to data structures supports functions), we can make all required configuration parameters (such as the dependencies of the services that we intend to deploy) explicit by using functions so that we know that all mandatory settings have been specified.

Although the Nix expression language is a first class-citizen concept for tasks carried out by the Nix package manager, we also want to use the same specifications to instruct tools that carry out activities that Nix does not implement, such as the tools that activate the services and restore state snapshots.

The Nix expression language is not designed to be consumed by other tools than Nix (as a sidenote: despite this limitation, it is still somewhat possible to use the Nix expression language independently of the package manager in experimental setups, such as this online tutorial, but the libexpr component of Nix does not have a stable interface or commitment to make the language portable across tools).

As a solution, I convert objects in the Nix expression language to XML, so that they can be consumed by any of the tools that implement the activities that Nix does not support.

Although this may sound conceptually straight forward, the amount of data that needs to be converted, and code that needs to be written to parse that data is growing bigger and more complex, and becomes increasingly harder to adjust and maintain.

To cope with this growing complexity, I have standardized a collection of Nix-XML conversion patterns, and wrote a library named: libnixxml that can be used to make data interchange in both directions more convenient.

Converting objects in the Nix expression language to XML


The Nix expression language supports a variety of language integrations. For example, it can export Nix objects to XML and JSON, and import from JSON and TOML data.

The following Nix attribute set:

{
message = "This is a test";
tags = [ "test""example" ];
}

can be converted to XML (with the builtins.toXML primop) or by running:


$ nix-instantiate --eval-only --xml --strict example.nix

resulting in the following XML data:


<?xml version='1.0' encoding='utf-8'?>
<expr>
<attrs>
<attr column="3" line="2" name="message" path="/home/sander/example.nix">
<string value="This is a test" />
</attr>
<attr column="3" line="3" name="tags" path="/home/sander/example.nix">
<list>
<string value="test" />
<string value="example" />
</list>
</attr>
</attrs>
</expr>

Although the above XML code fragment is valid XML, it is basically also just a literal translation of the underlying abstract syntax tree (AST) to XML.

An AST dump is not always very practical for consumption by an external application -- it is not very "readable", contains data that we do not always need (e.g. line and column data), and imposes (due to the structure) additional complexity on a program to parse the XML data to a domain model. As a result, exported XML data almost always needs to be converted to an XML format that is more practical for consumption.

For all the input models that Disnix consumes, I was originally handwriting XSL stylesheets converting the XML data to a format that can be more easily consumed and handwriting all the parsing code. Eventually, I derived a number of standard patterns.

For example, a more practical XML representation of the earlier shown Nix expression could be:


<?xml version="1.0"?>
<expr>
<message>This is a test</message>
<tags>
<elem>test</elem>
<elem>example</elem>
</tags>
</expr>

In the above expression, the type and meta information is discarded. The attribute set is translated to a collection of XML sub elements in which the element names correspond to the attribute keys. The list elements are translated to generic sub elements (the above example uses elem, but any element name can be picked). The above notation is IMO, more readable, more concise and easier to parse by an external program.

Attribute keys may be identifiers, but can also be strings containing characters that invalidate certain XML element names (e.g. < or >). It is also possible to use a slightly more verbose notation in which a generic element name is used and the name property is used for each attribute set key:


<?xml version="1.0"?>
<expr>
<attr name="message">This is a test</attr>
<attr name="tags">
<elem>test</elem>
<elem>example</elem>
</attr>
</expr>

When an application has a static domain model, it is not necessary to know any types (e.g. this conversion can be done in the application code using the application domain model). However, it may also be desired to construct data structures dynamically.

For dynamic object construction, type information needs to be known. Optionally, XML elements can be annotated with type information:


<?xml version="1.0"?>
<expr type="attrs">
<attr name="message" type="string">This is a test</attr>
<attr name="tags" type="list">
<elem type="string">test</elem>
<elem type="string">example</elem>
</attr>
</expr>

To automatically convert data to XML format following the above listed conventions, I have created a standardized XSL stylesheet and command-line tool that can automatically convert Nix expressions.

The following command generates the first XML code fragment:


$ nixexpr2xml --attr-style simple example.nix

We can use the verbose notation for attribute sets, by running:


$ nixexpr2xml --attr-style verbose example.nix

Type annotations can be enabled by running:


$ nixexpr2xml --attr-style verbose --enable-types example.nix

The root, attribute and list element representations as well as the attribute set and types properties use generic element and property names. Their names can also be adjusted, if desired:


$ nixexpr2xml --root-element-name root \
--list-element-name item \
--attr-element-name property \
--name-attribute-name key \
--type-attribute-name mytype
example.nix

Parsing a domain model


In addition to producing more "practical" XML data, I have also implemented utility functions that help me consuming the XML data to construct a domain model in the C programming language, that consists values (strings, integers etc.), structs, list-like data structures (e.g. arrays, linked lists) and table-like data structures, such as hash tables.

For example, the following XML document only containing a string:


<expr>hello</expr>

can be parsed to a string in C as follows:


#include <nixxml-parse.h>

xmlNodePtr element;
/* Open XML file and obtain root element */
xmlChar *value = NixXML_parse_value(element, NULL);
printf("value is: %s\n"); // value is: hello

We can also use functions to parse (nested) data structures. For example, to parse the following XML code fragment representing an attribute set:


<expr>
<attr name="firstName">Sander</attr>
<attr name="lastName">van der Burg</attr>
</expr>

We can use the following code snippet:


#include <stdlib.h>
#include <nixxml-parse.h>

xmlNodePtr element;

typedef struct
{
xmlChar *firstName;
xmlChar *lastName;
}
ExampleStruct;

void *create_example_struct(xmlNodePtr element, void *userdata)
{
return calloc(1, sizeof(ExampleStruct));
}

void parse_and_insert_example_struct_member(xmlNodePtr element, void *table, const xmlChar *key, void *userdata)
{
ExampleStruct *example = (ExampleStruct*)table;

if(xmlStrcmp(key, (xmlChar*) "firstName") == 0)
example->firstName = NixXML_parse_value(element, userdata);
else if(xmlStrcmp(key, (xmlChar*) "lastName") == 0)
example->lastName = NixXML_parse_value(element, userdata);
}

/* Open XML file and obtain root element */

ExampleStruct *example = NixXML_parse_verbose_heterogeneous_attrset(element, "attr", "name", NULL, create_example_struct, parse_and_insert_example_struct_member);

To parse the attribute set in the XML code fragment above (that uses a verbose notation) and derive a struct from it, we invoke the NixXML_parse_verbose_heterogeneous_attrset() function. The parameters specify that the XML code fragment should be parsed as follows:

  • It expects the name of the XML element of each attribute to be called: attr.
  • The property that refers to the name of the attribute is called: name.
  • To create a struct that stores the attributes in the XML file, the function: create_example_struct() will be executed that allocates memory for it and initializes all fields with NULL values.
  • The logic that parses the attribute values and assigns them to the struct members is in the parse_and_insert_example_member() function. The implementation uses NixXML_parse_value() (as shown in the previous example) to parse the attribute values.

In addition to parsing values as strings and attribute sets as structs, it is also possible to:

  • Parse lists, by invoking: NixXML_parse_list()
  • Parse uniformly typed attribute sets (in which every attribute set member has the same type), by invoking: NixXML_parse_verbose_attrset()
  • Parse attribute sets using the simple XML notation for attribute sets (as opposed to the verbose notation): NixXML_parse_simple_attrset() and NixXML_parse_simple_heterogeneous_attrset()

Printing Nix or XML representation of a domain model


In addition to parsing NixXML data to construct a domain model, the inverse process is also possible -- the API also provides convenience functions to print an XML or Nix representation of a domain model.

For example, the following string in C:


char *greeting = "Hello";

can be displayed as a string in the Nix expression language as follows:


#include <nixxml-print-nix.h>

NixXML_print_string_nix(stdout, greeting, 0, NULL); // outputs: "Hello"

or as an XML document, by running:


#include <nixxml-print-xml.h>

NixXML_print_open_root_tag(stdout, "expr");
NixXML_print_string_xml(stdout, greeting, 0, NULL, NULL);
NixXML_print_close_root_tag(stdout, "expr");

producing the following output:


<expr>Hello</expr>

The example struct shown in the previous section can be printed as a Nix expression with the following code:


#include <nixxml-print-nix.h>

void print_example_attributes_nix(FILE *file, const void *value, const int indent_level, void *userdata, NixXML_PrintValueFunc print_value)
{
ExampleStruct *example = (ExampleStruct*)value;
NixXML_print_attribute_nix(file, "firstName", example->firstName, indent_level, userdata, NixXML_print_string_nix);
NixXML_print_attribute_nix(file, "lastName", example->lastName, indent_level, userdata, NixXML_print_string_nix);
}

NixXML_print_attrset_nix(stdout, &example, 0, NULL, print_example_attributes_nix, NULL);

The above code fragment executes the function: NixXML_print_attrset_nix() to print the example struct as an attribute set. The attribute set printing function invokes the function: print_example_attributes_nix() to print the attribute set members.

The print_example_attributes_nix() function prints each attribute assignment. It uses the NixXML_print_string_nix() function (shown in the previous example) to print each member as a string in the Nix expression language.

The result of running the above code is the following Nix expression:


{
"firstName" = "Sander";
"lastName" = "van der Burg";
}

the same struct can be printed as XML (using the verbose notation for attribute sets) with the following code:


#include <nixxml-print-xml.h>

void print_example_attributes_xml(FILE *file, const void *value, const char *child_element_name, const char *name_property_name, const int indent_level, const char *type_property_name, void *userdata, NixXML_PrintXMLValueFunc print_value)
{
ExampleStruct *example = (ExampleStruct*)value;
NixXML_print_verbose_attribute_xml(file, child_element_name, name_property_name, "firstName", example->firstName, indent_level, NULL, userdata, NixXML_print_string_xml);
NixXML_print_verbose_attribute_xml(file, child_element_name, name_property_name, "lastName", example->lastName, indent_level, NULL, userdata, NixXML_print_string_xml);
}

NixXML_print_open_root_tag(stdout, "expr");
NixXML_print_verbose_attrset_xml(stdout, &example, "attr", "name", 0, NULL, NULL, print_example_attributes_xml, NULL);
NixXML_print_close_root_tag(stdout, "expr");

The above code fragment uses a similar strategy as the previous example (by invoking NixXML_print_verbose_attrset_xml()) to print the example struct as an XML file using a verbose notation for attribute sets.

The attribute set members are printed by the print_example_attributes_xml() function.

The result of running the above code is the following XML output:


<expr>
<attr name="firstName">Sander</attr>
<attr name="lastName">van der Burg</attr>
</expr>

In addition to printing values and attribute sets, it is also possible to:

  • Print lists in Nix and XML format: NixXML_print_list_nix(), NixXML_print_list_xml()
  • Print attribute sets in simple XML notation: NixXML_print_simple_attrset_xml()
  • Print strings as int, float or bool: NixXML_print_string_as_*_xml.
  • Print integers: NixXML_print_int_xml()
  • Disable indentation by setting the indent_level parameter to -1.
  • Print type annotated XML, by setting the type_property_name parameter to a string that is not NULL.

Using abstract data structures


There is no standardized library for abstract data structures in C, e.g. lists, maps, trees etc. As a result, each framework provides their own implementations of them. To parse lists and attribute sets (that have arbitrary structures), you need generalized data structures that are list-like or table-like.

libnixxml provides two sub libraries to demonstrate how integration with abstract data structures can be implemented. One sub library is called libnixxml-data that uses pointer arrays for lists and xmlHashTable for attribute sets, and another is called libnixxml-glib that integrates with GLib using GPtrArray structs for lists and GHashTables for attribute sets.

The following XML document:


<expr>
<elem>test</elem>
<elem>example</elem>
</expr@>

can be parsed as a pointer array (array of strings) as follows:


#include <nixxml-ptrarray.h>

xmlNodePtr element;
/* Open XML file and obtain root element */
void **array = NixXML_parse_ptr_array(element, "elem", NULL, NixXML_parse_value);

and printed as a Nix expression with:


NixXML_print_ptr_array_nix(stdout, array, 0, NULL, NixXML_print_string_nix);

and as XML with:


NixXML_print_open_root_tag(stdout, "expr");
NixXML_print_ptr_array_xml(stdout, array, "elem", 0, NULL, NULL, NixXML_print_string_xml);
NixXML_print_close_root_tag(stdout, "expr");

Similarly, there is a module that works with xmlHashTables providing a similar function interface as the pointer array module.

Working with generic NixXML nodes


By using generic data structures to represent lists and tables, type annotated NixXML data and a generic NixXML_Node struct (that indicates what kind of node we have, such as a value, list or attribute set) we can also automatically parse an entire document by using a single function call:


#include <nixxml-ptrarray.h>
#include <nixxml-xmlhashtable.h>
#include <nixxml-parse-generic.h>

xmlNodePtr element;
/* Open XML file and obtain root element */
NixXML_Node *node = NixXML_generic_parse_expr(element,
"type",
"name",
NixXML_create_ptr_array,
NixXML_create_xml_hash_table,
NixXML_add_value_to_ptr_array,
NixXML_insert_into_xml_hash_table,
NixXML_finalize_ptr_array);

The above function composes a generic NixXML_Node object. The function interface uses function pointers to compose lists and tables. These functions are provided by the pointer array and xmlHashTable modules in the libnixxml-data library.

We can also print an entire NixXML_Node object structure as a Nix expression:


#include <nixxml-print-generic-nix.h>

NixXML_print_generic_expr_nix(stdout,
node,
0,
NixXML_print_ptr_array_nix,
NixXML_print_xml_hash_table_nix);

as well as XML (using simple or verbose notation for attribute sets):


#include <nixxml-print-generic-xml.h>

NixXML_print_generic_expr_verbose_xml(stdout,
node,
0,
"expr",
"elem",
"attr",
"name",
"type",
NixXML_print_ptr_array_xml,
NixXML_print_xml_hash_table_verbose_xml);

Summary


The following table summarizes the concepts described in this blog post:

ConceptNix expression representationXML representationC application domain model
value"hello"hellochar*
list[ "hello""bye" ]<elem>hello</elem><elem>bye</elem>void**, linked list, ...
attribute set{ a = "hello"; b = "bye"; }<a>hello</a><b>bye</b>xmlHashTablePtr, struct, ...
attribute set{ a = "hello"; b = "bye"; }<attr name="a">hello</attr><attr name="b">bye</attr>xmlHashTablePtr, struct, ...

The above table shows the concepts that the NixXML defines, and how they can be represented in the Nix expression language, XML and in a domain model of a C application.

The representations of these concepts can be translated as follows:

  • To convert a raw AST XML representation of a Nix expression to NixXML, we can use the included XSL stylesheet or run the nixexpr2xml command.
  • XML concepts can be parsed to a domain model in a C application by invoking NixXML_parse_* functions for the appropriate concepts and XML representation.
  • Domain model elements can be printed as XML by invoking NixXML_print_*_xml functions.
  • Domain model elements can be printed in the Nix expression language by invoking NixXML_print_*_nix functions.

Benefits


I have re-engineered the current development versions of Disnix and the Dynamic Disnix toolsets to use libnixxml for data exchange. For Disnix, there is much fewer boilerplate code that I need to write for the parsing infrastructure, making it significantly easier to maintain it.

In the Dynamic Disnix, libnixxml provides even more benefits beyond a simpler parsing infrastructure. The Dynamic Disnix toolset provides deployment planning methods, and documentation and visualization tools. These concerns are orthogonal to the features of the core Disnix toolset -- there is first-class Nix/Disnix integration, but the features of Dynamic Disnix should work with any service-oriented system (having a model that works with services and dependencies) regardless of what technology is used to carry out the deployment process itself.

With libnixxml it is now quite easy to make all these tools both accept Nix and XML representations of their input models, and make them output data in both Nix and XML. It is now also possible to use most features of Dynamic Disnix, such as the visualization features described in the previous blog post, independently of Nix and Disnix.

Moreover, the deployment planning methods should now also be able to more conveniently invoke external tools, such as SAT-solvers.

Related work


libnixxml is not the only Nix language integration facility I wrote. I also wrote NiJS (that is JavaScript-based) and PNDP (that is PHP-based). Aside from the language (C programming language), the purpose of libnixxml is not to replicate the functionality of these two libraries in C.

Basically, libnixxml has the inverse purpose -- NiJS and PNDP are useful for systems that already have a domain model (e.g. a domain-specific configuration management tool), and make it possible to generate the required Nix expression language code to conveniently integrate with Nix.

In libnixxml, the Nix expression representation is the basis and libnixxml makes it more convenient for external programs to consume such a Nix expression. Moreover, libnixxml only facilitates data interchange, and not all Nix expression language features.

Conclusion


In this blog post, I have described libnixxml that makes XML-based data interchange with configurations in the Nix expression language and domain models in the C programming language more convenient. It is part of the current development version of Disnix and can be obtained as a separate project from my GitHub page.

A new input model transformation pipeline for Disnix

$
0
0
As explained in earlier blog posts, Disnix (as well as other tools in the Nix project) are driven by declarative specifications -- instead of describing the activities that need to be carried out to deploy a system (such as building and distributing packages), we specify all the relevant properties of a service-oriented system:

  • The services model describes all the services that can be deployed to target machines in a network, how they can be built from their sources, how they depend on each other and what their types are, so the the deployment system knows how they can be activated.
  • The infrastructure model captures all target machines in the network, their properties, and the containers they provide. Containers in a Disnix-context are services that manage the life-cycle of a component, such as an application server, service manager or database management service (DBMS).
  • The distribution model maps services to containers on the target machines.

By running the following command-line instruction:

$ disnix-env -s services.nix -i infrastructure.nix -d distribution.nix

Disnix infers all the activities that need to be executed to get the system in a running state, such as building packages from source code (or downloading substitutes from a binary cache), the distribution of packages, the activation of a system and taking and restoring state snapshots.

Conceptually, this approach may sound very simple but the implementation that infers the deployment process is not. Whilst the input models are declarative, they are not executable -- there is not a one-on-one mapping between properties in the input models and the activities that Disnix needs to carry out.

To be able to execute deployment activities, Disnix transforms the three input models into a single declarative specification (called a deployment manifest file) that contains one-on-one mappings between deployment artifacts (e.g. Nix profiles, Nix packages and snapshots) and deployment targets (the target machines and/or container services). The transformation pipeline fills in the blanks with default settings, and transforms the input models into several intermediate representations, before it gets transformed into the manifest file.

So far, the intermediate representations and final result were never well defined. Instead, they have organically evolved and were heavily revised several times. As a result of adding new features and not having well defined representations, it became very hard to make changes and reason about the correctness of the models.

In my previous blog post, I have developed libnixxml to make the integration between a data model defined in the Nix expression language and external tools (that implement deployment activities that Nix does not support) more convenient. I am primarily using this library to simplify the integration of manifest files with Disnix tools.

As an additional improvement, I have revised the transformation pipeline, with well-defined intermediate representations. Besides a better quality transformation pipeline with well-defined intermediate stages, the Disnix toolset can now also take the intermediate model representations as input parameters, which is quite convenient for integration with external tooling and experimentation purposes. Furthermore, a new input model has been introduced.

In the blog post, I will describe the steps in the transformation pipeline, and the intermediate representations of the deployment models.

Separated concerns: services, infrastructure, distribution models


As explained earlier in this blog post, Disnix deployment are primarily driven by three input models: the services, infrastructure and distribution models. The reason why I have picked three input models (as opposed to a single configuration file) is to separate concerns and allow these concerns to be reused in different kinds of deployment scenarios.

For example, we can write a simple services model (services.nix) that describes two services that have an inter-dependency on each other:

{distribution, invDistribution, system, pkgs}:

let customPkgs = import ../top-level/all-packages.nix {
inherit system pkgs;
};
in
rec {
HelloMySQLDB = {
name = "HelloMySQLDB";
pkg = customPkgs.HelloMySQLDB;
dependsOn = {};
type = "mysql-database";
};

HelloDBService = {
name = "HelloDBService";
pkg = customPkgs.HelloDBServiceWrapper;
dependsOn = {
inherit HelloMySQLDB;
};
type = "tomcat-webapplication";
};
}

The above services model captures two services with the following properties:

  • The HelloMySQLDB services refers to a MySQL database backend that stores data. The type property: mysql-database specifies which Dysnomia module should be used to manage the lifecycle of the service. For example, the mysql-database Dysnomia module will create the database on initial startup.
  • The HelloDBService is a web service that exposes the data stored in the database backend to the outside it world. Since it requires the presence of a MySQL database backend and needs to know where it has been deployed, the database backend been declared as an inter-dependency of the service (by means of the dependsOn attribute).

    The tomcat-webapplication type specifies that Disnix should use the Apache Tomcat Dysnomia module, to activate the corresponding Java-based web service inside the Apache Tomcat servlet container.

The services model captures the aspects of a service-oriented system from a functional perspective, without exposing much of the details of the environments they may run in. This is intentional -- the services are meant to be deployed to a variety of environments. Target agnostic services make it possible, for example, to write an infrastructure model of a test environment (infrastructure-test.nix):

{
test1 = {
properties = {
hostname = "test1.example.org";
};

containers = {
tomcat-webapplication = {
tomcatPort = 8080;
};
};
};

test2 = {
properties = {
hostname = "test2.example.org";
};

containers = {
tomcat-webapplication = {
tomcatPort = 8080;
};

mysql-database = {
mysqlPort = 3306;
mysqlUsername = "mysqluser";
mysqlPassword = builtins.readFile ./mysqlpw;
};
};
};
}

and a distribution model that maps the services to the target machines in the infrastructure model (distribution-test.nix):

{infrastructure}:

{
HelloMySQLDB = [ infrastructure.test2 ];
HelloDBService = [ infrastructure.test1 ];
}

With these three deployment models, we can deploy a system to a test environment, by running:

$ disnix-env -s services.nix \
-i infrastructure-test.nix \
-d distribution-test.nix

and later switch to a production environment using the same functional services model, after the system has been properly validated in the test environment:

$ disnix-env -s services.nix \
-i infrastructure-prod.nix \
-d distribution-prod.nix

Similarly, we can adjust the distribution model to only deploy a sub set of the services of a system for, say, experimentation purposes.

Unifying the input models into a single specification: the deployment architecture model


The first step in transforming the input models into a single executable specification, is unifying the specifications into one single declarative specification, that I will call the deployment architecture model. The name is derived from the concept of deployment architectures in software architecture terminology:
a description that specifies the distribution of software components over hardware nodes.

A Disnix deployment architecture model may look as follows:

{system, pkgs}:

let customPkgs = import ../top-level/all-packages.nix {
inherit system pkgs;
};
in
rec {
services = rec {
HelloMySQLDB = {
name = "HelloMySQLDB";
pkg = customPkgs.HelloMySQLDB;
dependsOn = {};
type = "mysql-database";

targets = [ infrastructure.test2 ];
};

HelloDBService = {
name = "HelloDBService";
pkg = customPkgs.HelloDBServiceWrapper;
dependsOn = {
inherit HelloMySQLDB;
};
type = "tomcat-webapplication";

targets = [ infrastructure.test1 ];
};
};

infrastructure = {
test1 = {
properties = {
hostname = "test1.example.org";
};

containers = {
tomcat-webapplication = {
tomcatPort = 8080;
};
};
};

test2 = {
properties = {
hostname = "test2.example.org";
};

containers = {
tomcat-webapplication = {
tomcatPort = 8080;
};

mysql-database = {
mysqlPort = 3306;
mysqlUsername = "mysqluser";
mysqlPassword = builtins.readFile ./mysqlpw;
};
};
};
}

The above deployment architecture defines has the following properties:

  • The services and infrastructure models are unified into a a single attribute set in which the services attribute refers to the available services and infrastructure attribute to the available deployment targets.
  • The separated distribution concern is completely eliminated -- the mappings in the distribution models are augmented to the corresponding services, by means of the targets attribute. The transformation step basically checks whether no targets property was specified already, and if there is not -- it will consider the targets in the distribution model the deployment targets of the service.

    The fact that the targets attribute will not be overridden, also makes it possible to already specify the targets in the services model, if desired.

In addition to the three deployment models, it is now also possible as an end-user to write a deployment architecture model and use that to automate deployments. The following command-line instruction will deploy a service-oriented system from a deployment architecture model:

$ disnix-env -A architecture.nix

Normalizing the deployment architecture model


Unifying models into a single deployment architecture specification is a good first step in producing an executable specification, but more needs to be done to fully reach that goal.

There are certain deployment properties that are unspecified in the examples shown earlier. For some configuration properties, Disnix provides reasonable default values, such as:

  • Each service can indicate whether they want their state to be managed by Dysnomia (with the property deployState), so that data will automatically be migrated when moving the service from one machine to another. The default setting is false and can be overridden with the --deploy-state parameter.

    If a service does not specify this property then Disnix will automatically propagate the default setting as a parameter.
  • Every target machine in the infrastructure model also has specialized settings for connecting to the target machines, building packages and running tasks concurrently:

    test2 = {
    properties = {
    hostname = "test2.example.org";
    };

    containers = {
    tomcat-webapplication = {
    tomcatPort = 8080;
    };

    mysql-database = {
    mysqlPort = 3306;
    mysqlUsername = "mysqluser";
    mysqlPassword = builtins.readFile ./mysqlpw;
    };

    clientInterface = "disnix-ssh-client";
    targetProperty = "hostname";
    numOfCores = 1;
    system = "x86_64-linux";
    };
    };

    If none of these advanced settings are provided, Disnix will assume that the every target machine has the same system architecture (system) as the coordinator machine (so that the Nix package manager does not have to delegate a build to a machine that has a compatible architecture), we use the Disnix SSH client (disnix-ssh-client) interface executable (clientInterface) to connect to the target machine (using the hostname property as a connection string) and we only run one activity per target machine concurrently: numOfCores.

In addition to unspecified properties (that need to be augmented with default values), we also have properties that are abstract specifications. These specifications need to be translated into more concrete representations:

  • As explained in an older blog post, the targets property -- that maps services to targets -- does not only map services to machines, but also to container services hosted on that machine. In most cases, you will only use one container instance per service type -- for example, running two MySQL DBMS services (e.g. one on TCP port 3306 and another on 3307) is far less common use case scenario.

    If no container mapping is provided, Disnix will do an auto-mapping to a container service that corresponds to the service's type property.

    The MySQLDBService's targets property shown in the last deployment architecture model gets translated into the following property:

    {system, pkgs}:

    rec
    {
    services = rec {
    HelloMySQLDB = {
    name = "HelloMySQLDB";
    ...

    targets = [
    rec {
    selectedContainer = "mysql-database";

    container = {
    mysqlPort = 3306;
    mysqlUsername = "mysqluser";
    mysqlPassword = builtins.readFile ./mysqlpw;
    };

    properties = {
    hostname = "test2.example.org";
    };

    clientInterface = "disnix-ssh-client";
    targetProperty = "hostname";
    numOfCores = 1;
    system = "x86_64-linux";
    }
    ];
    };
    };

    infrastructure = ...
    }

    As may be observed, the target provides a selectedContainer property to indicate to what container the service needs to be deployed. The properties of all the containers that the service does not need to know about are discarded.
  • Another property that needs to be extended is the inter-dependency specifications (dependsOn and connectsTo). Typically, inter-dependency specifications are only specified on a functional level -- a service typically only specifies that it depends on another service disregarding the location where that service may have been deployed.

    If no target location is specified, then Disnix will assume that the service has an inter-dependency on all possible locations where that dependency may be deployed. If an inter-dependency is redundantly deployed, then that service also has an inter-dependency on all redundant replicas.

    The fact that it is also possible to specify the targets of the inter-dependencies, makes it also possible to optimize certain deployments. For example, you can also optimize a service's performance by forcing it to bind to an inter-dependency that is deployed to the same target machine, so that it will not be affected by slow network connectivity.

    The dependsOn property of the HelloDBService will translate to:

    dependsOn = {
    HelloMySQLDB = {
    name = "HelloMySQLDB";
    pkg = customPkgs.HelloMySQLDB;
    dependsOn = {};
    type = "mysql-database";

    targets = [
    {
    selectedContainer = "mysql-database";

    container = {
    mysqlPort = 3306;
    mysqlUsername = "mysqluser";
    mysqlPassword = builtins.readFile ./mysqlpw;
    };

    properties = {
    hostname = "test2.example.org";
    };
    }
    ];
    };
    };

    In the above code fragment, the inter-dependency has been augmented with a targets property corresponding to the targets where that inter-dependency has been deployed to.


The last ingredient to generate an executable specification is building the services from source code so that we can map their build results to the target machines. To accomplish this, Disnix generates two invisible helper attributes for each service:

HelloDBService = {
name = "HelloDBService";
pkg = customPkgs.HelloDBServiceWrapper;
dependsOn = {
inherit HelloMySQLDB;
};
type = "tomcat-webapplication";

...

_systemsPerTarget = [ "x86_64-linux""x86_64-darwin" ];
_pkgsPerSystems = {
"x86_64-linux" = "/nix/store/91abq...-HelloDBService";
"x86_64-darwin" = "/nix/store/f1ap2...-HelloDBService";
};
};

The above code example shows the two "hidden" properties augmented to the HelloDBService:

  • The _systemsPerTarget specifies for which CPU architecture/operating systems the service must be built. Normally, services are target agnostic and should always yield the same Nix store path (with a build that is nearly bit-identical), but the system architecture of the target machine is an exception to deviate from this property -- it is also possible to deploy the same service to different CPU architectures/operating systems. In such cases the build result could be different.
  • The _pkgsPerSystem specifies for each system architecture, the Nix store path to the build result. A side effect of evaluating the Nix store path is the service also gets built from source code.

Finally, it will compose a deployment architecture model attribute named: targetPackages that refers to a list of Nix store paths to be distributed to each machine in the network:

{
targetPackages = {
test1 = [
"/nix/store/91abq...-HelloDBService"
];

test2 = [
"/nix/store/p9af1...-HelloMySQLDB"
];
};

services = ...
infrastructure = ...
}

The targetPackages attribute is useful for a variety of reasons, as we will see later.

Generating a deployment model


With a normalized architecture model, we can generate an executable specification that I will call a deployment model. The deployment model can be used for executing all remaining activities after the services have been built.

An example of a deployment model could be:

{
profiles = {
test1 = "/nix/store/...-test1";
test2 = "/nix/store/...-test2";
};

services = {
"ekfekrerw..." = {
name = "HelloMySQLDB";
pkg = "/nix/store/...";
type = "mysql-database";
dependsOn = [
];
connectsTo = [
];
};

"dfsjs9349..." = {
name = "HelloDBService";
pkg = "/nix/store/...";
type = "tomcat-webapplication";
dependsOn = [
{ target = "test1";
container = "mysql-database";
service = "ekfekrerw...";
}
];
connectsTo = [
];
};
};

infrastructure = {
test1 = {
properties = {
hostname = "test1.example.org";
};
containers = {
apache-webapplication = {
documentRoot = "/var/www";
};
};
system = "x86_64-linux";
numOfCores = 1;
clientInterface = "disnix-ssh-client";
targetProperty = "hostname";
};
test2 = {
properties = {
hostname = "test2.example.org";
};
containers = {
mysql-database = {
mysqlPort = "3306";
};
};
system = "x86_64-linux";
numOfCores = 1;
clientInterface = "disnix-ssh-client";
targetProperty = "hostname";
};
};

serviceMappings = [
{ service = "ekfekrerw...";
target = "test2";
container = "mysql-database";
}
{ service = "dfsjs9349...";
target = "test1";
container = "tomcat-webapplication";
}
];

snapshotMappings = [
{ service = "ekfekrerw...";
component = "HelloMySQLDB";
container = "mysql-database";
target = "test2";
}
];
}

  • The profiles attribute refers to Nix profiles mapped to target machines and is derived from the targetPackages property in the normalized deployment architecture model. From the profiles property Disnix derives all steps of the distribution phase in which all packages and their intra-dependencies are copied to machines in the network.
  • The services attribute refers to all services that can be mapped to machines. The keys in this attribute set are SHA256 hash codes are recursively computed from the Nix store path of the package, the type, and all the inter-dependency mappings. Using hash codes to identify the services makes it possible to easily see whether a service is identical to another or not (by comparing hash codes), so that upgrades can be done more efficiently.
  • The infrastructure attribute is unchanged compared to the deployment architecture model and still stores target machine properties.
  • The serviceMappings attribute maps services in the services attribute set, to target machines in the network stored in the infrastructure attribute set and containers hosted on the target machines.

    From these mappings, Disnix can derive the steps to activate and deactivate the services of which a system is composed, ensure that all dependencies are present and that the services are activated or deactivated in the right order.
  • The snapshotMappings attribute state that for each services mapped to a target machines and container, we also want to migrate the state (by taking and restoring snapshots) if the service gets moved from one machine to another.

Although a deployment model is quite low-level, it is now also possible to manually write one, and deploy it by running:

$ disnix-env -D deployment.nix

disnix-env invokes an external executable called: disnix-deploy that executes the remaining activities of deployment process after the build process succeeds. disnix-depoy as well as the tools that execute individual deployment activities are driven by a manifest files. A manifest file is simply a one-on-one translation of the deployment model in the Nix expression language to XML following the NixXML convention.

Generating a build model


To build the services from source code, Disnix simply uses Nix's build facilities to execute the build. If nothing special has been configured, all builds will be executed on the coordinator machine, but this may not always be desired.

Disnix also facilitates heterogeneous architecture support. For example, if the coordinator machine is a Linux machine and a target machine is macOS (which is not compatible with the Linux system architecture), then Nix should delegate the build to a remote machine that is capable of building it. This is not something that Disnix handles for you out of the box -- you must configure Nix yourself to allow builds to be delegated.

It is also possible to optionally let Disnix delegate builds to the target machines in the network. To make build delegation work, Disnix generates a build model from a normalized deployment architecture model:

{
derivations = [
{ "/nix/store/HelloMySQLDB-....drv"; interface = "test1"; }
{ "/nix/store/HelloDBService-....drv"; interface = "test2"; }
];

interfaces = {
test1 = {
targetAddress = "test1.example.org";
clientInterface = "disnix-ssh-client";
};

test2 = {
targetAddress = "test2.example.org";
clientInterface = "disnix-ssh-client";
};
};
}

The build model shown above defines the following properties:

  • The derivations attribute maps Nix store derivation files (low-level Nix specifications that capture build procedures and dependencies) to machines in the network that should perform the build. This information is used by Disnix to delegate store derivation closure to target machines, use Nix to build the packages remotely, and fetch the build results back to the coordinator machine.
  • The interfaces attribute is a sub set of the infrastructure model that contains the connectivity settings for each target machine.

By running the following command, you can execute a build model to delegate builds to remote machines and fetch their results back:

$ disnix-delegate -B build.nix

If the build delegation option is enabled (for example, by passing --build-on-targets parameter to disnix-env) then Disnix will work a so-called distributed derivation file. Similar to a manifest file, a distributed derivation file is a one-on-one translation from the build model written in the Nix expression language to XML using the NixXML convention.

Packages model


In the normalized architecture model and deployment model, we generate a targetPackages property that we can use to compose Nix profiles with packages from.

For a variety of reasons, I thought it would also be interesting to give the user direct control to use this property. A new feature in Disnix is that you can now also write a packages model:

{pkgs, system}:

{
test1 = [
pkgs.mc
];

test2 = [
pkgs.wget
pkgs.curl
];
}

The above packages model says that we should distribute the Midnight Commander package to the test1 machine, and wget and curl to the test2 machine.

Running the following command will deploy the packages to the target machines in the network:

$ disnix-env -i infrastructure.nix -P pkgs.nix

You can also combine the three common Disnix models with a package model:

$ disnix-env -s services.nix \
-i infrastructure.nix \
-d distribution.nix \
-P pkgs.nix

then Disnix will deploy the services that are distributed to target machines and the supplemental packages defined in the packages model.

The packages model is useful for a variety of reasons:

  • Although it is already possible to use Disnix as a simple package deployer (by setting the types of services to: package), the packages model approach makes it even easier. Furthermore, you also more easily specify sets of packages for target machines. The only thing you cannot do is deploying packages that have inter-dependencies on services, e.g. a client that is preconfigured to connect to a service.
  • The hybrid approach makes it possible to more smooth make a transition to Disnix when automating the deployment process of a system. You can start by managing the dependencies with Nix, then package pieces of the project as Nix packages, then use Disnix to deploy them to remote machines, and finally turn pieces of the system into services that can be managed by Disnix.

Conclusion


In this blog post, I have described a new transformation pipeline in Disnix with well-defined intermediate steps that transforms the input models to a deployment model that is consumable by the tools that implement the deployment activities.

The following diagram summarizes the input models, intermediate models and output models:


The new transformation pipeline has the following advantages over the old infrastructure:

  • The implementation is much easier to maintain and we can more easily reason about its correctness
  • We have access to a broader range of configuration properties. For example, it was previously not possible to select the targets of the inter-dependencies.
  • The output models: deployment and build models are much more easily consumable by the Disnix tools that execute the remainder of the deployment activities. The domain models in the code, also closely resemble the structure of the build and deployment models. This can also be partially attributed to libnixxml that I have described in my previous blog post.
  • We can more easily implement new input models, such as the packages model.
  • The implementation of the disnix-reconstruct tool that reconstructs the manifest on the coordinator machine from metadata stored on the target machines also has become much simpler -- we can get rid of most of the custom code and generate a deployment model instead.

Availability


The new pipeline is available in the current development version of Disnix and will become available for general use in the next Disnix release.

The deployment models described in this blog post are incompatible with the manifest file format used in the last stable release of Disnix. This means that after upgrading Disnix, you need to convert any previous deployment configuration by running the disnix-convert tool.

Some personal conventions for implementing domain models in C/C++ applications

$
0
0
I have written two data exchange libraries -- not so long ago, I have created libnixxml that can be used to work with XML data following the so-called NixXML convention, which is useful to facilitate integration with tools in the Nix ecosystem, while still having meaningful XML data formats that can be used independently.

Many years ago, I wrote libiff that makes it possible to parse Interchange File Format (IFF) files that use so-called "chunks" to structure and organize binary files.

The goal of these two data exchange libraries is not to only facilitate data interchange -- in addition, they have also been designed to assist the user in constructing domain models in the C (or C++) programming language.

With the term: domain model, I am basically referring to an organization of data structures, e.g. structs, classes and abstract data structures, such as hash tables, lists, maps and trees, that have a strong connection to a (well-defined) problem domain (expressible in a natural language). Deriving such an organization is an important ingredient in object oriented design, but not restricted to object orientation only.

In addition to implementing a domain model in a C or C++ application with an understandable mapping to the problem domain, I also typically want the implementation to provide one or more of the following non-functional and cross-functional properties:

  • The data integrity should be maintained as much as possible. It should be difficult, for example, to mutate properties of an object in such a way that they have representations that cannot be interpreted by the program. For example, if an object requires the presence of another object, then it should be difficult to construct objects that have dangling references.
  • We may want to read a representation of the domain model from an external source, such as a file, and construct a domain model from it. Because external sources cannot be trusted, we also want this process to be safe.
  • In addition to reading, we may also want to write a data representation of a domain model to an external source, such as a file or the standard output. We also want to write a file in such a way that it can be safely consumed again.
  • We may want want to check the integrity of the data model and have decent error reporting in case an inconsistency was found.
  • It should not take too much effort in maintaining and adjusting the implementation of a domain model.

To implement the above properties, I have slowly adopted a number of conventions that I will describe in this blog post.

In addition to C and C++, these conventions also have some relevance to other programming languages, although most "higher level" languages, such as Java and Python, already have many facilities in their standard APIs to implement the above properties, whereas in C and C++ this is mostly the implementer's responsibility and requires a developer to think more consciously.

Constructing objects


When constructing objects, there are two concerns that stand out for me the most -- first, when constructing an object, I want to make sure that they never have inconsistent properties. To facilitate that, the solution is probably obvious -- by creating a constructor function that takes all mandatory properties as parameters that uses these parameters to configure the object members accordingly.

My second concern is memory allocation -- in C and C++ objects (instances of a struct or class) can be allocated both on the stack or the heap. Each approach has their own advantages and disadvantages.

For example, working with stack memory is generally faster and data gets automatically discarded when the scope of a block terminates. A disadvantage is that sizes of the data structures must be known at compile time, and some platforms have a limit of how much data can be allocated on the stack.

Heap memory, can be dynamically allocated (e.g. the size of memory to be allocated does not need to know at compile time), but is slower to allocate, and it is the implementer's responsibility to free up the allocated data when it is no longer needed.

What I generally do is for simple data structures (that do not contain too many fields, or members referring to data structures that require heap memory), I provide an initializer function that can be used on an object that is allocated on the stack to initialize its members.

Whenever a data structure is more complex, i.e. when it has many fields or members that require heap memory, I will create a constructor function that allocates the right amount of heap memory in addition to initializing its members.

Destructing objects


When an object is constructed, it may typically have resources allocated that need to be freed up. An obvious resource is heap memory -- as described earlier, when heap memory was previously allocated (e.g. for the data structure itself, but also for some of its members), it, at a later point in time, also needs to be freed up. Not freeing up memory causes memory leaks eventually causing a program to run out of memory.

Another kind of resource -- that is IMO often overlooked -- are file descriptors. Whenever a file has been opened, it also needs to be explicitly closed to allow the operating system to assign it to another process. Some operating systems have a very limited amount of file descriptors that can be allocated resulting in problems if a program is running for longer periods of time.

To maintain consistency and keep an API understandable, I will always create a destructor function when a constructor function exists -- in some cases (in particular with objects that have no members that require heap memory), it is very tempting to just tell (or simply expect) the API consumer to call free() explicitly (because that is essentially the only thing that is required). To avoid confusion, I always define a destructor explicitly.

Parsing an object from external source


As suggested in the introduction, I (quite frequently) do not only want to construct an object from memory, but I want it to be constructed from a definition originating from an external resource, such as a file on disk. As a rule of thumb (for integrity and security reasons), external input cannot be trusted -- as a result, it needs to be reliably parsed and checked, for which the data interchange libraries I developed provide a solution.

There is a common pitfall that I have encountered quite frequently in the process of constructing an object -- I typically assign default values to primitive members (e.g. integers) and NULL pointers to members that have a pointer type. The most important reason why I want all member fields to be initialized is to prevent them from staying garbage leading to unpredictable results if they are used by accident. In C, C++ when using malloc() or new() memory is allocated, but not automatically cleared, to, for example, zero bytes.

By using NULL pointers, I can later check whether all mandatory properties have been set and raise an error if this is not case.

A really tricky case with NULL pointers are pointers referring to data structures that encapsulate data collections, such as arrays, lists or tables. In some cases, it is fine that the input file does not define any data elements. The result should be an empty data collection. However, following the strategy to assign a NULL pointer by default introduces a problem -- in locations where a data collection is expected, the program will typically crash caused by a segmentation fault, because the program attempts to dereference a NULL pointer.

When assigning NULL pointers, I will always ask myself the question what kind of meaning NULL has. If I cannot provide an explanation, then I will make sure that a value is initialized with some other value than NULL. In practice, this means when members are referring to data collections, I will construct an empty data collection instead of assigning a NULL pointer. For data elements (e.g. strings), assigning NULL pointers to check whether they have been set is fine.

Finally, I also have the habit to make it possible to read from any file descriptor. In UNIX and UNIX-like operating systems everything is a file, and a generic file descriptor interface makes it possible to consume data from any resource that exposes itself as a file, such as a network connection.

Serializing/exporting objects to an external resource


In addition to retrieving and parsing objects from external resources, it is often desirable to do the opposite as well: serializing/exporting objects to an external resource, such as a file on disk.

Data that is consumed from an external source cannot be trusted, but if the output is not generated properly, the output most likely cannot be trusted either and reliably be consumed again.

For example, when generating JSON data with strings, a string that contains a double quote: " needs to be properly escaped, which is very easily overlooked when using basic string manipulation operations. The data exchange libraries provide convenience functions to reliably print and escape values.

We may also want to pretty print the output, e.g. adding indention, so that it can also be read by humans. Typically I add facilities for pretty printing to the functions that generate output.

Similar to the assigning a NULL pointer "dilemma" for empty data collections, we also face the dilemma to print an empty data collection or no elements at all. Typically, I would pick the option to print an empty data structure instead of omitting it, but I have no hard requirements for either of these choices.

As with reading and parsing data from external sources, I also typically facilitate writing to file descriptors so that it is possible to write data to any kind of file, such as the standard output or a remote network resource.

Checking the integrity of objects


Generally, I use constructor functions or mutation functions to prevent breaking the integrity of objects, but it is not always fully possible to fully avoid problems, for example, while parsing data from external resources. In such scenarios, I also typically implement functionality that checks the integrity of an object.

One of the primary responsibilities of a checking function is to examine the validity of all data elements. For example, to check whether a mandatory field has been set (i.e. it is not NULL) and whether they have the right format.

In addition to checking validity of all data elements, I typically also recursively traverse the data structure members and check their validity. When an error has been encountered in an abstract data structure, I will typically indicate which element (e.g. the array index number, or hash table key) is the problem, so that it can be more easily diagnosed by the end user.

When all fields of an object have been considered valid, I may also want to check whether the object's relationships are valid. For example, an object should not have a dangling reference to a non-existent object, that could result in segmentation faults caused by dereferencing NULL pointers.

Instead of invoking a check function explicitly, it is also possible to make a check function an integral part of a parse or constructor function, but I prefer to keep a check function separate, for the following reasons:

  • We do not need to perform a check if we are certain that the operations that we carry out, changed in any data in the wrong way.
  • We may want to perform checks in various stages of program, such as after parsing, after construction or after certain critical updates.

Comparing objects


Another important concern is the ability to compare objects for equality and/or ordering. I also typically implement a comparison function for each data structure.

In theory, recursively comparing a structure of objects could become quite expensive, especially if there are many nested data structures with many data elements. As an optimization, it may be possible to maintain integrity hashes and only check values if these hashes change, but so far I have never had run into any situations in which performance is really a bottleneck.

Naming


When developing data structures and function, I also try to follow a consistent naming convention for data structures and functions. For example, I may want to use: create_<ds_name> for a function creating a data structure and delete_<ds_name> for a function deleting a data structure.

Furthermore, I try to give meaningful names to data structures that have a correspondence with the problem domain.

Modularity


Although not mandatory in C or C++, I also typically try to use one header and one implementation file per data structure and functions that are related to it -- similarly, I follow the same convention for abstract data structure usages.

My main motivation to do this is to keep things understandable -- a module with many responsibilities is typically more difficult to maintain and harder to read.

Furthermore, I try to make all functions that have no relevance to be exposed publicly static.

Discussion


The conventions described in this blog post work particularly well for my own projects -- I have been able to considerably improve the reliability and maintainability of my programs and the error reporting.

However, they are not guaranteed to be the "silver bullet" for all coding problems. Some limitations that I see are:

  • Finding a well-defined description of a domain and implementing a corresponding domain model sounds conceptually simple, but is typically much harder than expected. It typically takes me several iterations to get it (mostly) right.
  • The conventions only makes sense for programs/code areas that are primarily data driven. Workflows that are primarily computationally driven may often have different kinds of requirements, e.g. for performance reasons, and most likely require a different organization.
  • The conventions are not there to facilitate high performance (but also do not always necessarily work against it). For example, splitting up data structures and corresponding functions into modules, makes it impossible to apply certain compiler optimizations that are possible if code would not have been separated into sepearte compilation units. Integrity, security, and maintenance are properties I consider to have higher priority over performance.

On motivation and purpose

$
0
0
In this blog post, I will discuss an important and recurring non-technical subject that is common in our field of expertise: motivation. Pretty much everybody in the industry that I know (including myself) have motivational problems once in a while. There are a variety of reasons why people get motivated or demotivated. In my experience, lack of motivation is one of the more important reasons why people quit and change jobs.

I will elaborate about one of the ingredients that is important to me: purpose.

A common misunderstanding: technology


What I have noticed is that quite a few people think that software developers are generally motivated by technology -- for instance, they will be motivated if they can use the latest computer models and the latest and greatest software technologies to develop software. At the time of writing this blog post, I see many companies that have vacancies advertising with technologies, such as: Node.js, Angular, React, JavaScript, Python, Go, Docker, Kubernetes etc.

While it is true that my skills are stronger with one particular class of technology than another, and that I actually do have preferences, such as which kind of tool is best for deployment, technology alone is not something that gives me motivation.

Instead: development is done for a purpose. Typically, we develop software systems to accomplish certain goals for a certain audience. As developers, we want to reach these goals and offer good quality -- technology is in service of reaching these goals. This sometimes also means that I have to work with technology that is not my primary choice or something I am not familiar with.

In addition to using technology, we may have to do things that are not technical at all. Typically, developing software systems is team work. To work effectively as a team, communication is also very important. For example:

  • You may need to write good documentation so that ideas, requirements and other technical considerations are clear among team members. For this you also need proper writing skills and mastery of the English language, if your native language is different.
  • You may need to give trainings or knowledge transfer presentations to end users or fellow team members for which you need good presentation skills.

Examples


I have a lot of interesting anecdotes of unorthodox choices that I made in the past, that I can rationalize by explaining the purpose. Some of them are:

  • I did a PhD, which has number of implications for your career -- as a PhD student you are (fortunately!) employed in the Netherlands, so you will get a salary and additional benefits such as a pension. The biggest disadvantage is that you only get a temporary employment contract and your salary is much lower that an industry job in the same field. Without a permanent employment contract, for example, it is very difficult to get a mortgage to buy a house.

    Because of this "disadvantage", quite a few people think that the PhD degree is the reason that motivated me (because supposedly it provides you better job perspectives, which is generally not the case in software technology).

    My motivation was actually much different: the subject of my PhD research was software deployment. Before I started my PhD research, I already knew how difficult it was to construct software systems from source code, how to package components and how to deploy service-oriented systems that are distributed and facilitate technology diversity. To have the possibility to dedicate yourself to a subject for a few years and construct tools to automate and improve such deployment processes was the real reason why I wanted to do this.

    (As a sidenote: although constructing tools was my primary motivation and the main objective of research in software engineering, I also struggled a bit with motivation on a few occasions. In a blog post that I wrote in my final year, I explained what got me demotivated).

    I learned a lot of things while I was a PhD student. In addition to technology, I also considerably improved my writing skills, presentations skills, I started this blog, and I did quite a lot of traveling alone, which gives you all kinds of interesting experiences. All of these learning experiences were in service of reaching my main goal.
  • During my PhD, I was also a visiting researcher at Philips Healthcare. At Philips Healthcare, I was applying my research and tooling to medical systems. Most of the technology stack used at Philips were Microsoft technologies: Windows as an operating system, Internet Information Services as web server, SQL server as DBMS, .NET/C# as an implementation language, and so on.

    I was (and still am) a Linux/Free software/Open source person. As a result, I was always avoiding these technologies as much possible -- what I basically did not like about them is that were all proprietary and strongly tied to the Windows operating system. Furthermore, I did not like Internet Information Services because of its bad security reputation.

    At the same time, the deployment tool I was working on (Disnix), was also designed to facilitate technology diversity, including technologies that I did not like per se. As part of my work at Philips, I managed to automate the build processes of C#/.NET projects with the Nix package manager and I created Dysnomia plugins so that services implemented with Microsoft technology could be deployed with Disnix.

    I also learned quite a few things about the .NET packaging internals, such as strong names and the global assembly cache. Because I wanted to facilitate technology diversity, I was motivated to learn these concepts.
  • At Conference Compass, I developed a variety of Nix functions to build mobile applications (native Android, native iOS and Titanium). I was highly motivated for native Android, because of two reasons: I have an Android device myself and I consider it quite valuable to automate the build processes of complex Android apps including company applications.

    The iOS and Titanium build functions were less interesting. In particular, what I disliked the most about iOS is that I do not have such a device myself, and I really do not like the fact that app delivery to iOS devices (e.g. iPhone, iPad) rely on one single distribution channel: Apple. It is not even possible to deploy an app that you have developed yourself for a device that you own, without obtaining a certificate and provisioning profile from Apple!

    Still, I considered a conference app to be quite valuable. Our audience uses both iOS and Android devices. This means that iOS cannot be avoided, because that would disqualify a significant chunk of our audience.

    Furthermore, I also wanted to support technology diversity again -- to be able to build apps with the Nix package manager for any mobile platform is useful. The domain and technology diversity of Nix motivated me to also learn about these areas that I initially did not find interesting.
  • For my Nix and Disnix related work, I have developed several small utility libraries, e.g. for concurrency and data exchange, and I explored underlying concepts, such as layered build function abstractions. The primary reason to do these things is not because I was directly interested in concepts, but they significantly contribute to quality improvement of the deployment tools -- they make the infrastructure faster, more robust and easier to maintain.

What I typically do


To get motivated, I basically need to know my purpose, and then define and align goals. This is typically easier said than done and it requires quite a bit of exploration.

Basically, I have adopted the following habits whenever I am new to some organization:

  • Learn about the company: IMO is important to know (at least from a high level perspective) the kinds of products and/or services a company offers, because the work you do is primarily focused on improving business value. For example, when I had the feeling that I learned enough about the Mendix product and service, I wrote a small article about it on my blog and I am grateful to Mendix that I am allowed to do this.
  • Learn about the domain: in addition to the company product and/or service, it is also important to know in what domain it is active. You will get a better understanding about the customers, what they want and what kind of challenges you might face in reaching them.

    For example, at Philips I learned a lot about medical regulations, at Conference Compass I learned the true value of having digital versions of traditional paper programs (that cannot change after they have been printed) and at Mendix it is interesting to continuously think about what kinds of value can low-code development offer (in terms of speed, quality and target audience, such as non-technical developers).
  • Learn about your team and their contribfutions. Typically, in large organizations with big/complex services or products, you typically work on a particular component (or stack of components), not the product as a whole. For me, it was also interesting to see what my team's contribution is and what value it offers to end users.

    To fully answer that question, I wrote a simple tutorial page that explains how end users use our team's product -- it helped a lot to understand what my changes will contribute to and I noticed that it has been particularly helpful for on-boarding new team members.
  • Define and align goals. Typically, after learning about the company and the teams' products and/or services, you will probably see opportunities. For example, you may notice that there is something that can be improved with technology that you are familiar with. It is good to remember them and work with the team to address them. Be proactive.
  • Keep learning. In addition to opportunities, you may also probably experience confusion or realize that there are things that you do not know yet. I always try to allocate time to learn new things, both technical (e.g. new programming languages, frameworks, technologies) and non-technical (e.g. communication, the domain). From my experience, in software engineering there is only one constant and that constant is change.
  • Try to be pragmatic. This is an important personal lesson for me: since you are working in a team and every person is different (different opinions and priorities), you must sometimes accept that you can not always (directly) accomplish everything you want and that things will work out the way you have intended.

What organizations can do


In addition to the things you can do as an individual, you also need support from the organization. I highly value the following traits:

  • Transparency. It is very useful to inform teams about the impact of the work they do: both positive and negative. I have seen in the past that it is quite tempting, for example, that after a failure things get covered up. I personally believe it is important for developers to know about the strengths and weaknesses of the software they work on, so that they can make meaningful contributions to make something a success.
  • Opportunities to get in touch with end users and other relevant stakeholders. In many organizations, developers are rarely in touch with end users and that is typically for a good reason: they should not get distracted from their work.

    Although I consider preventing distraction a good thing, I personally believe it would not be a bad thing to get in touch with end users sometimes: it gives a developer direct insights in how well the product works and what a customer needs or struggles with.

    At Mendix, we sometimes have clients that will visit us to explain what they do with Mendix and what kinds of challenges they face -- everybody in the company is invited and has the opportunity to ask questions. This will not happen on a very frequent basis, but having these sessions once in a while is something I consider to be very valuable.
  • Offer time to learn and explore. Typically to reach goals, quite frequently developers need to expand their skill set by e.g. by learning new technologies or simply explore the possibilities. They should be offered the time to do this.
  • Taking technical debt seriously. Technical debt, a phenomenon that hinders evolution of a software system, by postponing/skipping certain kind of work (i.e. taking shortcuts) that should have been done (e.g. testing, documentation, refactoring) should also be taken seriously.

    When a system has a lot of technical debt, making changes and improving quality can be enormously (and unnecessarily) difficult and time consuming. In extreme cases, even a subtle change takes too much time. As a result, it becomes quite easy to lose track of the original purpose and easily causes developers lose their motivation.
  • Taking feedback from developers seriously. Developers typically raise concerns (such as quality issues) that may not always look urgent -- as a result, it is very tempting for organizations to always give priority to new features than quality improvements. This may sometimes cause quality to degrate significantly over time.

    Whilst developers are typically not against developing new features, they are very concerned about poor quality and high technical debt. If the latter grows out of hand too much, it is quite easy for developers to lose track of the original purpose of the product or service and lose motivation.

Conclusion


In this blog post, I have shared my experiences with motivation in relation to purpose. Although it may sound conceptually simple, learning about the purpose of a product and service and aligning goals is actually quite difficult -- it requires you to learn about an organization, a product or service, the people in the company, your team, and about yourself. It typically is quite a journey with interesting and, sometimes, a few non-interesting steps.

Finally, purpose is not the only factor that motivates or demotivates me as a developer, but it is an important one.

A Nix-based functional organization for managing processes

$
0
0
The Nix expression language and the Nix packages repository follow a number of unorthodox, but simple conventions that provide all kinds of benefits, such as the ability to conveniently construct multiple variants of packages and store them safely in isolation without any conflicts.

The scope of the Nix package manager, however, is limited to package deployment only. Other tools in the Nix project extend deployment to other kinds of domains, such as machine level deployment (NixOS), networks of machines (NixOps) and service-oriented systems (Disnix).

In addition to packages, there is also a category of systems (such as systems following the microservices paradigm) that are composed of running processes.

Recently, I have been automating deployments of several kinds of systems that are composed of running processes and I have investigated how we can map the most common Nix packaging conventions to construct specifications that we can use to automate the deployment of these kinds of systems.

Some common Nix packaging conventions


The Nix package manager implements a so-called purely functional deployment model. In Nix, packages are constructed in the Nix expression language from pure functions in which side effects are eliminated as much as possible, such as undeclared dependencies residing in global directories, such as /lib and /bin.

The function parameters of a build function refer to all required inputs to construct the package, such as the build instructions, the source code, environment variables and all required build-time dependencies, such as compilers, build tools and libraries.

A big advantage of eliminating side effects (or more realistically: significantly reducing side effects) is to support reproducible deployment -- when building the same package with the same inputs on a different machine, we should get a (nearly) bit-identical result.

Strong reproducibility guarantees, for example, make it possible to optimize package deployments by only building a package from source code once and then downloading binary substitutes from remote servers that can be trusted.

In addition to the fact that packages are constructed by executing pure functions (with some caveats), the Nixpkgs repository -- that contains a large set of well known free and open source packages -- follows a number of conventions. One of such conventions is that most package build recipes reside in separate files and that each recipe declares a function.

An example of such a build recipe is:

{ stdenv, fetchurl, pkgconfig, glib, gpm, file, e2fsprogs
, perl, zip, unzip, gettext, libssh2, openssl}:

stdenv.mkDerivation rec {
pname = "mc";
version = "4.8.23";

src = fetchurl {
url = "http://www.midnight-commander.org/downloads/${pname}-${version}.tar.xz";
sha256 = "077z7phzq3m1sxyz7li77lyzv4rjmmh3wp2vy86pnc4387kpqzyx";
};

buildInputs = [
pkgconfig perl glib slang zip unzip file gettext libssh2 openssl
];

configureFlags = [ "--enable-vfs-smb" ];

meta = {
description = "File Manager and User Shell for the GNU Project";
homepage = http://www.midnight-commander.org;
maintainers = [ stdenv.lib.maintainers.sander ];
platforms = with stdenv.lib.platforms; linux ++ darwin;
};
}

The Nix expression shown above (pkgs/tools/misc/mc/default.nix) describes how to build the Midnight Commander from source code and its inputs:

  • The first line declares a function in which the function arguments refer to all dependencies required to build Midnight Commander: stdenv refers to an environment that provides standard UNIX utilities, such as cat and ls and basic build utilities, such as gcc and make. fetchurl is a utility function that can be used to download artifacts from remote locations and that can verify the integrity of the downloaded artifact.

    The remainder of the function arguments refer to packages that need to be provided as build-time dependencies, such as tools and libraries.
  • In the function body, we invoke the stdenv.mkDerivation function to construct a Nix package from source code.

    By default, if no build instructions are provided, it will automatically execute the standard GNU Autotools/GNU Make build procedure: ./configure; make; make install, automatically downloads and unpacks the tarball specified by the src parameter, and uses buildInputs to instruct the configure script to automatically find the dependencies it needs.

A function definition that describes a package build recipe is not very useful on its own -- to be able to build a package, it needs to be invoked with the appropriate parameters.

A Nix package is composed in a top-level Nix expression (pkgs/top-level/all-packages.nix) that declares one big data structure: an attribute set, in which every attribute name refers to a possible variant of a package (typically only one) and each value to a function invocation that builds the package, with the desired versions of variants of the dependencies that a package may need:

{ system ? builtins.currentSystem }:

rec {
stdenv = ...
fetchurl = ...
pkgconfig = ...
glib = ...

...

openssl = import ../development/libraries/openssl {
inherit stdenv fetchurl zlib ...;
};

mc = import ../tools/misc/mc {
inherit stdenv fetchurl pkgconfig glib gpm file e2fsprogs perl;
inherit zip unzip gettext libssh2 openssl;
};
}

The last attribute (mc) in the attribute set shown above, builds a specific variant of Midnight Commander, by passing the dependencies that it needs as parameters. It uses the inherit language construct to bind the parameters that are declared in the same lexical scope.

All the dependencies that Midnight Commander needs are declared in the same attribute set and composed in a similar way.

(As a sidenote: in the above example, we explicitly propagate all function parameters, which is quite verbose and tedious. In Nixpkgs, it is also possible to use a convenience function called: callPackage that will automatically pass the attributes with the same names as the function arguments as parameters.)

With the composition expression above and running the following command-line instruction:

$ nix-build all-packages.nix -A mc
/nix/store/wp3r8qv4k510...-mc-4.8.23

The Nix package manager will first deploy all build-time dependencies that Midnight Commander needs, and will then build Midnight Commander from source code. The build result is stored in the Nix store (/nix/store/...-mc-4.8.23), in which all build artifacts reside in isolation in their own directories.

We can start Midnight Commander by providing the full path to the mc executable:

$ /nix/store/wp3r8qv4k510...-mc-4.8.23/bin/mc

The prefix of every artifact in the Nix store is a SHA256 hash code derived from all inputs provided to the build function. The SHA256 hash prefix makes it possible to safely store multiple versions and variants of the same package next to each other, because they never share the same name.

If Nix happens to compute a SHA256 that is already in the Nix store, then the build result is exactly the same, preventing Nix from doing the same build again.

Because the Midnight Commander build recipe is a function, we can also adjust the function parameters to build different variants of the same package. For example, by changing the openssl parameter, we can build a Midnight Commander variant that uses a specific version of OpenSSL that is different than the default version:

{ system ? builtins.currentSystem }:

rec {
stdenv = ...
fetchurl = ...
pkgconfig = ...
glib = ...

...

openssl_1_1_0 = import ../development/libraries/openssl/1.1.0.nix {
inherit stdenv fetchurl zlib ...;
};

mc_alternative = import ../tools/misc/mc {
inherit stdenv fetchurl pkgconfig glib gpm file e2fsprogs perl;
inherit zip unzip gettext libssh2;
openssl = openssl_1_1_0; # Use a different OpenSSL version
};
}

We can build our alternative Midnight Commander variant as follows:

$ nix-build all-packages.nix -A mc_alternative
/nix/store/0g0wm23y85nc0y...-mc-4.8.23

As may be noticed, we get a different Nix store path, because we build Midnight Commander with different build inputs.

Although the purely functional model provides all kinds of nice benefits (such as reproducibility, the ability conveniently construct multiple variants of a package, and storing them in isolation without any conflicts), it also has a big inconvenience from a user point of view -- as a user, it is very impractical to remember the SHA256 hash prefixes of a package to start a program.

As a solution, Nix also makes it possible to construct user environments (probably better known as Nix profiles), by using the nix-env tool or using the buildEnv {} function in Nixpkgs.

User environments are symlink trees that blend the content of a set of packages into a single directory in the Nix store so that they can be accessed from one single location. By adding the bin/ sub folder of a user environment to the PATH environment variable, it becomes possible for a user to start a command-line executable without specifying a full path.

For example, with the nix-env tool we can install the Midnight Commander in a Nix profile:

$ nix-env -f all-packages.nix -iA mc

and then start it as follows:

$ mc

The above command works if the Nix profile is in the PATH environment variable of the user.

Mapping packaging conventions to process management


There are four important packaging conventions that the Nix package manager and the Nixpkgs repository follow that I want to emphasize:

  • Invoking the derivation function (typically through stdenv.mkDerivation or an abstraction built around it) builds a package from its build inputs.
  • Every package build recipe defines a function in which the function parameters refer to all possible build inputs. We can use this functions to compose all kinds of variants of a package.
  • Invoking a package build recipe function constructs a particular variant of a package and stores the result in the Nix store.
  • Nix profiles blend the content of a collection of packages into one directory and makes them accessible from a single location.

(As a sidenote: There is some discussion in the Nix community about these concepts. For example, one of the (self-)criticisms is that the Nix expression language, that is specifically designed as a DSL for package management, has no package concept in the language.

Despite this oddity, I personally think that functions are a simple and powerful concept. The only thing that is a bit of a poor decision is to call the mechanism that executes build is called: derivation which sounds a bit abstract).

Process management is quite different from package management -- we need to have an executable deployed first (typically done by a package manager, such as Nix), but in addition, we also need to manage the life-cycle of a process, such as starting and stopping it. These facilities are not Nix's responsibility. Instead, we need to work with a process manager that can facilitate these.

Furthermore, systems composed of running processes have a kind of dependency relationship that Nix does not manage -- they may also communicate with other processes (e.g. via a network connection or UNIX domain sockets).

As a consequence, they require the presence of other processes in order to work. This means that processes need to be activated in the right order or, alternatively, the communication between two dependent processes need to be queued until both are available.

If these dependency requirements are not met, then a system may not work. For example, a web application process is useless if the database backend is not available.

In order to fully automate the deployment of systems that are composed of running processes, we can do package management with Nix first and then we need to:

  • Integrate with a process manager, by generating artifacts that a process manager can work with, such as scripts and/or configuration files.
  • Make it possible to specify the process dependencies so that they can be managed (by a process manager or by other means) and activated in the right order.

Generating sysvinit scripts


There a variety of means to manage processes. A simple (and for today's standards maybe an old fashioned and perhaps controversial) way to manage processes is by using sysvinit scripts (also known as LSB Init compliant scripts).

A sysvinit script implements a set of activities and a standardized interface allowing us to manage the lifecycle of a specific process, or a group of processes.

For example, on a traditional Linux distribution, we can start a process, such as the Nginx web server, with the following command:

$ /etc/init.d/nginx start

and stop it as follows:

$ /etc/init.d/nginx stop

A sysvinit script is straight forward to implement and follows a number of conventions:

#!/bin/bash

## BEGIN INIT INFO
# Provides: nginx
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Should-Start: webapp
# Should-Stop: webapp
# Description: Nginx
## END INIT INFO

. /lib/lsb/init-functions

case "$1" in
start)
log_info_msg "Starting Nginx..."
mkdir -p /var/nginx/logs
start_daemon /usr/bin/nginx -c /etc/nginx.conf -p /var/nginx
evaluate_retval
;;

stop)
log_info_msg "Stopping Nginx..."
killproc /usr/bin/nginx
evaluate_retval
;;

reload)
log_info_msg "Reloading Nginx..."
killproc /usr/bin/nginx -HUP
evaluate_retval
;;

restart)
$0 stop
sleep 1
$0 start
;;

status)
statusproc /usr/bin/nginx
;;

*)
echo "Usage: $0 {start|stop|reload|restart|status}"
exit 1
;;
esac

  • A sysvinit script typically starts by providing some metadata, such a description, in which runlevels it needs to be started and stopped, and which dependencies the script has.

    In classic Linux distributions, meta information is typically ignored, but more sophisticated process managers, such as systemd, can use it to automatically configure the activation/deactivation ordering.
  • The body defines a case statement that executes a requested activity.
  • Activities use a special construct (in the example above it is: evaluate_retval) to display the status of an instruction, typically whether a process has started or stopped successfully or not, using appropriate colors (e.g. red in case of a failure, green in case of sucess).
  • sysvinit scripts typically define a number of commonly used activities: start starts a process, stop stops a process, reload sends a HUP signal to the process to let it reload its configuration (if applicable), restart restarts the process, status indicates the status, and there is a fallback activity that displays the usage to the end user to show which activities can be executed.

sysvinit scripts use number of utility functions that are defined by the Linux Standards Base (LSB):

  • start_daemon is a utility function that is typically used for starting a process. It has the expectation that the process daemonizes -- a process that daemonizes will fork another process that keeps running in the background and then terminates immediately.

    Controlling a daemonized processes is a bit tricky -- when spawning a process the shell can tell its process id (PID), so that it can be controlled, but it cannot tell you the PID of the process that gets daemonized by the invoked process, because that is beyond the shell's control.

    As a solution, most programs that daemonize will write a PID file (e.g. /var/run/nginx.pid) that can be used to determine the PID of the daemon so that it can be controlled.

    To do proper housekeeping, the start_daemon function will check whether such a PID file already exists, and will only start the process when it needs to.
  • Stopping a process, or sending it a different kind of signal, is typically done with the killproc function.

    This function will search for the corresponding PID file of the process (by default, a PID file that has the same name as the executable or a specified PID file) and uses the corresponding PID content to terminate the daemon. As a fallback, if no PID file exists, it will scan the entire process table and kills the process with the same name.
  • We can determine the status of a process (e.g. whether it is running or not), with the statusproc function that also consults the corresponding PID file or scans the process table if needed.

Most common system software have the ability to deamonize, such as nginx, the Apache HTTP server, MySQL and PostgreSQL. Unfortunately, application services (such as microservices) that are implemented with technologies such as Python, Node.js or Java Springboot do not have this ability out of the box.

Fortunately, we can use an external utility, such as libslack's daemon command, to let these foreground-only processes daemonize. Although it is possible to conveniently daemonize external processes, this functionality is not part of the LSB standard.

For example, using the following command to start the web application front-end process will automatically daemonize a foreground process, such as a simple Node.js web application, and creates a PID file so that it can be controlled by the sysvinit utility functions:

$ daemon -U -i /home/sander/webapp/app.js

In addition to manually starting and stopping sysvinit script, sysvinit scripts are also typically started on startup and stopped on shutdown, or when a user switches between runlevels. These processes are controlled by symlinks that reside in an rc.d directory that have specific prefixes:

/etc/
init.d/
webapp
nginx
rc0.d/
K98nginx -> ../init.d/nginx
K99webapp -> ../init.d/webapp
rc1.d/
K98nginx -> ../init.d/nginx
K99webapp -> ../init.d/webapp
rc2.d/
K98nginx -> ../init.d/nginx
K99webapp -> ../init.d/webapp
rc3.d/
S00webapp -> ../init.d/nginx
S01nginx -> ../init.d/webapp
rc4.d/
S00webapp -> ../init.d/nginx
S01nginx -> ../init.d/webapp
rc5.d/
S00webapp -> ../init.d/nginx
S01nginx -> ../init.d/webapp
rc6.d/
K98nginx -> ../init.d/nginx
K99webapp -> ../init.d/webapp

In the above directory listing, every rc?.d directory contains symlinks to scripts in the init.d directory.

The first character of each symlink file indicates whether an init.d script should be started (S) or stopped (K). The two numeric digits that follow indicate the order in which the scripts need to be started and stopped.

Each runlevel has a specific purpose as described in the LSB standard. In the above situation, when we boot the system in multi-user mode on the console (run level 3), first our Node.js web application will be started, followed by nginx. On a reboot (when we enter runlevel 6) nginx and then the web application will be stopped. Basically, the stop order is the reverse of the start order.

To convienently automate the deployment of sysvinit scripts, I have created a utility function called: createSystemVInitScript that makes it possible to generate sysvinit script with the Nix package manager.

We can create a Nix expression that generates a sysvinit script for nginx, such as:

{createSystemVInitScript, nginx}:

let
configFile = ./nginx.conf;
stateDir = "/var";
in
createSystemVInitScript {
name = "nginx";
description = "Nginx";
activities = {
start = ''
mkdir -p ${stateDir}/logs
log_info_msg "Starting Nginx..."
loadproc ${nginx}/bin/nginx -c ${configFile} -p ${stateDir}
evaluate_retval
'';
stop = ''
log_info_msg "Stopping Nginx..."
killproc ${nginx}/bin/nginx
evaluate_retval
'';
reload = ''
log_info_msg "Reloading Nginx..."
killproc ${nginx}/bin/nginx -HUP
evaluate_retval
'';
restart = ''
$0 stop
sleep 1
$0 start
'';
status = "statusproc ${nginx}/bin/nginx";
};
runlevels = [ 3 4 5 ];
}

The above expression defines a function in which the function parameters refer to all dependencies that we need to construct the sysvinit script to manage a nginx server: createSystemVInitScript is the utility function that create sysvinit scripts, nginx is the package that provides Nginx.

In the body, we invoke the: createSystemVInitScript to construct a sysvinit script:

  • The name corresponds to name of the sysvinit script and the description to the description displayed in the metadata header.
  • The activities parameter refers to an attribute set in which every name refers to an activity and every value to the shell commands that need to be executed for this activity.

    We can use this parameter to specify the start, stop, reload, restart and status activities for nginx. The function abstraction will automatically configure the fallback activity that displays the usage to the end-user including the activities that the script supports.
  • The runlevels parameter indicates in which runlevels the init.d script should be started. For these runlevels, the function will create start symlinks. An implication is that for the runlevels that are not specified (0, 1, 2, and 6) the script will automatically create stop symlinks.

As explained earlier, sysvinit script use conventions. One of such conventions is that most activities typically display a description, then execute a command, and finally display the status of that command, such as:

log_info_msg "Starting Nginx..."
loadproc ${nginx}/bin/nginx -c ${configFile} -p ${stateDir}
evaluate_retval

The createSystemVInit script also a notion of instructions, that are automatically translated into activities displaying task descriptions (derived from the general description) and the status. Using the instructions parameter allows us to simplify the above expression to:

{createSystemVInitScript, nginx}:

let
configFile = ./nginx.conf;
stateDir = "/var";
in
createSystemVInitScript {
name = "nginx";
description = "Nginx";
instructions = {
start = {
activity = "Starting";
instruction = ''
mkdir -p ${stateDir}/logs
loadproc ${nginx}/bin/nginx -c ${configFile} -p ${stateDir}
'';
};
stop = {
activity = "Stopping";
instruction = "killproc ${nginx}/bin/nginx";
};
reload = {
activity = "Reloading";
instruction = "killproc ${nginx}/bin/nginx -HUP";
};
};
activities = {
status = "statusproc ${nginx}/bin/nginx";
};
runlevels = [ 3 4 5 ];
}

In the above expression, the start, stop and reload activities have been simplified by defining them as instructions allowing us to write less repetitive boilerplate code.

We can reduce the amount of boilerplate code even further -- the kind of activities that we need to implement for managing process are typically mostly the same. When we want to manage a process, we typically want a start, stop, restart, status activity and, if applicable, a reload activity if a process knows how to handle the HUP signal.

Instead of speciying activities or instructions, tt is also possible to specify which process we want to manage, and what kind of parameters the process should take:

{createSystemVInitScript, nginx}:

let
configFile = ./nginx.conf;
stateDir = "/var";
in
createSystemVInitScript {
name = "nginx";
description = "Nginx";
initialize = ''
mkdir -p ${stateDir}/logs
'';
process = "${nginx}/bin/nginx";
args = [ "-c" configFile "-p" stateDir ];
runlevels = [ 3 4 5 ];
}

From the process and args parameters, the createSystemVInitScript automatically derives all relevant activities that we need to manage the process. It is also still possible to augment or override the generated activities by means of the instructions or activities parameters.

Besides processes that already have the ability to daemonize, it is also possible to automatically daemonize foreground processes with this function abstraction. This is particularly useful to generate a sysvinit script for the Node.js web application service, that lacks this ability:

{createSystemVInitScript}:

let
webapp = (import ./webapp {}).package;
in
createSystemVInitScript {
name = "webapp";
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = 5000;
};
}

In the above Nix expression, we set the parameter: processIsDaemon to false (the default value is: true) to indicate that the process is not a deamon, but a foreground process. The createSystemVInitScript function will generate a start activity that invokes the daemon command to daemonize it.

Another interesting feature is that we can specify process dependency relationships. For example, an nginx server can act as a reverse proxy for the Node.js web application.

To reliably activate the entire system, we must make sure that the web application process is deployed before Nginx is deployed. If we activate the system in the opposite order, then the reverse proxy may redirect users to an non-existent web application causing them to see 502 bad gateway errors.

We can use the dependency parameter with a reference to a sysvinit script to indicate that this sysvinit script has a dependency. For example, we can revise the Nginx sysvinit script expression as follows:

{createSystemVInitScript, nginx, webapp}:

let
configFile = ./nginx.conf;
stateDir = "/var";
in
createSystemVInitScript {
name = "nginx";
description = "Nginx";
initialize = ''
mkdir -p ${stateDir}/logs
'';
process = "${nginx}/bin/nginx";
args = [ "-c" configFile "-p" stateDir ];
runlevels = [ 3 4 5 ];
dependencies = [ webapp ];
}

In the above example, we pass the webapp sysvinit script as a dependency (through the dependencies parameter). Adding it as a dependency causes the generator to compute a start sequence number for the nginx script will be higher than the web app sysvinit script and stop sequence number lower than the web app script.

The different sequence numbers ensure that webapp is started before nginx starts, and that the nginx stops before the webapp stops.

Configuring managed processes


So far composing sysvinit scripts is still very to similar to composing ordinary Nix packages. We can also extend the four Nix packaging conventions described in the introduction to create a process management discipline.

Similar to the convention in which every package is in a separate file, and defines a function in which the function parameters refers to all package dependencies, we can extend this convention for processes to also include relevant parameters to configure a service.

For example, we can write a Nix expression for the web application process as follows:

{createSystemVInitScript, port ? 5000}:

let
webapp = (import /home/sander/webapp {}).package;
in
createSystemVInitScript {
name = "webapp";
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = port;
};
}

In the above expression, the port function parameter allows us to configure the TCP port where the web application listens to (and defaults to 5000).

We can also make the configuration of nginx configurable. For example, we can create a function abstraction that creates a configuration for nginx to let it act as a reverse proxy for the web application process shown earlier:

{createSystemVInitScript, stdenv, writeTextFile, nginx
, runtimeDir, stateDir, logDir, port ? 80, webapps ? []}:

let
nginxStateDir = "${stateDir}/nginx";
in
import ./nginx.nix {
inherit createSystemVInitScript nginx instanceSuffix;
stateDir = nginxStateDir;

dependencies = map (webapp: webapp.pkg) webapps;

configFile = writeTextFile {
name = "nginx.conf";
text = ''
error_log ${nginxStateDir}/logs/error.log;
pid ${runtimeDir}/nginx.pid;

events {
worker_connections 190000;
}

http {
${stdenv.lib.concatMapStrings (dependency: ''
upstream webapp${toString dependency.port} {
server localhost:${toString dependency.port};
}
'') webapps}

${stdenv.lib.concatMapStrings (dependency: ''
server {
listen ${toString port};
server_name ${dependency.dnsName};

location / {
proxy_pass http://webapp${toString dependency.port};
}
}
'') webapps}
}
'';
};
}

The above Nix expression's funtion header defines, in addition to the package dependencies, process configuration parameters that make it possible to configure the TCP port that Nginx listens to (port 80 by default) and to which web applications it should forward requests based on their virtual host property.

In the body, these properties are used to generate a nginx.conf file that defines virtualhosts for each web application process. It forwards incoming requests to the appropriate web application instance. To connect to a web application instance, it uses the port number that the webapp instance configuration provides.

Similar to ordinary Nix expressions, Nix expressions for processes also need to be composed, by passing the appropriate function parameters. This can be done in a process composition expression that has the following structure:

{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
}:

let
createSystemVInitScript = import ./create-sysvinit-script.nix {
inherit (pkgs) stdenv writeTextFile daemon;
inherit runtimeDir tmpDir;

createCredentials = import ./create-credentials.nix {
inherit (pkgs) stdenv;
};

initFunctions = import ./init-functions.nix {
basePackages = [
pkgs.coreutils
pkgs.gnused
pkgs.inetutils
pkgs.gnugrep
pkgs.sysvinit
];
inherit (pkgs) stdenv;
inherit runtimeDir;
};
};
in
rec {
webapp = rec {
port = 5000;
dnsName = "webapp.local";

pkg = import ./webapp.nix {
inherit createSystemVInitScript port;
};
};

nginxReverseProxy = rec {
port = 80;

pkg = import ./nginx-reverse-proxy.nix {
inherit createSystemVInitScript;
inherit stateDir logDir runtimeDir port;
inherit (pkgs) stdenv writeTextFile nginx;
webapps = [ webapp ];
};
};
}

The above expression (processes.nix) has the following structure:

  • The expression defines a function in which the function parameters allow common properties that apply to all processes to be configured: pkgs refers to the set of Nixpkgs that contains a big collection of free and open source packages, system refers to the system architecture to build packages for, and stateDir to the directory where processes should store their state (which is /var according to the LSB standard).

    The remaining parameters specify the runtime, log and temp directories that are typically sub directories in the state directory.
  • In the let block, we compose our createSystemVInitScript function using the relevant state directory parameters, base packages and utility functions.
  • In the body, we construct an attribute set in which every name represents a process name and every value an attribute set that contains process properties.
  • One reserved process property of a process attribute set is the pkg property that refers to a package providing the sysvinit script.
  • The remaining process properties can be freely chosen and can be consumed by any process that has a dependency on it.

    For example, the nginxReverseProxy service uses the port and dnsName properties of the webapp process to configure nginx to forward requests to the provided DNS host name (webapp.local) to the web application process listening on the specified TCP port (5000).

Using the above composition Nix expression for processes and the following command-line instruction, we can build the sysvinit script for the web application process:

$ nix-build processes.nix -A webapp

We can start the web application process by using the generated sysvinit script, as follows:

$ ./result/bin/etc/rc.d/init.d/webapp start

and stop it as follows:

$ ./result/bin/etc/rc.d/init.d/webapp stop

We can also build the nginx reverse proxy in a similar way, but to properly activate it, we must make sure that the webapp process is activated first.

To reliably manage a set of processes and activate them in the right order, we can also generate a Nix profile that contains all init.d scripts and rc.d symlinks for stopping and starting:

{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
}:

let
buildSystemVInitEnv = import ./build-sysvinit-env.nix {
inherit (pkgs) buildEnv;
};
in
buildSystemVInitEnv {
processes = import ./processes.nix {
inherit pkgs system;
};
}

The above expression imports the process composition shown earlier, and invokes the buildSystemVInitEnv to compose a Nix profile out of it. We can build this environment as follows:

$ nix-build profile.nix

Visually, the content of the Nix profile can presented as follows:


In the above diagram the ovals denote processes and the arrows denote process dependency relationships. The arrow indicates that the webapp process needs to be activated before the nginxReverseProxy.

We can use the system's rc script manage the starting and stopping the processes when runlevels are switched. Runlevel 0 and 6 make it possible to start the processes on startup and stopped on shutdown.

In addition to the system's rc script, we can also directly control the processes in a Nix profile -- I have created a utility script called: rcswitch that makes it possible to manually start all processes in a profile:

$ rcswitch ./result/etc/rc.d/rc3.d

we can also use the rcswitch command to do an upgrade from one set of processes to another:

$ rcswitch ./result/etc/rc.d/rc.3 ./oldresult/etc/rc.d/rc3.d

The above command checks which of the sysvinit scripts exist in both profiles and will only deactivate obsolete processes and activate new processes.

With the rcrunactivity command it is possible to run arbitrary activities on all processes in a profile. For example, the following command will show all statuses:

$ rcactivity status ./result/etc/rc.d/rc3.d

Deploying services as an unprivileged user


The process composition expression shown earlier is also a Nix function that takes various kinds of state properties as parameters.

By default, it has been configured in such a way that it facilitates production deployments. For example, it stores the state of all services in the global /var directory. Only the super user has the permissions to alter the structure of the global /var directory.

It is also possible to change these configuration parameters in such a way that it becomes possible as an unprivileged user to do process deployment.

For example, by changing the port number of the nginxReverseProxy process to a value higher than 1024, such as 8080 (an unprivileged user is not allowed to bind any services to ports below 1024), and changing the stateDir parameter to a directory in a user's home directory, we can deploy our web application service and Nginx reverse proxy as an unprivileged user:

$ nix-build processes.nix --argstr stateDir /home/sander/var \
-A nginxReverseProxy

By overriding the stateDir parameter, the resulting Nginx process has been configured to store all state in /home/sander/var as opposed to the global /var that cannot be modified by an unprivileged user.

As a unprivileged user, I should be able to start the Nginx reverse proxy as follows:

$ ./result/etc/rc.d/init.d/nginx start

The above Nginx instance can be reached by opening: http://localhost:8080 in a web browser.

Creating multiple process instances


So far, we have only been deploying single instances of processes. For the Nginx reverse proxy example, it may also be desired to deploy multiple instances of the webapp process so that we can manage forwardings for multiple virtual domains.

We can adjust the Nix expression for the webapp to make it possible to create multiple process instances:

{createSystemVInitScript}:
{port, instanceSuffix ? ""}:

let
webapp = (import ./webapp {}).package;
instanceName = "webapp${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
inherit instanceName;
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = port;
};
}

The above Nix expression is a modified webapp build recipe that facilitates instantiation:

  • We have split the Nix expression into two nested functions. The first line: the outer function header defines all dependencies and configurable properties that apply to all services instances.
  • The inner function header allows all instance specific properties to be configured so that multiple instances can co-exist. An example of such a property is the port parameter -- only one service can bind to a specific TCP port. Configuring an instance to bind to different port allows two instances co-exist.

    The instanceSuffix parameter makes it possible to give each webapp process a unique name (e.g. by providing a numeric value).

    From the package name and instance suffix a unique instanceName is composed. Propagating the instanceName to the createSystemVInitScript function instructs the daemon command to create a unique PID file (not a PID file that corresponds to the executable name) for each daemon process so that multiple instances can be controlled independently.

Although this may sound as a very uncommon use case, it is also possible to change the Nix expression for the Nginx reverse proxy to support multiple instances.

Typically, for system services, such as web servers and database servers, it is very uncommon to run multiple instances at the same time. Despite the fact that it is uncommon, it is actually possible and quite useful for development and/or experimentation purposes:

{ createSystemVInitScript, stdenv, writeTextFile, nginx
, runtimeDir, stateDir, logDir}:

{port ? 80, webapps ? [], instanceSuffix ? ""}:

let
instanceName = "nginx${instanceSuffix}";
nginxStateDir = "${stateDir}/${instanceName}";
in
import ./nginx.nix {
inherit createSystemVInitScript nginx instanceSuffix;
stateDir = nginxStateDir;

dependencies = map (webapp: webapp.pkg) webapps;

configFile = writeTextFile {
name = "nginx.conf";
text = ''
error_log ${nginxStateDir}/logs/error.log;
pid ${runtimeDir}/${instanceName}.pid;

events {
worker_connections 190000;
}

http {
${stdenv.lib.concatMapStrings (dependency: ''
upstream webapp${toString dependency.port} {
server localhost:${toString dependency.port};
}
'') webapps}

${stdenv.lib.concatMapStrings (dependency: ''
server {
listen ${toString port};
server_name ${dependency.dnsName};

location / {
proxy_pass http://webapp${toString dependency.port};
}
}
'') webapps}
}
'';
};
}

The code fragment above shows a revised Nginx expression that supports instantiation:

  • Again, the Nix expression defines a nested function in which the outer function header refers to configuration properties for all services, whereas the inner function header refers to all conflicting parameters that need to be changed so that multiple instances can co-exist.
  • The port parameter allows the TCP port where Nginx bind to be configured. To have two instances co-existing they both need to bind to unreserved ports.
  • As with the previous example, the instanceSuffix parameter makes it possible to compose unique names for each Nginx instance. The instanceName variable that is composed from it, is used to create and configure a dedicate state directory, and a unique PID file that does not conflict with other Nginx instances.

With this new convention of nested functions for instantiatable services means that we have to compose these expressions twice. First, we need to pass all parameters that configure properties that apply to all service instances. This can be done in a Nix expression that has the following structure:

{ pkgs
, system
, stateDir
, logDir
, runtimeDir
, tmpDir
}:

let
createSystemVInitScript = import ./create-sysvinit-script.nix {
inherit (pkgs) stdenv writeTextFile daemon;
inherit runtimeDir tmpDir;

createCredentials = import ./create-credentials.nix {
inherit (pkgs) stdenv;
};

initFunctions = import ./init-functions.nix {
basePackages = [
pkgs.coreutils
pkgs.gnused
pkgs.inetutils
pkgs.gnugrep
pkgs.sysvinit
];
inherit (pkgs) stdenv;
inherit runtimeDir;
};
};
in
{
webapp = import ./webapp.nix {
inherit createSystemVInitScript;
};

nginxReverseProxy = import ./nginx-reverse-proxy.nix {
inherit createSystemVInitScript stateDir logDir runtimeDir;
inherit (pkgs) stdenv writeTextFile nginx;
};
}

The above Nix expression is something we could call a constructors expression (constructors.nix) that returns an attribute set in which each member refers to a function that allows us to compose a specific process instance.

By using the constructors expression shown above, we can create a processes composition expression that works with multiple instances:

{ pkgs ? import  { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/home/sbu"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
}:

let
constructors = import ./constructors.nix {
inherit pkgs system stateDir runtimeDir logDir tmpDir;
};
in
rec {
webapp1 = rec {
port = 5000;
dnsName = "webapp1.local";

pkg = constructors.webapp {
inherit port;
instanceSuffix = "1";
};
};

webapp2 = rec {
port = 5001;
dnsName = "webapp2.local";

pkg = constructors.webapp {
inherit port;
instanceSuffix = "2";
};
};

webapp3 = rec {
port = 5002;
dnsName = "webapp3.local";

pkg = constructors.webapp {
inherit port;
instanceSuffix = "3";
};
};

webapp4 = rec {
port = 5003;
dnsName = "webapp4.local";

pkg = constructors.webapp {
inherit port;
instanceSuffix = "4";
};
};

nginxReverseProxy = rec {
port = 8080;

pkg = constructors.nginxReverseProxy {
webapps = [ webapp1 webapp2 webapp3 webapp4 ];
inherit port;
};
};

webapp5 = rec {
port = 6002;
dnsName = "webapp5.local";

pkg = constructors.webapp {
inherit port;
instanceSuffix = "5";
};
};

webapp6 = rec {
port = 6003;
dnsName = "webapp6.local";

pkg = constructors.webapp {
inherit port;
instanceSuffix = "6";
};
};

nginxReverseProxy2 = rec {
port = 8081;

pkg = constructors.nginxReverseProxy {
webapps = [ webapp5 webapp6 ];
inherit port;
instanceSuffix = "2";
};
};
}

In the above expression, we import the constructors expression, as shown earlier. In the body, we construct multiple instances of these processes by using the constructors functions:

  • We compose six web application instances (webapp1, webapp2, ..., webapp6), each of them listening on a unique TCP port.
  • We compose two Nginx instances (nginxReverseProxy, nginxReverseProxy2). The first instance listens on TCP port 8080 and redirects the user to any of the first three web application processes, based on the virtual host name. The other Nginx instance listens on TCP port 8081, redirecting the user to the remaining web apps based on the virtual host name.

We can represent the above composition expression visually, as follows:


As with the previous examples, we can deploy each process instance individually:

$ nix-build processes.nix -A webapp3
$ ./result/etc/rc.d/init.d/webapp3 start

Or the the whole set as a Nix profile:

$ nix-build profile.nix
$ rcswitch ./result/etc/rc.d/rc3.d

Again, the rcswitch command will make sure that all processes are activated in the right order. This means that the webapp processes are activated first, followed by the Nginx reverse proxies.

Managing user accounts/state with Dysnomia


Most of the deployment of the processes can be automated in a stateless way -- Nix can deploy the executable as a Nix package and the sysvinit script can manage the lifecycle.

There is another concern, that we may also want to address. Typically, it is not recommended to run processes as a root user, such as essential system services, for security and safety reasons.

In order to run a process as an unprivileged user, an unprivileged group and user account must be creaed first by some means. Furthermore, when undeploying a process, we probably also want to remove the dedicated user and group.

User account management is a feature that the Nix package manager does not support -- Nix only works with files stored in the Nix store and cannot/will not (by design) change any files on the host system, such as /etc/passwd where the user accounts are stored.

I have created a deployment tool for state management (Dysnomia) that can be used for this purpose. It facilitates a plugin system that can manage deployment activities for components that Nix does not support: activating, deactivating, taking snapshots, restoring snapshots etc.

I have created a Dysnomia plugin called: sysvinit-script that can activate or deactivate a process by invoking a sysvinit script, and it can also create or discard users and groups from a declarative configuration file that is included with a sysvinit script.

We can revise a process Nix expression to start a process as an unprivileged user:

{createSystemVInitScript}:
{port, instanceSuffix ? ""}:

let
webapp = (import ./webapp {}).package;
instanceName = "webapp${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
inherit instanceName;
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = port;
};
user = instanceName;

credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
}

The above Nix expression is a revised webapp Nix expression that facilitates user switching:

  • The user parameter specifies that we want to run the process as an unprivileged user. Because this process can also be instantiated, we have to make sure that it gets a unique name. To facilitate that, we create a user with the same username as the instance name.
  • The credentials parameter refers to a specification that instructs the sysvinit-script Dysnomia plugin to create an unprivileged user and group on activation, and discard them on deactivation.

For production purposes (e.g. when we deploy processes as the root user), switching to unprivileged users is useful, but for development purposes, such as running a set of processes as an unprivileged user, we cannot switch users because we may not have the permissions to do so.

For convenience purposes, it is also possible to globally disable user switching, which we can do as follows:

{ pkgs
, stateDir
, logDir
, runtimeDir
, tmpDir
, forceDisableUserChange
}:

let
createSystemVInitScript = import ./create-sysvinit-script.nix {
inherit (pkgs) stdenv writeTextFile daemon;
inherit runtimeDir tmpDir forceDisableUserChange;

createCredentials = import ./create-credentials.nix {
inherit (pkgs) stdenv;
};

initFunctions = import ./init-functions.nix {
basePackages = [
pkgs.coreutils
pkgs.gnused
pkgs.inetutils
pkgs.gnugrep
pkgs.sysvinit
];
inherit (pkgs) stdenv;
inherit runtimeDir;
};
};
in
{
...
}

In the above example, the forceDisableUserChange parameter can be used to globally disable user switching for all sysvinit scripts composed in the expression. It invokes a feature of the createSystemVInitScript to ignore any user settings that might have been propagated to it.

With the following command we can deploy a process that does not switch users, despite having user settings configured in the process Nix expressions:

$ nix-build processes.nix --arg forceDisableUserChange true

Distributed process deployment with Disnix


As explained earlier, I have adopted four common Nix package conventions and extended them suit the needs of process management.

This is not the only solution that I have implemented that builds on these four conventions -- the other solution is Disnix, that extends Nix's packaging principles to (distributed) service-oriented systems.

Disnix extends Nix expressions for ordinary packages with another category of dependencies: inter-dependencies that model dependencies on services that may have been deployed to remote machines in a network and require a network connection to work.

In Disnix, a service expression is a nested function in which the outer function header specificies all intra-dependencies (local dependencies, such as build tools and libraries), and the inner function header refers to inter-dependencies.

It is also possible to combine the concepts of process deployment described in this blog post with the service-oriented system concepts of Disnix, such as inter-dependencies -- the example with Nginx reverse proxies and web application processes can also be extended to work in a network of machines.

Besides deploying a set processes (that may have dependencies on each other) to a single machine, it is also possible to deploy the web application processes to different machines in the network than the machine where the Nginx reverse proxy is deployed to.

We can configure the reverse proxy in such a way that it will forward requests to the machine where the web application processes may have been deployed to.

{ createSystemVInitScript, stdenv, writeTextFile, nginx
, runtimeDir, stateDir, logDir
}:

{port ? 80, instanceSuffix ? ""}:

interDeps:

let
instanceName = "nginx${instanceSuffix}";
nginxStateDir = "${stateDir}/${instanceName}";
in
import ./nginx.nix {
inherit createSystemVInitScript nginx instanceSuffix;
stateDir = nginxStateDir;

dependencies = map (dependencyName:
let
dependency = builtins.getAttr dependencyName interDeps;
in
dependency.pkg
) dependencies;

configFile = writeTextFile {
name = "nginx.conf";
text = ''
error_log ${nginxStateDir}/logs/error.log;
pid ${runtimeDir}/${instanceName}.pid;

events {
worker_connections 190000;
}

http {
${stdenv.lib.concatMapStrings (dependencyName:
let
dependency = builtins.getAttr dependencyName interDeps;
in
''
upstream webapp${toString dependency.port} {
server ${dependency.target.properties.hostname}:${toString dependency.port};
}
'') (builtins.attrNames interDeps)}

${stdenv.lib.concatMapStrings (dependencyName:
let
dependency = builtins.getAttr dependencyName interDeps;
in
''
server {
listen ${toString port};
server_name ${dependency.dnsName};

location / {
proxy_pass http://webapp${toString dependency.port};
}
}
'') (builtins.attrNames interDeps)}
}
'';
};
}

The above Nix expression is a revised Nginx configuration that also works with inter-dependencies:

  • The above Nix expression defines three nested functions. The purpose of the outermost function (the first line) is to configure all local dependencies that are common to all process instances. The middle function defines all process instance parameters that are potentially conflicting and need to be configurd with unique values so that multiple instances can co-exist. The third (inner-most) function refers to the inter-dependencies of this process: services that may reside on a different machine in the network and need to be reached with a network connection.
  • The inter-dependency function header (interDeps:) takes an arbitrary number of dependencies. These inter-dependencies refer to all web application process instances that the Nginx reverse proxy should redirect to.
  • In the body, we generate an nginx.conf that uses the inter-dependencies to set up the forwardings.

    Compared to the previous Nginx reverse proxy example, it will use the dependency.target.properties.hostname property that refers to the hostname of the machine where the web application process is deployed to instead of a forwarding to localhost. This makes it possible to connect to a web application process that may have been deployed to another machine.
  • The inter-dependencies are also passed to the dependencies function parameter of the Nginx function. This will ensure that if Nginx and a web application process are distributed to the same machine by Disnix, they will also get activated in the right order by the system's rc script on startup.

A with the previous examples, we need to compose the above Disnix expression multiple times. The composition of the constructors can be done in the constructors expression (as shown in the previous examples).

The processes' instance dependencies and inter-dependencies can be configured in the Disnix services model, that shares many similarities with process composition expression, shown earlier. As a matter of fact, a Disnix services model is a superset of it:

{ pkgs, distribution, invDistribution, system
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? true
}:

let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir;
inherit forceDisableUserChange;
};
in
rec {
webapp = rec {
name = "webapp";
port = 5000;
dnsName = "webapp.local";
pkg = constructors.webapp {
inherit port;
};
type = "sysvinit-script";
};

nginxReverseProxy = rec {
name = "nginxReverseProxy";
port = 8080;
pkg = constructors.nginxReverseProxy {
inherit port;
};
dependsOn = {
inherit webapp;
};
type = "sysvinit-script";
};
}

The above Disnix services model defines two services (representing processes) that have an inter-dependency on each other, as specified with the dependsOn parameter property of each service.

The sysvinit-scripttype property instructs Disnix to deploy the services as processes managed by a sysvinit script. In a Disnix-context, services have no specific form or meaning, and can basically represent anything. The type property is used to tell Disnix with what kind of service we are dealing with.

To properly configure remote dependencies we also need to know the target machines where we can deploy to and what their properties are. This is where we can use an infrastructure model for.

For example, a simple infrastructure model of two machines could be:

{
test1.properties.hostname = "test1";
test2.properties.hostname = "test2";
}

We must also tell Disnix to which target machines we want to distribute the services. This can be done in a distribution model:

{infrastructure}:

{
webapp = [ infrastructure.test1 ];
nginxReverseProxy = [ infrastructure.test2 ];
}

In the above distribution model we distribute the webapp process to the first target machine and the nginxReverseProxy to the second machine. Because both services are deployed to different machines in the network, the nginxReverseProxy uses a network link to forward incoming requests to the web application.

By running the following command-line instruction:

$ disnix-env -s services.nix -i infrastructure.nix -d distribution.nix

Disnix will deploy the processes to the target machines defined in the distribution model.

The result is the following deployment architecture:


As may be noticed by looking at the above diagram, the process dependency manifest itself as a network link managed as an inter-dependency by Disnix.

Conclusion


In this blog post, I have described a Nix-based functional organization for managing processes based on four simple Nix packaging conventions. This approach offers the following benefits:

  • Integration with many process managers that manage the lifecycle of a process (in this particular blog post: using sysvinit scripts).
  • The ability to relocate state to other locations, which is useful to facilitate unprivileged user deployments.
  • The ability to create multiple instances of processes, by making conflicting properties configurable.
  • Disabling user switching, which is useful to facilitate unprivileged user deployments.
  • It can be used on any Linux system that has the Nix package manager installed. It can be used on NixOS, but NixOS is not a requirement.

Related work


Integrating process management with Nix package deployment is not a new subject, nor something that is done for the first time.

Many years ago, there was the "trace" Subversion repository (that was named after the research project TraCE: Transparent Configuration Environments funded by NWO/Jacquard), the repository in which all Nix-related development was done before the transition was made to Github (before 2012).

In the trace repository, there was also a services project that could be used to generate sysvinit-like scripts that could be used on any Linux distribution, and several non-Linux systems as well, such as FreeBSD.

Eelco Dolstra's PhD thesis Chapter 9 describes a distributed deployment prototype that extends the init script approach to networks of machines. The prototype facilitates the distribution of init scripts to remote machines and also facilitates heterogeneous operating systems deployment -- an init script can be built for multiple operating systems, such as Linux and FreeBSD.

Although the prototype shares some concepts with Disnix and the process management described in this blog post support, it also lacks many features -- it has no notion of process dependencies, inter-dependencies, the ability to separate services/processes and infrastructure, and to specify distribution mappings between process and target machines including the deployment of redundant instances.

Originally, NixOS used to work with the generated scripts from services sub project in the trace repository, but quite quickly adopted Upstart as its init system. Gradually, the init scripts and upstart jobs got integrated, and eventually replaced by Upstart jobs completely. As a result, it was no longer possible to run services independently of NixOS.

NixOS is a Linux distribution whose static aspects are fully managed by Nix, including user packages, configuration files, the Linux kernel, and kernel modules. NixOS machine configurations are deployed from a single declarative specification.

Although NixOS is an extension of Nix deployment principles to machine-level deployment, a major conceptual difference between NixOS and the Nix packages repository is that NixOS generates a big data structure made out of all potential configuration options that NixOS provides. It uses this (very big) generated data structure as an input for an activation script that will initialize all dynamic system parts, such as populating the state directories (e.g. /var) and loading systemd jobs.

In early incarnations of NixOS, the organization of the repository was quite monolithic -- there was one NixOS file that defines all configuration options for all possible system configuration aspetcts, one file that defines the all the system user accounts, one file that defines all global configuration files in /etc. When it was desired to add a new system service, all these global configuration files need to be modified.

Some time later (mid 2009), the NixOS module system was introduced that makes it possible to isolate all related configuration aspects of, for example, a system service into a separate module. Despite the fact that configuration aspects are isolated, the NixOS module system has the ability (through a concept called fixed points) refer to properties of the entire configuration. The NixOS module system merges all configuration aspects of all modules into a single configuration data structure.

The NixOS module system is quite powerful. In many ways, it is much more powerful than the process management approach described in this blog post. The NixOS module system allows you to refer, override and adjust any system configuration aspect in any module.

For example, a system service, such as the OpenSSH server, can automatically configure the firewall module in such a way that it will open the SSH port (port 22). With the functional approach described in this blog post, everything has to be made explicit and must be propagated through function arguments. This is probably more memory efficient, but a lot less flexible, and more tedious to write.

There are also certain things that NixOS and the NixOS module system cannot do. For example, with NixOS, it is not possible to create multiple instances of system services which the process management conventions described in this blog post can.

NixOS has another drawback -- evaluating system configurations requires all possible NixOS configuration options to be evaluated. There are actually quite a few of of them.

As a result, evaluating a NixOS configuration is slow and quite memory consuming. For single systems, this is typically not a big problem, but for networked NixOS/NixOps configurations, this may be a problem -- for example, I have an old laptop with 4 GiB of RAM that can no longer deploy a test network of three VirtualBox machines using the latest stable NixOS release (19.09), because the Nix evaluator runs out of memory.

Furthermore, NixOS system services can only be used when you install NixOS as your system's software distribution. It is currently not possible to install Nix on a conventional Linux distribution and use NixOS' system services (systemd services) independently of the entire operating system.

The lack of being able to deploy system services independently is not a limitation of the NixOS module system -- there is also an external project called nix-darwin that uses the NixOS module system to generate launchd services, that can be run on top of macOS.

The idea to have a separate function header for creating instances of processes is also not entirely new -- a couple of years ago I have revised the internal deployment model of Disnix to support multiple container instances.

In a Disnix-context, containers can represent anything that can host multiple service instances, such as a process manager, application container, or database management system. I was already using the convention to have a separate function header that makes it possible to create multiple instances of services. In this blog post, I have extended this formalism specifically for managing processes.

Discussion


In this blog post, I have picked sysvinit scripts for process management. The reason why I have picked an old-fashioned solution is not that I consider this to be the best process management facility, or that systemd, the init system that NixOS uses, is a bad solution.

My first reason to choose sysvinit scripts is because it is more universally supported than systemd.

The second reason is that I want to emphasize the value that a functional organization can provide, independent of the process management solution.

Using sysvinit scripts for managing process have all kinds of drawbacks and IMO there is a legitimate reason why alternatives exist, such as systemd (but also other solutions).

For example, controlling daemonized processes is difficult and fragile -- the convention that daemons should follow is to create PID files, but it is not a hard guarantee daemons will comply and that nothing will go wrong. As a result, a daemonized process may escape control of the process manager. systemd, for example, puts all processes that it needs to control in a cgroup and as a result, cannot escape systemd's control.

Furthermore, you may also want to use the more advanced features of the Linux kernel, such as namespaces and cgroups to prevent process from interfering with other processes on the system and the available system resources that a system provides. Namespaces and cgroups are a first class feature in systemd.

If you do not like sysvinit scripts: the functional organization described in this blog post is actually not specifically designed for sysvinit -- it is actually process manager agnostic. I have also implemented a function called: createSystemdService that makes it possible to construct systemd services.

The following Nix expression composes a systemd service for the web application process, shown earlier:


{stdenv, createSystemdService}:
{port, instanceSuffix ? ""}:

let
webapp = (import ./webapp {}).package;
instanceName = "webapp${instanceSuffix}";
in
createSystemdService {
name = instanceName;

environment = {
PORT = port;
};

Unit = {
Description = "Example web application";
Documentation = http://example.com;
};

Service = {
ExecStart = "${webapp}/lib/node_modules/webapp/app.js";
};
}

I also tried supervisord -- we can write the following Nix expression to compose a supervisord program configuration file of the web application process:


{stdenv, createSupervisordProgram}:
{port, instanceSuffix ? ""}:

let
webapp = (import ./webapp {}).package;
instanceName = "webapp${instanceSuffix}";
in
createSupervisordProgram {
name = instanceName;

command = "${webapp}/lib/node_modules/webapp/app.js";
environment = {
PORT = port;
};
}

Switching process managers retains our ability to benefit from the facilities that the functional configuration framework provides -- we can use it manage process dependencies, configure state directories, disable user management and when we use Disnix: manage inter-dependencies and bind it to services that are not processes.

Despite the fact that sysvinit scripts are primitive, there are also two advantages that I see over more "modern alternatives", such as systemd:

  • Systemd and supervisord require the presence of a deamon that manages processes (i.e. the systemd and supervisord deamons). sysvinit scripts are self-contained from a process management perspective -- the Nix package manager provides the package dependencies that the sysvinit scripts needs (e.g. basic shell utilities, sysvinit commands), but other than that, it does not require anything else.
  • We can also easily deploy sysvinit scripts to any Linux distribution that has the Nix package manager installed. There are no additional requirements. Systemd services, for example, require the presence of the systemd daemon. Furthermore, we also have to interfere with the host system's systemd service that may also be used to manage essential system services.
  • We can also easily use sysvinit scripts to deploy processes as an unprivileged user to a machine that has a single-user Nix installation -- the sysvinit script infrastructure does not require any tools or daemons that require super user privileges.

Acknowledgements


I have borrowed the init-functions script from the LFS Bootscripts package of the Linux from Scratch project to get an implementation of the utility functions that the LSB standard describes.

Availability and future work


The functionality described in this blog post is still a work in progress and only a first milestone in a bigger objective.

The sysvinit functionality resides in an experimental branch of the Nix low-level experiments repository. The sysvinit-script Dysnomia plugin resides in an experimental branch of the Dysnomia repository.

In the next blog post, I will introduce another interesting concept that we can integrate into the functional process management framework.

Viewing all 159 articles
Browse latest View live