#!/usr/bin/perl #Copyright (C) 2009, Tom Judge # ---------------------------------------------------------------------------- # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return Tom Judge. # ---------------------------------------------------------------------------- use strict; use warnings; use English; use Fcntl ':flock'; open (MUTEX, "> /var/run/cfengine-jailmanager-mutex") or die ("Cant open mutex"); if (!flock(MUTEX,LOCK_EX|LOCK_NB)) { print "Jail Manager already Running\n"; exit; } ###Workaround to avoid annoying messages when cfengine runs on a server with no DBI.pm eval "require DBI"; if ($@) { exit; } require DBI; ###Workaround -end use Data::Dumper; my $EZJAIL_ADMIN = "/usr/local/bin/ezjail-admin"; my $EZJAIL_RC = "/usr/local/etc/rc.d/ezjail.sh"; my $hostname = `/bin/hostname -s`; chomp $hostname; my $RACKTABLES_HOST = $ARGV[0]; my $RACKTABLES_DB = $ARGV[1]; my $RACKTABLES_USER = $ARGV[2]; my $RACKTABLES_PASS = $ARGV[3]; my @allclasses = split (":","$ENV{CFALLCLASSES}"); my %classes; foreach my $class (@allclasses) { $classes{$class} = 1; } ## We should only run on Jail Hosts if (!defined($classes{ROLE_JailHost})) { exit; } ## We should probably only run after we have a base jail. if (! -e "/data/jails/basejail") { print "** DRAGON ALERT: No jail base yet, its not my time **\n"; exit; } ## We need the list of current jails to be able to work out what to do. #STA JID IP Hostname Root Directory #--- ----- --------------- ---------------------------- ------------------------- #DS N/A 172.17.0.106 testjail.usdmm.com /data/jails/testjail.usdmm.com my %current_jails; # Fetch the current jails my @ezjail_list = `$EZJAIL_ADMIN list`; ## trim off the first 2 lines of headers shift @ezjail_list; shift @ezjail_list; for my $jail (@ezjail_list) { chomp $jail; my ($jail_status, $jail_id, $jail_ip, $jail_name, $jail_root) = split /\s+/, $jail; $current_jails{$jail_name}->{'ip'} = $jail_ip; $current_jails{$jail_name}->{'id'} = $jail_id; $current_jails{$jail_name}->{'root'} = $jail_root; ## Parse flags (see ezjail-admin(1)) if ($jail_status =~ /D/) { $current_jails{$jail_name}->{'base'} = "dir"; } elsif ($jail_status =~ /I/) { $current_jails{$jail_name}->{'base'} = "image"; if ($jail_status =~ /B/) { $current_jails{$jail_name}->{'image_type'} = 'bde'; } elsif ($jail_status =~ /E/) { $current_jails{$jail_name}->{'image_type'} = 'eli'; } else { $current_jails{$jail_name}->{'image_type'} = 'raw'; } } if ($jail_status =~ /R/) { $current_jails{$jail_name}->{'status'} = "running"; } elsif ($jail_status =~ /A/) { $current_jails{$jail_name}->{'status'} = "attached"; } elsif ($jail_status =~ /S/) { $current_jails{$jail_name}->{'status'} = "stopped"; } } ## Connect to the management DB my $dsn = "DBI:mysql:$RACKTABLES_DB:$RACKTABLES_HOST"; my $dbh = DBI->connect ($dsn, $RACKTABLES_USER, $RACKTABLES_PASS, {RaiseError=>1, PrintError=>0}); ## Get the list of jails for this host my $get_jails = $dbh->prepare( "SELECT Jail.name, INET_NTOA(Jail.ip) as ip, Flavour.name as flavour_name, Jail.deployable, Jail.enabled FROM Jail ". "LEFT JOIN Flavour on Flavour.id=Jail.flavour_id ". "LEFT JOIN RackObject on Jail.host_object_id=RackObject.id ". "WHERE LOWER(RackObject.name)=?" ); $get_jails->execute($hostname); my %required_jails; my $jail; while ($jail = $get_jails->fetchrow_hashref()) { print "MY JAIL: ".$jail->{"name"}." - ".$jail->{"ip"}."\n"; $required_jails{$jail->{"name"}}->{"ip"}=$jail->{"ip"}; #### ### PRE FLIGHT CHECKS #### ## Check that the jail is allowed to be deployed if ($jail->{"deployable"} eq 'no') { print "** DRAGON ALERT: Jail not deployable, nothing to see here (".$jail->{"name"}."), move along please **\n"; next; } ## Check that the IP is configured on an interface somewhere my $jailip=$jail->{"ip"}; if (scalar(grep(/\s$jailip\s/,`/sbin/ifconfig`)) != 1) { print "** DRAGON ALERT: Jail IP Not Configured, nothing to see here (".$jail->{"name"}."), move along please **\n"; next; } ## Check that the flavour exists if (! -e "/data/jails/flavours/".$jail->{"flavour_name"}) { print "** DRAGON ALERT: Jail Flavour Does Not Exist, nothing to see here (".$jail->{"name"}."), move along please **\n"; next; } #### ### PRE FLIGHT CHECKS PASSED ### ### We are good to start working with the jail now #### if (defined($current_jails{$jail->{"name"}})) { print "Jail exists: ".$jail->{"name"}."\n"; if ($current_jails{$jail->{"name"}}->{"status"} eq "stopped") { if ($jail->{"enabled"} eq 'yes') { print "Starting Jail: ".$jail->{"name"}."\n"; my @start_command = ($EZJAIL_RC, "start", $jail->{"name"}); system (@start_command); } } elsif ($current_jails{$jail->{"name"}}->{"status"} eq "running") { if ($jail->{"enabled"} eq 'no') { print "Stopping Jail: ".$jail->{"name"}."\n"; my @start_command = ($EZJAIL_RC, "stop", $jail->{"name"}); system (@start_command); } } my $ports = "/data/jails/".$jail->{"name"}."/usr/ports"; if ( -d $ports ) { `rm -Rf $ports`; symlink ( "../../basejail/usr/ports", $ports); } } else { print "Creating Jail: ".$jail->{"name"}."\n"; ## Here we go time to fly create me a jail please my @create_command = ($EZJAIL_ADMIN, "create", "-f", $jail->{"flavour_name"}, $jail->{"name"}, $jail->{"ip"}); system(@create_command); # Mount /usr/home in the jail. my $fstab = $jail->{"name"}; $fstab =~ s/[\.-]/_/g; $fstab = "/etc/fstab.$fstab"; my $home_fstab = "/usr/home /data/jails/".$jail->{"name"}."/usr/home nullfs rw 0 0\n"; open FSTAB, '>>', $fstab; print FSTAB $home_fstab; close FSTAB; #Fix permissions #/data/jails/basejail/usr and usr/lib32 and /data/jails/newjail/basejail my $dirtofix; foreach $dirtofix ("/data/jails/basejail/usr" , "/data/jails/basejail/usr/lib32" , "/data/jails/newjail/basejail" , "/data/jails/basejail") { warn "Could not chmod \"$dirtofix\": $!" unless chmod (0755,$dirtofix); } if ($jail->{"enabled"} eq 'yes') { my @start_command = ($EZJAIL_RC, "start", $jail->{"name"}); system (@start_command); } } } ## ## Delete jails that are not supposed to be here ## for my $jail_name (keys(%current_jails)) { if (!defined($required_jails{$jail_name})) { print "Starting Removal Of Jail: $jail_name\n"; if ($current_jails{$jail_name}->{"status"} eq "running") { print "Stopping Jail: $jail_name\n"; my @stop_command = ($EZJAIL_RC, "stop", $jail_name); system (@stop_command); } print "Deleting Jail Filesystem: $jail_name\n"; my @delete_command = ($EZJAIL_ADMIN, "delete", "-w", $jail_name); system(@delete_command); } } $get_jails->finish(); $dbh->disconnect(); flock(MUTEX,LOCK_UN); close(MUTEX);