]> git.proxmox.com Git - pve-cluster.git/blobdiff - data/PVE/DataCenterConfig.pm
dc config: mark HA CRS scheduler sub-property optional
[pve-cluster.git] / data / PVE / DataCenterConfig.pm
index fa8ba4a579c04fb2520bbfe1f24f633ae26467d6..c50a0cdb87576c852f3050664e831f7bcfa98b39 100644 (file)
@@ -3,10 +3,23 @@ package PVE::DataCenterConfig;
 use strict;
 use warnings;
 
-use PVE::JSONSchema;
+use PVE::JSONSchema qw(parse_property_string);
 use PVE::Tools;
 use PVE::Cluster;
 
+my $crs_format = {
+    ha => {
+       type => 'string',
+       enum => ['basic', 'static'],
+       optional => 1,
+       default => 'basic',
+       description => "Use this resource scheduler mode for HA.",
+       verbose_description => "Configures how the HA manager should select nodes to start or ".
+           "recover services. With 'basic', only the number of services is used, with 'static', ".
+           "static CPU and memory configuration of services is considered.",
+    },
+};
+
 my $migration_format = {
     type => {
        default_key => 1,
@@ -25,6 +38,20 @@ my $migration_format = {
     },
 };
 
+my $notification_format = {
+    'package-updates' => {
+       type => 'string',
+       enum => ['auto', 'always', 'never'],
+       description => "Control when the daily update job should send out notification mails.",
+       verbose_description => "Control how often the daily update job should send out notification mails:\n"
+           ."* 'auto' daily for systems with a valid subscription, as those are assumed to be "
+           ." production-ready and thus should know about pending updates.\n"
+           ."* 'always' every update, if there are new pending updates.\n"
+           ."* 'never' never send a notification for new pending updates.\n",
+       default => 'auto',
+    },
+};
+
 my $ha_format = {
     shutdown_policy => {
        type => 'string',
@@ -51,6 +78,25 @@ my $ha_format = {
     }
 };
 
+my $next_id_format = {
+    lower => {
+       type => 'integer',
+       description => "Lower, inclusive boundary for free next-id API range.",
+       min => 100,
+       max => 1000 * 1000 * 1000 - 1,
+       default => 100,
+       optional => 1,
+    },
+    upper => {
+       type => 'integer',
+       description => "Upper, exclusive boundary for free next-id API range.",
+       min => 100,
+       max => 1000 * 1000 * 1000,
+       default => 1000 * 1000, # lower than the maximum on purpose
+       optional => 1,
+    },
+};
+
 my $u2f_format = {
     appid => {
        type => 'string',
@@ -66,6 +112,40 @@ my $u2f_format = {
     },
 };
 
+my $webauthn_format = {
+    rp => {
+       type => 'string',
+       description =>
+           'Relying party name. Any text identifier.'
+           .' Changing this *may* break existing credentials.',
+       format_description => 'RELYING_PARTY',
+       optional => 1,
+    },
+    origin => {
+       type => 'string',
+       description =>
+           'Site origin. Must be a `https://` URL (or `http://localhost`).'
+           .' Should contain the address users type in their browsers to access'
+           .' the web interface.'
+           .' Changing this *may* break existing credentials.',
+       format_description => 'URL',
+       optional => 1,
+    },
+    id => {
+       type => 'string',
+       description =>
+           'Relying party ID. Must be the domain name without protocol, port or location.'
+           .' Changing this *will* break existing credentials.',
+       format_description => 'DOMAINNAME',
+       optional => 1,
+    },
+    'allow-subdomains' => {
+       type => 'boolean',
+       description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
+       optional => 1,
+       default => 1,
+    },
+};
 
 PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
 sub pve_verify_mac_prefix {
@@ -78,10 +158,77 @@ sub pve_verify_mac_prefix {
     return $mac_prefix;
 }
 
+my $COLOR_RE = '[0-9a-fA-F]{6}';
+my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
+
+my $tag_style_format = {
+    'shape' => {
+       optional => 1,
+       type => 'string',
+       enum => ['full', 'circle', 'dense', 'none'],
+       default => 'circle',
+       description => "Tag shape for the web ui tree. 'full' draws the full tag. "
+           ."'circle' draws only a circle with the background color. "
+           ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
+           ."'none' disables showing the tags.",
+    },
+    'color-map' => {
+       optional => 1,
+       type => 'string',
+       pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
+       typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
+       description => "Manual color mapping for tags (semicolon separated).",
+    },
+    ordering => {
+       optional => 1,
+       type => 'string',
+       enum => ['config', 'alphabetical'],
+       default => 'alphabetical',
+       description => 'Controls the sorting of the tags in the web-interface and the API update.',
+    },
+    'case-sensitive' => {
+       type => 'boolean',
+       description => 'Controls if filtering for unique tags on update should check case-sensitive.',
+       optional => 1,
+       default => 0,
+    },
+};
+
+my $user_tag_privs_format = {
+    'user-allow' => {
+       optional => 1,
+       type => 'string',
+       enum => ['none', 'list', 'existing', 'free'],
+       default => 'free',
+       description => "Controls tag usage for users without `Sys.Modify` on `/` by either "
+           ."allowing `none`, a `list`, already `existing` or anything (`free`).",
+       verbose_description => "Controls which tags can be set or deleted on resources a user "
+           ."controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always "
+           ." unrestricted. "
+           ."* 'none' no tags are usable. "
+           ."* 'list' tags from 'user-allow-list' are usable. "
+           ."* 'existing' like list, but already existing tags of resources are also usable."
+           ."* 'free' no tag restrictions.",
+    },
+    'user-allow-list' => {
+       optional => 1,
+       type => 'string',
+       pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
+       typetext => "<tag>[;<tag>...]",
+       description => "List of tags users are allowed to set and delete (semicolon separated) "
+           ."for 'user-allow' values 'list' and 'existing'.",
+    },
+};
+
 my $datacenter_schema = {
     type => "object",
     additionalProperties => 0,
     properties => {
+       crs => {
+           optional => 1,
+           type => 'string', format => $crs_format,
+           description => "Cluster resource scheduling settings.",
+       },
        keyboard => {
            optional => 1,
            type => 'string',
@@ -122,6 +269,7 @@ my $datacenter_schema = {
            description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
            pattern => "http://.*",
        },
+       # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
        migration_unsecure => {
            optional => 1,
            type => 'boolean',
@@ -129,6 +277,12 @@ my $datacenter_schema = {
              "For secure private networks you can disable it to speed up " .
              "migration. Deprecated, use the 'migration' property instead!",
        },
+       'next-id' => {
+           optional => 1,
+           type => 'string',
+           format => $next_id_format,
+           description => "Control the range for the free VMID auto-selection pool.",
+       },
        migration => {
            optional => 1,
            type => 'string', format => $migration_format,
@@ -137,7 +291,9 @@ my $datacenter_schema = {
        console => {
            optional => 1,
            type => 'string',
-           description => "Select the default Console viewer. You can either use the builtin java applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
+           description => "Select the default Console viewer. You can either use the builtin java"
+               ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
+           # FIXME: remove 'applet' with 8.0 (add pve7to8 check!)
            enum => ['applet', 'vv', 'html5', 'xtermjs'],
        },
        email_from => {
@@ -174,6 +330,11 @@ my $datacenter_schema = {
            format => 'mac-prefix',
            description => 'Prefix for autogenerated MAC addresses.',
        },
+       notify => {
+           optional => 1,
+           type => 'string', format => $notification_format,
+           description => "Cluster-wide notification settings.",
+       },
        bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
        u2f => {
            optional => 1,
@@ -181,6 +342,12 @@ my $datacenter_schema = {
            format => $u2f_format,
            description => 'u2f',
        },
+       webauthn => {
+           optional => 1,
+           type => 'string',
+           format => $webauthn_format,
+           description => 'webauthn configuration',
+       },
        description => {
            type => 'string',
            description => "Datacenter description. Shown in the web-interface datacenter notes panel."
@@ -188,6 +355,26 @@ my $datacenter_schema = {
            maxLength => 64 * 1024,
            optional => 1,
        },
+       'tag-style' => {
+           optional => 1,
+           type => 'string',
+           description => "Tag style options.",
+           format => $tag_style_format,
+       },
+       'user-tag-access' => {
+           optional => 1,
+           type => 'string',
+           description => "Privilege options for user-settable tags",
+           format => $user_tag_privs_format,
+       },
+       'registered-tags' => {
+           optional => 1,
+           type => 'string',
+           description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
+               ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
+           pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
+           typetext => "<tag>[;<tag>...]",
+       },
     },
 };
 
@@ -197,31 +384,63 @@ sub get_datacenter_schema { return $datacenter_schema };
 sub parse_datacenter_config {
     my ($filename, $raw) = @_;
 
+    $raw = '' if !defined($raw);
+
     # description may be comment or key-value pair (or both)
     my $comment = '';
-    my @lines = split(/\n/, $raw);
-    foreach my $line (@lines) {
-       if ($line =~ /^\#(.*)\s*$/) {
+    for my $line (split(/\n/, $raw)) {
+       if ($line =~ /^\#(.*)$/) {
            $comment .= PVE::Tools::decode_text($1) . "\n";
        }
     }
 
     # parse_config ignores lines with # => use $raw
-    my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw // '');
+    my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
 
     $res->{description} = $comment;
 
+    if (my $crs = $res->{crs}) {
+       $res->{crs} = parse_property_string($crs_format, $crs);
+    }
 
     if (my $migration = $res->{migration}) {
-       $res->{migration} = PVE::JSONSchema::parse_property_string($migration_format, $migration);
+       $res->{migration} = parse_property_string($migration_format, $migration);
+    }
+
+    if (my $next_id = $res->{'next-id'}) {
+       $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
     }
 
     if (my $ha = $res->{ha}) {
-       $res->{ha} = PVE::JSONSchema::parse_property_string($ha_format, $ha);
+       $res->{ha} = parse_property_string($ha_format, $ha);
+    }
+    if (my $notify = $res->{notify}) {
+       $res->{notify} = parse_property_string($notification_format, $notify);
     }
 
     if (my $u2f = $res->{u2f}) {
-       $res->{u2f} = PVE::JSONSchema::parse_property_string($u2f_format, $u2f);
+       $res->{u2f} = parse_property_string($u2f_format, $u2f);
+    }
+
+    if (my $webauthn = $res->{webauthn}) {
+       $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
+    }
+
+    if (my $tag_style = $res->{'tag-style'}) {
+       $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
+    }
+
+    if (my $user_tag_privs = $res->{'user-tag-access'}) {
+       $res->{'user-tag-access'} =
+           parse_property_string($user_tag_privs_format, $user_tag_privs);
+
+       if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
+           $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
+       }
+    }
+
+    if (my $admin_tags = $res->{'registered-tags'}) {
+       $res->{'registered-tags'} = [split(';', $admin_tags)];
     }
 
     # for backwards compatibility only, new migration property has precedence
@@ -256,21 +475,56 @@ sub write_datacenter_config {
        $cfg->{console} = 'html5';
     }
 
-    if (ref($cfg->{migration})) {
-       my $migration = $cfg->{migration};
+    if (ref(my $crs = $cfg->{crs})) {
+       $cfg->{crs} = PVE::JSONSchema::print_property_string($crs, $crs_format);
+    }
+
+    if (ref(my $migration = $cfg->{migration})) {
        $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
     }
 
-    if (ref($cfg->{ha})) {
-       my $ha = $cfg->{ha};
+    if (defined(my $next_id = $cfg->{'next-id'})) {
+        $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
+
+       my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
+       my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
+
+       die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
+
+       $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
+    }
+
+    if (ref(my $ha = $cfg->{ha})) {
        $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
     }
+    if (ref(my $notify = $cfg->{notify})) {
+       $cfg->{notify} = PVE::JSONSchema::print_property_string($notify, $notification_format);
+    }
 
-    if (ref($cfg->{u2f})) {
-       my $u2f = $cfg->{u2f};
+    if (ref(my $u2f = $cfg->{u2f})) {
        $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
     }
 
+    if (ref(my $webauthn = $cfg->{webauthn})) {
+       $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
+    }
+
+    if (ref(my $tag_style = $cfg->{'tag-style'})) {
+       $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
+    }
+
+    if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
+       if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
+           $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
+       }
+       $cfg->{'user-tag-access'} =
+           PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
+    }
+
+    if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
+       $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
+    }
+
     my $comment = '';
     # add description as comment to top of file
     my $description = $cfg->{description} || '';
@@ -283,8 +537,10 @@ sub write_datacenter_config {
     return $comment . "\n" . $dump;
 }
 
-PVE::Cluster::cfs_register_file('datacenter.cfg',
-                 \&parse_datacenter_config,
-                 \&write_datacenter_config);
+PVE::Cluster::cfs_register_file(
+    'datacenter.cfg',
+    \&parse_datacenter_config,
+    \&write_datacenter_config,
+);
 
 1;