On 2/21/19 3:10 PM, Oguz Bektas wrote: > Allows adding and deleting qdevice through pvecm. > > Requirements: > * All hosts need corosync-qdevice installed. > * Box serving as QDevice needs corosync-qnetd installed. > * Root SSH access from Proxmox host to QDevice > > Original email with patch from Thomas: > https://pve.proxmox.com/pipermail/pve-devel/2018-July/033041.html > > v2 changes the following: > * use modified methods from corosync-qdevice-net-certutil > quick_start() instead, to avoid a two-way root ssh connection > requirement > * utilise /etc/pve to copy certificates during initialization > * removed some functions/variables which are not needed anymore > > Will have to take a look at the FIXME stuffs inside, so this is more > of a POC as of now. > > Signed-off-by: Oguz Bektas <o.bek...@proxmox.com> > --- > data/PVE/CLI/pvecm.pm | 264 > ++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 264 insertions(+) > > diff --git a/data/PVE/CLI/pvecm.pm b/data/PVE/CLI/pvecm.pm > index 55c3f15..09f81fc 100755 > --- a/data/PVE/CLI/pvecm.pm > +++ b/data/PVE/CLI/pvecm.pm > @@ -63,6 +63,266 @@ __PACKAGE__->register_method ({ > return undef; > }}); > > +my $foreach_member = sub { > + my ($code, $noerr) = @_; > + > + my $members = PVE::Cluster::get_members(); > + foreach my $node (sort keys %$members) { > + if (my $ip = $members->{$node}->{ip}) { > + $code->($node, $ip); > + } else { > + die "cannot get the cluster IP for node '$node'.\n" if !$noerr; > + warn "cannot get the cluster IP for node '$node'.\n"; > + return undef; > + } > + } > +}; > + > +__PACKAGE__->register_method ({ > + name => 'setup_qdevice', > + path => 'setup_qdevice', > + method => 'PUT', > + description => "Setup the use of a QDevice", > + parameters => { > + additionalProperties => 0, > + properties => { > + address => { > + type => 'string', format => 'ip', > + description => "Specifies the network address of an external > corosync QDevice" , > + }, > + network => { > + type => 'string', > + format => 'CIDR', > + description => 'The network which should be used to connect to > the external qdevice', > + optional => 1, > + }, > + force => { > + type => 'boolean', > + description => "Do not throw error on possible dangerous > operations.", > + optional => 1, > + }, > + }, > + }, > + returns => { type => 'null' }, > + > + code => sub { > + my ($param) = @_; > + > + PVE::Corosync::check_conf_exists(1); > + > + if (!PVE::Cluster::check_cfs_quorum(1)) { > + print "ERROR: cluster must have quorum, aborting\n"; > + return undef; > + } > + > + my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
error out if this is empty? qdevice makes no sense on stand-alone node. > + > + die "ERROR: QDevice already configured!\n" > + if defined($conf->{main}->{quorum}->{device}) && !$param->{force}; s/ERROR// as we seldom do this and in the gui you'd get double "error" strings, IIRC. > + > + my $network = $param->{network}; > + > + my $members = PVE::Cluster::get_members(); > + foreach my $node (sort keys %$members) { > + die "All nodes must be online! Node $node is offline, aborting.\n" > + if !$members->{$node}->{online}; > + } > + > + my $qnetd_addr = $param->{address}; > + my $base_dir = "/etc/corosync/qdevice/net"; > + my $db_dir_qnetd = "/etc/corosync/qnetd/nssdb"; > + my $db_dir_node = "$base_dir/nssdb"; > + my $ca_export_base = "qnetd-cacert.crt"; > + my $ca_export_file = "$db_dir_qnetd/$ca_export_base"; > + my $crq_file_base = "qdevice-net-node.crq"; > + my $p12_file_base = "qdevice-net-node.p12"; > + my $qdevice_certutil = "corosync-qdevice-net-certutil"; > + my $qnetd_certutil= "corosync-qnetd-certutil"; > + my $clustername = $conf->{main}->{totem}->{cluster_name}; > + > + > + # copy SSH key to qdevice unnecessary "what" comment. > + run_command(['ssh-copy-id', '-i', '/root/.ssh/id_rsa', > "root\@$qnetd_addr"]); > + > + if (-d $db_dir_node) { > + # FIXME: check on all nodes?! I know this comment is from me, but I'd just check it local only and if something exists on the other node just overwrite it. > + if ($param->{force}) { > + rmtree $db_dir_node; > + } else { > + die "QDevice certificate store already initialised, set force > to delete!\n"; > + } > + } > + > + my $ssh_cmd = ['ssh', '-o', 'BatchMode=yes']; > + my $scp_cmd = ['scp', '-o', 'BatchMode=yes']; > + > + print "Setup certificates for secure connection\n"; > + # initialize qnetd server > + print "INFO: initializing qnetd server\n"; > + run_command([ > + @$ssh_cmd, "root\@$qnetd_addr", > + $qnetd_certutil, "-i", > + "||", "true" # avoid exit code if already initialized just use noerr parameter for run_command if you want to ignore the exit code? > + ]); > + > + # copy CA cert to all nodes and initialize them # "copy" is wrong above, it's already on /etc/pve thus all nodes have it already? > + print "INFO: copying CA cert and initializing on all nodes\n"; > + run_command([@$scp_cmd, "root\@$qnetd_addr:$ca_export_file", > "/etc/pve/$ca_export_base"]); > + $foreach_member->(sub { > + my ($node, $ip) = @_; > + run_command([ > + @$ssh_cmd, "root\@$ip", $qdevice_certutil, "-i", we could but "'-l', 'root'" to the base ssh command to make those calls a bit shorter. > + "-c", "/etc/pve/$ca_export_base" I'd rather have a bit longer line than breaking the same command over multiple, at least if it isn't really really long. > + ]); > + }); > + unlink "/etc/pve/$ca_export_base"; > + > + # generate cert request this comment does not makes any sense if the same info is below in the print statement? > + print "INFO: generating cert request\n"; > + run_command([$qdevice_certutil, "-r", "-n", "$clustername"]); > + > + # copy exported cert request to qnetd server same as above > + print "INFO: copying exported cert request to qnetd server\n"; > + run_command([@$scp_cmd, "$db_dir_node/$crq_file_base", > "root\@$qnetd_addr:/tmp"]); > + > + # sign and export cluster certificate same as above > + print "INFO: sign and export cluster cert\n"; > + run_command([ > + @$ssh_cmd, "root\@$qnetd_addr", "$qnetd_certutil", "-s", "-c", > + "/tmp/$crq_file_base", "-n", "$clustername" > + ]); > + > + # copy exported CRT to master node same as above > + print "INFO: copy exported crt to master node\n"; > + run_command([ > + @$scp_cmd, > "root\@$qnetd_addr:$db_dir_qnetd/cluster-$clustername.crt", > + "$db_dir_node" > + ]);> + > + # import certificate same as above > + print "INFO: import certificate\n"; > + run_command(["$qdevice_certutil", "-M", "-c", > "$db_dir_node/cluster-$clustername.crt"]); > + > + # copy pk12 cert to all nodes and import it same as above > + print "INFO: copy and import pk12 cert to all nodes\n"; > + run_command([@$scp_cmd, "$db_dir_node/$p12_file_base", "/etc/pve/"]); > + $foreach_member->(sub { > + my ($node, $ip) = @_; > + run_command([ > + @$ssh_cmd, "root\@$ip", "$qdevice_certutil", "-m", "-c", > + "/etc/pve/$p12_file_base" > + ]); > + }); > + unlink "/etc/pve/$p12_file_base"; > + > + my $model = "net"; > + my $algorithm = 'ffsplit'; > + if (scalar($members) & 1) { I know this is from me, but maybe we should check that early, before doing so much work. > + if ($param->{force}) { > + $algorithm = 'lms'; > + } else { > + die "Clusters with an odd node count are not officially > supported!\n"; > + } > + } > + > + my $code = sub { > + my $conf = PVE::Cluster::cfs_read_file("corosync.conf"); > + my $quorum_section = $conf->{main}->{quorum}; > + > + die "Qdevice already configured, must be deleted before setting up > new one!\n" > + if defined($quorum_section->{device}); # must not be forced! > + > + my $qdev_section = { > + model => $model, > + "$model" => { > + tls => 'on', > + host => $qnetd_addr, > + algorithm => $algorithm, > + } > + }; > + $qdev_section->{votes} = 1 if $algorithm eq 'ffsplit'; > + > + $quorum_section->{device} = $qdev_section; > + > + PVE::Corosync::atomic_write_conf($conf); > + }; > + > + print "INFO: add QDevice to cluster configuration\n"; > + PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code); > + die $@ if $@; > + > + $foreach_member->(sub { > + my ($node, $ip) = @_; > + print "INFO: start and enable corosync qdevice daemon on node > '$node'...\n"; > + run_command([@$ssh_cmd, $ip, 'systemctl', 'start', > 'corosync-qdevice']); > + run_command([@$ssh_cmd, $ip, 'systemctl', 'enable', > 'corosync-qdevice']); > + }); > + > + run_command(['corosync-cfgtool', '-R']); # do cluster wide config reload > + > + print "Done\n"; > + > + return undef; > +}}); > + > +__PACKAGE__->register_method ({ > + name => 'delete_qdevice', > + path => 'delete_qdevice', > + method => 'DELETE', > + description => "Remove a configured QDevice", > + parameters => { > + additionalProperties => 0, > + properties => {}, > + }, > + returns => { type => 'null' }, > + > + code => sub { > + my ($param) = @_; > + > + PVE::Corosync::check_conf_exists(1); > + > + if (!PVE::Cluster::check_cfs_quorum(1)) { > + # FIXME: *all* nodes must be online > + print "ERROR: cluster must have quorum\n"; > + return undef; > + } > + > + my $ssh_cmd = ['ssh', '-o', 'BatchMode=yes']; > + > + my $code = sub { > + my $conf = PVE::Cluster::cfs_read_file("corosync.conf"); > + my $quorum_section = $conf->{main}->{quorum}; > + > + die "ERROR: No QDevice configured!\n" if > !defined($quorum_section->{device}); > + > + delete $quorum_section->{device}; > + > + PVE::Corosync::atomic_write_conf($conf); > + > + # cleanup qdev state (cert storage) > + my $qdev_state_dir = "/etc/corosync/qdevice"; > + #rmtree $qdev_state_dir; > + > + $foreach_member->(sub { > + my (undef, $ip) = @_; > + run_command([@$ssh_cmd, $ip, '--', 'rm', '-rf', $qdev_state_dir > ]); > + }); > + }; > + > + PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code); > + die $@ if $@; > + > + $foreach_member->(sub { > + my (undef, $ip) = @_; > + run_command([@$ssh_cmd, $ip, 'systemctl', 'stop', > 'corosync-qdevice']); > + run_command([@$ssh_cmd, $ip, 'systemctl', 'disable', > 'corosync-qdevice']); > + }); > + > + run_command(['corosync-cfgtool', '-R']); > + > + return undef; > +}}); > + > __PACKAGE__->register_method ({ > name => 'add', > path => 'add', > @@ -396,6 +656,10 @@ our $cmddef = { > expected => [ __PACKAGE__, 'expected', ['expected']], > updatecerts => [ __PACKAGE__, 'updatecerts', []], > mtunnel => [ __PACKAGE__, 'mtunnel', ['extra-args']], > + qdevice => { > + setup => [ __PACKAGE__, 'setup_qdevice', ['address']], > + delete => [ __PACKAGE__, 'delete_qdevice', []], > + } > }; > > 1; > _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel