612ec0a5c4
We use a simple prefix notation to express complex expressions.
468 lines
10 KiB
Perl
468 lines
10 KiB
Perl
package PVE::API2::HAConfig;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use PVE::SafeSyslog;
|
|
use PVE::Tools;
|
|
use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
|
|
use PVE::RESTHandler;
|
|
use PVE::RPCEnvironment;
|
|
use PVE::JSONSchema qw(get_standard_option);
|
|
use PVE::Exception qw(raise_param_exc);
|
|
|
|
use base qw(PVE::RESTHandler);
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'index',
|
|
path => '',
|
|
method => 'GET',
|
|
description => "Directory index.",
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Audit' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {},
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => "object",
|
|
properties => {
|
|
id => { type => 'string' },
|
|
},
|
|
},
|
|
links => [ { rel => 'child', href => "{id}" } ],
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $res = [
|
|
{ id => 'config' },
|
|
{ id => 'changes' },
|
|
{ id => 'groups' },
|
|
];
|
|
|
|
return $res;
|
|
}});
|
|
|
|
my $load_cluster_conf = sub {
|
|
my $oldconf;
|
|
my $newconf;
|
|
|
|
my $code = sub {
|
|
$oldconf = PVE::Cluster::cfs_read_file('cluster.conf');
|
|
$newconf = PVE::Cluster::cfs_read_file('cluster.conf.new');
|
|
};
|
|
|
|
cfs_lock_file('cluster.conf', undef, $code);
|
|
die $@ if $@;
|
|
|
|
if (!$newconf->{children}) {
|
|
return wantarray ? ($oldconf, undef) : $oldconf;
|
|
}
|
|
|
|
return $newconf if !wantarray;
|
|
|
|
# test if there is different content
|
|
|
|
my $oldstr = PVE::Cluster::write_cluster_conf("fake.cfg", $oldconf);
|
|
my $newstr = PVE::Cluster::write_cluster_conf("fake.cfg", $newconf);
|
|
|
|
return ($oldconf, undef) if $oldstr eq $newstr; # same content
|
|
|
|
# comput diff to display on GUI
|
|
|
|
my $oldfn = '/etc/pve/cluster.conf';
|
|
my $newfn = '/etc/pve/cluster.conf.new';
|
|
|
|
my $diff = PVE::INotify::ccache_compute_diff($oldfn, $newfn);
|
|
|
|
return ($newconf, $diff);
|
|
};
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'get_config',
|
|
path => 'config',
|
|
method => 'GET',
|
|
description => "Read cluster configuartion (cluster.conf). If you have any uncommitted changes in cluster.conf.new that content is returned instead.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Audit' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {},
|
|
},
|
|
returns => {
|
|
type => "object",
|
|
properties => {},
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
|
|
my ($conf, $diff) = &$load_cluster_conf();
|
|
|
|
$rpcenv->set_result_attrib('changes', $diff);
|
|
|
|
return $conf;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'get_changes',
|
|
path => 'changes',
|
|
method => 'GET',
|
|
description => "Get pending changes (unified diff between cluster.conf and cluster.conf.new",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Audit' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {},
|
|
},
|
|
returns => { type => "string", optional => 1 },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my ($conf, $diff) = &$load_cluster_conf();
|
|
|
|
return $diff;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'revert_changes',
|
|
path => 'changes',
|
|
method => 'DELETE',
|
|
description => "Revert pending changes (remove cluster.conf.new)",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Modify' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {},
|
|
},
|
|
returns => { type => "null" },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
if (!unlink("/etc/pve/cluster.conf.new")) {
|
|
die "unlink failed - $!\n";
|
|
}
|
|
|
|
return;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'commit_config',
|
|
path => 'changes',
|
|
method => 'POST',
|
|
description => "Commit cluster configuartion. Pending changes from cluster.conf.new are written to cluster.conf. This triggers a CMan reload on all nodes.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Modify' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {},
|
|
},
|
|
returns => { type => "null" },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $cmd = ['ccs_config_validate', '-l', '/etc/pve/cluster.conf.new'];
|
|
my $out = '';
|
|
eval {
|
|
# first line on stderr contains error message
|
|
PVE::Tools::run_command($cmd, errfunc => sub { $out .= shift if !$out; });
|
|
};
|
|
if (my $err = $@) {
|
|
chomp $out;
|
|
$out = "unknown error" if !$out;
|
|
die "config validation failed: $out\n";
|
|
}
|
|
|
|
PVE::Cluster::check_cfs_quorum();
|
|
|
|
my $code = sub {
|
|
if (!rename('/etc/pve/cluster.conf.new', '/etc/pve/cluster.conf')) {
|
|
die "commit failed - $!\n";
|
|
}
|
|
};
|
|
|
|
cfs_lock_file('cluster.conf', undef, $code);
|
|
die $@ if $@;
|
|
|
|
return;
|
|
}});
|
|
|
|
my $read_cluster_conf_new = sub {
|
|
|
|
my $conf = PVE::Cluster::cfs_read_file('cluster.conf.new');
|
|
if (!$conf->{children}) {
|
|
$conf = PVE::Cluster::cfs_read_file('cluster.conf');
|
|
}
|
|
return $conf;
|
|
};
|
|
|
|
my $update_cluster_conf_new = sub {
|
|
my ($conf) = @_;
|
|
$conf->{children}->[0]->{config_version}++;
|
|
cfs_write_file('cluster.conf.new', $conf);
|
|
};
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'list_groups',
|
|
path => 'groups',
|
|
method => 'GET',
|
|
description => "List resource groups.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Audit' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {},
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => "object",
|
|
properties => {},
|
|
},
|
|
links => [ { rel => 'child', href => "{id}" } ],
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $conf = &$read_cluster_conf_new();
|
|
|
|
my $res = [];
|
|
|
|
my $rmsec = PVE::Cluster::cluster_conf_lookup_rm_section($conf, 0, 1);
|
|
return $res if !$rmsec;
|
|
|
|
foreach my $child (@{$rmsec->{children}}) {
|
|
if ($child->{text} eq 'pvevm') {
|
|
push @$res, { id => "$child->{text}:$child->{vmid}" };
|
|
} elsif ($child->{text} eq 'service') {
|
|
push @$res, { id => "$child->{text}:$child->{name}" };
|
|
}
|
|
}
|
|
|
|
return $res;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'create_group',
|
|
path => 'groups',
|
|
method => 'POST',
|
|
description => "Create a new resource groups.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Modify' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
vmid => get_standard_option('pve-vmid'),
|
|
autostart => {
|
|
optional => 1,
|
|
type => 'boolean',
|
|
description => "Service is started when a quorum forms.",
|
|
}
|
|
},
|
|
},
|
|
returns => { type => "null" },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $vmlist = PVE::Cluster::get_vmlist();
|
|
raise_param_exc({ id => "no such vmid '$param->{vmid}'"})
|
|
if !($vmlist && $vmlist->{ids} && $vmlist->{ids}->{$param->{vmid}});
|
|
|
|
PVE::Cluster::check_cfs_quorum();
|
|
|
|
my $code = sub {
|
|
|
|
my $conf = &$read_cluster_conf_new();
|
|
|
|
my $pvevm = PVE::Cluster::cluster_conf_lookup_pvevm($conf, 1, $param->{vmid});
|
|
|
|
$pvevm->{autostart} = $param->{autostart} ? 1 : 0;
|
|
|
|
&$update_cluster_conf_new($conf);
|
|
};
|
|
|
|
cfs_lock_file('cluster.conf', undef, $code);
|
|
die $@ if $@;
|
|
|
|
return;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'update_group',
|
|
path => 'groups/{id}',
|
|
method => 'PUT',
|
|
description => "Update resource groups settings.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Modify' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
id => {
|
|
type => 'string',
|
|
description => "The resource group ID (for example 'pvevm:200').",
|
|
},
|
|
autostart => {
|
|
optional => 1,
|
|
type => 'boolean',
|
|
description => "Service is started when a quorum forms.",
|
|
}
|
|
},
|
|
},
|
|
returns => { type => "null" },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $vmid;
|
|
|
|
if ($param->{id} =~ m/^pvevm:(\d+)$/) {
|
|
$vmid = int($1);
|
|
} else {
|
|
raise_param_exc({ id => "unsupported group type '$param->{id}'"});
|
|
}
|
|
|
|
PVE::Cluster::check_cfs_quorum();
|
|
|
|
my $code = sub {
|
|
|
|
my $conf = &$read_cluster_conf_new();
|
|
|
|
my $pvevm = PVE::Cluster::cluster_conf_lookup_pvevm($conf, 0, $vmid);
|
|
|
|
$pvevm->{autostart} = $param->{autostart} ? 1 : 0;
|
|
|
|
&$update_cluster_conf_new($conf);
|
|
};
|
|
|
|
cfs_lock_file('cluster.conf', undef, $code);
|
|
die $@ if $@;
|
|
|
|
return;
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'read_group',
|
|
path => 'groups/{id}',
|
|
method => 'GET',
|
|
description => "List resource groups.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Audit' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
id => {
|
|
type => 'string',
|
|
description => "The resource group ID (for example 'pvevm:200').",
|
|
}
|
|
},
|
|
},
|
|
returns => {
|
|
type => "object",
|
|
properties => {},
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $conf = &$read_cluster_conf_new();
|
|
|
|
if (my $rmsec = PVE::Cluster::cluster_conf_lookup_rm_section($conf, 0, 1)) {
|
|
foreach my $child (@{$rmsec->{children}}) {
|
|
if ($child->{text} eq 'pvevm') {
|
|
my $id = "$child->{text}:$child->{vmid}";
|
|
if ($id eq $param->{id}) {
|
|
$child->{id} = $id;
|
|
return $child;
|
|
}
|
|
} elsif ($child->{text} eq 'service') {
|
|
my $id = "$child->{text}:$child->{name}";
|
|
if ($id eq $param->{id}) {
|
|
$child->{id} = $id;
|
|
return $child;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
raise_param_exc({ id => "no such group"});
|
|
}});
|
|
|
|
__PACKAGE__->register_method({
|
|
name => 'delete_group',
|
|
path => 'groups/{id}',
|
|
method => 'DELETE',
|
|
description => "Delete resource group.",
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', [ 'Sys.Modify' ]],
|
|
},
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
id => {
|
|
type => 'string',
|
|
description => "The resource group ID (for example 'pvevm:200').",
|
|
}
|
|
},
|
|
},
|
|
returns => { type => "null" },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
PVE::Cluster::check_cfs_quorum();
|
|
|
|
my $code = sub {
|
|
my $conf = &$read_cluster_conf_new();
|
|
|
|
my $found;
|
|
if (my $rmsec = PVE::Cluster::cluster_conf_lookup_rm_section($conf, 0, 1)) {
|
|
my $oldlist = $rmsec->{children};
|
|
$rmsec->{children} = [];
|
|
foreach my $child (@$oldlist) {
|
|
if ($child->{text} eq 'pvevm') {
|
|
if ("$child->{text}:$child->{vmid}" eq $param->{id}) {
|
|
$found = 1;
|
|
next;
|
|
}
|
|
} elsif ($child->{text} eq 'service') {
|
|
if ("$child->{text}:$child->{name}" eq $param->{id}) {
|
|
$found = 1;
|
|
next;
|
|
}
|
|
}
|
|
push @{$rmsec->{children}}, $child;
|
|
}
|
|
}
|
|
|
|
raise_param_exc({ id => "no such group"}) if !$found;
|
|
|
|
&$update_cluster_conf_new($conf);
|
|
};
|
|
|
|
cfs_lock_file('cluster.conf', undef, $code);
|
|
die $@ if $@;
|
|
|
|
return;
|
|
}});
|
|
|
|
1;
|