report error without nmap output
[pxelator] / lib / PXElator / client.pm
1 package client;
2
3 use warnings;
4 use strict;
5 use autodie;
6
7 use File::Slurp;
8 use Data::Dump qw/dump/;
9 use File::Path;
10
11 use server;
12 use format;
13 use ip;
14 use ping;
15 use kvm;
16
17 our $debug = $server::debug;
18
19 sub mkbasedir {
20         my $path = shift;
21         $path =~ s{(^.*)/[^/]+$}{$1};
22         mkdir $path unless -d $path;
23         return $path;
24 }
25
26 sub mac_path { $server::conf . '/mac/' . $_[0] }
27 sub  ip_path { $server::conf . '/ip/'  . join('/', @_) }
28 sub conf_value {
29         my $path = shift;
30         my $value;
31         if ( -l $path ) {
32                 $value = readlink $path;
33                 $value =~ s{.*/([^/]+)$}{$1};
34         } elsif ( -f $path ) {
35                 $value = read_file $path;
36         } else {
37                 warn "W: $path not file or symlink\n";
38         }
39         return $value;
40 }
41
42 sub conf {
43         my $ip  = shift;
44         my $name = shift;
45         my ( $default, $value );
46         if ( $#_ == 0 ) {
47                 $value = shift;
48         } elsif ( $#_ == 1 && $_[0] eq 'default' ) {
49                 $default = $_[1]
50         }
51
52         my $path = ip_path $ip;
53         mkdir $path unless -e $path;
54         warn "WARNING: $path not directory" unless -d $path;
55         $path .= '/' . $name;
56
57         if ( defined $value ) {
58                 mkbasedir  $path;
59                 write_file $path, $value;
60                 warn "update $path = $value";
61         } elsif ( ! -e $path && defined $default ) {
62                 mkbasedir  $path;
63                 write_file $path, $default;
64                 warn "default $path = $default";
65                 $value = $default;
66         } elsif ( -l $path ) {
67                 $value = readlink $path;
68         } elsif ( -f $path ) {
69                 $value = read_file $path;
70                 chomp $value;
71         } else {
72                 warn "# $name missing $path\n" if $debug;
73         }
74         return $value;
75 }
76
77 sub all_conf {
78         my $ip = shift;
79         my $path = ip_path $ip || return;
80         my $conf;
81         foreach my $file ( glob("$path/*"), glob("$path/*/*") ) {
82                 my $name = $file;
83                 $name =~ s{^$path/+}{} || die "can't remove $path from $name";
84                 next if -d $file;
85                 $conf->{ $name } =
86                         -l $file ? readlink  $file :
87                         -f $file ? read_file $file :
88                         '?';
89         }
90         return $conf;
91 }
92 sub next_ip($) {
93         my $mac = shift;
94         $mac = format::mac($mac);
95
96         if ( $server::new_clients > 0 ) {
97                 warn "# clients left: ", --$server::new_clients;
98         } else {
99                 warn "W: no new clients accepted";
100                 return '0.0.0.0';
101         }
102
103         my $prefix = $server::ip;
104         $prefix =~ s{\.\d+$}{.};
105         my $addr = $server::ip_from || die;
106         my $ip = $prefix . $addr;
107
108         while ( -e ip_path($ip) || ping::host($ip) ) {
109                 $ip = $prefix . $addr++;
110                 die "all addresses allocated!" if $addr == $server::ip_to;
111                 warn "skip $ip\n";
112         }
113
114         warn "next_ip $ip\n";
115
116         save_ip_mac( $ip, $mac );
117
118         return $ip;
119 }
120
121 sub save_ip_mac {
122         my ($ip,$mac) = @_;
123         $mac = format::mac($mac);
124         return if $mac eq '00:00:00:00:00:00' || $ip eq '0.0.0.0';
125
126         mkdir ip_path($ip) unless -e ip_path($ip);
127
128         my $mac_path = mac_path($mac);
129         unlink $mac_path if -l $mac_path;       # XXX audit?
130         symlink ip_path($ip), $mac_path;
131         write_file( ip_path($ip,'mac'), $mac );
132 }
133
134 sub ip_from_mac($) {
135         my $mac = shift;
136         $mac = format::mac($mac);
137
138         my $mac_path = mac_path $mac;
139         return unless -e $mac_path;
140
141         my $ip;
142
143         if ( -f $mac_path ) {
144                 $ip = read_file $mac_path;
145                 unlink $mac_path;
146                 symlink ip_path($ip), $mac_path;
147                 warn "I: upgrade to mac symlink $mac_path\n";
148         } elsif ( -l $mac_path ) {
149                 $ip = conf_value $mac_path;
150         } else {
151                 die "$mac_path not file or symlink";
152         }
153
154         return $ip;
155 }
156
157 sub mac_from_ip($) {
158         my $ip = shift;
159         conf_value ip_path($ip, 'mac');
160 }
161
162 sub change_ip($$) {
163         my ($old, $new) = @_;
164         return if $old eq $new;
165         my $mac = mac_from_ip($old) || die "no mac for $old";
166         rename ip_path($old), ip_path($new);
167         unlink mac_path($mac);
168         symlink ip_path($new), mac_path($mac);
169         return $new;
170 }
171
172 sub all_ips {
173         sort { ip::to_int($a) cmp ip::to_int($b) }
174         map {
175                 my $ip = $_;
176                 $ip =~ s{^.+/ip/}{};
177                 autocreate_params( $ip );
178                 $ip;
179         } glob("$server::conf/ip/*") 
180 }
181
182 sub remove {
183         my $ip = shift;
184         if ( my $mac = mac_from_ip $ip ) {
185                 unlink mac_path($mac);
186         }
187         rmtree "$server::conf/ip/$ip";
188 }
189
190 sub arp_mac_dev {
191         my $arp = {
192                 map {
193                         my @c = split(/\s+/,$_);
194                         if ( $#c == 5 ) {
195                                 client::save_ip_mac( $c[0], $c[3] );
196                                 ( uc $c[3] => $c[5] )
197                         } else {
198                         }
199                 } read_file('/proc/net/arp')
200         };
201
202         warn "# arp ",dump( $arp );
203         return $arp;
204 }
205
206 sub rebuild_mac_links {
207         warn "# rebuild mac links";
208         foreach my $ip ( all_ips ) {
209                 my $mac = ip_path $ip, 'mac';
210                 if ( -e $mac ) {
211                         $mac = read_file $mac;
212                         chomp $mac;
213                         save_ip_mac( $ip, $mac );
214                         warn "## $ip $mac\n";
215                 }
216         }
217 }
218
219 sub autocreate_params {
220         my $ip = shift;
221         my $mac = mac_from_ip $ip;
222         if ( $mac =~ m{^AC:DE:48:00:00} && ! defined conf( $ip, 'kvm' ) ) {
223                 conf( $ip, 'kvm', default => kvm::nr_from_mac( $mac ) );
224                 warn "# create kvm for $ip";
225         }
226 }
227
228 1;