[pve-devel] [PATCH v10 pve-network 15/35] add dns plugin

Alexandre Derumier aderumier at odiso.com
Mon Oct 5 17:08:52 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