Configuring a simple routing change with cfengine 2 for linux

Unfortunately, this is one of those "not-very-portable" things that other config management tool advocates will harp on about cfengine. They might be right but you still have to cross your fingers and hope somebody got everything abstracted just the way you want it, and that the particular thing you want to do is built into the tool. So...let's just get started.

This example file assumes we have a larger cfengine infrastructure setup, namely that there is an update.conf and cfagent.conf in /var/cfengine/inputs. update.conf sets some basic vars like domain and copies current copies of all other files from a central host. In cfagent.conf, we do this:
import:
linux::
     cf.route

I would choose to make cf.route work on all platforms, and not be platform specific in cfagent.conf. However it is not yet so I temporarily limit to linux. You could choose to import different files in Solaris by checking system class (solarisx86, sunos_5_10, sunos_i86pc or sunos_i86pc_5_10 classes for example - there is no class as generic as "linux" for Solaris).

To begin, here's our config as a whole:

#
# routing additions
#

classes:

BondedPrivate = ( FileExists(/etc/sysconfig/network-scripts/ifcfg-bond0.4010) )

control: 

BondedPrivate::
device = ( "bond0.4010" )

!BondedPrivate::
device = ( ExecShellResult(/sbin/ifconfig | /bin/grep -B2 10\.1.*\..*  | /bin/grep -o eth[0-1]) )

linux::
ifcfg_route = ( "route-$(device)" )

editfiles:

ndt|lmd01|lmd02::
#ipv4_10::
{ /etc/sysconfig/network-scripts/$(ifcfg_route)
        AutoCreate
        AppendIfNoSuchLine "10.1.1.0/24 via 10.10.1.2"
        AppendIfNoSuchLine "10.10.2.0/23 via 10.10.1.2"
        AppendIfNoSuchLine "224.0.0.0/4 via 10.10.1.2"
        AppendIfNoSuchLine "10.10.128.0/20 via 10.10.1.2"
        DefineClasses "RoutesAdded"
}

shellcommands:

RoutesAdded::
"/bin/echo Modified $(ifcfg_route), now updating runtime routes"
"/sbin/route add -net 10.1.1.0/24 gw 10.10.1.2 $(device)"
"/sbin/route add -net 10.10.0.0/22 gw 10.10.1.2 $(device)"
"/sbin/route add -net 224.0.0.0/4 gw 10.10.1.2 $(device)"
"/sbin/route add -net 10.10.128.0/20 gw 10.10.1.2 $(device)"

Let's go through this in detail now:

To begin with, we know that we need to modify different files depending on whether our hosts use a bonded network config or not. So let's figure that out by checking if there is an ifcfg file for a bonded interface in vlan 4010, and setting classes in the "classes" section of this config:

classes:
BondedPrivate = ( FileExists(/etc/sysconfig/network-scripts/ifcfg-bond0.4010) )

Next, we use the classes set above to set variables in the "control" section. Since I don't necessarily know if the private network interface is on a specific interface except in cases of bonded interfaces, I exec a shell command using ifconfig and grep to find out which one carries our private IP and set a variable using the result. Of course you could get more complicated here as needed. If BondedPrivate was set true based on the check in "classes", we set the device to bond0.4010. If not, we see which ethX interface carries it.
control: 

BondedPrivate::
device = ( "bond0.4010" )

!BondedPrivate::
device = ( ExecShellResult(/sbin/ifconfig | /bin/grep -B2 10\.1.*\..*  | /bin/grep -o eth[0-1]) )
There are also "interface" variables which resolve to the interface ip address referenced like: "ipv4[eth0]". No doubt they could be creatively used to similar effect as what I did with grep in a shell command.

Now we set the file we will modify. From above, the last class in action was "!BondedPrivate". Everything after this point only applies to machines that are NOT in the BondedPrivate class. Likewise everything after the BondedPrivate:: statement, which of course only has one line before we set the opposite context.

So we need to specify something else now since we wish to enter a more generic context. You could use "any" but I used "linux" because this is only relevant on that OS. The astute reader might note that I should be so specific in setting the variables and classes that came before so by the time we get here we are ready to setup for all OS. The astute reader might also note that everything is pretty linux specific here and in solaris I would need to actually configure /etc/rc2.d/S76-static-routes with "route add" commands in addition to adjusting later "route add" commands based on having that OS class set. Ahem...hand waving...moving on now...

linux::
ifcfg_route = ( "route-$(device)" )

Now we get to the actual actions. We want to edit some files so we start an "editfiles" section. The file we actually want to edit was setup in "ifcfg_route" as above, so we'll use a variable here. For temporary testing, I specified three hosts that use bonded and non-bonded interfaces to test everything. Really, I want to apply to all hosts with a private interface so I will use the class "ipv4_10" to specify that when ready. If the file doesn't exist, I want to make it, so I say "AutoCreate" in my edit.
editfiles: 

ndt|lmd01|lmd02::
#ipv4_10::
{ /etc/sysconfig/network-scripts/$(ifcfg_route)
        AutoCreate
        AppendIfNoSuchLine "10.1.1.0/24 via 10.10.1.2"
        AppendIfNoSuchLine "10.10.2.0/23 via 10.10.1.2"
        AppendIfNoSuchLine "224.0.0.0/4 via 10.10.1.2"
        AppendIfNoSuchLine "10.10.128.0/20 via 10.10.1.2"
        DefineClasses "RoutesAdded"
}

Notice the last line, that says "DefineClasses". If one of the "AppendIfNoSuchLine" statements takes effect, I want a class defined to use later to take some action. The class will only be defined if cfengine actually edits the file.

So what action to take? Well we could just do a restart of the whole network, but that isn't very nice and can be disruptive.

Instead, we'll manually add the routes just configured so they are available right away without restarting the system or the network. This is done through running a command, so now we enter a "shellcommands" section. If the class "RoutesAdded" gets defined due to there being an edit to a route configuration file, we will go ahead and add all the routes that might have been defined. You could be more granular about this, but then you have to do separate edits and define 4 separate classes...I personally did not think it worth the trouble. If a route exists, it will simply fail to be defined again. In this case, we aren't really worried about "cleaning up" old routes. If we were, we might be doing some "DeleteLinesMatching" actions in our edit stanzas before appending new routes to the config.

shellcommands:

RoutesAdded::
"/bin/echo Modified $(ifcfg_route), now updating runtime routes"
"/sbin/route add -net 10.1.1.0/24 gw 10.10.1.2 $(device)"
"/sbin/route add -net 10.10.0.0/22 gw 10.10.1.2 $(device)"
"/sbin/route add -net 224.0.0.0/4 gw 10.10.1.2 $(device)"
"/sbin/route add -net 10.10.128.0/20 gw 10.10.1.2 $(device)"

The "echo" command will show up as output which cfengine will email. If the "route add" commands produce output that would also be emailed (all output in our configuration is also sent to syslog).

Anyone could set up a file like this and test on specific hosts by limiting with classes (host::) as I did above. The other parts of cfengine you don't really have to worry about at this point - any file in /var/cfengine/inputs on our "manage" host ends up being copied to cfengine managed hosts and imported as specified in cfagent.conf (also copied from manage).

-- BenMeekhof - 21 Nov 2009
Topic revision: r2 - 24 May 2010, BenMeekhof
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback