]>
git.proxmox.com Git - pve-cluster.git/blob - data/PVE/Cluster.pm
8 use Storable
qw(dclone) ;
13 use Digest
:: HMAC_SHA1
;
30 use Data
:: Dumper
; # fixme: remove
32 # x509 certificate utils
34 my $basedir = "/etc/pve" ;
35 my $authdir = " $basedir/priv " ;
36 my $lockdir = "/etc/pve/priv/lock" ;
38 my $authprivkeyfn = " $authdir/authkey .key" ;
39 my $authpubkeyfn = " $basedir/authkey .pub" ;
40 my $pveca_key_fn = " $authdir/pve -root-ca.key" ;
41 my $pveca_srl_fn = " $authdir/pve -root-ca.srl" ;
42 my $pveca_cert_fn = " $basedir/pve -root-ca.pem" ;
43 # this is just a secret accessable by the web browser
44 # and is used for CSRF prevention
45 my $pvewww_key_fn = " $basedir/pve -www.key" ;
48 my $ssh_rsa_id_priv = "/root/.ssh/id_rsa" ;
49 my $ssh_rsa_id = "/root/.ssh/id_rsa.pub" ;
50 my $ssh_host_rsa_id = "/etc/ssh/ssh_host_rsa_key.pub" ;
51 my $sshglobalknownhosts = "/etc/ssh/ssh_known_hosts" ;
52 my $sshknownhosts = "/etc/pve/priv/known_hosts" ;
53 my $sshauthkeys = "/etc/pve/priv/authorized_keys" ;
54 my $sshd_config_fn = "/etc/ssh/sshd_config" ;
55 my $rootsshauthkeys = "/root/.ssh/authorized_keys" ;
56 my $rootsshauthkeysbackup = "${rootsshauthkeys}.org" ;
57 my $rootsshconfig = "/root/.ssh/config" ;
62 'datacenter.cfg' => 1 ,
64 'corosync.conf.new' => 1 ,
67 'priv/shadow.cfg' => 1 ,
71 'ha/crm_commands' => 1 ,
72 'ha/manager_status' => 1 ,
73 'ha/resources.cfg' => 1 ,
77 # only write output if something fails
83 my $record_output = sub {
89 PVE
:: Tools
:: run_command
( $cmd, outfunc
=> $record_output,
90 errfunc
=> $record_output );
101 sub check_cfs_quorum
{
104 # note: -w filename always return 1 for root, so wee need
105 # to use File::lstat here
106 my $st = File
:: stat :: lstat ( " $basedir/local " );
107 my $quorate = ( $st && (( $st -> mode & 0200 ) != 0 ));
109 die "cluster not ready - no quorum? \n " if ! $quorate && ! $noerr ;
114 sub check_cfs_is_mounted
{
117 my $res = - l
" $basedir/local " ;
119 die "pve configuration filesystem not mounted \n "
128 check_cfs_is_mounted
();
130 my @required_dirs = (
133 " $basedir/nodes/$nodename " ,
134 " $basedir/nodes/$nodename/lxc " ,
135 " $basedir/nodes/$nodename/qemu -server" ,
136 " $basedir/nodes/$nodename/openvz " ,
137 " $basedir/nodes/$nodename/priv " );
139 foreach my $dir ( @required_dirs ) {
141 mkdir ( $dir ) || $! == EEXIST
|| die "unable to create directory ' $dir ' - $!\n " ;
148 return if - f
" $authprivkeyfn " ;
150 check_cfs_is_mounted
();
152 mkdir $authdir || $! == EEXIST
|| die "unable to create dir ' $authdir ' - $!\n " ;
154 my $cmd = "openssl genrsa -out ' $authprivkeyfn ' 2048" ;
155 run_silent_cmd
( $cmd );
157 $cmd = "openssl rsa -in ' $authprivkeyfn ' -pubout -out ' $authpubkeyfn '" ;
163 return if - f
$pveca_key_fn ;
166 run_silent_cmd
([ 'openssl' , 'genrsa' , '-out' , $pveca_key_fn, '2048' ]);
169 die "unable to generate pve ca key: \n $@ " if $@ ;
174 if (- f
$pveca_key_fn && - f
$pveca_cert_fn ) {
180 # we try to generate an unique 'subject' to avoid browser problems
181 # (reused serial numbers, ..)
182 my $nid = ( split ( /\s/ , `md5sum ' $pveca_key_fn '` ))[ 0 ] || time ();
185 run_silent_cmd
([ 'openssl' , 'req' , '-batch' , '-days' , '3650' , '-new' ,
186 '-x509' , '-nodes' , '-key' ,
187 $pveca_key_fn, '-out' , $pveca_cert_fn, '-subj' ,
188 "/CN=Proxmox Virtual Environment/OU= $nid/O =PVE Cluster Manager CA/" ]);
191 die "generating pve root certificate failed: \n $@ " if $@ ;
196 sub gen_pve_ssl_key
{
199 die "no node name specified" if ! $nodename ;
201 my $pvessl_key_fn = " $basedir/nodes/$nodename/pve -ssl.key" ;
203 return if - f
$pvessl_key_fn ;
206 run_silent_cmd
([ 'openssl' , 'genrsa' , '-out' , $pvessl_key_fn, '2048' ]);
209 die "unable to generate pve ssl key for node ' $nodename ': \n $@ " if $@ ;
212 sub gen_pve_www_key
{
214 return if - f
$pvewww_key_fn ;
217 run_silent_cmd
([ 'openssl' , 'genrsa' , '-out' , $pvewww_key_fn, '2048' ]);
220 die "unable to generate pve www key: \n $@ " if $@ ;
226 PVE
:: Tools
:: file_set_contents
( $pveca_srl_fn, $serial );
229 sub gen_pve_ssl_cert
{
230 my ( $force, $nodename, $ip ) = @_ ;
232 die "no node name specified" if ! $nodename ;
233 die "no IP specified" if ! $ip ;
235 my $pvessl_cert_fn = " $basedir/nodes/$nodename/pve -ssl.pem" ;
237 return if ! $force && - f
$pvessl_cert_fn ;
239 my $names = "IP:127.0.0.1,IP:::1,DNS:localhost" ;
241 my $rc = PVE
:: INotify
:: read_file
( 'resolvconf' );
245 my $fqdn = $nodename ;
247 $names .= ",DNS: $nodename " ;
249 if ( $rc && $rc ->{ search
}) {
250 $fqdn = $nodename . "." . $rc ->{ search
};
251 $names .= ",DNS: $fqdn " ;
254 my $sslconf = <<__EOD;
255 RANDFILE = /root/.rnd
260 distinguished_name = req_distinguished_name
261 req_extensions = v3_req
263 string_mask = nombstr
265 [ req_distinguished_name ]
266 organizationalUnitName = PVE Cluster Node
267 organizationName = Proxmox Virtual Environment
271 basicConstraints = CA:FALSE
273 keyUsage = nonRepudiation, digitalSignature, keyEncipherment
274 subjectAltName = $names
277 my $cfgfn = "/tmp/pvesslconf- $$ .tmp" ;
278 my $fh = IO
:: File-
> new ( $cfgfn, "w" );
282 my $reqfn = "/tmp/pvecertreq- $$ .tmp" ;
285 my $pvessl_key_fn = " $basedir/nodes/$nodename/pve -ssl.key" ;
287 run_silent_cmd
([ 'openssl' , 'req' , '-batch' , '-new' , '-config' , $cfgfn,
288 '-key' , $pvessl_key_fn, '-out' , $reqfn ]);
294 die "unable to generate pve certificate request: \n $err " ;
297 update_serial
( "0000000000000000" ) if ! - f
$pveca_srl_fn ;
300 run_silent_cmd
([ 'openssl' , 'x509' , '-req' , '-in' , $reqfn, '-days' , '3650' ,
301 '-out' , $pvessl_cert_fn, '-CAkey' , $pveca_key_fn,
302 '-CA' , $pveca_cert_fn, '-CAserial' , $pveca_srl_fn,
303 '-extfile' , $cfgfn ]);
309 die "unable to generate pve ssl certificate: \n $err " ;
316 sub gen_pve_node_files
{
317 my ( $nodename, $ip, $opt_force ) = @_ ;
319 gen_local_dirs
( $nodename );
323 # make sure we have a (cluster wide) secret
324 # for CSRFR prevention
327 # make sure we have a (per node) private key
328 gen_pve_ssl_key
( $nodename );
330 # make sure we have a CA
331 my $force = gen_pveca_cert
();
333 $force = 1 if $opt_force ;
335 gen_pve_ssl_cert
( $force, $nodename, $ip );
338 my $vzdump_cron_dummy = <<__EOD;
339 # cluster wide vzdump cron schedule
340 # Atomatically generated file - do not edit
342 PATH="/usr/sbin:/usr/bin:/sbin:/bin"
346 sub gen_pve_vzdump_symlink
{
348 my $filename = "/etc/pve/vzdump.cron" ;
350 my $link_fn = "/etc/cron.d/vzdump" ;
352 if ((- f
$filename ) && (! - l
$link_fn )) {
353 rename ( $link_fn, "/root/etc_cron_vzdump.org" ); # make backup if file exists
354 symlink ( $filename, $link_fn );
358 sub gen_pve_vzdump_files
{
360 my $filename = "/etc/pve/vzdump.cron" ;
362 PVE
:: Tools
:: file_set_contents
( $filename, $vzdump_cron_dummy )
365 gen_pve_vzdump_symlink
();
372 my $ipcc_send_rec = sub {
373 my ( $msgid, $data ) = @_ ;
375 my $res = PVE
:: IPCC
:: ipcc_send_rec
( $msgid, $data );
377 die "ipcc_send_rec failed: $!\n " if ! defined ( $res ) && ( $! != 0 );
382 my $ipcc_send_rec_json = sub {
383 my ( $msgid, $data ) = @_ ;
385 my $res = PVE
:: IPCC
:: ipcc_send_rec
( $msgid, $data );
387 die "ipcc_send_rec failed: $!\n " if ! defined ( $res ) && ( $! != 0 );
389 return decode_json
( $res );
392 my $ipcc_get_config = sub {
395 my $bindata = pack "Z*" , $path ;
396 my $res = PVE
:: IPCC
:: ipcc_send_rec
( 6 , $bindata );
397 if (! defined ( $res )) {
398 return undef if ( $! != 0 );
405 my $ipcc_get_status = sub {
406 my ( $name, $nodename ) = @_ ;
408 my $bindata = pack "Z[256]Z[256]" , $name, ( $nodename || "" );
409 return PVE
:: IPCC
:: ipcc_send_rec
( 5 , $bindata );
412 my $ipcc_update_status = sub {
413 my ( $name, $data ) = @_ ;
415 my $raw = ref ( $data ) ? encode_json
( $data ) : $data ;
417 my $bindata = pack "Z[256]Z*" , $name, $raw ;
419 return & $ipcc_send_rec ( 4 , $bindata );
423 my ( $priority, $ident, $tag, $msg ) = @_ ;
425 my $bindata = pack "CCCZ*Z*Z*" , $priority, bytes
:: length ( $ident ) + 1 ,
426 bytes
:: length ( $tag ) + 1 , $ident, $tag, $msg ;
428 return & $ipcc_send_rec ( 7 , $bindata );
431 my $ipcc_get_cluster_log = sub {
432 my ( $user, $max ) = @_ ;
434 $max = 0 if ! defined ( $max );
436 my $bindata = pack "VVVVZ*" , $max, 0 , 0 , 0 , ( $user || "" );
437 return & $ipcc_send_rec ( 8 , $bindata );
444 my $res = & $ipcc_send_rec_json ( 1 );
445 #warn "GOT1: " . Dumper($res);
446 die "no starttime \n " if ! $res ->{ starttime
};
448 if (! $res ->{ starttime
} || ! $versions ->{ starttime
} ||
449 $res ->{ starttime
} != $versions ->{ starttime
}) {
450 #print "detected changed starttime\n";
468 if (! $clinfo ->{ version
} || $clinfo ->{ version
} != $versions ->{ clinfo
}) {
469 #warn "detected new clinfo\n";
470 $clinfo = & $ipcc_send_rec_json ( 2 );
480 if (! $vmlist ->{ version
} || $vmlist ->{ version
} != $versions ->{ vmlist
}) {
481 #warn "detected new vmlist1\n";
482 $vmlist = & $ipcc_send_rec_json ( 3 );
501 return $clinfo ->{ nodelist
};
506 my $nodelist = $clinfo ->{ nodelist
};
510 my $nodename = PVE
:: INotify
:: nodename
();
512 if (! $nodelist || ! $nodelist ->{ $nodename }) {
513 return [ $nodename ];
516 return [ keys %$nodelist ];
519 sub broadcast_tasklist
{
523 & $ipcc_update_status ( "tasklist" , $data );
529 my $tasklistcache = {};
534 my $kvstore = $versions ->{ kvstore
} || {};
536 my $nodelist = get_nodelist
();
539 foreach my $node ( @$nodelist ) {
540 next if $nodename && ( $nodename ne $node );
542 my $ver = $kvstore ->{ $node }->{ tasklist
} if $kvstore ->{ $node };
543 my $cd = $tasklistcache ->{ $node };
544 if (! $cd || ! $ver || ! $cd ->{ version
} ||
545 ( $cd ->{ version
} != $ver )) {
546 my $raw = & $ipcc_get_status ( "tasklist" , $node ) || '[]' ;
547 my $data = decode_json
( $raw );
549 $cd = $tasklistcache ->{ $node } = {
553 } elsif ( $cd && $cd ->{ data
}) {
554 push @$res, @{ $cd ->{ data
}};
558 syslog
( 'err' , $err ) if $err ;
565 my ( $rrdid, $data ) = @_ ;
568 & $ipcc_update_status ( "rrd/ $rrdid " , $data );
575 my $last_rrd_dump = 0 ;
576 my $last_rrd_data = "" ;
582 my $diff = $ctime - $last_rrd_dump ;
584 return $last_rrd_data ;
589 $raw = & $ipcc_send_rec ( 10 );
601 while ( $raw =~ s/^(.*)\n// ) {
602 my ( $key, @ela ) = split ( /:/ , $1 );
604 next if !( scalar ( @ela ) > 1 );
605 $res ->{ $key } = \
@ela ;
609 $last_rrd_dump = $ctime ;
610 $last_rrd_data = $res ;
615 sub create_rrd_data
{
616 my ( $rrdname, $timeframe, $cf ) = @_ ;
618 my $rrddir = "/var/lib/rrdcached/db" ;
620 my $rrd = " $rrddir/$rrdname " ;
624 day
=> [ 60 * 30 , 70 ],
625 week
=> [ 60 * 180 , 70 ],
626 month
=> [ 60 * 720 , 70 ],
627 year
=> [ 60 * 10080 , 70 ],
630 my ( $reso, $count ) = @{ $setup ->{ $timeframe }};
631 my $ctime = $reso*int ( time ()/ $reso );
632 my $req_start = $ctime - $reso*$count ;
634 $cf = "AVERAGE" if ! $cf ;
642 my $socket = "/var/run/rrdcached.sock" ;
643 push @args, "--daemon" => "unix: $socket " if - S
$socket ;
645 my ( $start, $step, $names, $data ) = RRDs
:: fetch
( $rrd, $cf, @args );
647 my $err = RRDs
:: error
;
648 die "RRD error: $err\n " if $err ;
650 die "got wrong time resolution ( $step != $reso ) \n "
654 my $fields = scalar ( @$names );
655 for my $line ( @$data ) {
656 my $entry = { 'time' => $start };
659 for ( my $i = 0 ; $i < $fields ; $i++ ) {
660 my $name = $names ->[ $i ];
661 if ( defined ( my $val = $line ->[ $i ])) {
662 $entry ->{ $name } = $val ;
664 # we only add entryies with all data defined
665 # extjs chart has problems with undefined values
669 push @$res, $entry if ! $found_undefs ;
675 sub create_rrd_graph
{
676 my ( $rrdname, $timeframe, $ds, $cf ) = @_ ;
678 # Using RRD graph is clumsy - maybe it
679 # is better to simply fetch the data, and do all display
680 # related things with javascript (new extjs html5 graph library).
682 my $rrddir = "/var/lib/rrdcached/db" ;
684 my $rrd = " $rrddir/$rrdname " ;
686 my @ids = PVE
:: Tools
:: split_list
( $ds );
688 my $ds_txt = join ( '_' , @ids );
690 my $filename = "${rrd}_${ds_txt}.png" ;
694 day
=> [ 60 * 30 , 70 ],
695 week
=> [ 60 * 180 , 70 ],
696 month
=> [ 60 * 720 , 70 ],
697 year
=> [ 60 * 10080 , 70 ],
700 my ( $reso, $count ) = @{ $setup ->{ $timeframe }};
703 "--imgformat" => "PNG" ,
707 "--start" => - $reso*$count,
711 my $socket = "/var/run/rrdcached.sock" ;
712 push @args, "--daemon" => "unix: $socket " if - S
$socket ;
714 my @coldef = ( '#00ddff' , '#ff0000' );
716 $cf = "AVERAGE" if ! $cf ;
719 foreach my $id ( @ids ) {
720 my $col = $coldef [ $i++ ] || die "fixme: no color definition" ;
721 push @args, "DEF:${id}= $rrd :${id}: $cf " ;
723 if ( $id eq 'cpu' || $id eq 'iowait' ) {
724 push @args, "CDEF:${id}_per=${id},100,*" ;
725 $dataid = "${id}_per" ;
727 push @args, "LINE2:${dataid}${col}:${id}" ;
730 push @args, '--full-size-mode' ;
732 # we do not really store data into the file
733 my $res = RRDs
:: graphv
( '' , @args );
735 my $err = RRDs
:: error
;
736 die "RRD error: $err\n " if $err ;
738 return { filename
=> $filename, image
=> $res ->{ image
} };
741 # a fast way to read files (avoid fuse overhead)
745 return & $ipcc_get_config ( $path );
748 sub get_cluster_log
{
749 my ( $user, $max ) = @_ ;
751 return & $ipcc_get_cluster_log ( $user, $max );
756 sub cfs_register_file
{
757 my ( $filename, $parser, $writer ) = @_ ;
759 $observed ->{ $filename } || die "unknown file ' $filename '" ;
761 die "file ' $filename ' already registered" if $file_info ->{ $filename };
763 $file_info ->{ $filename } = {
769 my $ccache_read = sub {
770 my ( $filename, $parser, $version ) = @_ ;
772 $ccache ->{ $filename } = {} if ! $ccache ->{ $filename };
774 my $ci = $ccache ->{ $filename };
776 if (! $ci ->{ version
} || ! $version || $ci ->{ version
} != $version ) {
777 # we always call the parser, even when the file does not exists
778 # (in that case $data is undef)
779 my $data = get_config
( $filename );
780 $ci ->{ data
} = & $parser ( "/etc/pve/ $filename " , $data );
781 $ci ->{ version
} = $version ;
784 my $res = ref ( $ci ->{ data
}) ? dclone
( $ci ->{ data
}) : $ci ->{ data
};
789 sub cfs_file_version
{
794 if ( $filename =~ m!^nodes/[^/]+/(openvz|qemu-server)/(\d+)\.conf$! ) {
795 my ( $type, $vmid ) = ( $1, $2 );
796 if ( $vmlist && $vmlist ->{ ids
} && $vmlist ->{ ids
}->{ $vmid }) {
797 $version = $vmlist ->{ ids
}->{ $vmid }->{ version
};
799 $infotag = "/ $type/ " ;
800 } elsif ( $filename =~ m!^nodes/[^/]+/lxc/(\d+)/config$! ) {
802 if ( $vmlist && $vmlist ->{ ids
} && $vmlist ->{ ids
}->{ $vmid }) {
803 $version = $vmlist ->{ ids
}->{ $vmid }->{ version
};
807 $infotag = $filename ;
808 $version = $versions ->{ $filename };
811 my $info = $file_info ->{ $infotag } ||
812 die "unknown file type ' $filename ' \n " ;
814 return wantarray ?
( $version, $info ) : $version ;
820 my ( $version, $info ) = cfs_file_version
( $filename );
821 my $parser = $info ->{ parser
};
823 return & $ccache_read ( $filename, $parser, $version );
827 my ( $filename, $data ) = @_ ;
829 my ( $version, $info ) = cfs_file_version
( $filename );
831 my $writer = $info ->{ writer
} || die "no writer defined" ;
833 my $fsname = "/etc/pve/ $filename " ;
835 my $raw = & $writer ( $fsname, $data );
837 if ( my $ci = $ccache ->{ $filename }) {
838 $ci ->{ version
} = undef ;
841 PVE
:: Tools
:: file_set_contents
( $fsname, $raw );
845 my ( $lockid, $timeout, $code, @param ) = @_ ;
849 # this timeout is for aquire the lock
850 $timeout = 10 if ! $timeout ;
852 my $filename = " $lockdir/$lockid " ;
854 my $msg = "can't aquire cfs lock ' $lockid '" ;
861 die " $msg : pve cluster filesystem not online. \n " ;
864 local $SIG { ALRM
} = sub { die "got lock request timeout \n " ; };
868 if (!( mkdir $filename )) {
869 print STDERR
"trying to aquire cfs lock ' $lockid ' ..." ;
871 if (!( mkdir $filename )) {
872 ( utime 0 , 0 , $filename ); # cfs unlock request
874 print STDERR
" OK \n " ;
881 # fixed command timeout: cfs locks have a timeout of 120
882 # using 60 gives us another 60 seconds to abort the task
884 local $SIG { ALRM
} = sub { die "got lock timeout - aborting command \n " ; };
886 cfs_update
(); # make sure we read latest versions inside code()
888 $res = & $code ( @param );
897 if ( $err && ( $err eq "got lock request timeout \n " ) &&
898 ! check_cfs_quorum
()){
899 $err = " $msg : no quorum! \n " ;
902 if (! $err || $err !~ /^got lock timeout -/ ) {
903 rmdir $filename ; # cfs unlock
917 my ( $filename, $timeout, $code, @param ) = @_ ;
919 my $info = $observed ->{ $filename } || die "unknown file ' $filename '" ;
921 my $lockid = "file- $filename " ;
922 $lockid =~ s/[.\/]/ _
/ g
;
924 & $cfs_lock ( $lockid, $timeout, $code, @param );
927 sub cfs_lock_storage
{
928 my ( $storeid, $timeout, $code, @param ) = @_ ;
930 my $lockid = "storage- $storeid " ;
932 & $cfs_lock ( $lockid, $timeout, $code, @param );
950 my ( $priority, $ident, $msg ) = @_ ;
952 if ( my $tmp = $log_levels ->{ $priority }) {
956 die "need numeric log priority" if $priority !~ /^\d+$/ ;
958 my $tag = PVE
:: SafeSyslog
:: tag
();
960 $msg = "empty message" if ! $msg ;
962 $ident = "" if ! $ident ;
963 $ident = encode
( "ascii" , decode_utf8
( $ident ),
964 sub { sprintf " \\ u %04x " , shift });
966 my $utf8 = decode_utf8
( $msg );
968 my $ascii = encode
( "ascii" , $utf8, sub { sprintf " \\ u %04x " , shift });
971 syslog
( $priority, "< %s > %s " , $ident, $ascii );
973 syslog
( $priority, " %s " , $ascii );
976 eval { & $ipcc_log ( $priority, $ident, $tag, $ascii ); };
978 syslog
( "err" , "writing cluster log failed: $@ " ) if $@ ;
981 sub check_vmid_unused
{
982 my ( $vmid, $noerr ) = @_ ;
984 my $vmlist = get_vmlist
();
986 my $d = $vmlist ->{ ids
}->{ $vmid };
987 return 1 if ! defined ( $d );
989 return undef if $noerr ;
991 die "VM $vmid already exists \n " if $d ->{ type
} eq 'qemu' ;
993 die "CT $vmid already exists \n " ;
996 sub check_node_exists
{
997 my ( $nodename, $noerr ) = @_ ;
999 my $nodelist = $clinfo ->{ nodelist
};
1000 return 1 if $nodelist && $nodelist ->{ $nodename };
1002 return undef if $noerr ;
1004 die "no such cluster node ' $nodename ' \n " ;
1007 # this is also used to get the IP of the local node
1008 sub remote_node_ip
{
1009 my ( $nodename, $noerr ) = @_ ;
1011 my $nodelist = $clinfo ->{ nodelist
};
1012 if ( $nodelist && $nodelist ->{ $nodename }) {
1013 if ( my $ip = $nodelist ->{ $nodename }->{ ip
}) {
1014 return $ip if ! wantarray ;
1015 my $family = $nodelist ->{ $nodename }->{ address_family
};
1017 $nodelist ->{ $nodename }->{ address_family
} =
1019 PVE
:: Tools
:: get_host_address_family
( $ip );
1021 return ( $ip, $family );
1025 # fallback: try to get IP by other means
1026 my ( $family, $packed_ip );
1029 my @res = PVE
:: Tools
:: getaddrinfo_all
( $nodename );
1030 $family = $res [ 0 ]->{ family
};
1031 $packed_ip = ( PVE
:: Tools
:: unpack_sockaddr_in46
( $res [ 0 ]->{ addr
}))[ 2 ];
1035 die "hostname lookup failed: \n $@ " if ! $noerr ;
1039 my $ip = Socket
:: inet_ntop
( $family, $packed_ip );
1040 if ( $ip =~ m/^127\.|^::1$/ ) {
1041 die "hostname lookup failed - got local IP address ( $nodename = $ip ) \n " if ! $noerr ;
1045 return wantarray ?
( $ip, $family ) : $ip ;
1048 # ssh related utility functions
1050 sub ssh_merge_keys
{
1051 # remove duplicate keys in $sshauthkeys
1052 # ssh-copy-id simply add keys, so the file can grow to large
1055 if (- f
$sshauthkeys ) {
1056 $data = PVE
:: Tools
:: file_get_contents
( $sshauthkeys, 128 * 1024 );
1061 if (- f
$rootsshauthkeysbackup ) {
1063 $data .= PVE
:: Tools
:: file_get_contents
( $rootsshauthkeysbackup, 128 * 1024 );
1068 # always add ourself
1069 if (- f
$ssh_rsa_id ) {
1070 my $pub = PVE
:: Tools
:: file_get_contents
( $ssh_rsa_id );
1072 $data .= " \n $pub\n " ;
1077 my @lines = split ( /\n/ , $data );
1078 foreach my $line ( @lines ) {
1079 if ( $line !~ /^#/ && $line =~ m/(^|\s)ssh-(rsa|dsa)\s+(\S+)\s+\S+$/ ) {
1080 next if $vhash ->{ $3 }++;
1082 $newdata .= " $line\n " ;
1085 PVE
:: Tools
:: file_set_contents
( $sshauthkeys, $newdata, 0600 );
1087 if ( $found_backup && - l
$rootsshauthkeys ) {
1088 # everything went well, so we can remove the backup
1089 unlink $rootsshauthkeysbackup ;
1093 sub setup_sshd_config
{
1095 my $conf = PVE
:: Tools
:: file_get_contents
( $sshd_config_fn );
1097 return if $conf =~ m/^PermitRootLogin\s+yes\s*$/m ;
1099 if ( $conf !~ s/^#?PermitRootLogin.*$/PermitRootLogin yes/m ) {
1101 $conf .= " \n PermitRootLogin yes \n " ;
1104 PVE
:: Tools
:: file_set_contents
( $sshd_config_fn, $conf );
1106 PVE
:: Tools
:: run_command
([ 'systemctl' , 'reload-or-restart' , 'sshd' ]);
1109 sub setup_rootsshconfig
{
1111 # create ssh key if it does not exist
1112 if (! - f
$ssh_rsa_id ) {
1113 mkdir '/root/.ssh/' ;
1114 system ( "echo|ssh-keygen -t rsa -N '' -b 2048 -f ${ssh_rsa_id_priv}" );
1117 # create ssh config if it does not exist
1118 if (! - f
$rootsshconfig ) {
1120 if ( my $fh = IO
:: File-
> new ( $rootsshconfig, O_CREAT
| O_WRONLY
| O_EXCL
, 0640 )) {
1121 # this is the default ciphers list from debian openssl0.9.8 except blowfish is added as prefered
1122 print $fh "Ciphers blowfish-cbc,aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc \n " ;
1128 sub setup_ssh_keys
{
1134 if (! - f
$sshauthkeys ) {
1136 if (- f
$rootsshauthkeys ) {
1137 $old = PVE
:: Tools
:: file_get_contents
( $rootsshauthkeys, 128 * 1024 );
1139 if ( my $fh = IO
:: File-
> new ( $sshauthkeys, O_CREAT
| O_WRONLY
| O_EXCL
, 0400 )) {
1140 PVE
:: Tools
:: safe_print
( $sshauthkeys, $fh, $old ) if $old ;
1146 warn "can't create shared ssh key database ' $sshauthkeys ' \n "
1147 if ! - f
$sshauthkeys ;
1149 if (- f
$rootsshauthkeys && ! - l
$rootsshauthkeys ) {
1150 if (! rename ( $rootsshauthkeys , $rootsshauthkeysbackup )) {
1151 warn "rename $rootsshauthkeys failed - $!\n " ;
1155 if (! - l
$rootsshauthkeys ) {
1156 symlink $sshauthkeys, $rootsshauthkeys ;
1159 if (! - l
$rootsshauthkeys ) {
1160 warn "can't create symlink for ssh keys ' $rootsshauthkeys ' -> ' $sshauthkeys ' \n " ;
1162 unlink $rootsshauthkeysbackup if $import_ok ;
1166 sub ssh_unmerge_known_hosts
{
1167 return if ! - l
$sshglobalknownhosts ;
1170 $old = PVE
:: Tools
:: file_get_contents
( $sshknownhosts, 128 * 1024 )
1171 if - f
$sshknownhosts ;
1173 PVE
:: Tools
:: file_set_contents
( $sshglobalknownhosts, $old );
1176 sub ssh_merge_known_hosts
{
1177 my ( $nodename, $ip_address, $createLink ) = @_ ;
1179 die "no node name specified" if ! $nodename ;
1180 die "no ip address specified" if ! $ip_address ;
1184 if (! - f
$sshknownhosts ) {
1185 if ( my $fh = IO
:: File-
> new ( $sshknownhosts, O_CREAT
| O_WRONLY
| O_EXCL
, 0600 )) {
1190 my $old = PVE
:: Tools
:: file_get_contents
( $sshknownhosts, 128 * 1024 );
1194 if ((! - l
$sshglobalknownhosts ) && (- f
$sshglobalknownhosts )) {
1195 $new = PVE
:: Tools
:: file_get_contents
( $sshglobalknownhosts, 128 * 1024 );
1198 my $hostkey = PVE
:: Tools
:: file_get_contents
( $ssh_host_rsa_id );
1199 die "can't parse $ssh_rsa_id " if $hostkey !~ m/^(ssh-rsa\s\S+)(\s.*)?$/ ;
1208 my $merge_line = sub {
1209 my ( $line, $all ) = @_ ;
1211 if ( $line =~ m/^(\S+)\s(ssh-rsa\s\S+)(\s.*)?$/ ) {
1214 if (! $vhash ->{ $key }) {
1216 if ( $key =~ m/\|1\|([^\|\s]+)\|([^\|\s]+)$/ ) {
1217 my $salt = decode_base64
( $1 );
1219 my $hmac = Digest
:: HMAC_SHA1-
> new ( $salt );
1220 $hmac -> add ( $nodename );
1221 my $hd = $hmac -> b64digest . '=' ;
1222 if ( $digest eq $hd ) {
1223 if ( $rsakey eq $hostkey ) {
1224 $found_nodename = 1 ;
1229 $hmac = Digest
:: HMAC_SHA1-
> new ( $salt );
1230 $hmac -> add ( $ip_address );
1231 $hd = $hmac -> b64digest . '=' ;
1232 if ( $digest eq $hd ) {
1233 if ( $rsakey eq $hostkey ) {
1234 $found_local_ip = 1 ;
1247 while ( $old && $old =~ s/^((.*?)(\n|$))// ) {
1249 next if $line =~ m/^\s*$/ ; # skip empty lines
1250 next if $line =~ m/^#/ ; # skip comments
1251 & $merge_line ( $line, 1 );
1254 while ( $new && $new =~ s/^((.*?)(\n|$))// ) {
1256 next if $line =~ m/^\s*$/ ; # skip empty lines
1257 next if $line =~ m/^#/ ; # skip comments
1258 & $merge_line ( $line );
1262 my $add_known_hosts_entry = sub {
1263 my ( $name, $hostkey ) = @_ ;
1265 my $hmac = Digest
:: HMAC_SHA1-
> new ( " $addIndex " . time ());
1266 my $b64salt = $hmac -> b64digest . '=' ;
1267 $hmac = Digest
:: HMAC_SHA1-
> new ( decode_base64
( $b64salt ));
1269 my $digest = $hmac -> b64digest . '=' ;
1270 $data .= "|1| $b64salt | $digest $hostkey\n " ;
1273 if (! $found_nodename || ! $found_local_ip ) {
1274 & $add_known_hosts_entry ( $nodename, $hostkey ) if ! $found_nodename ;
1275 & $add_known_hosts_entry ( $ip_address, $hostkey ) if ! $found_local_ip ;
1278 PVE
:: Tools
:: file_set_contents
( $sshknownhosts, $data );
1280 return if ! $createLink ;
1282 unlink $sshglobalknownhosts ;
1283 symlink $sshknownhosts, $sshglobalknownhosts ;
1285 warn "can't create symlink for ssh known hosts ' $sshglobalknownhosts ' -> ' $sshknownhosts ' \n "
1286 if ! - l
$sshglobalknownhosts ;
1290 my $datacenter_schema = {
1292 additionalProperties
=> 0 ,
1297 description
=> "Default keybord layout for vnc server." ,
1298 enum
=> PVE
:: Tools
:: kvmkeymaplist
(),
1303 description
=> "Default GUI language." ,
1304 enum
=> [ 'en' , 'de' ],
1309 description
=> "Specify external http proxy which is used for downloads (example: 'http://username:password\ @host :port/')" ,
1310 pattern
=> "http://.*" ,
1312 migration_unsecure
=> {
1315 description
=> "Migration is secure using SSH tunnel by default. For secure private networks you can disable it to speed up migration." ,
1320 description
=> "Select the default Console viewer. You can either use the builtin java applet (VNC), an external virt-viewer comtatible application (SPICE), or an HTML5 based viewer (noVNC)." ,
1321 enum
=> [ 'applet' , 'vv' , 'html5' ],
1326 format
=> 'email-opt' ,
1327 description
=> "Specify email address to send notification from (default is root @\$hostname )" ,
1332 # make schema accessible from outside (for documentation)
1333 sub get_datacenter_schema
{ return $datacenter_schema };
1335 sub parse_datacenter_config
{
1336 my ( $filename, $raw ) = @_ ;
1338 return PVE
:: JSONSchema
:: parse_config
( $datacenter_schema, $filename, $raw );
1341 sub write_datacenter_config
{
1342 my ( $filename, $cfg ) = @_ ;
1344 return PVE
:: JSONSchema
:: dump_config
( $datacenter_schema, $filename, $cfg );
1347 cfs_register_file
( 'datacenter.cfg' ,
1348 \
& parse_datacenter_config
,
1349 \
& write_datacenter_config
);
1351 # a very simply parser ...
1352 sub parse_corosync_conf
{
1353 my ( $filename, $raw ) = @_ ;
1357 my $digest = Digest
:: SHA
:: sha1_hex
( defined ( $raw ) ?
$raw : '' );
1360 $raw =~ s/\r?\n/ /g ;
1365 my @tokens = split ( /\s/ , $raw );
1367 my $conf = { section
=> 'main' , children
=> [] };
1370 my $section = $conf ;
1372 while ( defined ( my $token = shift @tokens )) {
1373 my $nexttok = $tokens [ 0 ];
1375 if ( $nexttok && ( $nexttok eq '{' )) {
1376 shift @tokens ; # skip '{'
1381 push @{ $section ->{ children
}}, $new_section ;
1382 push @$stack, $section ;
1383 $section = $new_section ;
1387 if ( $token eq '}' ) {
1388 $section = pop @$stack ;
1389 die "parse error - uncexpected '}' \n " if ! $section ;
1394 die "missing ':' after key ' $key ' \n " if ! ( $key =~ s/:$// );
1396 die "parse error - no value for ' $key ' \n " if ! defined ( $nexttok );
1397 my $value = shift @tokens ;
1399 push @{ $section ->{ children
}}, { key
=> $key, value
=> $value };
1402 $conf ->{ digest
} = $digest ;
1407 my $dump_corosync_section ;
1408 $dump_corosync_section = sub {
1409 my ( $section, $prefix ) = @_ ;
1411 my $raw = $prefix . $section ->{ section
} . " { \n " ;
1413 my @list = grep { defined ( $_ ->{ key
}) } @{ $section ->{ children
}};
1414 foreach my $child ( sort { $a ->{ key
} cmp $b ->{ key
}} @list ) {
1415 $raw .= $prefix . " $child ->{key}: $child ->{value} \n " ;
1418 @list = grep { defined ( $_ ->{ section
}) } @{ $section ->{ children
}};
1419 foreach my $child ( sort { $a ->{ section
} cmp $b ->{ section
}} @list ) {
1420 $raw .= & $dump_corosync_section ( $child, " $prefix " );
1423 $raw .= $prefix . "} \n\n " ;
1429 sub write_corosync_conf
{
1430 my ( $filename, $conf ) = @_ ;
1436 die "no main section" if $conf ->{ section
} ne 'main' ;
1438 my @list = grep { defined ( $_ ->{ key
}) } @{ $conf ->{ children
}};
1439 foreach my $child ( sort { $a ->{ key
} cmp $b ->{ key
}} @list ) {
1440 $raw .= " $child ->{key}: $child ->{value} \n " ;
1443 @list = grep { defined ( $_ ->{ section
}) } @{ $conf ->{ children
}};
1444 foreach my $child ( sort { $a ->{ section
} cmp $b ->{ section
}} @list ) {
1445 $raw .= & $dump_corosync_section ( $child, $prefix );
1451 sub corosync_conf_version
{
1452 my ( $conf, $noerr, $new_value ) = @_ ;
1454 foreach my $child (@{ $conf ->{ children
}}) {
1455 next if ! defined ( $child ->{ section
});
1456 if ( $child ->{ section
} eq 'totem' ) {
1457 foreach my $e (@{ $child ->{ children
}}) {
1458 next if ! defined ( $e ->{ key
});
1459 if ( $e ->{ key
} eq 'config_version' ) {
1461 $e ->{ value
} = $new_value ;
1463 } elsif ( my $version = int ( $e ->{ value
})) {
1472 return undef if $noerr ;
1474 die "invalid corosync config - unable to read version \n " ;
1477 # read only - use "rename corosync.conf.new corosync.conf" to write
1478 PVE
:: Cluster
:: cfs_register_file
( 'corosync.conf' , \
& parse_corosync_conf
);
1479 # this is read/write
1480 PVE
:: Cluster
:: cfs_register_file
( 'corosync.conf.new' , \
& parse_corosync_conf
,
1481 \
& write_corosync_conf
);