[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