CFEngine 3.3.0 Release Notes

April 11, 2012

Update [April 12, 2012]: Added information about the new templating engine.

Highlights

The CFEngine Community 3.3.0 release is here, with the largest set of improvements since the introduction of version 3.0! We have added Virtualization and SQL promises to the open source edition and introduced service-promises for Unix. A new templating engine is in place. Some potential “gotcha” issues have been fixed, to streamline and simplify the use of CFEngine. The embedded database code has been significantly refactored, optimized and made much more robust. A large number of useful variables, classes and functions have been introduced. And as usual, a set of bugs have been fixed.

We will look more in-depth into the following:

  • Unix service promises
  • New templating engine
  • Virtualization promises
  • SQL-promises
  • User-experience enhancements
  • New classes, functions and variables
  • Improvements to the embedded-db code

Download now ยป

Unix service promises

One standard service bundle

A very powerful and convenient promise type called services has now been implemented for Unix users. Have a look at the following CFEngine snippet.

bundle agent service_catalogue
{
services:
  "syslog"  service_policy => "start";
  "www"     service_policy => "stop";
}

This will ensure the syslog service is running and that the web service is not running. At this point you are probably a bit suspicious on how CFEngine can know how to manage the syslog and web service in your specific environment. The secret lies in CFEngine’s powerful abstraction capabilities.

To manage a Unix service, there are three things CFEngine needs to know: the process name, the start command and the stop command. If you do not give CFEngine any additional information about this for your service, it will look for a reserved bundle to describe this: standard_services.

For our example above, it looks like the following.

bundle agent standard_services(service, state)
{
vars:

 debian::

  "startcommand[www]"     string => "/etc/init.d/apache2 start";
  "stopcommand[www]"      string => "/etc/init.d/apache2 stop";
  "processname[www]"      string => "apache2";

  "startcommand[syslog]"  string => "/etc/init.d/rsyslog start";
  "stopcommand[syslog]"   string => "/etc/init.d/rsyslog stop";
  "processname[syslog]"   string => "rsyslogd";


classes:

  "start" expression => strcmp("start","$(state)");
  "stop"  expression => strcmp("stop","$(state)");

processes:

  start::

    ".*$(processname[$(service)]).*"

             comment => "Verify that the service appears in the process table",
       restart_class => "restart_$(service)";

  stop::

    ".*$(processname[$(service)]).*"

            comment => "Verify that the service does not appear in the process table",
       process_stop => "$(stopcommand[$(service)])",
            signals => { "term", "kill"};

  commands:

   "$(startcommand[$(service)])"

            comment => "Execute command to restart the $(service) service",
         ifvarclass => "restart_$(service)";
}

Let us have a look at what is going on here. As you can see, the standard_services bundle takes two parameters: the service name and state. If you look at our service_catalogue bundle above, you can see that the promiser is the service name and the service_policy attribute is passed as the state parameter.

The standard_services bundle defines the three required strings for each service for each platform: the start and stop commands, and the process name. If we are to support OpenSUSE for the www service, we would need to add three new strings since the process and executable is called “httpd” here. Thus it is very easy to extend the support for new platforms, as well as new services. The complete policy is available here for download here.

You may also include other promises in the standard_services bundle, such as promises for installing packages and service-specific configuration. However, in these more complex scenarios, you probably want to create a separate bundle for each service. We will consider this next.

Separate bundle for each service

Let us modify our policy slightly to the following.

bundle agent service_catalogue_separate   {
services:
  "foo" service_policy => "start",
        service_method => service_bundle_separate;

  "bar" service_policy => "stop",
        service_method => service_bundle_separate;
}

body service_method service_bundle_separate
{
service_bundle => $(this.promiser)("$(this.service_policy)");
}


bundle agent foo(service_policy)
{
reports:
cfengine_3::
  "we need to ensure $(service_policy) of foo";
}

bundle agent bar(service_policy)
{
reports:
cfengine_3::
  "we need to ensure $(service_policy) of bar";
}

From the above, you can see the service_method body that is the glue to what we are doing. In it, the service_bundle attribute defines which bundle should be called. In our previous example, we did not define this, so the service attribute would default to standard_services.

