summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Hanreich <s.hanreich@proxmox.com>2025-07-29 11:29:30 +0200
committerThomas Lamprecht <t.lamprecht@proxmox.com>2025-07-29 12:59:51 +0200
commitf5966bbacd2998234795fb02f56bff24c7ecf7d6 (patch)
treee60247bd424744276281349640b5fdc6f8df248b
parentf7d4f22a42a506e6cd60f0de8fbac9f580b85c6f (diff)
api: add lock-token parameter to all api calls
The parameter is optional, so all existing create/update/delete invocations can work as before, only failing if the global lock is currently set. This ensures backwards-compatibility with the existing calls to the API in the frontend. If the lock is set, users will get an error message when trying to modify the configuration from the web UI. Co-authored-by: Gabriel Goller <g.goller@proxmox.com> Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> Link: https://lore.proxmox.com/20250729092933.90118-3-g.goller@proxmox.com
-rw-r--r--src/PVE/API2/Network/SDN/Controllers.pm21
-rw-r--r--src/PVE/API2/Network/SDN/Dns.pm21
-rw-r--r--src/PVE/API2/Network/SDN/Fabrics/Fabric.pm8
-rw-r--r--src/PVE/API2/Network/SDN/Fabrics/FabricNode.pm9
-rw-r--r--src/PVE/API2/Network/SDN/Ipams.pm21
-rw-r--r--src/PVE/API2/Network/SDN/Subnets.pm22
-rw-r--r--src/PVE/API2/Network/SDN/Vnets.pm21
-rw-r--r--src/PVE/API2/Network/SDN/Zones.pm21
-rw-r--r--src/PVE/Network/SDN.pm9
-rw-r--r--src/PVE/Network/SDN/Fabrics.pm2
10 files changed, 143 insertions, 12 deletions
diff --git a/src/PVE/API2/Network/SDN/Controllers.pm b/src/PVE/API2/Network/SDN/Controllers.pm
index e6eb4cb..5c2b6c3 100644
--- a/src/PVE/API2/Network/SDN/Controllers.pm
+++ b/src/PVE/API2/Network/SDN/Controllers.pm
@@ -168,13 +168,19 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(),
+ parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $type = extract_param($param, 'type');
my $id = extract_param($param, 'controller');
+ my $lock_token = extract_param($param, 'lock-token');
my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($type);
my $opts = $plugin->check_config($id, $param, 1, 1);
@@ -204,6 +210,7 @@ __PACKAGE__->register_method({
},
"create sdn controller object failed",
+ $lock_token,
);
return undef;
@@ -219,7 +226,12 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(),
+ parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@@ -227,6 +239,7 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'controller');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -257,6 +270,7 @@ __PACKAGE__->register_method({
},
"update sdn controller object failed",
+ $lock_token,
);
return undef;
@@ -281,6 +295,7 @@ __PACKAGE__->register_method({
completion => \&PVE::Network::SDN::Controllers::complete_sdn_controllers,
},
),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
},
},
returns => { type => 'null' },
@@ -288,6 +303,7 @@ __PACKAGE__->register_method({
my ($param) = @_;
my $id = extract_param($param, 'controller');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -307,6 +323,7 @@ __PACKAGE__->register_method({
},
"delete sdn controller object failed",
+ $lock_token,
);
return undef;
diff --git a/src/PVE/API2/Network/SDN/Dns.pm b/src/PVE/API2/Network/SDN/Dns.pm
index c82e354..48fc2f6 100644
--- a/src/PVE/API2/Network/SDN/Dns.pm
+++ b/src/PVE/API2/Network/SDN/Dns.pm
@@ -123,13 +123,19 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/dns', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Dns::Plugin->createSchema(),
+ parameters => PVE::Network::SDN::Dns::Plugin->createSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $type = extract_param($param, 'type');
my $id = extract_param($param, 'dns');
+ my $lock_token = extract_param($param, 'lock-token');
my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($type);
my $opts = $plugin->check_config($id, $param, 1, 1);
@@ -157,6 +163,7 @@ __PACKAGE__->register_method({
},
"create sdn dns object failed",
+ $lock_token,
);
return undef;
@@ -172,7 +179,12 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/dns', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(),
+ parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@@ -180,6 +192,7 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'dns');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -209,6 +222,7 @@ __PACKAGE__->register_method({
},
"update sdn dns object failed",
+ $lock_token,
);
return undef;
@@ -233,6 +247,7 @@ __PACKAGE__->register_method({
completion => \&PVE::Network::SDN::Dns::complete_sdn_dns,
},
),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
},
},
returns => { type => 'null' },
@@ -240,6 +255,7 @@ __PACKAGE__->register_method({
my ($param) = @_;
my $id = extract_param($param, 'dns');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -255,6 +271,7 @@ __PACKAGE__->register_method({
},
"delete sdn dns object failed",
+ $lock_token,
);
return undef;
diff --git a/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm b/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm
index 8c47b1b..1201654 100644
--- a/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm
+++ b/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm
@@ -138,6 +138,8 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $lock_token = extract_param($param, 'lock-token');
+
PVE::Network::SDN::lock_sdn_config(
sub {
my $config = PVE::Network::SDN::Fabrics::config();
@@ -149,6 +151,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Fabrics::write_config($config);
},
"adding fabric failed",
+ $lock_token,
);
},
});
@@ -170,6 +173,7 @@ __PACKAGE__->register_method({
},
code => sub {
my ($param) = @_;
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -184,6 +188,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Fabrics::write_config($config);
},
"updating fabric failed",
+ $lock_token,
);
},
});
@@ -208,6 +213,8 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $lock_token = extract_param($param, 'lock-token');
+
PVE::Network::SDN::lock_sdn_config(
sub {
my $id = extract_param($param, 'id');
@@ -253,6 +260,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Fabrics::write_config($config);
},
"deleting fabric failed",
+ $lock_token,
);
},
});
diff --git a/src/PVE/API2/Network/SDN/Fabrics/FabricNode.pm b/src/PVE/API2/Network/SDN/Fabrics/FabricNode.pm
index b288844..000e4c3 100644
--- a/src/PVE/API2/Network/SDN/Fabrics/FabricNode.pm
+++ b/src/PVE/API2/Network/SDN/Fabrics/FabricNode.pm
@@ -153,6 +153,8 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $lock_token = extract_param($param, 'lock-token');
+
PVE::Network::SDN::lock_sdn_config(
sub {
my $config = PVE::Network::SDN::Fabrics::config();
@@ -164,6 +166,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Fabrics::write_config($config);
},
"adding node failed",
+ $lock_token,
);
},
});
@@ -190,6 +193,8 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $lock_token = extract_param($param, 'lock-token');
+
PVE::Network::SDN::lock_sdn_config(
sub {
my $fabric_id = extract_param($param, 'fabric_id');
@@ -204,6 +209,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Fabrics::write_config($config);
},
"updating node failed",
+ $lock_token,
);
},
});
@@ -233,6 +239,8 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $lock_token = extract_param($param, 'lock-token');
+
PVE::Network::SDN::lock_sdn_config(
sub {
my $fabric_id = extract_param($param, 'fabric_id');
@@ -247,6 +255,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Fabrics::write_config($config);
},
"deleting node failed",
+ $lock_token,
);
},
});
diff --git a/src/PVE/API2/Network/SDN/Ipams.pm b/src/PVE/API2/Network/SDN/Ipams.pm
index e30d28f..89e05ee 100644
--- a/src/PVE/API2/Network/SDN/Ipams.pm
+++ b/src/PVE/API2/Network/SDN/Ipams.pm
@@ -128,13 +128,19 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(),
+ parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $type = extract_param($param, 'type');
my $id = extract_param($param, 'ipam');
+ my $lock_token = extract_param($param, 'lock-token');
my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($type);
my $opts = $plugin->check_config($id, $param, 1, 1);
@@ -164,6 +170,7 @@ __PACKAGE__->register_method({
},
"create sdn ipam object failed",
+ $lock_token,
);
return undef;
@@ -179,7 +186,12 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(),
+ parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@@ -187,6 +199,7 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'ipam');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -216,6 +229,7 @@ __PACKAGE__->register_method({
},
"update sdn ipam object failed",
+ $lock_token,
);
return undef;
@@ -240,6 +254,7 @@ __PACKAGE__->register_method({
completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams,
},
),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
},
},
returns => { type => 'null' },
@@ -247,6 +262,7 @@ __PACKAGE__->register_method({
my ($param) = @_;
my $id = extract_param($param, 'ipam');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -264,6 +280,7 @@ __PACKAGE__->register_method({
},
"delete sdn zone object failed",
+ $lock_token,
);
return undef;
diff --git a/src/PVE/API2/Network/SDN/Subnets.pm b/src/PVE/API2/Network/SDN/Subnets.pm
index c9f5452..fc56532 100644
--- a/src/PVE/API2/Network/SDN/Subnets.pm
+++ b/src/PVE/API2/Network/SDN/Subnets.pm
@@ -190,13 +190,19 @@ __PACKAGE__->register_method({
description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'",
user => 'all',
},
- parameters => PVE::Network::SDN::SubnetPlugin->createSchema(),
+ parameters => PVE::Network::SDN::SubnetPlugin->createSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $type = extract_param($param, 'type');
my $cidr = extract_param($param, 'subnet');
+ my $lock_token = extract_param($param, 'lock-token');
my $vnet = $param->{vnet};
my $privs = ['SDN.Allocate'];
@@ -234,6 +240,7 @@ __PACKAGE__->register_method({
},
"create sdn subnet object failed",
+ $lock_token,
);
return undef;
@@ -250,7 +257,12 @@ __PACKAGE__->register_method({
description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'",
user => 'all',
},
- parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(),
+ parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@@ -258,6 +270,7 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'subnet');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
+ my $lock_token = extract_param($param, 'lock-token');
my $vnet = $param->{vnet};
@@ -295,6 +308,7 @@ __PACKAGE__->register_method({
},
"update sdn subnet object failed",
+ $lock_token,
);
return undef;
@@ -321,6 +335,7 @@ __PACKAGE__->register_method({
completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets,
},
),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
},
},
returns => { type => 'null' },
@@ -329,6 +344,8 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'subnet');
my $vnet = extract_param($param, 'vnet');
+ my $lock_token = extract_param($param, 'lock-token');
+
my $privs = ['SDN.Allocate'];
&$check_vnet_access($vnet, $privs);
@@ -354,6 +371,7 @@ __PACKAGE__->register_method({
},
"delete sdn subnet object failed",
+ $lock_token,
);
return undef;
diff --git a/src/PVE/API2/Network/SDN/Vnets.pm b/src/PVE/API2/Network/SDN/Vnets.pm
index 5608283..e6eb5d4 100644
--- a/src/PVE/API2/Network/SDN/Vnets.pm
+++ b/src/PVE/API2/Network/SDN/Vnets.pm
@@ -205,13 +205,19 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::VnetPlugin->createSchema(),
+ parameters => PVE::Network::SDN::VnetPlugin->createSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $type = extract_param($param, 'type');
my $id = extract_param($param, 'vnet');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Cluster::check_cfs_quorum();
mkdir("/etc/pve/sdn");
@@ -238,6 +244,7 @@ __PACKAGE__->register_method({
},
"create sdn vnet object failed",
+ $lock_token,
);
return undef;
@@ -254,7 +261,12 @@ __PACKAGE__->register_method({
description => "Require 'SDN.Allocate' permission on '/sdn/zones/<zone>/<vnet>'",
user => 'all',
},
- parameters => PVE::Network::SDN::VnetPlugin->updateSchema(),
+ parameters => PVE::Network::SDN::VnetPlugin->updateSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@@ -262,6 +274,7 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'vnet');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
+ my $lock_token = extract_param($param, 'lock-token');
my $privs = ['SDN.Allocate'];
&$check_vnet_access($id, $privs);
@@ -307,6 +320,7 @@ __PACKAGE__->register_method({
},
"update sdn vnet object failed",
+ $lock_token,
);
return undef;
@@ -332,6 +346,7 @@ __PACKAGE__->register_method({
completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets,
},
),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
},
},
returns => { type => 'null' },
@@ -339,6 +354,7 @@ __PACKAGE__->register_method({
my ($param) = @_;
my $id = extract_param($param, 'vnet');
+ my $lock_token = extract_param($param, 'lock-token');
my $privs = ['SDN.Allocate'];
&$check_vnet_access($id, $privs);
@@ -356,6 +372,7 @@ __PACKAGE__->register_method({
},
"delete sdn vnet object failed",
+ $lock_token,
);
return undef;
diff --git a/src/PVE/API2/Network/SDN/Zones.pm b/src/PVE/API2/Network/SDN/Zones.pm
index e53e6e7..0e4726b 100644
--- a/src/PVE/API2/Network/SDN/Zones.pm
+++ b/src/PVE/API2/Network/SDN/Zones.pm
@@ -207,13 +207,19 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/zones', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Zones::Plugin->createSchema(),
+ parameters => PVE::Network::SDN::Zones::Plugin->createSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $type = extract_param($param, 'type');
my $id = extract_param($param, 'zone');
+ my $lock_token = extract_param($param, 'lock-token');
my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type);
my $opts = $plugin->check_config($id, $param, 1, 1);
@@ -256,6 +262,7 @@ __PACKAGE__->register_method({
},
"create sdn zone object failed",
+ $lock_token,
);
return;
@@ -271,7 +278,12 @@ __PACKAGE__->register_method({
permissions => {
check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']],
},
- parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(),
+ parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(
+ undef,
+ {
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
+ },
+ ),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@@ -279,6 +291,7 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'zone');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
+ my $lock_token = extract_param($param, 'lock-token');
if ($delete) {
$delete = [PVE::Tools::split_list($delete)];
@@ -344,6 +357,7 @@ __PACKAGE__->register_method({
},
"update sdn zone object failed",
+ $lock_token,
);
return;
@@ -368,6 +382,7 @@ __PACKAGE__->register_method({
completion => \&PVE::Network::SDN::Zones::complete_sdn_zones,
},
),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
},
},
returns => { type => 'null' },
@@ -375,6 +390,7 @@ __PACKAGE__->register_method({
my ($param) = @_;
my $id = extract_param($param, 'zone');
+ my $lock_token = extract_param($param, 'lock-token');
PVE::Network::SDN::lock_sdn_config(
sub {
@@ -391,6 +407,7 @@ __PACKAGE__->register_method({
PVE::Network::SDN::Zones::write_config($cfg);
},
"delete sdn zone object failed",
+ $lock_token,
);
return;
diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm
index 9f18f76..8a963cb 100644
--- a/src/PVE/Network/SDN.pm
+++ b/src/PVE/Network/SDN.pm
@@ -51,6 +51,15 @@ PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running
my $LOCK_TOKEN_FILE = "/etc/pve/sdn/.lock";
+PVE::JSONSchema::register_standard_option(
+ 'pve-sdn-lock-token',
+ {
+ type => 'string',
+ description => "the token for unlocking the global SDN configuration",
+ optional => 1,
+ },
+);
+
# improve me : move status code inside plugins ?
sub ifquery_check {
diff --git a/src/PVE/Network/SDN/Fabrics.pm b/src/PVE/Network/SDN/Fabrics.pm
index 796d149..d90992a 100644
--- a/src/PVE/Network/SDN/Fabrics.pm
+++ b/src/PVE/Network/SDN/Fabrics.pm
@@ -128,6 +128,7 @@ sub node_properties {
node_id => get_standard_option('pve-sdn-fabric-node-id'),
protocol => get_standard_option('pve-sdn-fabric-protocol'),
digest => get_standard_option('pve-config-digest'),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
ip => {
type => 'string',
format => 'ipv4',
@@ -227,6 +228,7 @@ sub fabric_properties {
id => get_standard_option('pve-sdn-fabric-id'),
protocol => get_standard_option('pve-sdn-fabric-protocol'),
digest => get_standard_option('pve-config-digest'),
+ 'lock-token' => get_standard_option('pve-sdn-lock-token'),
ip_prefix => {
type => 'string',
format => 'CIDR',