[pve-devel] [PATCH v9 pve-network 15/26] add dns plugin
Alexandre Derumier
aderumier at odiso.com
Mon Sep 28 10:43:25 CEST 2020
Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
PVE/API2/Network/SDN.pm | 7 +
PVE/API2/Network/SDN/Dns.pm | 242 ++++++++++++++++++++++++++
PVE/API2/Network/SDN/Makefile | 2 +-
PVE/Network/SDN/Dns.pm | 57 ++++++
PVE/Network/SDN/Dns/Makefile | 8 +
PVE/Network/SDN/Dns/Plugin.pm | 117 +++++++++++++
PVE/Network/SDN/Dns/PowerdnsPlugin.pm | 201 +++++++++++++++++++++
PVE/Network/SDN/Ipams/PVEPlugin.pm | 1 -
PVE/Network/SDN/Ipams/Plugin.pm | 2 +-
PVE/Network/SDN/Makefile | 3 +-
PVE/Network/SDN/SubnetPlugin.pm | 53 ++++--
PVE/Network/SDN/Subnets.pm | 156 +++++++++++++++--
PVE/Network/SDN/Vnets.pm | 12 +-
13 files changed, 814 insertions(+), 47 deletions(-)
create mode 100644 PVE/API2/Network/SDN/Dns.pm
create mode 100644 PVE/Network/SDN/Dns.pm
create mode 100644 PVE/Network/SDN/Dns/Makefile
create mode 100644 PVE/Network/SDN/Dns/Plugin.pm
create mode 100644 PVE/Network/SDN/Dns/PowerdnsPlugin.pm
diff --git a/PVE/API2/Network/SDN.pm b/PVE/API2/Network/SDN.pm
index 6055fe5..0a5fa33 100644
--- a/PVE/API2/Network/SDN.pm
+++ b/PVE/API2/Network/SDN.pm
@@ -17,6 +17,7 @@ use PVE::API2::Network::SDN::Vnets;
use PVE::API2::Network::SDN::Zones;
use PVE::API2::Network::SDN::Subnets;
use PVE::API2::Network::SDN::Ipams;
+use PVE::API2::Network::SDN::Dns;
use base qw(PVE::RESTHandler);
@@ -45,6 +46,11 @@ __PACKAGE__->register_method ({
path => 'ipams',
});
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Dns",
+ path => 'dns',
+});
+
__PACKAGE__->register_method({
name => 'index',
path => '',
@@ -76,6 +82,7 @@ __PACKAGE__->register_method({
{ id => 'controllers' },
{ id => 'subnets' },
{ id => 'ipams' },
+ { id => 'dns' },
];
return $res;
diff --git a/PVE/API2/Network/SDN/Dns.pm b/PVE/API2/Network/SDN/Dns.pm
new file mode 100644
index 0000000..ea26af3
--- /dev/null
+++ b/PVE/API2/Network/SDN/Dns.pm
@@ -0,0 +1,242 @@
+package PVE::API2::Network::SDN::Dns;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+use PVE::Cluster qw(cfs_read_file cfs_write_file);
+use PVE::Network::SDN;
+use PVE::Network::SDN::Dns;
+use PVE::Network::SDN::Dns::Plugin;
+use PVE::Network::SDN::Dns::PowerdnsPlugin;
+
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my $sdn_dns_type_enum = PVE::Network::SDN::Dns::Plugin->lookup_types();
+
+my $api_sdn_dns_config = sub {
+ my ($cfg, $id) = @_;
+
+ my $scfg = dclone(PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id));
+ $scfg->{dns} = $id;
+ $scfg->{digest} = $cfg->{digest};
+
+ return $scfg;
+};
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "SDN dns index.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/dns/<dns>'",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ type => {
+ description => "Only list sdn dns of specific type",
+ type => 'string',
+ enum => $sdn_dns_type_enum,
+ optional => 1,
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => { dns => { type => 'string'},
+ type => { type => 'string'},
+ },
+ },
+ links => [ { rel => 'child', href => "{dns}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+
+ my $cfg = PVE::Network::SDN::Dns::config();
+
+ my @sids = PVE::Network::SDN::Dns::sdn_dns_ids($cfg);
+ my $res = [];
+ foreach my $id (@sids) {
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+ next if !$rpcenv->check_any($authuser, "/sdn/dns/$id", $privs, 1);
+
+ my $scfg = &$api_sdn_dns_config($cfg, $id);
+ next if $param->{type} && $param->{type} ne $scfg->{type};
+
+ my $plugin_config = $cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+ push @$res, $scfg;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read',
+ path => '{dns}',
+ method => 'GET',
+ description => "Read sdn dns configuration.",
+ permissions => {
+ check => ['perm', '/sdn/dns/{dns}', ['SDN.Allocate']],
+ },
+
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ dns => get_standard_option('pve-sdn-dns-id'),
+ },
+ },
+ returns => { type => 'object' },
+ code => sub {
+ my ($param) = @_;
+
+ my $cfg = PVE::Network::SDN::Dns::config();
+
+ return &$api_sdn_dns_config($cfg, $param->{dns});
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ protected => 1,
+ path => '',
+ method => 'POST',
+ description => "Create a new sdn dns object.",
+ permissions => {
+ check => ['perm', '/sdn/dns', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Dns::Plugin->createSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $type = extract_param($param, 'type');
+ my $id = extract_param($param, 'dns');
+
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($type);
+ my $opts = $plugin->check_config($id, $param, 1, 1);
+
+ # create /etc/pve/sdn directory
+ PVE::Cluster::check_cfs_quorum();
+ mkdir("/etc/pve/sdn");
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+
+ my $scfg = undef;
+ if ($scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id, 1)) {
+ die "sdn dns object ID '$id' already defined\n";
+ }
+
+ $dns_cfg->{ids}->{$id} = $opts;
+
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type});
+ $plugin->on_update_hook($opts);
+
+ PVE::Network::SDN::Dns::write_config($dns_cfg);
+
+ }, "create sdn dns object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update',
+ protected => 1,
+ path => '{dns}',
+ method => 'PUT',
+ description => "Update sdn dns object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/dns', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'dns');
+ my $digest = extract_param($param, 'digest');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+
+ PVE::SectionConfig::assert_if_modified($dns_cfg, $digest);
+
+ my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type});
+ my $opts = $plugin->check_config($id, $param, 0, 1);
+
+ foreach my $k (%$opts) {
+ $scfg->{$k} = $opts->{$k};
+ }
+
+ $plugin->on_update_hook($scfg);
+
+ PVE::Network::SDN::Dns::write_config($dns_cfg);
+
+ }, "update sdn dns object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ protected => 1,
+ path => '{dns}',
+ method => 'DELETE',
+ description => "Delete sdn dns object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/dns', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ dns => get_standard_option('pve-sdn-dns-id', {
+ completion => \&PVE::Network::SDN::Dns::complete_sdn_dns,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'dns');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $cfg = PVE::Network::SDN::Dns::config();
+
+ my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type});
+
+ delete $cfg->{ids}->{$id};
+ PVE::Network::SDN::Dns::write_config($cfg);
+
+ }, "delete sdn dns object failed");
+
+ return undef;
+ }});
+
+1;
diff --git a/PVE/API2/Network/SDN/Makefile b/PVE/API2/Network/SDN/Makefile
index 1117dfa..3683fa4 100644
--- a/PVE/API2/Network/SDN/Makefile
+++ b/PVE/API2/Network/SDN/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm
+SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm
PERL5DIR=${DESTDIR}/usr/share/perl5
diff --git a/PVE/Network/SDN/Dns.pm b/PVE/Network/SDN/Dns.pm
new file mode 100644
index 0000000..c2e153a
--- /dev/null
+++ b/PVE/Network/SDN/Dns.pm
@@ -0,0 +1,57 @@
+package PVE::Network::SDN::Dns;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+use JSON;
+
+use PVE::Tools qw(extract_param dir_glob_regex run_command);
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Network;
+
+use PVE::Network::SDN::Dns::PowerdnsPlugin;
+use PVE::Network::SDN::Dns::Plugin;
+
+PVE::Network::SDN::Dns::PowerdnsPlugin->register();
+PVE::Network::SDN::Dns::Plugin->init();
+
+
+sub sdn_dns_config {
+ my ($cfg, $id, $noerr) = @_;
+
+ die "no sdn dns ID specified\n" if !$id;
+
+ my $scfg = $cfg->{ids}->{$id};
+ die "sdn '$id' does not exist\n" if (!$noerr && !$scfg);
+
+ return $scfg;
+}
+
+sub config {
+ my $config = cfs_read_file("sdn/dns.cfg");
+ return $config;
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file("sdn/dns.cfg", $cfg);
+}
+
+sub sdn_dns_ids {
+ my ($cfg) = @_;
+
+ return keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_dns {
+ my ($cmdname, $pname, $cvalue) = @_;
+
+ my $cfg = PVE::Network::SDN::Dns::config();
+
+ return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Dns::sdn_dns_ids($cfg) ];
+}
+
+1;
+
diff --git a/PVE/Network/SDN/Dns/Makefile b/PVE/Network/SDN/Dns/Makefile
new file mode 100644
index 0000000..81cd2a1
--- /dev/null
+++ b/PVE/Network/SDN/Dns/Makefile
@@ -0,0 +1,8 @@
+SOURCES=Plugin.pm PowerdnsPlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Dns/$$i; done
diff --git a/PVE/Network/SDN/Dns/Plugin.pm b/PVE/Network/SDN/Dns/Plugin.pm
new file mode 100644
index 0000000..baa9316
--- /dev/null
+++ b/PVE/Network/SDN/Dns/Plugin.pm
@@ -0,0 +1,117 @@
+package PVE::Network::SDN::Dns::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Tools qw(run_command);
+use PVE::JSONSchema;
+use PVE::Cluster;
+use HTTP::Request;
+use LWP::UserAgent;
+use JSON;
+
+use Data::Dumper;
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/dns.cfg',
+ sub { __PACKAGE__->parse_config(@_); },
+ sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-dns-id', {
+ description => "The SDN dns object identifier.",
+ type => 'string', format => 'pve-sdn-dns-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-dns-id', \&parse_sdn_dns_id);
+sub parse_sdn_dns_id {
+ my ($id, $noerr) = @_;
+
+ if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
+ return undef if $noerr;
+ die "dns ID '$id' contains illegal characters\n";
+ }
+ return $id;
+}
+
+my $defaultData = {
+
+ propertyList => {
+ type => {
+ description => "Plugin type.",
+ type => 'string', format => 'pve-configid',
+ },
+ ttl => { type => 'integer', optional => 1 },
+ dns => get_standard_option('pve-sdn-dns-id',
+ { completion => \&PVE::Network::SDN::Dns::complete_sdn_dns }),
+ },
+};
+
+sub private {
+ return $defaultData;
+}
+
+sub parse_section_header {
+ my ($class, $line) = @_;
+
+ if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
+ my ($type, $id) = (lc($1), $2);
+ my $errmsg = undef; # set if you want to skip whole section
+ eval { PVE::JSONSchema::pve_verify_configid($type); };
+ $errmsg = $@ if $@;
+ my $config = {}; # to return additional attributes
+ return ($type, $id, $errmsg, $config);
+ }
+ return undef;
+}
+
+
+sub add_a_record {
+ my ($class, $plugin_config, $type, $zone, $reversezone, $hostname, $ip) = @_;
+}
+
+sub del_a_record {
+ my ($class, $plugin_config, $hostname, $ip) = @_;
+}
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+}
+
+#helpers
+sub api_request {
+ my ($method, $url, $headers, $data) = @_;
+
+ my $encoded_data = to_json($data) if $data;
+
+ my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
+
+ my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 30);
+ my $proxy = undef;
+
+ if ($proxy) {
+ $ua->proxy(['http', 'https'], $proxy);
+ } else {
+ $ua->env_proxy;
+ }
+
+ $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00);
+
+ my $response = $ua->request($req);
+ my $code = $response->code;
+
+ if ($code !~ /^2(\d+)$/) {
+ my $msg = $response->message || 'unknown';
+ die "Invalid response from server: $code $msg\n";
+ }
+
+ my $raw = '';
+ if (defined($response->decoded_content)) {
+ $raw = $response->decoded_content;
+ } else {
+ $raw = $response->content;
+ }
+ return from_json($raw) if $raw ne '';
+}
+
+1;
diff --git a/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/PVE/Network/SDN/Dns/PowerdnsPlugin.pm
new file mode 100644
index 0000000..8c5dd90
--- /dev/null
+++ b/PVE/Network/SDN/Dns/PowerdnsPlugin.pm
@@ -0,0 +1,201 @@
+package PVE::Network::SDN::Dns::PowerdnsPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+use JSON;
+use Net::IP;
+
+use base('PVE::Network::SDN::Dns::Plugin');
+
+sub type {
+ return 'powerdns';
+}
+
+sub properties {
+ return {
+ url => {
+ type => 'string',
+ },
+ key => {
+ type => 'string',
+ },
+ };
+}
+
+sub options {
+
+ return {
+ url => { optional => 0},
+ key => { optional => 0 },
+ ttl => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+
+sub add_a_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400;
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
+ my $fqdn = $hostname.".".$zone.".";
+
+
+ my $record = { content => $ip,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => $ttl,
+ changetype => "REPLACE",
+ records => [ $record ] };
+
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error add $fqdn to zone $zone: $@";
+ }
+}
+
+sub add_ptr_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400;
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ my $reverseip = join(".", reverse(split(/\./, $ip)))."in-addr.arpa.";
+ my $fqdn = $hostname.".".$zone.".";
+ my $type = "PTR";
+
+ my $record = { content => $fqdn,
+ disabled => JSON::false,
+ name => $reverseip,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $reverseip,
+ type => $type,
+ ttl => $ttl,
+ changetype => "REPLACE",
+ records => [ $record ] };
+
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error add $reverseip to zone $zone: $@";
+ }
+}
+
+sub del_a_record {
+ my ($class, $plugin_config, $zone, $hostname) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+ my $fqdn = $hostname.".".$zone.".";
+ my $type = "PTR";
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ changetype => "DELETE",
+ records => [] };
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error delete $fqdn from zone $zone: $@";
+ }
+}
+
+sub del_ptr_record {
+ my ($class, $plugin_config, $zone, $ip) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ my $reverseip = join(".", reverse(split(/\./, $ip)))."in-addr.arpa.";
+ my $type = "PTR";
+
+ my $rrset = { name => $reverseip,
+ type => $type,
+ changetype => "DELETE",
+ records => [] };
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error delete $reverseip from zone $zone: $@";
+ }
+}
+
+sub verify_zone {
+ my ($class, $plugin_config, $zone) = @_;
+
+ #verify that api is working
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ eval {
+ PVE::Network::SDN::Dns::Plugin::api_request("GET", "$url/zones/$zone", $headers);
+ };
+
+ if ($@) {
+ die "can't read zone $zone: $@";
+ }
+}
+
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+
+ #verify that api is working
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ eval {
+ PVE::Network::SDN::Dns::Plugin::api_request("GET", "$url", $headers);
+ };
+
+ if ($@) {
+ die "dns api error: $@";
+ }
+}
+
+1;
+
+
diff --git a/PVE/Network/SDN/Ipams/PVEPlugin.pm b/PVE/Network/SDN/Ipams/PVEPlugin.pm
index 0dfc8a4..99af0ed 100644
--- a/PVE/Network/SDN/Ipams/PVEPlugin.pm
+++ b/PVE/Network/SDN/Ipams/PVEPlugin.pm
@@ -99,7 +99,6 @@ sub add_next_freeip {
while(1) {
my $ip = $iplist->ip();
++$iplist;
- print "nextip: $ip\n";
next if defined($s->{ips}->{$ip});
$freeip = $ip;
last;
diff --git a/PVE/Network/SDN/Ipams/Plugin.pm b/PVE/Network/SDN/Ipams/Plugin.pm
index fc736b8..683346c 100644
--- a/PVE/Network/SDN/Ipams/Plugin.pm
+++ b/PVE/Network/SDN/Ipams/Plugin.pm
@@ -110,7 +110,7 @@ sub api_request {
my $response = $ua->request($req);
my $code = $response->code;
- if ($code !~ /2(\d+)$/) {
+ if ($code !~ /^2(\d+)$/) {
my $msg = $response->message || 'unknown';
die "Invalid response from server: $code $msg\n";
}
diff --git a/PVE/Network/SDN/Makefile b/PVE/Network/SDN/Makefile
index fb68856..92cfcd0 100644
--- a/PVE/Network/SDN/Makefile
+++ b/PVE/Network/SDN/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm
+SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm
PERL5DIR=${DESTDIR}/usr/share/perl5
@@ -9,4 +9,5 @@ install:
make -C Controllers install
make -C Zones install
make -C Ipams install
+ make -C Dns install
diff --git a/PVE/Network/SDN/SubnetPlugin.pm b/PVE/Network/SDN/SubnetPlugin.pm
index 6224065..3769e04 100644
--- a/PVE/Network/SDN/SubnetPlugin.pm
+++ b/PVE/Network/SDN/SubnetPlugin.pm
@@ -65,22 +65,25 @@ sub properties {
type => 'string',
description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]",
},
- #cloudinit, dhcp options
- nameservers => {
- type => 'string', format => 'address-list',
- description => " dns nameserver",
+ dns => {
+ type => 'string',
+ description => "dns api server",
},
- #cloudinit, dhcp options
- searchdomain => {
+ reversedns => {
type => 'string',
+ description => "reverse dns api server",
},
- dhcp => {
- type => 'boolean',
- description => "enable dhcp for this subnet",
+ dnszone => {
+ type => 'string',
+ description => "dns domain zone ex: mydomain.com",
},
- dns_driver => {
+ reversednszone => {
type => 'string',
- description => "Develop some dns registrations plugins (powerdns,...)",
+ description => "reverse dns zone ex: 0.168.192.in-addr.arpa",
+ },
+ dnszoneprefix => {
+ type => 'string',
+ description => "dns domain zone prefix ex: 'adm' -> <hostname>.adm.mydomain.com",
},
ipam => {
type => 'string',
@@ -93,11 +96,12 @@ sub options {
return {
gateway => { optional => 1 },
routes => { optional => 1 },
- nameservers => { optional => 1 },
- searchdomain => { optional => 1 },
snat => { optional => 1 },
- dhcp => { optional => 1 },
- dns_driver => { optional => 1 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ reversednszone => { optional => 1 },
+ dnszoneprefix => { optional => 1 },
ipam => { optional => 1 },
};
}
@@ -105,12 +109,25 @@ sub options {
sub on_update_hook {
my ($class, $subnetid, $subnet_cfg) = @_;
- my $subnet = $subnetid =~ s/-/\//r;
- my $subnet_matcher = subnet_matcher($subnet);
+ my $cidr = $subnetid =~ s/-/\//r;
+ my $subnet_matcher = subnet_matcher($cidr);
+
+ my $subnet = $subnet_cfg->{ids}->{$subnetid};
- my $gateway = $subnet_cfg->{ids}->{$subnetid}->{gateway};
+ my $gateway = $subnet->{gateway};
+ my $dns = $subnet->{dns};
+ my $dnszone = $subnet->{dnszone};
+ my $reversedns = $subnet->{reversedns};
+ my $reversednszone = $subnet->{reversednszone};
+
+ #to: for /32 pointotoping, allow gateway outside the subnet
raise_param_exc({ gateway => "$gateway is not in subnet $subnet"}) if $gateway && !$subnet_matcher->($gateway);
+ raise_param_exc({ dns => "missing dns provider"}) if $dnszone && !$dns;
+ raise_param_exc({ dnszone => "missing dns zone"}) if $dns && !$dnszone;
+ raise_param_exc({ reversedns => "missing dns provider"}) if $reversednszone && !$reversedns;
+ raise_param_exc({ reversednszone => "missing dns zone"}) if $reversedns && !$reversednszone;
+
}
sub on_delete_hook {
diff --git a/PVE/Network/SDN/Subnets.pm b/PVE/Network/SDN/Subnets.pm
index 3ce2d44..4e8353e 100644
--- a/PVE/Network/SDN/Subnets.pm
+++ b/PVE/Network/SDN/Subnets.pm
@@ -5,8 +5,10 @@ use warnings;
use Net::Subnet qw(subnet_matcher);
use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use Net::IP;
use PVE::Network::SDN::Ipams;
+use PVE::Network::SDN::Dns;
use PVE::Network::SDN::SubnetPlugin;
PVE::Network::SDN::SubnetPlugin->register();
PVE::Network::SDN::SubnetPlugin->init();
@@ -75,41 +77,157 @@ sub find_ip_subnet {
return ($subnetid, $subnet);
}
+my $verify_dns_zone = sub {
+ my ($zone, $dns) = @_;
+
+ return if !$zone || !$dns;
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+ my $plugin_config = $dns_cfg->{ids}->{$dns};
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+ $plugin->verify_zone($plugin_config, $zone);
+};
+
+my $add_dns_record = sub {
+ my ($zone, $dns, $hostname, $dnszoneprefix, $ip, $reverse) = @_;
+
+ return if !$zone || !$dns || !$hostname || !$ip;
+
+ $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+ my $plugin_config = $dns_cfg->{ids}->{$dns};
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+ if($reverse) {
+ $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip);
+ } else {
+ $plugin->add_a_record($plugin_config, $zone, $hostname, $ip);
+ }
+};
+
+my $del_dns_record = sub {
+ my ($zone, $dns, $hostname, $dnszoneprefix, $ip, $reverse) = @_;
+
+ return if !$zone || !$dns || !$hostname || !$ip;
+
+ $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+ my $plugin_config = $dns_cfg->{ids}->{$dns};
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+ if($reverse) {
+ $plugin->del_ptr_record($plugin_config, $zone, $ip);
+ } else {
+ $plugin->del_a_record($plugin_config, $zone, $hostname);
+ }
+};
+
sub next_free_ip {
- my ($subnetid, $subnet) = @_;
+ my ($subnetid, $subnet, $hostname) = @_;
+
+ my $cidr = undef;
+ my $ip = undef;
my $ipamid = $subnet->{ipam};
- return if !$ipamid;
+ my $dns = $subnet->{dns};
+ my $dnszone = $subnet->{dnszone};
+ my $reversedns = $subnet->{reversedns};
+ my $reversednszone = $subnet->{reversednszone};
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+ #verify dns zones before ipam
+ &$verify_dns_zone($dnszone, $dns);
+ &$verify_dns_zone($reversednszone, $reversedns);
+
+ if($ipamid) {
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet);
+ ($ip, undef) = split(/\//, $cidr);
+ }
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- my $ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet);
- return $ip;
+ eval {
+ #add dns
+ &$add_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip);
+ #add reverse dns
+ &$add_dns_record($reversednszone, $reversedns, $hostname, $dnszoneprefix, $ip, 1);
+ };
+ if ($@) {
+ #rollback
+ my $err = $@;
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname)
+ };
+ die $err;
+ }
+ return $cidr;
}
sub add_ip {
- my ($subnetid, $subnet, $ip) = @_;
+ my ($subnetid, $subnet, $ip, $hostname) = @_;
my $ipamid = $subnet->{ipam};
- return if !$ipamid;
+ my $dns = $subnet->{dns};
+ my $dnszone = $subnet->{dnszone};
+ my $reversedns = $subnet->{reversedns};
+ my $reversednszone = $subnet->{reversednszone};
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+ #verify dns zones before ipam
+ &$verify_dns_zone($dnszone, $dns);
+ &$verify_dns_zone($reversednszone, $reversedns);
+
+ if ($ipamid) {
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ $plugin->add_ip($plugin_config, $subnetid, $ip);
+ }
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- $plugin->add_ip($plugin_config, $subnetid, $ip);
+ eval {
+ #add dns
+ &$add_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip);
+ #add reverse dns
+ &$add_dns_record($reversednszone, $reversedns, $hostname, $dnszoneprefix, $ip, 1);
+ };
+ if ($@) {
+ #rollback
+ my $err = $@;
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname)
+ };
+ die $err;
+ }
}
sub del_ip {
- my ($subnetid, $subnet, $ip) = @_;
+ my ($subnetid, $subnet, $ip, $hostname) = @_;
my $ipamid = $subnet->{ipam};
- return if !$ipamid;
+ my $dns = $subnet->{dns};
+ my $dnszone = $subnet->{dnszone};
+ my $reversedns = $subnet->{reversedns};
+ my $reversednszone = $subnet->{reversednszone};
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+ &$verify_dns_zone($dnszone, $dns);
+ &$verify_dns_zone($reversednszone, $reversedns);
+
+ if ($ipamid) {
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ $plugin->del_ip($plugin_config, $subnetid, $ip);
+ }
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- $plugin->del_ip($plugin_config, $subnetid, $ip);
+ eval {
+ &$del_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip);
+ &$del_dns_record($reversednszone, $reversedns, $hostname, $dnszoneprefix, $ip, 1);
+ };
+ if ($@) {
+ warn $@;
+ }
}
1;
diff --git a/PVE/Network/SDN/Vnets.pm b/PVE/Network/SDN/Vnets.pm
index 6ea3a9a..c9916b1 100644
--- a/PVE/Network/SDN/Vnets.pm
+++ b/PVE/Network/SDN/Vnets.pm
@@ -55,7 +55,7 @@ sub get_vnet {
}
sub get_next_free_ip {
- my ($vnet, $ipversion) = @_;
+ my ($vnet, $hostname, $ipversion) = @_;
$ipversion = 4 if !$ipversion;
my $subnets_cfg = PVE::Network::SDN::Subnets::config();
@@ -71,7 +71,7 @@ sub get_next_free_ip {
$subnet = $subnets_cfg->{ids}->{$subnetid};
if ($subnet && $subnet->{ipam}) {
eval {
- $ip = PVE::Network::SDN::Subnets::next_free_ip($subnetid, $subnet);
+ $ip = PVE::Network::SDN::Subnets::next_free_ip($subnetid, $subnet, $hostname);
};
warn $@ if $@;
}
@@ -83,23 +83,23 @@ sub get_next_free_ip {
}
sub add_ip {
- my ($vnet, $cidr, $name) = @_;
+ my ($vnet, $cidr, $hostname) = @_;
my ($ip, $mask) = split(/\//, $cidr);
my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $vnet->{subnets});
return if !$subnet->{ipam};
- PVE::Network::SDN::Subnets::add_ip($subnetid, $subnet, $ip);
+ PVE::Network::SDN::Subnets::add_ip($subnetid, $subnet, $ip, $hostname);
}
sub del_ip {
- my ($vnet, $cidr) = @_;
+ my ($vnet, $cidr, $hostname) = @_;
my ($ip, $mask) = split(/\//, $cidr);
my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $vnet->{subnets});
return if !$subnet->{ipam};
- PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip);
+ PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname);
}
1;
--
2.20.1
More information about the pve-devel
mailing list