Now, however, we are making the service_bundle expand to the current promiser, which in our example is “foo” and “bar”. These are now the bundles that are in charge of handling their respective services. Running the above policy would make the agent produce the following.

$ cf-agent -Kf ./service_catalogue_separate.cf
R: we need to ensure start of foo
R: we need to ensure stop of bar

Now we just need to tell CFEngine how to work with foo and bar.

At this stage, I hope you agree that services: promises will simplify and clarify your policy significantly. As always, input and feedback is much appreciated.

New templating engine

Another very convenient new feature in CFEngine 3.3.0 is the existence of a new templating engine, with extensive new capabilities in addition to scalar expansion, which was available in previous versions.

As an example, consider the following bundle:

bundle agent test
{
  vars:
    "list" slist => { "1", "2", "3"};

  files:
    "/tmp/expander"
      create => "true",
      edit_template => "$(sys.workdir)/templates/input.txt";
}

And the following template file:

#This is a template file /templates/input.txt

These lines apply to anyone

[%CFEngine solaris.Monday:: %]
Everything here applies only to solaris on Mondays
until overridden...

[%CFEngine linux:: %]
Everything after here now applies now to linux only.

[%CFEngine BEGIN %]
This is a block of text
That contains list variables: $(test.list)
With text before and after.
[%CFEngine END %]

nameserver $(test.list)

