[pve-devel] [RFC manager 2/5] add node configuration file and API
Wolfgang Bumiller
w.bumiller at proxmox.com
Fri Apr 13 13:27:23 CEST 2018
On Wed, Apr 11, 2018 at 10:08:48AM +0200, Fabian Grünbichler wrote:
> this currently only contains a description and the node-specific ACME
> configuration, but I am sure we can find other goodies to put there.
>
> Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
> ---
> PVE/API2/Makefile | 1 +
> PVE/Makefile | 1 +
> PVE/API2/NodeConfig.pm | 99 ++++++++++++++++++++++++
> PVE/API2/Nodes.pm | 7 ++
> PVE/NodeConfig.pm | 205 +++++++++++++++++++++++++++++++++++++++++++++++++
> 5 files changed, 313 insertions(+)
> create mode 100644 PVE/API2/NodeConfig.pm
> create mode 100644 PVE/NodeConfig.pm
>
> diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile
> index 86d75d36..51b8b30a 100644
> --- a/PVE/API2/Makefile
> +++ b/PVE/API2/Makefile
> @@ -14,6 +14,7 @@ PERLSOURCE = \
> Pool.pm \
> Tasks.pm \
> Network.pm \
> + NodeConfig.pm \
> Services.pm
>
> all:
> diff --git a/PVE/Makefile b/PVE/Makefile
> index 395faf8a..56d27d13 100644
> --- a/PVE/Makefile
> +++ b/PVE/Makefile
> @@ -11,6 +11,7 @@ PERLSOURCE = \
> AutoBalloon.pm \
> CephTools.pm \
> Report.pm \
> + NodeConfig.pm \
> VZDump.pm
>
> all: pvecfg.pm ${SUBDIRS}
> diff --git a/PVE/API2/NodeConfig.pm b/PVE/API2/NodeConfig.pm
> new file mode 100644
> index 00000000..8c976974
> --- /dev/null
> +++ b/PVE/API2/NodeConfig.pm
> @@ -0,0 +1,99 @@
> +package PVE::API2::NodeConfig;
> +
> +use strict;
> +use warnings;
> +
> +use PVE::JSONSchema qw(get_standard_option);
> +use PVE::NodeConfig;
> +use PVE::Tools qw(extract_param);
> +
> +use base qw(PVE::RESTHandler);
> +
> +my $node_config_schema = PVE::NodeConfig::get_nodeconfig_schema();
> +my $node_config_properties = {
> + delete => {
> + type => 'string', format => 'pve-configid-list',
> + description => "A list of settings you want to delete.",
> + optional => 1,
> + },
> + digest => {
> + type => 'string',
> + description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
> + maxLength => 40,
> + optional => 1,
> + },
> + node => get_standard_option('pve-node'),
> +};
> +
> +foreach my $opt (keys %{$node_config_schema}) {
> + $node_config_properties->{$opt} = $node_config_schema->{$opt};
> +}
> +
> +__PACKAGE__->register_method({
> + name => 'get_config',
> + path => '',
> + method => 'GET',
> + description => "Get node configuration options.",
> + permissions => {
> + check => ['perm', '/', [ 'Sys.Audit' ]],
> + },
> + parameters => {
> + additionalProperties => 0,
> + properties => {
> + node => get_standard_option('pve-node'),
> + },
> + },
> + returns => {
> + type => "object",
> + properties => {},
> + },
> + code => sub {
> + my ($param) = @_;
> +
> + return PVE::NodeConfig::load_config($param->{node});
> + }});
> +
> +__PACKAGE__->register_method({
> + name => 'set_options',
> + path => '',
> + method => 'PUT',
> + description => "Set node configuration options.",
> + permissions => {
> + check => ['perm', '/', [ 'Sys.Modify' ]],
> + },
> + protected => 1,
> + parameters => {
> + additionalProperties => 0,
> + properties => $node_config_properties,
> + },
> + returns => { type => "null" },
> + code => sub {
> + my ($param) = @_;
> +
> + my $delete = extract_param($param, 'delete');
> + my $node = extract_param($param, 'node');
> + my $digest = extract_param($param, 'digest');
> +
> + my $code = sub {
> + my $conf = PVE::NodeConfig::load_config($node);
> +
> + PVE::Tools::assert_if_modified($digest, $conf->{digest});
> +
> + foreach my $opt (keys %$param) {
> + $conf->{$opt} = $param->{$opt};
> + }
> +
> + foreach my $opt (PVE::Tools::split_list($delete)) {
> + delete $conf->{$opt};
> + };
> +
> + PVE::NodeConfig::write_config($node, $conf);
> + };
> +
> + PVE::NodeConfig::lock_config($node, $code);
> + die $@ if $@;
> +
> + return undef;
> + }});
> +
> +1;
> diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm
> index 3eb38315..42b932cf 100644
> --- a/PVE/API2/Nodes.pm
> +++ b/PVE/API2/Nodes.pm
> @@ -41,6 +41,7 @@ use PVE::API2::APT;
> use PVE::API2::Ceph;
> use PVE::API2::Firewall::Host;
> use PVE::API2::Replication;
> +use PVE::API2::NodeConfig;
> use Digest::MD5;
> use Digest::SHA;
> use PVE::API2::Disks;
> @@ -118,6 +119,11 @@ __PACKAGE__->register_method ({
> path => 'replication',
> });
>
> +__PACKAGE__->register_method ({
> + subclass => "PVE::API2::NodeConfig",
> + path => 'config',
> +});
> +
> __PACKAGE__->register_method ({
> name => 'index',
> path => '',
> @@ -171,6 +177,7 @@ __PACKAGE__->register_method ({
> { name => 'stopall' },
> { name => 'netstat' },
> { name => 'firewall' },
> + { name => 'config' },
> ];
>
> return $result;
> diff --git a/PVE/NodeConfig.pm b/PVE/NodeConfig.pm
> new file mode 100644
> index 00000000..33317e02
> --- /dev/null
> +++ b/PVE/NodeConfig.pm
> @@ -0,0 +1,205 @@
> +package PVE::NodeConfig;
> +
> +use strict;
> +use warnings;
> +
> +use PVE::CertHelpers;
> +use PVE::JSONSchema qw(get_standard_option);
> +use PVE::Tools qw(file_get_contents file_set_contents lock_file);
> +
> +my $node_config_lock = '/var/lock/pvenode.lock';
> +
> +PVE::JSONSchema::register_format('pve-acme-domain', sub {
> + my ($domain, $noerr) = @_;
> +
> + my $label = qr/[a-z][a-z0-9_-]*/i;
> +
> + return $domain if $domain =~ /^$label(?:\.$label)+$/;
> + return undef if $noerr;
> + die "value does not look like a valid domain name";
> +});
> +
> +sub config_file {
> + my ($node) = @_;
> +
> + return "/etc/pve/nodes/${node}/config";
> +}
> +
> +sub load_config {
> + my ($node) = @_;
> +
> + my $filename = config_file($node);
> + my $raw = eval { PVE::Tools::file_get_contents($filename); };
> + return {} if !$raw;
> +
> + return parse_node_config($raw);
> +}
> +
> +sub write_config {
> + my ($node, $conf) = @_;
> +
> + my $filename = config_file($node);
> +
> + my $raw = write_node_config($conf);
> +
> + PVE::Tools::file_set_contents($filename, $raw);
> +}
> +
> +sub lock_config {
> + my ($node, $code, @param) = @_;
> +
> + my $res = lock_file($node_config_lock, 10, $code, @param);
> +
> + die $@ if $@;
> +
> + return $res;
> +}
> +
> +my $confdesc = {
> + description => {
> + type => 'string',
> + description => 'Node description/comment.',
> + optional => 1,
> + },
> +};
> +
> +my $acmedesc = {
> + account => get_standard_option('pve-acme-account-name'),
> + domains => {
> + type => 'string',
> + format => 'pve-acme-domain-list',
> + format_description => 'domain[;domain;...]',
> + description => 'List of domains for this node\'s ACME certificate',
> + },
> +};
> +PVE::JSONSchema::register_format('pve-acme-node-conf', $acmedesc);
> +
> +$confdesc->{acme} = {
> + type => 'string',
> + description => 'Node specific ACME settings.',
> + format => $acmedesc,
> + optional => 1,
> +};
> +
> +sub check_type {
We have this in pve-container and I think qemu-server as well(?), so we
should move this to PVE::JSONSchema - which already contains a
different function of this name, but that one is private and can simply
be renamed as it shouldn't be in use anywhere else atm.
> + my ($key, $value) = @_;
> +
> + die "unknown setting '$key'\n" if !$confdesc->{$key};
> +
> + my $type = $confdesc->{$key}->{type};
> +
> + if (!defined($value)) {
> + die "got undefined value\n";
> + }
> +
> + if ($value =~ m/[\n\r]/) {
> + die "property contains a line feed\n";
> + }
> +
> + if ($type eq 'boolean') {
> + return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
> + return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
> + die "type check ('boolean') failed - got '$value'\n";
> + } elsif ($type eq 'integer') {
> + return int($1) if $value =~ m/^(\d+)$/;
> + die "type check ('integer') failed - got '$value'\n";
> + } elsif ($type eq 'number') {
> + return $value if $value =~ m/^(\d+)(\.\d+)?$/;
> + die "type check ('number') failed - got '$value'\n";
> + } elsif ($type eq 'string') {
> + if (my $fmt = $confdesc->{$key}->{format}) {
> + PVE::JSONSchema::check_format($fmt, $value);
> + return $value;
> + }
> + return $value;
> + } else {
> + die "internal error"
> + }
> +}
> +sub parse_node_config {
> + my ($content) = @_;
> +
> + return undef if !defined($content);
> +
> + my $conf = {
> + digest => Digest::SHA::sha1_hex($content),
> + };
> + my $descr = '';
> +
> + my @lines = split(/\n/, $content);
> + foreach my $line (@lines) {
> + if ($line =~ /^\#(.*)\s*$/) {
> + $descr .= PVE::Tools::decode_text($1) . "\n";
> + next;
> + }
> + if ($line =~ /^description:\s*(.*\S)\s*$/) {
> + $descr .= PVE::Tools::decode_text($1) . "\n";
> + next;
> + }
> + if ($line =~ /^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
> + my $key = $1;
> + my $value = $2;
> + eval { $value = check_type($key, $value); };
> + warn "cannot parse value of '$key' in node config: $@" if $@;
> + $conf->{$key} = $value;
> + } else {
> + warn "cannot parse line '$line' in node config\n";
> + }
> + }
> +
> + $conf->{description} = $descr if $descr;
> +
> + return $conf;
> +}
> +
> +sub write_node_config {
> + my ($conf) = @_;
> +
> + my $raw = '';
> + # add description as comment to top of file
> + my $descr = $conf->{description} || '';
> + foreach my $cl (split(/\n/, $descr)) {
> + $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
> + }
> +
> + for my $key (sort keys %$conf) {
> + next if ($key eq 'description');
> + next if ($key eq 'digest');
> +
> + my $value = $conf->{$key};
> + die "detected invalid newline inside property '$key'\n"
> + if $value =~ m/\n/;
> + $raw .= "$key: $value\n";
> + }
> +
> + return $raw;
> +}
> +
> +sub parse_acme {
> + my ($data, $noerr) = @_;
> +
> + $data //= '';
> +
> + my $res = eval { PVE::JSONSchema::parse_property_string($acmedesc, $data); };
> + if ($@) {
> + return undef if $noerr;
> + die $@;
> + }
> +
> + $res->{domains} = [ PVE::Tools::split_list($res->{domains}) ];
> +
> + return $res;
> +}
> +
> +sub print_acme {
> + my ($acme) = @_;
> +
> + $acme->{domains} = join(';', $acme->{domains}) if $acme->{domains};
> + return PVE::JSONSchema::print_property_string($acme, $acmedesc);
> +}
> +
> +sub get_nodeconfig_schema {
> + return $confdesc;
> +}
> +
> +1;
> --
> 2.14.2
>
>
> _______________________________________________
> pve-devel mailing list
> pve-devel at pve.proxmox.com
> https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
More information about the pve-devel
mailing list