From facbf7c130fa19faffb7622ebb5e0cb0dd2aa8e3 Mon Sep 17 00:00:00 2001 From: Gabor Gombas Date: Tue, 4 Sep 2018 15:34:57 +0200 Subject: [PATCH] DHCP plugin updates Add tests, adapted from aii-core. Replace bare open() with CAF::FileReader to make the tests work. Include some improvements which accumulated on our side. --- aii-core/src/main/perl/Shellfe.pm | 5 + .../src/main/pan/quattor/aii/dhcp/config.pan | 41 ++-- .../src/main/pan/quattor/aii/dhcp/rpms.pan | 0 .../src/main/pan/quattor/aii/dhcp/schema.pan | 17 +- aii-dhcp/src/main/perl/dhcp.pm | 41 ++-- aii-dhcp/src/main/perl/dhcp.pod | 0 aii-dhcp/src/test/perl/dhcp.t | 198 ++++++++++++++++++ aii-dhcp/src/test/resources/dhcp.pan | 9 + .../src/test/resources/host1.example.com.pan | 14 ++ .../src/test/resources/host2.example.com.pan | 13 ++ .../src/test/resources/host3.example1.com.pan | 11 + .../src/test/resources/host4.example.com.pan | 17 ++ .../src/test/resources/host5.example.com.pan | 12 ++ .../src/test/resources/host6.example1.com.pan | 12 ++ 14 files changed, 354 insertions(+), 36 deletions(-) mode change 100755 => 100644 aii-dhcp/src/main/pan/quattor/aii/dhcp/config.pan mode change 100755 => 100644 aii-dhcp/src/main/pan/quattor/aii/dhcp/rpms.pan mode change 100755 => 100644 aii-dhcp/src/main/pan/quattor/aii/dhcp/schema.pan mode change 100755 => 100644 aii-dhcp/src/main/perl/dhcp.pm mode change 100755 => 100644 aii-dhcp/src/main/perl/dhcp.pod create mode 100644 aii-dhcp/src/test/perl/dhcp.t create mode 100644 aii-dhcp/src/test/resources/dhcp.pan create mode 100644 aii-dhcp/src/test/resources/host1.example.com.pan create mode 100644 aii-dhcp/src/test/resources/host2.example.com.pan create mode 100644 aii-dhcp/src/test/resources/host3.example1.com.pan create mode 100644 aii-dhcp/src/test/resources/host4.example.com.pan create mode 100644 aii-dhcp/src/test/resources/host5.example.com.pan create mode 100644 aii-dhcp/src/test/resources/host6.example1.com.pan diff --git a/aii-core/src/main/perl/Shellfe.pm b/aii-core/src/main/perl/Shellfe.pm index a4f52fa6..bb138dc3 100755 --- a/aii-core/src/main/perl/Shellfe.pm +++ b/aii-core/src/main/perl/Shellfe.pm @@ -43,6 +43,7 @@ use constant PROFILEINFO => 'profiles-info.xml'; use constant NODHCP => 'nodhcp'; use constant NONBP => 'nonbp'; use constant NOOSINSTALL=> 'noosinstall'; +use constant DISCOVERY => '/system/aii/discovery'; use constant OSINSTALL => '/system/aii/osinstall'; use constant NBP => '/system/aii/nbp'; use constant CDBURL => 'cdburl'; @@ -537,6 +538,10 @@ sub dhcp { my ($self, $node, $st, $cmd, $dhcpmgr) = @_; + # If the profile has a discovery plugin configured, then don't second-guess + # it - it may or may not be ISC DHCP + return if $st->{configuration}->elementExists (DISCOVERY); + return unless $st->{configuration}->elementExists (DHCPPATH); my $mac; 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..a7d2b41c --- 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,21 @@ # ${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{} }; - -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..0c5650df --- 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(); @@ -312,12 +310,24 @@ sub Configure } my $ip = $self->get_ip($bootable, $tree); + 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 $opts = $config->getElement("/system/aii/dhcp")->getTree(); 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 +341,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 +384,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");