The lines that start with [%CFEngine indicate directives for the template engine. There are two types of directives:

[%CFEngine classexpression:: %] indicates that the following lines (until the end of the file, or the next class expression) will be included in the output only when the `classexpression` is true.

[%CFEngine BEGIN/END %] denotes a block of lines that should be handled as a single unit, particularly for the purposes of list expansion.

In the new template engine, list expansion works just like in the CFEngine policy language: if you reference a list variable as a scalar (as in $(test.list) in the example), the line or block of text in which it is included will be repeated for every value of the list.

Virtualization promises

Another new promise type, guest_environments, is introduced in CFEngine Community 3.3.0. On systems with a hypervisor running, this allows you to make promises about virtual machine guests. Thanks to interfacing with the libvirt library, a range of hypervisors are supported, including KVM, Xen, VMware and more. CFEngine supports creating, starting, suspending, stopping, and deleting virtual guest machines. Currently, CFEngine’s cf-agent must be running on the hypervisor of the guest machine to manage it.

We will go through a couple of examples for managing the life-cycle of a virtual machine, using KVM/QEMU as hypervisor. A complete self-contained policy with all the examples below is available here.

Creating a virtual machine

bundle agent kvm_create
{

guest_environments:
 "my_host_machine"
   environment_resources => mykvm("my_host_machine", "x86_64", "1", "1048576", "/var/lib/libvirt/images/ubuntu104-64-clone.img"),
   environment_type      => "kvm",
   environment_state     => "create",
   environment_host      => "ubuntu";
}

$ cf-agent -Kvf ./guest_environment_kvm.cf
...
core>  -> Selecting environment type "kvm" -> "qemu:///session"
core>  -> Found 0 running guest environments on this host (including enclosure)
core>  -> Found 6 dormant guest environments on this host
core>  ---> Found a suspended, domain environment called "centos62-64"
core>  ---> Found a suspended, domain environment called "rhel55-64"
core>  ---> Found a suspended, domain environment called "ubuntu104-64"
core>  ---> Found a suspended, domain environment called "win2003-32"
core>  ---> Found a suspended, domain environment called "win2008-64"
core>  ---> Found a suspended, domain environment called "ubuntu104-64-2"
core>  -> Created a virtual domain "my_host_machine"
...

In this example, we are passing the machine name, architecture, cpu count, memory size and disk image to the resource-body. The resource body takes care of inputting these variables into a XML-structure that libvirt understands. Again, we are leveraging the benefits of CFEngine’s unique ability to create arbitrary abstraction levels with promise bodies. Note that this example assumes a QEMU/KVM image is created with a basic OS installed.

It is possible to set up a more complex scenario, where the disk image is built from a PXE-boot service, but this is out of the scope of these notes. Please leave a comment if you would like more advanced tutorials.

Under the hood of the mykvm body, libvirt configuration is given as an XML-file to provide the necessary guest machine definition. There are more examples of the syntax in the libvirt documentation (http://libvirt.org/formatnode.html).

Another interesting aspect of this promise is the environment_host promise attribute: it specifies where the guest should be created. On hosts where this class is set, the promise will be checked, on all other hosts, CFEngine will ensure the machine is not running.

Suspending a machine

Continuing with our example above, all that needs to be done to suspend our machine is the following.

bundle agent kvm_suspend
{
guest_environments:
 "my_host_machine"
   environment_type      => "kvm",
   environment_state     => "suspended",
   environment_host      => "ubuntu";
}

$ cf-agent -Kvf ./guest_environment_kvm.cf -b kvm_suspend
...
core>  -> Virtual domain "my_host_machine" running, suspending
...

As usual, CFEngine has nice convergent behavior for virtual machines too.

$ cf-agent -Kvf ./guest_environment_kvm.cf -b kvm_suspend
...
core>  -> Virtual domain "my_host_machine" is suspended - promise kept
...

Deleting a machine

Stopping and deleting the machine definition easily be done as follows.

bundle agent kvm_delete
{
guest_environments:
 "my_host_machine"
   environment_type      => "kvm",
   environment_state     => "delete",
   environment_host      => "ubuntu";
}

$ cf-agent -Kvf ./guest_environment_kvm.cf -b kvm_delete
...
core>  -> Deleted virtual domain "my_host_machine"
...

This covers the basic use of guest_environments promises in CFEngine. There are more advanced features to be found for creation by looking at the libvirt documentation. If you have a use-case for virtual machines that you do not see covered, please do not hesitate to let us know.

SQL-promises

The databases: promise type has been available in the commercial CFEngine Nova edition for some time. We decided to move this to the Community edition, so that a broader set of users can benefit from it. This allows you to make promises about the structure of MySQL and PostgreSQL database tables. Please note that it is currently not possible to make promises about rows of SQL databases, we had to leave some features for future releases.

Let us jump straight into an example. We will use MySQL as back-end in this example, but thanks to the generic CFEngine policy language, all that is needed to adjust to PostgreSQL is to change the database_server attribute to local_postgresql below.

We would like the database cfengine_db to contain the table users. The users-table should have three fields: username, password and email. The creation of this table (and database) can be done in CFEngine as follows.

bundle agent databases
{
databases:

  "cfengine_db/users"

    database_operation => "create",
    database_type => "sql",
    database_columns => {
                        "username,varchar,50",
                        "password,varchar,80",
                        "email,varchar,20",
                        },
    database_server => local_mysql("root", "");
}

By running CFEngine, you see the following output.

$ cf-agent -Kf sql.cf
 !! The database did not contain the promised table "cfengine_db.users"
 -> Database.table cfengine_db.users doesn't seem to exist, creating
I: Made in version 'not specified' of '/home/a10003/.cfagent/inputs/sql.cf' near line 13
 -> Trying to create table users

After this run, you can verify from MySQL that CFEngine did indeed create the database and table.

mysql> desc users;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| username | varchar(50) | YES  |     | NULL    |       |
| password | varchar(80) | YES  |     | NULL    |       |
| email    | varchar(20) | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+  3 rows in set (0.00 sec)

If the table is already there, but deviates from the CFEngine promise, CFEngine will only warn you about this. This is because it could be dangerous to automatically start dropping table columns. If you would nonetheless like to see support for altering tables, please add a comment or make a feature request at the CFEngine bugtracker.

The complete policy for the above is available for download.

User-experience enhancements

As I wrote in my introductory blog post, there were two issues I wanted to see fixed.

The first one relates to the automatic reloading of policy for the CFEngine scheduler, cf-execd. This process has the important role of running CFEngine at the schedule defined in policy, and will also email you the output from CFEngine if you configure it to. In earlier releases, you had to kill and restart cf-exexcd if you changed its policy, such as changing the run schedule or the mail address. Needless to say, this is a quite risky operation: if cf-execd does not come back up, cf-agent does not get run and your host is no longer maintained by CFEngine.

I am therefore quite happy to announce that we have now implemented automatic reloading of the cf-execd policy, just like the other components!

The second issue is related to expansion of variables in the policy. Consider the following policy.

bundle common def
{
vars:
 "global_list" slist => { "a", "b", "c" };
}


bundle agent test
{
reports:
 cfengine_3::
  "$(def.global_list)";
}

This now correctly expands, without the need to copy the list to a local scope:

$ cf-agent -Kf ./global_list_expansion.cf
R: a
R: b
R: c

You can test this yourself with the policy found here.

The file cf_promises_validated that is being created in masterfiles (see the 3.1.2 change log) is now filled with a date-string when the policy changes instead of being touched. This is because touching the file as a means for detecting policy updates is more fragile if clocks are not synchronized across the policy server and clients. You will also see that we have changed from u_dcp to u_rcp in the failsafe.cf policy that we bundle with CFEngine, and I suggest that you also change your policy if you currently use the u_dcp body.

Furthermore, we changed the default failsafe.cf u_rcp body to have trustkey set to false instead of true. This is because this policy is run after the client is bootstrapped, so there is no need for the client to accept new keys at this stage, tightening security further.

You can find all these policy enhancements in /var/cfengine/masterfiles when you install one of the packages we provide, or in masterfiles in the CFEngine Community github repository.

We believe these improvements will make CFEngine behave more to the user expectations and relieve beginners from unnecessary pains.

New classes, functions and variables

Quite a few useful new variables are made available, most notably the $(sys.last_policy_update) which tells you the timestamp of the most recent policy change at the client. In the following example, we use the variables and show how they expand.

bundle edit_line my_motd
{
vars:
  "interfaces_str"  string => join(", ","sys.interfaces");
  "ipaddresses_str" string => join(", ","sys.ip_addresses");

insert_lines:
"This system is managed by CFEngine.
The policy was last updated on $(sys.last_policy_update).
The system has $(sys.cpus) cpus.
Network interfaces on this system are $(interfaces_str),
and the ip-addresses assigned are $(ipaddresses_str).";
}

This results in the following file on an example system:

This system is managed by CFEngine.
The policy was last updated on Fri Mar 23 15:03:02 2012.
The system has 4 cpus.
Network interfaces on this system are wlan0, virbr0,
and the ip-addresses assigned are 192.168.1.16, 192.168.122.1.

The new and useful functions dirname(), lsdir(), and maplist() have also been implemented.

Improvements to the embedded-db code

CFEngine relies on an embedded db library to save state across runs, such as when cf-serverd and cf-agent makes connections to remote systems (aka. the last-seen database). However, it turned out that the db access would become a bottleneck on very busy systems, because CFEngine was storing information on every connection. By very busy, I mean around a thousand connections a minute.

We needed to improve on this, because a thousand connections per minute is not close to what we want CFEngine to scale to. So we refactored the embedded database code, to allow a much higher degree of paralellisation and adjust the database structure to make the query patterns much more efficient. We also introduced self-healing of broken database files: they will now get moved away and recreated.

To standardise CFEngine installations, we dropped support for the BerkeleyDB and the more experimental sqlite backends. As we annonced this, Oracle actually reached out and wanted to ensure continued support for BerkeleyDB in CFEngine. We will see if they offer help to maintain the backend.

The remaining tokyocabinet and quick dbm backends are much faster and safer to use. Use tokyocabinet if you can, but if this does not compile on your system, you can use the more compatible quick dbm library instead. All our binaries are built with tokyocabinet.

More details

There are just too many improvements to cover in the 3.3.0 release, so we could only cover the highlights. There are some more details in the ChangeLog-file at CFEngine’s github repository. Also, the bugtracker contains a list of all issues that have been resolved for CFEngine 3.3.0.

Download today!

As usual, we relieve our community users with the hassle of compiling their own binaries, by providing free binaries for the following platforms.

  • Debian 5 and 6
  • Fedora 14 and 15
  • Red Hat Enterprise Linux 4, 5 and 6
  • Suse 9, 10 and 11
  • Ubuntu 8, 9, 10 and 11

Free registration at the engine room is required to get the binaries. These binaries are built according to CFEngine standards, all dependencies are embedded and support for the new SQL and virtualization promises is included.

Download, install and test within minutes!