diff --git a/aii-core/src/main/perl/Shellfe.pm b/aii-core/src/main/perl/Shellfe.pm index 80f9f44f..c3bac0ed 100755 --- a/aii-core/src/main/perl/Shellfe.pm +++ b/aii-core/src/main/perl/Shellfe.pm @@ -49,6 +49,7 @@ use constant USEMODULE => "use " . MODULEBASE; use constant PROFILEINFO => 'profiles-info.xml'; use constant NODHCP => 'nodhcp'; +use constant DISCOVERY => '/system/aii/discovery'; use constant OSINSTALL => '/system/aii/osinstall'; use constant NBP => '/system/aii/nbp'; use constant CDBURL => 'cdburl'; @@ -608,6 +609,14 @@ sub dhcp my ($self, $st, $cmd, $dhcpmgr) = @_; my $name = $st->{name}; + + # If the profile has a discovery plugin configured, then don't second-guess + # it - it may or may not be ISC DHCP + if ($st->{configuration}->elementExists(DISCOVERY)) { + $self->verbose("Found discovery configuration for path ".DISCOVERY." for $name. Skipping"); + return; + }; + my $tree = $st->{configuration}->getTree(DHCPPATH); if (! $tree) { $self->verbose("No configuration for DHCP path ".DHCPPATH." for $name. Skipping"); diff --git a/aii-dhcp/src/main/pan/quattor/aii/dhcp/config.pan b/aii-dhcp/src/main/pan/quattor/aii/dhcp/config.pan old mode 100755 new mode 100644 index 60c07cb1..a3638690 --- a/aii-dhcp/src/main/pan/quattor/aii/dhcp/config.pan +++ b/aii-dhcp/src/main/pan/quattor/aii/dhcp/config.pan @@ -2,24 +2,31 @@ # ${developer-info} # ${author-info} # ${build-info} -template quattor/aii/${project.artifactId}/config; +template quattor/aii/dhcp/config; -include 'quattor/aii/${project.artifactId}/schema'; +include 'quattor/aii/dhcp/schema'; -# TFTP server -# This is optional. Only it is necessary if your TFTP server is running on -# a different machine than the DHCP server -# -# "/system/aii/dhcp/options/tftpserver" = "tftp.mydomain.org" -# +bind "/system/aii/discovery/dhcp" = structure_dhcp_module_info; + +prefix "/system/aii/discovery/dhcp"; + +@documentation{ + Enable the plugin +} +"enabled" = true; + +bind "/system/aii/dhcp" = structure_dhcp_dhcp_info; + +prefix "/system/aii/dhcp"; + +@documentation{ + Override the TFT server for this node +} variable AII_DHCP_TFTPSERVER ?= null; -"/system/aii/dhcp/options/tftpserver" ?= AII_DHCP_TFTPSERVER; - -# Additional DHCP options (optional). -# Warning: They will be added in the host declaration of dhcpd.conf file, so -# do not forget the ';' at the end -# -#"/system/aii/dhcp/addoptions" = "options blu-blo-bli bla;"; -# +"tftpserver" ?= AII_DHCP_TFTPSERVER; + +@documentation{ + Additional options to include in the host definition +} variable AII_DHCP_ADDOPTIONS ?= null; -"/system/aii/dhcp/options/addoptions" ?= AII_DHCP_ADDOPTIONS; +"options" ?= AII_DHCP_ADDOPTIONS; diff --git a/aii-dhcp/src/main/pan/quattor/aii/dhcp/rpms.pan b/aii-dhcp/src/main/pan/quattor/aii/dhcp/rpms.pan old mode 100755 new mode 100644 diff --git a/aii-dhcp/src/main/pan/quattor/aii/dhcp/schema.pan b/aii-dhcp/src/main/pan/quattor/aii/dhcp/schema.pan old mode 100755 new mode 100644 index 6e54e223..46fd78be --- a/aii-dhcp/src/main/pan/quattor/aii/dhcp/schema.pan +++ b/aii-dhcp/src/main/pan/quattor/aii/dhcp/schema.pan @@ -3,12 +3,23 @@ # ${author-info} # ${build-info} -unique template quattor/aii/${project.artifactId}/schema; +unique template quattor/aii/dhcp/schema; + +type structure_dhcp_module_info = { + "enabled" ? boolean +}; -# Information needed for creating the Kickstart file type structure_dhcp_dhcp_info = { + @{TFTP server to use for this node, instead of the host where AII runs} "tftpserver" ? string + @{Name of the file to boot} + "filename" ? string + @{ + Custom options to include in the host definition. Note: if the type + of an option requires quoting, then the quotes must be included in + the value you specify in templates. + } "options" ? string{} + @{Verify hostname in DNS} + "verifyhostname" ? boolean }; - -bind "/system/aii/${project.artifactId}" = structure_dhcp_dhcp_info; diff --git a/aii-dhcp/src/main/perl/dhcp.pm b/aii-dhcp/src/main/perl/dhcp.pm old mode 100755 new mode 100644 index 6b408078..f9918496 --- a/aii-dhcp/src/main/perl/dhcp.pm +++ b/aii-dhcp/src/main/perl/dhcp.pm @@ -5,6 +5,7 @@ use Socket; use Exporter; use Sys::Hostname; use CAF::Lock qw (FORCE_IF_STALE); +use CAF::FileReader; use CAF::FileWriter; use CAF::Process; @@ -70,7 +71,7 @@ sub update_dhcp_config_file $nodes_regexp .= $node->{NAME} . '|' . $node->{FQDN} . '|'; } $nodes_regexp =~ s/\|$//; # remove last '|' - $nodes_regexp = '\\n\s*host\s+(' . $nodes_regexp . ')\s*\{[^}]*\}'; + $nodes_regexp = '\\n\s*host\s+(' . $nodes_regexp . ')\s*(\{(?:[^{}]|(?2))*\})'; $text =~ s/$nodes_regexp//gm; # @@ -138,6 +139,11 @@ sub update_dhcp_config_file push @newnodes, "$indent\t next-server $node->{ST_IP_TFTP};"; } + # DHCP filename option + if ($node->{FILENAME}) { + push @newnodes, "$indent\t filename \"$node->{FILENAME}\";"; + } + # additional options if ($node->{MORE_OPT}) { push @newnodes, "$indent\t $node->{MORE_OPT}"; @@ -255,14 +261,14 @@ sub update_dhcp_config { } $self->debug(3, "Locked dhcp configuration"); $self->debug(3,"DHCP configuration file : $filename"); - if (!open(FILE, "< $filename")) { + my $fh = CAF::FileReader->new($filename, log => $self); + if ($EC->error()) { $self->error("dhcp: update configuration: ". "file access error $filename"); return(1); } - local $/ = undef; - $text = ; - close (FILE); + $text = "$fh"; + $fh->close(); # # Add/removal of nodes @@ -272,18 +278,10 @@ sub update_dhcp_config { return(1); } - # - # Backup the old one - # - if (!rename ($filename, $filename . '.pre_aii')) { - $self->error("dhcp: error creating backup copy $filename.pre_aii"); - return(1); - } - # # Write the new dhcp configuration file # - my $file = CAF::FileWriter->open($filename, mode => 0664, log => $this_app); + my $file = CAF::FileWriter->new($filename, mode => 0664, log => $this_app, backup => '.pre_aii'); $file->print($text); $file->close(); @@ -297,6 +295,9 @@ sub Configure my $tree = $config->getElement("/system/network")->getTree(); my $fqdn = $tree->{hostname} . "." . $tree->{domainname}; + + my $opts = $config->getElement("/system/aii/dhcp")->getTree(); + # Find the bootable interface my $cards = $config->getElement("/hardware/cards/nic")->getTree(); my $bootable = undef; @@ -312,12 +313,37 @@ sub Configure } my $ip = $self->get_ip($bootable, $tree); - my $opts = $config->getElement("/system/aii/dhcp")->getTree(); + if ($opts->{verifyhostname}) { + my $fqdn_ip = gethostbyname($fqdn); + $fqdn_ip = inet_ntoa($fqdn_ip) if defined($fqdn_ip); + if (defined($fqdn_ip)) { + if ($fqdn_ip ne $ip) { + $self->error("aii-dhcp: fqdn $fqdn ip $fqdn_ip does not match configured ip $ip"); + } + } else { + $self->error("aii-dhcp: failed to obtain IP address for fqdn $fqdn"); + return; + } + } + + + my $server_ip = gethostbyname(hostname()); + $server_ip = inet_ntoa($server_ip) if defined($server_ip); + if (!defined($server_ip)) { + $self->error("aii-dhcp: failed to obtain own IP address"); + return; + } + my $tftpserver = ""; + my $filename = ""; my $additional = ""; if ($opts->{tftpserver}) { $tftpserver = $opts->{tftpserver}; } + if ($opts->{filename}) { + $filename = $opts->{filename}; + $filename =~ s/BOOTSRVIP/$server_ip/; + } if ($opts->{options}) { foreach my $k (sort keys %{$opts->{options}}) { $additional .= "option $k $opts->{options}->{$k};\n"; @@ -331,6 +357,7 @@ sub Configure IP => unpack('N', Socket::inet_aton($ip)), MAC => $cards->{$bootable}->{hwaddr}, ST_IP_TFTP => $tftpserver, + FILENAME => $filename, MORE_OPT => $additional, }; if ($this_app->option('use_fqdn')) { @@ -373,7 +400,7 @@ sub Unconfigure IP => $ip, }; $self->update_dhcp_config([], [$nodeconfig]); - return 0; + return 1; } 1; diff --git a/aii-dhcp/src/main/perl/dhcp.pod b/aii-dhcp/src/main/perl/dhcp.pod old mode 100755 new mode 100644 diff --git a/aii-dhcp/src/test/perl/dhcp.t b/aii-dhcp/src/test/perl/dhcp.t new file mode 100644 index 00000000..9373ad53 --- /dev/null +++ b/aii-dhcp/src/test/perl/dhcp.t @@ -0,0 +1,198 @@ +use strict; +use warnings; + +BEGIN { + use Socket; + use Sys::Hostname; + our $hostname = hostname(); + + *CORE::GLOBAL::gethostbyname = sub { + my $name = shift; + if ($name eq $hostname) { + return ($name, 1, 2, 3, inet_aton("12.12.0.2")); + } else { + die("cannot lookup $name"); + } + }; +} + +use Test::More; +use Test::MockModule; +use Test::Quattor qw(host1.example.com host2.example.com host3.example1.com host4.example.com host5.example.com host6.example1.com); +use NCM::Component::dhcp; +use CAF::FileWriter; +use CAF::Object; +use Readonly; + +my $mlock = Test::MockModule->new('CAF::Lock'); +my $lock = 0; +$mlock->mock('set_lock', sub {$lock++; return 1;}); + + +my $cfg_1 = get_config_for_profile('host1.example.com'); +my $cfg_2 = get_config_for_profile('host2.example.com'); +my $cfg_3 = get_config_for_profile('host3.example1.com'); +my $cfg_4 = get_config_for_profile('host4.example.com'); +my $cfg_5 = get_config_for_profile('host5.example.com'); +my $cfg_6 = get_config_for_profile('host6.example1.com'); + +my @configure = ($cfg_1, $cfg_2, $cfg_3, $cfg_4); + +my @remove = ($cfg_1, $cfg_5, $cfg_6); + +my $dhcpd = <{CONFIG}->define("dhcpconf"); +$this_app->{CONFIG}->set("dhcpconf", "/path/dhcpd.conf"); +$this_app->{CONFIG}->define("restartcmd"); +$this_app->{CONFIG}->set("restartcmd", "/path/my_dhcp restart"); + +my $comp = NCM::Component::dhcp->new('dhcp_config'); + +mkdir('target/test') if ! -d 'target/test'; + +set_file_contents('/path/dhcpd.conf', $dhcpd); + +foreach my $cfg (@remove) { + my $status = $comp->Unconfigure($cfg); + ok($status, "Unconfigure"); +} + +foreach my $cfg (@configure) { + my $status = $comp->Configure($cfg); + ok($status, "Configure"); +} + +my $fh = get_file('/path/dhcpd.conf'); +is("$fh", $new_dhcpd, "generated new config file"); + +$comp->finish(); +ok(get_command('/path/my_dhcp restart'), 'dhcpd restarted'); + +done_testing(); diff --git a/aii-dhcp/src/test/resources/dhcp.pan b/aii-dhcp/src/test/resources/dhcp.pan new file mode 100644 index 00000000..79f781a8 --- /dev/null +++ b/aii-dhcp/src/test/resources/dhcp.pan @@ -0,0 +1,9 @@ +unique template dhcp; + +include "quattor/aii/dhcp/config"; + +"/system/aii/discovery/dhcp/enabled" = true; + +"/hardware/cards/nic/eth0" = dict( + "boot", true, +); diff --git a/aii-dhcp/src/test/resources/host1.example.com.pan b/aii-dhcp/src/test/resources/host1.example.com.pan new file mode 100644 index 00000000..41a5399a --- /dev/null +++ b/aii-dhcp/src/test/resources/host1.example.com.pan @@ -0,0 +1,14 @@ +object template host1.example.com; + +include 'dhcp'; + +"/system/aii/dhcp/tftpserver" = 'host0.example.com'; +"/system/aii/dhcp/options/default-lease-time" = '259200'; + +"/hardware/cards/nic/eth0/hwaddr" = "00:11:22:33:44:55"; + +prefix "/system/network"; + +"hostname" = "host1"; +"domainname" = "example.com"; +"interfaces/eth0" = dict("ip", "10.11.0.1", "netmask", "255.255.255.0"); diff --git a/aii-dhcp/src/test/resources/host2.example.com.pan b/aii-dhcp/src/test/resources/host2.example.com.pan new file mode 100644 index 00000000..47f0d5b5 --- /dev/null +++ b/aii-dhcp/src/test/resources/host2.example.com.pan @@ -0,0 +1,13 @@ +object template host2.example.com; + +include 'dhcp'; + +"/system/aii/dhcp/tftpserver" = 'host0.example.com'; + +"/hardware/cards/nic/eth0/hwaddr" = "00:11:22:33:44:66"; + +prefix "/system/network"; + +"hostname" = "host2"; +"domainname" = "example.com"; +"interfaces/eth0" = dict("ip", "10.11.0.2", "netmask", "255.255.255.0"); diff --git a/aii-dhcp/src/test/resources/host3.example1.com.pan b/aii-dhcp/src/test/resources/host3.example1.com.pan new file mode 100644 index 00000000..77b700bf --- /dev/null +++ b/aii-dhcp/src/test/resources/host3.example1.com.pan @@ -0,0 +1,11 @@ +object template host3.example1.com; + +include 'dhcp'; + +"/hardware/cards/nic/eth0/hwaddr" = "00:11:22:33:44:88"; + +prefix "/system/network"; + +"hostname" = "host3"; +"domainname" = "example1.com"; +"interfaces/eth0" = dict("ip", "10.11.2.3", "netmask", "255.255.255.0"); diff --git a/aii-dhcp/src/test/resources/host4.example.com.pan b/aii-dhcp/src/test/resources/host4.example.com.pan new file mode 100644 index 00000000..46ec53a2 --- /dev/null +++ b/aii-dhcp/src/test/resources/host4.example.com.pan @@ -0,0 +1,17 @@ +object template host4.example.com; + +include 'dhcp'; + +"/system/aii/dhcp/tftpserver" = 'host0.example.com'; +"/system/aii/dhcp/filename" = 'http://BOOTSRVIP/bootimage'; +"/system/aii/dhcp/options/default-lease-time" = '259200'; +"/system/aii/dhcp/options/dhcp-message" = '"Quoted string"'; +"/system/aii/dhcp/options/domain-name-servers" = '8.8.4.4,8.8.8.8'; + +"/hardware/cards/nic/eth0/hwaddr" = "00:11:22:33:44:99"; + +prefix "/system/network"; + +"hostname" = "host4"; +"domainname" = "example.com"; +"interfaces/eth0" = dict("ip", "10.11.0.4", "netmask", "255.255.255.0"); diff --git a/aii-dhcp/src/test/resources/host5.example.com.pan b/aii-dhcp/src/test/resources/host5.example.com.pan new file mode 100644 index 00000000..5cc2588c --- /dev/null +++ b/aii-dhcp/src/test/resources/host5.example.com.pan @@ -0,0 +1,12 @@ +object template host5.example.com; + +include 'dhcp'; + + +"/hardware/cards/nic/eth0/hwaddr" = "00:11:22:33:44:cc"; + +prefix "/system/network"; + +"hostname" = "host5"; +"domainname" = "example.com"; +"interfaces/eth0" = dict("ip", "10.11.0.5", "netmask", "255.255.255.0"); diff --git a/aii-dhcp/src/test/resources/host6.example1.com.pan b/aii-dhcp/src/test/resources/host6.example1.com.pan new file mode 100644 index 00000000..e96fcce2 --- /dev/null +++ b/aii-dhcp/src/test/resources/host6.example1.com.pan @@ -0,0 +1,12 @@ +object template host6.example1.com; + +include 'dhcp'; + + +"/hardware/cards/nic/eth0/hwaddr" = "00:11:22:33:44:cc"; + +prefix "/system/network"; + +"hostname" = "host6"; +"domainname" = "example1.com"; +"interfaces/eth0" = dict("ip", "10.11.2.6", "netmask", "255.255.255.0");